pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Boszormenyi Zoltan <zb@cybertec.at>
To: Michael Meskes <meskes@postgresql.org>
To: PG Hackers <pgsql-hackers@postgresql.org>
Cc: Robert Haas <robertmhaas@gmail.com>
Cc: Heikki Linnakangas <heikki.linnakangas@enterprisedb.com>
Cc: Bruce Momjian <bruce@momjian.us>
Subject: Re: ECPG FETCH readahead
Date: Wed, 16 Nov 2011 20:51:16 +0100
Message-ID: <4EC41434.7010603@cybertec.at> (raw)
In-Reply-To: <4CB6D3C8.3020709@cybertec.at>
References: <201006232042.o5NKgb503695@momjian.us>
	<4C2308FD.1090300@cybertec.at>
	<4C231F9E.6000102@enterprisedb.com>
	<20100624121922.GD24137@feivel.credativ.lan>
	<AANLkTinij9ULnS54zKsdAJnhvuj-07-8-T-D=Nwk6f14@mail.gmail.com>
	<4CB6D3C8.3020709@cybertec.at>

2010-10-14 11:56 keltezéssel, Boszormenyi Zoltan írta:
> Hi,
>
> Robert Haas írta:
>> On Thu, Jun 24, 2010 at 8:19 AM, Michael Meskes <meskes@postgresql.org> wrote:
>>   
>>> On Thu, Jun 24, 2010 at 12:04:30PM +0300, Heikki Linnakangas wrote:
>>>     
>>>> Is there a reason not to enable it by default? I'm a bit worried
>>>> that it will receive no testing if it's not always on.
>>>>       
>>> Yes, there is a reason, namely that you don't need it in native mode at all.
>>> ECPG can read as many records as you want in one go, something ESQL/C
>>> apparently cannot do. This patch is a workaround for that restriction. I still
>>> do not really see that this feature gives us an advantage in native mode
>>> though.
>>>     
>> So, who has the next action on this patch?  Does Zoltan need to revise
>> it, or does Michael need to review it, or where are we?
>>   
> Michael reviewed it shortly in private and I need to send
> a new revision anyway, regardless of his comments.
> I will refresh it ASAP.

The ASAP took a little long. The attached patch is in git diff format,
because (1) the regression test intentionally doesn't do ECPGdebug()
so the patch isn't dominated by a 2MB stderr file, so this file is empty
and (2) regular diff cannot cope with empty new files.

Anyway, here is the new version with a new feature.
Implementation details:

- New ecpglib/cursor.c handles the client-side accounting of cursors.
- Functions in ecpglib/execute.c are split into smaller functions
  so useful operations can be used by the new cursor.c
- ecpg -r fetch_readahead enables readahead by default for all cursors.
- Default readahead size is 256 rows, it can be modified by an environment
  variable, ECPGFETCHSZ.
- *NEW FEATURE* Readahead can be individually enabled or disabled
  by ECPG-side grammar:
        DECLARE curname [ [ NO ] READAHEAD ] CURSOR FOR ...
  Without [ NO ] READAHEAD, the default behaviour is used for cursors.
- Since the server and the client may disagree on the cursor position
  if readahead is used, ECPG preprocessor throws an error if
  WHERE CURRENT OF is used on such cursors.
- The default assumed behaviour of cursors with readahead is NO SCROLL.
  If you want readahead and backward scrolling, SCROLL keyword is mandatory.

The regression test creates a table with 513 rows, so it spans 2 full and
1 incomplete readahead window. It reads the table with two cursors, one
with readahead and one without by 1 records forward and backward.
This is repeated with reading 5 records at a time. Then the whole table is
read into a chain of sqlda structures forward and backward. All other
regression tests pass as well.

The original regression tests also pass with these changes, the split of
execute.c was risky in this regard. Now the split is done more cleanly
than in the previous version, the file is not as rearranged as before.

Best regards,
Zoltán Böszörményi

-- 
----------------------------------
Zoltán Böszörményi
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt, Austria
Web: http://www.postgresql-support.de
     http://www.postgresql.at/
diff --git a/doc/src/sgml/ecpg.sgml b/doc/src/sgml/ecpg.sgml
index 68833ca..69b49e0 100644
--- a/doc/src/sgml/ecpg.sgml
+++ b/doc/src/sgml/ecpg.sgml
@@ -454,6 +454,32 @@ EXEC SQL COMMIT;
    details.
   </para>
 
+  <para>
+   ECPG may use cursor readahead to improve performance of programs
+   that use single-row FETCH statements. Option <literal>-r fetch_readahead</literal>
+   option for ECPG modifies the default for all cursors from <literal>NO READAHEAD</literal>
+   to <literal>READAHEAD</literal>. Explicit <literal>READAHEAD</literal> or
+   <literal>NO READAHEAD</literal> turns cursor readahead on or off for the specified cursor,
+   respectively. The default readahead size is 256 rows, this may be modified by setting
+   the <literal>ECPGFETCHSZ</literal> environment variable to a different value.
+  </para>
+
+  <para>
+   Scrolling behaviour differs from the documented one at <xref linkend="sql-declare">
+   when readahead is used for a cursor. ECPG treats cursors as <literal>NO SCROLL</literal>
+   when neither <literal>SCROLL</literal> nor <literal>NO SCROLL</literal> are specified.
+   When backward fetching or positioning is attempted on a <literal>NO SCROLL READAHEAD</literal>
+   cursor, error code 55000 (Object not in prerequisite state) is returned to the application.
+  </para>
+
+  <para>
+   For cursors used in <command>UPDATE</command> or <command>DELETE</command>
+   with the <literal>WHERE CURRENT OF</literal> clause, cursor readahead must be
+   turned off, otherwise <command>ecpg</command> throws an error. This restriction
+   is because when the client side uses readahead, the idea about the cursor position
+   in the dataset may differ between the server and the client.
+  </para>
+
    <note>
     <para>
      The ECPG <command>DECLARE</command> command does not actually
@@ -6583,8 +6609,8 @@ EXEC SQL DEALLOCATE DESCRIPTOR mydesc;
 
    <refsynopsisdiv>
 <synopsis>
-DECLARE <replaceable class="PARAMETER">cursor_name</replaceable> [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ] CURSOR [ { WITH | WITHOUT } HOLD ] FOR <replaceable class="PARAMETER">prepared_name</replaceable>
-DECLARE <replaceable class="PARAMETER">cursor_name</replaceable> [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ] CURSOR [ { WITH | WITHOUT } HOLD ] FOR <replaceable class="PARAMETER">query</replaceable>
+DECLARE <replaceable class="PARAMETER">cursor_name</replaceable> [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ] [ [ NO ] READAHEAD ] CURSOR [ { WITH | WITHOUT } HOLD ] FOR <replaceable class="PARAMETER">prepared_name</replaceable>
+DECLARE <replaceable class="PARAMETER">cursor_name</replaceable> [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ] [ [ NO ] READAHEAD ] CURSOR [ { WITH | WITHOUT } HOLD ] FOR <replaceable class="PARAMETER">query</replaceable>
 </synopsis>
    </refsynopsisdiv>
 
@@ -6639,11 +6665,36 @@ DECLARE <replaceable class="PARAMETER">cursor_name</replaceable> [ BINARY ] [ IN
       </listitem>
      </varlistentry>
     </variablelist>
+   </refsect1>
+
+   <refsect1>
+    <title>Cursor options</title>
 
     <para>
-     For the meaning of the cursor options,
-     see <xref linkend="sql-declare">.
+     For the meaning of other cursor options, see <xref linkend="sql-declare">.
     </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>READAHEAD</literal></term>   
+      <term><literal>NO READAHEAD</literal></term>   
+       <listitem>
+        <para>
+         <literal>READAHEAD</literal> makes the ECPG preprocessor and runtime library
+         use a client-side cursor accounting and data readahead during
+         <command>FETCH</command>. This improves performance for programs that use
+         single-row <command>FETCH</command> statements.
+        </para>
+
+        <para>
+         <literal>NO READAHEAD</literal> disables data readahead in case
+         <parameter>-r fetch_readahead</parameter> is used for compiling the file.
+        </para>
+       </listitem>
+      </varlistentry>
+
+    </variablelist>
+
    </refsect1>
 
    <refsect1>
diff --git a/doc/src/sgml/ref/ecpg-ref.sgml b/doc/src/sgml/ref/ecpg-ref.sgml
index 9c13e93..22dfe44 100644
--- a/doc/src/sgml/ref/ecpg-ref.sgml
+++ b/doc/src/sgml/ref/ecpg-ref.sgml
@@ -166,6 +166,14 @@ PostgreSQL documentation
          </para>
          </listitem>
         </varlistentry>
+        <varlistentry>
+         <term><option>fetch_readahead</option></term>
+         <listitem>
+         <para>
+         Turn on cursor readahead by default.
+         </para>
+         </listitem>
+        </varlistentry>
        </variablelist></para>
      </listitem>
     </varlistentry>
diff --git a/src/interfaces/ecpg/ecpglib/Makefile b/src/interfaces/ecpg/ecpglib/Makefile
index 2b2ffb6..1f46ff6 100644
--- a/src/interfaces/ecpg/ecpglib/Makefile
+++ b/src/interfaces/ecpg/ecpglib/Makefile
@@ -25,7 +25,7 @@ override CFLAGS += $(PTHREAD_CFLAGS)
 LIBS := $(filter-out -lpgport, $(LIBS))
 
 OBJS= execute.o typename.o descriptor.o sqlda.o data.o error.o prepare.o memory.o \
-	connect.o misc.o path.o pgstrcasecmp.o \
+	connect.o misc.o path.o pgstrcasecmp.o cursor.o \
 	$(filter snprintf.o strlcpy.o win32setlocale.o, $(LIBOBJS))
 
 # thread.c is needed only for non-WIN32 implementation of path.c
diff --git a/src/interfaces/ecpg/ecpglib/connect.c b/src/interfaces/ecpg/ecpglib/connect.c
index 997046b..7eda052 100644
--- a/src/interfaces/ecpg/ecpglib/connect.c
+++ b/src/interfaces/ecpg/ecpglib/connect.c
@@ -456,6 +456,7 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p
 
 	this->cache_head = NULL;
 	this->prep_stmts = NULL;
+	this->cursor_desc = NULL;
 
 	if (all_connections == NULL)
 		this->next = NULL;
diff --git a/src/interfaces/ecpg/ecpglib/cursor.c b/src/interfaces/ecpg/ecpglib/cursor.c
new file mode 100644
index 0000000..ff6e40d
--- /dev/null
+++ b/src/interfaces/ecpg/ecpglib/cursor.c
@@ -0,0 +1,708 @@
+/*
+ * FETCH readahead support routines
+ */
+
+#define POSTGRES_ECPG_INTERNAL
+#include "postgres_fe.h"
+
+#include <limits.h>
+
+#include "ecpgtype.h"
+#include "ecpglib.h"
+#include "ecpgerrno.h"
+#include "extern.h"
+
+static struct cursor_descriptor *add_cursor(int lineno, struct connection *con, const char *name, bool scrollable, int64 n_tuples, bool *existing);
+static struct cursor_descriptor *find_cursor(struct connection *con, const char *name);
+static void del_cursor(struct connection *con, const char *name);
+
+/* default fetch size, set on the first call to ECPGopen()  */
+#define DEFAULTFETCHSIZE	(256)
+static int	fetch_size = 0;
+
+/*
+ * Add a new cursor descriptor, maintain alphabetic order
+ */
+static struct cursor_descriptor *
+add_cursor(int lineno, struct connection *con, const char *name, bool scrollable, int64 n_tuples, bool *existing)
+{
+	struct cursor_descriptor *desc,
+				*ptr, *prev = NULL;
+	bool	found = false;
+
+	if (!name || name[0] == '\0')
+	{
+		if (existing)
+			*existing = false;
+		return NULL;
+	}
+
+	ptr = con->cursor_desc;
+	while (ptr)
+	{
+		int ret = strcasecmp(ptr->name, name);
+
+		if (ret == 0)
+		{
+			found = true;
+			break;
+		}
+		if (ret > 0)
+			break;
+
+		prev = ptr;
+		ptr = ptr->next;
+	}
+
+	if (found)
+	{
+		if (existing)
+			*existing = true;
+		return ptr;
+	}
+
+	desc = (struct cursor_descriptor *)ecpg_alloc(sizeof(struct cursor_descriptor), lineno);
+	if (!desc)
+		return NULL;
+	desc->name = ecpg_strdup(name, lineno);
+	if (!desc->name)
+	desc->res = NULL;
+	desc->scrollable = scrollable;
+	desc->n_tuples = n_tuples;
+	desc->start_pos = 0;
+	desc->cache_pos = 0;
+	desc->next = ptr;
+
+	if (prev)
+		prev->next = desc;
+	else
+		con->cursor_desc = desc;
+
+	if (existing)
+		*existing = false;
+	return desc;
+}
+
+static struct cursor_descriptor *
+find_cursor(struct connection *con, const char *name)
+{
+	struct cursor_descriptor *desc = con->cursor_desc;
+	bool	found = false;
+
+	if (!name)
+		return NULL;
+
+	while (desc)
+	{
+		int ret = strcasecmp(desc->name, name);
+
+		if (ret == 0)
+		{
+			found = true;
+			break;
+		}
+		if (ret > 0)
+			break;
+		desc = desc->next;
+	}
+
+	return (found ? desc : NULL);
+}
+
+static void
+del_cursor(struct connection *con, const char *name)
+{
+	struct cursor_descriptor *ptr, *prev = NULL;
+	bool	found = false;
+
+	ptr = con->cursor_desc;
+	while (ptr)
+	{
+		int ret = strcasecmp(ptr->name, name);
+
+		if (ret == 0)
+		{
+			found = true;
+			break;
+		}
+		if (ret > 0)
+			break;
+
+		prev = ptr;
+		ptr = ptr->next;
+	}
+
+	if (found)
+	{
+		if (prev)
+			prev->next = ptr->next;
+		else
+			con->cursor_desc = ptr->next;
+
+		ecpg_free(ptr->name);
+		if (ptr->res)
+			PQclear(ptr->res);
+		ecpg_free(ptr);
+	}
+}
+
+bool
+ECPGopen(const int lineno, const int compat, const int force_indicator,
+		const char *connection_name, const bool questionmarks,
+		const char *curname, const int st, const char *query, ...)
+{
+	va_list		args;
+	bool		ret, scrollable;
+	char	   *new_query, *ptr, *whold, *noscroll, *scroll, *dollar0;
+	struct sqlca_t *sqlca = ECPGget_sqlca();
+
+	if (!query)
+	{
+		ecpg_raise(lineno, ECPG_EMPTY, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, NULL);
+		return false;
+	}
+	ptr = strstr(query, "for ");
+	if (!ptr)
+	{
+		ecpg_raise(lineno, ECPG_INVALID_STMT, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, NULL);
+		return false;
+	}
+	whold = strstr(query, "with hold ");
+	dollar0 = strstr(query, "$0");
+
+	noscroll = strstr(query, "no scroll ");
+	scroll = strstr(query, "scroll ");
+	scrollable = (noscroll == NULL) && (scroll != NULL) && (scroll < ptr);
+
+	new_query = ecpg_alloc(strlen(curname) + strlen(ptr) + (whold ? 10 : 0) + 32, lineno);
+	if (!new_query)
+		return false;
+	sprintf(new_query, "declare %s %s cursor %s%s",
+					(dollar0 && (dollar0 < ptr) ? "$0" : curname),
+					(scrollable ? "scroll" : "no scroll"),
+					(whold ? "with hold " : ""),
+					ptr);
+
+	/* Set the fetch size the first time we are called. */
+	if (fetch_size == 0)
+	{
+		char	   *fsize_str = getenv("ECPGFETCHSZ");
+		char	   *endptr = NULL;
+		int		fsize;
+
+		if (fsize_str)
+		{
+			fsize = strtoul(fsize_str, &endptr, 10);
+			if (endptr || (fsize < 4))
+				fetch_size = DEFAULTFETCHSIZE;
+			else
+				fetch_size = fsize;
+		}
+		else
+			fetch_size = DEFAULTFETCHSIZE;
+	}
+
+	va_start(args, query);
+	ret = ecpg_do(lineno, compat, force_indicator, connection_name, questionmarks, st, new_query, args);
+	va_end(args);
+
+	ecpg_free(new_query);
+
+	/*
+	 * If statement went OK, add the cursor and discover the
+	 * number of rows in the recordset. This will slow down OPEN
+	 * but we gain a lot with caching.
+	 */
+	if (ret /* && sqlca->sqlerrd[2] == 0 */)
+	{
+		struct connection *con = ecpg_get_connection(connection_name);
+		struct cursor_descriptor *cur;
+		bool	existing;
+		int64	n_tuples;
+
+		if (scrollable)
+		{
+			PGresult   *res;
+			char	   *query;
+			char	   *endptr = NULL;
+
+			query = ecpg_alloc(strlen(curname) + strlen("move all in ") + 2, lineno);
+			sprintf(query, "move all in %s", curname);
+			res = PQexec(con->connection, query);
+			n_tuples = strtoull(PQcmdTuples(res), &endptr, 10);
+			PQclear(res);
+			ecpg_free(query);
+
+			/* Go back to the beginning of the resultset. */
+			query = ecpg_alloc(strlen(curname) + strlen("move absolute 0 in ") + 2, lineno);
+			sprintf(query, "move absolute 0 in %s", curname);
+			res = PQexec(con->connection, query);
+			PQclear(res);
+			ecpg_free(query);
+		}
+		else
+		{
+			n_tuples = 0;
+		}
+
+		/* Add the cursor */
+		cur = add_cursor(lineno, con, curname, scrollable, n_tuples, &existing);
+
+		/*
+		 * Report the number of tuples for the [scrollable] cursor.
+		 * The server didn't do it for us.
+		 */
+		sqlca->sqlerrd[2] = (cur->n_tuples < LONG_MAX ? cur->n_tuples : LONG_MAX);
+	}
+
+	return ret;
+}
+
+static bool
+ecpg_cursor_execute(struct statement * stmt, struct cursor_descriptor *cur)
+{
+	char		tmp[64];
+	char	   *query;
+	int64		start_pos;
+
+	if ((cur->cache_pos >= cur->start_pos) && cur->res && (cur->cache_pos < cur->start_pos + PQntuples(cur->res)))
+	{
+		stmt->results = cur->res;
+		ecpg_free_params(stmt, true, stmt->lineno);
+		return true;
+	}
+	else if (!cur->scrollable && cur->res && (PQntuples(cur->res) < fetch_size) && (cur->cache_pos >= cur->start_pos + PQntuples(cur->res)))
+	{
+		cur->endoftuples = true;
+		stmt->results = cur->res;
+		ecpg_free_params(stmt, true, stmt->lineno);
+		return true;
+	}
+
+	if ((PQtransactionStatus(stmt->connection->connection) == PQTRANS_IDLE) && !stmt->connection->autocommit)
+	{
+		stmt->results = PQexec(stmt->connection->connection, "begin transaction");
+		if (!ecpg_check_PQresult(stmt->results, stmt->lineno, stmt->connection->connection, stmt->compat))
+		{
+			ecpg_free_params(stmt, false, stmt->lineno);
+			return false;
+		}
+		PQclear(stmt->results);
+	}
+
+	/*
+	 * Compute the tuple position before the resultset. E.g.:
+	 * MOVE ABSOLUTE 0 + FETCH NEXT <fetch_size> will result
+	 * in a recordset having tuples 1 ... fetch_size
+	 */
+	start_pos = (cur->cache_pos - 1) / fetch_size;
+	start_pos *= fetch_size;
+
+	if (cur->scrollable)
+	{
+		sprintf(tmp, "%lld", (long long)start_pos);
+		query = ecpg_alloc(strlen(tmp) + strlen(cur->name) + 20, stmt->lineno);
+		sprintf(query, "move absolute %s in %s", tmp, cur->name);
+
+		ecpg_log("ecpg_cursor_execute on line %d: query: %s; on connection %s\n", stmt->lineno, query, stmt->connection->name);
+
+		stmt->results = PQexec(stmt->connection->connection, query);
+
+		if (!ecpg_check_PQresult(stmt->results, stmt->lineno, stmt->connection->connection, stmt->compat))
+		{
+			ecpg_free_params(stmt, false, stmt->lineno);
+			return false;
+		}
+
+		PQclear(stmt->results);
+		ecpg_free(query);
+	}
+
+	sprintf(tmp, "%lld", (long long)fetch_size);
+	query = ecpg_alloc(strlen(tmp) + strlen(cur->name) + 24, stmt->lineno);
+	sprintf(query, "fetch forward %s from %s", tmp, cur->name);
+
+	if (cur->res)
+		PQclear(cur->res);
+
+	ecpg_log("ecpg_cursor_execute on line %d: query: %s; with %d parameter(s) on connection %s\n", stmt->lineno, query, stmt->nparams, stmt->connection->name);
+
+	if (stmt->nparams == 0)
+	{
+		cur->res = PQexec(stmt->connection->connection, query);
+		ecpg_log("ecpg_cursor_execute on line %d: using PQexec\n", stmt->lineno);
+	}
+	else
+	{
+		/* shouldn't happen */
+		cur->res = PQexecParams(stmt->connection->connection, query, stmt->nparams, NULL, stmt->param_values, NULL, NULL, 0);
+		ecpg_log("ecpg_cursor_execute on line %d: using PQexecParams\n", stmt->lineno);
+	}
+
+	stmt->results = cur->res;
+
+	ecpg_free_params(stmt, true, stmt->lineno);
+
+	if (!ecpg_check_PQresult(cur->res, stmt->lineno, stmt->connection->connection, stmt->compat))
+	{
+		stmt->results = cur->res = NULL;
+		cur->start_pos = 0;
+		return false;
+	}
+
+	/* The tuple position in the cursor is 1 based. */
+	cur->start_pos = start_pos + 1;
+
+	if (!cur->scrollable && PQntuples(cur->res) == 0)
+		cur->endoftuples = true;
+
+	ecpg_free(query);
+
+	return true;
+}
+
+bool
+ECPGfetch(const int lineno, const int compat, const int force_indicator,
+		const char *connection_name, const bool questionmarks,
+		const char *curname, enum ECPG_cursor_direction direction, const char *amount,
+		const int st, const char *query, ...)
+{
+	struct cursor_descriptor *cur;
+	struct statement *stmt;
+	va_list			args;
+	bool			move, fetchall = false, negate = false;
+	const char	   *amount1 = amount;
+	int			step;
+	int64			n_amount, count, start_idx;
+	struct sqlca_t		   *sqlca = ECPGget_sqlca();
+
+	if (!query)
+	{
+		ecpg_raise(lineno, ECPG_EMPTY, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, NULL);
+		return false;
+	}
+
+	move = (strncmp(query, "move ", 5) == 0);
+
+	cur = find_cursor(ecpg_get_connection(connection_name), curname);
+	if (!cur)
+	{
+		ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_INVALID_CURSOR_NAME, (connection_name) ? connection_name : ecpg_gettext("<empty>"));
+		return false;
+	}
+
+	va_start(args, query);
+
+	if (!ecpg_do_prologue(lineno, compat, force_indicator, connection_name, questionmarks, (enum ECPG_statement_type) st, query, args, &stmt))
+		return false;
+
+	if (!ecpg_build_params(stmt))
+	{
+		ecpg_do_epilogue(stmt);
+		va_end(args);
+		return false;
+	}
+
+	if (amount[0] == '$')
+		amount1 = stmt->dollarzero[0];
+	else if (amount[0] == '-' || amount[0] == '+')
+	{
+		/*
+		 * Handle negative and explicitely positive constants in
+		 * FETCH/MOVE ABSOLUTE/RELATIVE const.
+		 * E.g. '-2' arrives as '- 2', '+2' arrives as '+ 2'.
+		 * strtoll() under Linux stops processing at the space.
+		 */
+		if (amount[0] == '-')
+			negate = true;
+		amount1 = amount + 1;
+		while (*amount1 == ' ')
+			amount1++;
+	}
+
+	if (strcmp(amount, "all") == 0)
+	{
+		fetchall = true;
+		if (cur->scrollable)
+		{
+			switch (direction)
+			{
+				case ECPGc_forward:
+					n_amount = cur->n_tuples - cur->cache_pos;
+					break;
+				case ECPGc_backward:
+					n_amount = cur->cache_pos;
+					break;
+				default:
+					ecpg_raise(lineno, ECPG_EMPTY, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, NULL);
+					return false;
+			}
+		}
+		else
+			n_amount = LONG_MAX;
+	}
+	else
+	{
+		char	   *endptr;
+
+		n_amount = strtoll(amount1, &endptr, 10);
+		if (negate)
+			n_amount = -n_amount;
+	}
+
+	switch (direction)
+	{
+		case ECPGc_absolute:
+			if (cur->scrollable)
+			{
+				if (n_amount < 0)
+					n_amount = 1 + cur->n_tuples + n_amount;
+				else if (n_amount > cur->n_tuples)
+					n_amount = cur->n_tuples + 1;
+			}
+			else
+			{
+				if (n_amount < 0)
+				{
+					ecpg_free_params(stmt, true, stmt->lineno);
+					ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+					break;
+				}
+				if (n_amount < cur->cache_pos)
+				{
+					ecpg_free_params(stmt, true, stmt->lineno);
+					ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+					break;
+				}
+			}
+			cur->cache_pos = n_amount;
+
+			if (!move)
+			{
+				if (cur->cache_pos > 0 && ((cur->scrollable && cur->cache_pos <= cur->n_tuples) || (!cur->scrollable && !cur->endoftuples)))
+				{
+					if (!ecpg_cursor_execute(stmt, cur))
+					{
+						ecpg_do_epilogue(stmt);
+						va_end(args);
+						return false;
+					}
+
+					if (!cur->scrollable && cur->endoftuples)
+					{
+						ecpg_do_epilogue(stmt);
+						va_end(args);
+						ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
+						return false;
+					}
+
+					start_idx = cur->cache_pos - cur->start_pos;
+
+					if (!ecpg_process_output(stmt, start_idx, start_idx + 1, 0, true, false))
+					{
+						ecpg_do_epilogue(stmt);
+						va_end(args);
+						return false;
+					}
+				}
+				else
+				{
+					ecpg_free_params(stmt, true, stmt->lineno);
+					ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
+				}
+			}
+			else
+			{
+				ecpg_free_params(stmt, true, stmt->lineno);
+			}
+			sqlca->sqlerrd[2] = (cur->cache_pos && cur->cache_pos <= cur->n_tuples ? 1 : 0);
+			break;
+
+		case ECPGc_relative:
+relative:
+			if (!cur->scrollable)
+			{
+				if (n_amount < 0)
+				{
+					ecpg_free_params(stmt, true, stmt->lineno);
+					ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+					break;
+				}
+			}
+
+			cur->cache_pos += n_amount;
+			if (cur->cache_pos < 0)
+				cur->cache_pos = 0;
+			else if (cur->cache_pos > cur->n_tuples)
+				cur->cache_pos = cur->n_tuples + 1;
+
+			if (!move)
+			{
+				if (cur->cache_pos > 0 && ((cur->scrollable && cur->cache_pos <= cur->n_tuples) || (!cur->scrollable && !cur->endoftuples)))
+				{
+					if (!ecpg_cursor_execute(stmt, cur))
+					{
+						ecpg_do_epilogue(stmt);
+						va_end(args);
+						return false;
+					}
+
+					if (!cur->scrollable && cur->endoftuples)
+					{
+						ecpg_do_epilogue(stmt);
+						va_end(args);
+						ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
+						return false;
+					}
+
+					start_idx = cur->cache_pos - cur->start_pos;
+
+					if (!ecpg_process_output(stmt, start_idx, start_idx + 1, 0, true, false))
+					{
+						ecpg_do_epilogue(stmt);
+						va_end(args);
+						return false;
+					}
+				}
+				else
+				{
+					ecpg_free_params(stmt, true, stmt->lineno);
+					ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
+				}
+			}
+			else
+			{
+				ecpg_free_params(stmt, true, stmt->lineno);
+			}
+			sqlca->sqlerrd[2] = (cur->cache_pos && cur->cache_pos <= cur->n_tuples ? 1 : 0);
+			break;
+
+		case ECPGc_forward:
+		case ECPGc_backward:
+			if (n_amount == 0)
+				goto relative;
+
+			step = (n_amount > 0 ? 1 : -1);
+			if (direction == ECPGc_backward)
+				step = -step;
+			if (n_amount < 0)
+				n_amount = -n_amount;
+
+			if (!cur->scrollable && step < 0)
+			{
+				ecpg_free_params(stmt, true, stmt->lineno);
+				ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+				break;
+			}
+
+			if (move)
+			{
+				int64 new_pos = cur->cache_pos + step * n_amount;
+				int64 diff;
+
+				if (new_pos < 1)
+					new_pos = 0;
+				if (new_pos > cur->n_tuples)
+					new_pos = cur->n_tuples + 1;
+
+				diff = new_pos - cur->cache_pos;
+				sqlca->sqlerrd[2] = (diff >= 0 ? diff : -diff);
+				cur->cache_pos = new_pos;
+
+				ecpg_free_params(stmt, true, stmt->lineno);
+				break;
+			}
+
+			for (count = 0; (cur->scrollable && count < n_amount) ||
+					(!cur->scrollable && ((fetchall && !cur->endoftuples) || (!fetchall && count < n_amount))); count++)
+			{
+				cur->cache_pos += step;
+				if (cur->cache_pos < 1)
+				{
+					cur->cache_pos = 0;
+					break;
+				}
+				else
+				{
+					if (cur->scrollable && cur->cache_pos > cur->n_tuples)
+					{
+						cur->cache_pos = cur->n_tuples + 1;
+						break;
+					}
+					else if (!cur->scrollable && cur->endoftuples)
+					{
+						break;
+					}
+				}
+
+				if (!ecpg_cursor_execute(stmt, cur))
+				{
+					ecpg_do_epilogue(stmt);
+					va_end(args);
+					return false;
+				}
+
+				if (!cur->scrollable && cur->endoftuples)
+					break;
+
+				start_idx = cur->cache_pos - cur->start_pos;
+
+				if (!ecpg_process_output(stmt, start_idx, start_idx + 1, count, true, (count > 0)))
+				{
+					ecpg_do_epilogue(stmt);
+					va_end(args);
+					return false;
+				}
+			}
+
+			if (count == 0)
+			{
+				ecpg_free_params(stmt, true, stmt->lineno);
+				ecpg_do_epilogue(stmt);
+				va_end(args);
+				ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
+				return false;
+			}
+
+			sqlca->sqlerrd[2] = count;
+
+			break;
+
+		default:
+			ecpg_free_params(stmt, true, stmt->lineno);
+			ecpg_do_epilogue(stmt);
+			va_end(args);
+			ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_INVALID_CURSOR_DEFINITION, (connection_name) ? connection_name : ecpg_gettext("<empty>"));
+			return false;
+	}
+
+	ecpg_do_epilogue(stmt);
+	va_end(args);
+	return true;
+}
+
+bool
+ECPGclose(const int lineno, const int compat, const int force_indicator,
+		const char *connection_name, const bool questionmarks,
+		const char *curname, const int st, const char *query, ...)
+{
+	struct connection  *con;
+	va_list			args;
+	bool			ret;
+
+	con = ecpg_get_connection(connection_name);
+
+	if (!find_cursor(con, curname))
+	{
+		ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_INVALID_CURSOR_NAME, (connection_name) ? connection_name : ecpg_gettext("<empty>"));
+		return false;
+	}
+
+	va_start(args, query);
+	ret = ecpg_do(lineno, compat, force_indicator, connection_name, questionmarks, st, query, args);
+	va_end(args);
+
+	del_cursor(con, curname);
+
+	return ret;
+}
diff --git a/src/interfaces/ecpg/ecpglib/data.c b/src/interfaces/ecpg/ecpglib/data.c
index fc04556..6cbf327 100644
--- a/src/interfaces/ecpg/ecpglib/data.c
+++ b/src/interfaces/ecpg/ecpglib/data.c
@@ -120,7 +120,7 @@ check_special_value(char *ptr, double *retval, char **endptr)
 }
 
 bool
-ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
+ecpg_get_data(const PGresult *results, int var_index, int act_tuple, int act_field, int lineno,
 			  enum ECPGttype type, enum ECPGttype ind_type,
 			  char *var, char *ind, long varcharsize, long offset,
 			  long ind_offset, enum ARRAY_TYPE isarray, enum COMPAT_MODE compat, bool force_indicator)
@@ -167,20 +167,20 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 	{
 		case ECPGt_short:
 		case ECPGt_unsigned_short:
-			*((short *) (ind + ind_offset * act_tuple)) = value_for_indicator;
+			*((short *) (ind + ind_offset * var_index)) = value_for_indicator;
 			break;
 		case ECPGt_int:
 		case ECPGt_unsigned_int:
-			*((int *) (ind + ind_offset * act_tuple)) = value_for_indicator;
+			*((int *) (ind + ind_offset * var_index)) = value_for_indicator;
 			break;
 		case ECPGt_long:
 		case ECPGt_unsigned_long:
-			*((long *) (ind + ind_offset * act_tuple)) = value_for_indicator;
+			*((long *) (ind + ind_offset * var_index)) = value_for_indicator;
 			break;
 #ifdef HAVE_LONG_LONG_INT
 		case ECPGt_long_long:
 		case ECPGt_unsigned_long_long:
-			*((long long int *) (ind + ind_offset * act_tuple)) = value_for_indicator;
+			*((long long int *) (ind + ind_offset * var_index)) = value_for_indicator;
 			break;
 #endif   /* HAVE_LONG_LONG_INT */
 		case ECPGt_NO_INDICATOR:
@@ -192,7 +192,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					 * Informix has an additional way to specify NULLs note
 					 * that this uses special values to denote NULL
 					 */
-					ECPGset_noind_null(type, var + offset * act_tuple);
+					ECPGset_noind_null(type, var + offset * var_index);
 				}
 				else
 				{
@@ -243,10 +243,10 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 		if (binary)
 		{
 			if (varcharsize == 0 || varcharsize * offset >= size)
-				memcpy(var + offset * act_tuple, pval, size);
+				memcpy(var + offset * var_index, pval, size);
 			else
 			{
-				memcpy(var + offset * act_tuple, pval, varcharsize * offset);
+				memcpy(var + offset * var_index, pval, varcharsize * offset);
 
 				if (varcharsize * offset < size)
 				{
@@ -255,20 +255,20 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					{
 						case ECPGt_short:
 						case ECPGt_unsigned_short:
-							*((short *) (ind + ind_offset * act_tuple)) = size;
+							*((short *) (ind + ind_offset * var_index)) = size;
 							break;
 						case ECPGt_int:
 						case ECPGt_unsigned_int:
-							*((int *) (ind + ind_offset * act_tuple)) = size;
+							*((int *) (ind + ind_offset * var_index)) = size;
 							break;
 						case ECPGt_long:
 						case ECPGt_unsigned_long:
-							*((long *) (ind + ind_offset * act_tuple)) = size;
+							*((long *) (ind + ind_offset * var_index)) = size;
 							break;
 #ifdef HAVE_LONG_LONG_INT
 						case ECPGt_long_long:
 						case ECPGt_unsigned_long_long:
-							*((long long int *) (ind + ind_offset * act_tuple)) = size;
+							*((long long int *) (ind + ind_offset * var_index)) = size;
 							break;
 #endif   /* HAVE_LONG_LONG_INT */
 						default:
@@ -307,13 +307,13 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					switch (type)
 					{
 						case ECPGt_short:
-							*((short *) (var + offset * act_tuple)) = (short) res;
+							*((short *) (var + offset * var_index)) = (short) res;
 							break;
 						case ECPGt_int:
-							*((int *) (var + offset * act_tuple)) = (int) res;
+							*((int *) (var + offset * var_index)) = (int) res;
 							break;
 						case ECPGt_long:
-							*((long *) (var + offset * act_tuple)) = (long) res;
+							*((long *) (var + offset * var_index)) = (long) res;
 							break;
 						default:
 							/* Cannot happen */
@@ -336,13 +336,13 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					switch (type)
 					{
 						case ECPGt_unsigned_short:
-							*((unsigned short *) (var + offset * act_tuple)) = (unsigned short) ures;
+							*((unsigned short *) (var + offset * var_index)) = (unsigned short) ures;
 							break;
 						case ECPGt_unsigned_int:
-							*((unsigned int *) (var + offset * act_tuple)) = (unsigned int) ures;
+							*((unsigned int *) (var + offset * var_index)) = (unsigned int) ures;
 							break;
 						case ECPGt_unsigned_long:
-							*((unsigned long *) (var + offset * act_tuple)) = (unsigned long) ures;
+							*((unsigned long *) (var + offset * var_index)) = (unsigned long) ures;
 							break;
 						default:
 							/* Cannot happen */
@@ -353,7 +353,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 #ifdef HAVE_LONG_LONG_INT
 #ifdef HAVE_STRTOLL
 				case ECPGt_long_long:
-					*((long long int *) (var + offset * act_tuple)) = strtoll(pval, &scan_length, 10);
+					*((long long int *) (var + offset * var_index)) = strtoll(pval, &scan_length, 10);
 					if (garbage_left(isarray, scan_length, compat))
 					{
 						ecpg_raise(lineno, ECPG_INT_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
@@ -365,7 +365,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 #endif   /* HAVE_STRTOLL */
 #ifdef HAVE_STRTOULL
 				case ECPGt_unsigned_long_long:
-					*((unsigned long long int *) (var + offset * act_tuple)) = strtoull(pval, &scan_length, 10);
+					*((unsigned long long int *) (var + offset * var_index)) = strtoull(pval, &scan_length, 10);
 					if ((isarray && *scan_length != ',' && *scan_length != '}')
 						|| (!isarray && !(INFORMIX_MODE(compat) && *scan_length == '.') && *scan_length != '\0' && *scan_length != ' '))		/* Garbage left */
 					{
@@ -400,10 +400,10 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					switch (type)
 					{
 						case ECPGt_float:
-							*((float *) (var + offset * act_tuple)) = dres;
+							*((float *) (var + offset * var_index)) = dres;
 							break;
 						case ECPGt_double:
-							*((double *) (var + offset * act_tuple)) = dres;
+							*((double *) (var + offset * var_index)) = dres;
 							break;
 						default:
 							/* Cannot happen */
@@ -415,9 +415,9 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					if (pval[0] == 'f' && pval[1] == '\0')
 					{
 						if (offset == sizeof(char))
-							*((char *) (var + offset * act_tuple)) = false;
+							*((char *) (var + offset * var_index)) = false;
 						else if (offset == sizeof(int))
-							*((int *) (var + offset * act_tuple)) = false;
+							*((int *) (var + offset * var_index)) = false;
 						else
 							ecpg_raise(lineno, ECPG_CONVERT_BOOL,
 									   ECPG_SQLSTATE_DATATYPE_MISMATCH,
@@ -427,16 +427,16 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					else if (pval[0] == 't' && pval[1] == '\0')
 					{
 						if (offset == sizeof(char))
-							*((char *) (var + offset * act_tuple)) = true;
+							*((char *) (var + offset * var_index)) = true;
 						else if (offset == sizeof(int))
-							*((int *) (var + offset * act_tuple)) = true;
+							*((int *) (var + offset * var_index)) = true;
 						else
 							ecpg_raise(lineno, ECPG_CONVERT_BOOL,
 									   ECPG_SQLSTATE_DATATYPE_MISMATCH,
 									   NULL);
 						break;
 					}
-					else if (pval[0] == '\0' && PQgetisnull(results, act_tuple, act_field))
+					else if (pval[0] == '\0' && PQgetisnull(results, var_index, act_field))
 					{
 						/* NULL is valid */
 						break;
@@ -451,7 +451,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 				case ECPGt_unsigned_char:
 				case ECPGt_string:
 					{
-						char	   *str = (char *) (var + offset * act_tuple);
+						char	   *str = (char *) (var + offset * var_index);
 
 						if (varcharsize == 0 || varcharsize > size)
 						{
@@ -479,20 +479,20 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 								{
 									case ECPGt_short:
 									case ECPGt_unsigned_short:
-										*((short *) (ind + ind_offset * act_tuple)) = size;
+										*((short *) (ind + ind_offset * var_index)) = size;
 										break;
 									case ECPGt_int:
 									case ECPGt_unsigned_int:
-										*((int *) (ind + ind_offset * act_tuple)) = size;
+										*((int *) (ind + ind_offset * var_index)) = size;
 										break;
 									case ECPGt_long:
 									case ECPGt_unsigned_long:
-										*((long *) (ind + ind_offset * act_tuple)) = size;
+										*((long *) (ind + ind_offset * var_index)) = size;
 										break;
 #ifdef HAVE_LONG_LONG_INT
 									case ECPGt_long_long:
 									case ECPGt_unsigned_long_long:
-										*((long long int *) (ind + ind_offset * act_tuple)) = size;
+										*((long long int *) (ind + ind_offset * var_index)) = size;
 										break;
 #endif   /* HAVE_LONG_LONG_INT */
 									default:
@@ -508,7 +508,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 				case ECPGt_varchar:
 					{
 						struct ECPGgeneric_varchar *variable =
-						(struct ECPGgeneric_varchar *) (var + offset * act_tuple);
+						(struct ECPGgeneric_varchar *) (var + offset * var_index);
 
 						variable->len = size;
 						if (varcharsize == 0)
@@ -524,20 +524,20 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 								{
 									case ECPGt_short:
 									case ECPGt_unsigned_short:
-										*((short *) (ind + offset * act_tuple)) = variable->len;
+										*((short *) (ind + offset * var_index)) = variable->len;
 										break;
 									case ECPGt_int:
 									case ECPGt_unsigned_int:
-										*((int *) (ind + offset * act_tuple)) = variable->len;
+										*((int *) (ind + offset * var_index)) = variable->len;
 										break;
 									case ECPGt_long:
 									case ECPGt_unsigned_long:
-										*((long *) (ind + offset * act_tuple)) = variable->len;
+										*((long *) (ind + offset * var_index)) = variable->len;
 										break;
 #ifdef HAVE_LONG_LONG_INT
 									case ECPGt_long_long:
 									case ECPGt_unsigned_long_long:
-										*((long long int *) (ind + ind_offset * act_tuple)) = variable->len;
+										*((long long int *) (ind + ind_offset * var_index)) = variable->len;
 										break;
 #endif   /* HAVE_LONG_LONG_INT */
 									default:
@@ -604,9 +604,9 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					pval = scan_length;
 
 					if (type == ECPGt_numeric)
-						PGTYPESnumeric_copy(nres, (numeric *) (var + offset * act_tuple));
+						PGTYPESnumeric_copy(nres, (numeric *) (var + offset * var_index));
 					else
-						PGTYPESnumeric_to_decimal(nres, (decimal *) (var + offset * act_tuple));
+						PGTYPESnumeric_to_decimal(nres, (decimal *) (var + offset * var_index));
 
 					PGTYPESnumeric_free(nres);
 					break;
@@ -657,7 +657,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					}
 					pval = scan_length;
 
-					PGTYPESinterval_copy(ires, (interval *) (var + offset * act_tuple));
+					PGTYPESinterval_copy(ires, (interval *) (var + offset * var_index));
 					free(ires);
 					break;
 
@@ -701,7 +701,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 						}
 					}
 
-					*((date *) (var + offset * act_tuple)) = ddres;
+					*((date *) (var + offset * var_index)) = ddres;
 					pval = scan_length;
 					break;
 
@@ -745,7 +745,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 						}
 					}
 
-					*((timestamp *) (var + offset * act_tuple)) = tres;
+					*((timestamp *) (var + offset * var_index)) = tres;
 					pval = scan_length;
 					break;
 
@@ -762,6 +762,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 
 				/* set array to next entry */
 				++act_tuple;
+				++var_index;
 
 				/* set pval to the next entry */
 
diff --git a/src/interfaces/ecpg/ecpglib/descriptor.c b/src/interfaces/ecpg/ecpglib/descriptor.c
index 17a956e..2ec4922 100644
--- a/src/interfaces/ecpg/ecpglib/descriptor.c
+++ b/src/interfaces/ecpg/ecpglib/descriptor.c
@@ -438,7 +438,7 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...)
 
 		/* desparate try to guess something sensible */
 		stmt.connection = ecpg_get_connection(NULL);
-		ecpg_store_result(ECPGresult, index, &stmt, &data_var);
+		ecpg_store_result(ECPGresult, 0, PQntuples(ECPGresult), index, &stmt, &data_var, 0);
 
 		setlocale(LC_NUMERIC, oldlocale);
 		ecpg_free(oldlocale);
diff --git a/src/interfaces/ecpg/ecpglib/error.c b/src/interfaces/ecpg/ecpglib/error.c
index ee553fd..7148f50 100644
--- a/src/interfaces/ecpg/ecpglib/error.c
+++ b/src/interfaces/ecpg/ecpglib/error.c
@@ -268,6 +268,20 @@ ecpg_raise(int line, int code, const char *sqlstate, const char *str)
 					 ecpg_gettext("could not connect to database \"%s\" on line %d"), str, line);
 			break;
 
+		case ECPG_INVALID_CURSOR:
+			if (strcmp(sqlstate, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE) == 0)
+				snprintf(sqlca->sqlerrm.sqlerrmc, sizeof(sqlca->sqlerrm.sqlerrmc),
+
+			/*
+			 * translator: this string will be truncated at 149 characters
+			 * expanded.
+			 */
+				ecpg_gettext("cursor can only scan forward"));
+			else
+				snprintf(sqlca->sqlerrm.sqlerrmc, sizeof(sqlca->sqlerrm.sqlerrmc), str);
+
+			break;
+
 		default:
 			snprintf(sqlca->sqlerrm.sqlerrmc, sizeof(sqlca->sqlerrm.sqlerrmc),
 
diff --git a/src/interfaces/ecpg/ecpglib/execute.c b/src/interfaces/ecpg/ecpglib/execute.c
index b8e48a3..12f34f6 100644
--- a/src/interfaces/ecpg/ecpglib/execute.c
+++ b/src/interfaces/ecpg/ecpglib/execute.c
@@ -109,6 +109,7 @@ free_statement(struct statement * stmt)
 	free_variable(stmt->outlist);
 	ecpg_free(stmt->command);
 	ecpg_free(stmt->name);
+	ecpg_free(stmt->oldlocale);
 	ecpg_free(stmt);
 }
 
@@ -311,12 +312,12 @@ ecpg_is_type_an_array(int type, const struct statement * stmt, const struct vari
 
 
 bool
-ecpg_store_result(const PGresult *results, int act_field,
-				  const struct statement * stmt, struct variable * var)
+ecpg_store_result(const PGresult *results, int start, int end, int act_field,
+				  const struct statement * stmt, struct variable * var, int var_index)
 {
 	enum ARRAY_TYPE isarray;
 	int			act_tuple,
-				ntuples = PQntuples(results);
+				ntuples = end - start;
 	bool		status = true;
 
 	if ((isarray = ecpg_is_type_an_array(PQftype(results, act_field), stmt, var)) == ECPG_ARRAY_ERROR)
@@ -367,7 +368,7 @@ ecpg_store_result(const PGresult *results, int act_field,
 					if (!var->varcharsize && !var->arrsize)
 					{
 						/* special mode for handling char**foo=0 */
-						for (act_tuple = 0; act_tuple < ntuples; act_tuple++)
+						for (act_tuple = start; act_tuple < end; act_tuple++)
 							len += strlen(PQgetvalue(results, act_tuple, act_field)) + 1;
 						len *= var->offset;		/* should be 1, but YMNK */
 						len += (ntuples + 1) * sizeof(char *);
@@ -376,7 +377,7 @@ ecpg_store_result(const PGresult *results, int act_field,
 					{
 						var->varcharsize = 0;
 						/* check strlen for each tuple */
-						for (act_tuple = 0; act_tuple < ntuples; act_tuple++)
+						for (act_tuple = start; act_tuple < end; act_tuple++)
 						{
 							int			len = strlen(PQgetvalue(results, act_tuple, act_field)) + 1;
 
@@ -397,7 +398,7 @@ ecpg_store_result(const PGresult *results, int act_field,
 		}
 		else
 		{
-			for (act_tuple = 0; act_tuple < ntuples; act_tuple++)
+			for (act_tuple = start; act_tuple < end; act_tuple++)
 				len += PQgetlength(results, act_tuple, act_field);
 		}
 
@@ -433,11 +434,11 @@ ecpg_store_result(const PGresult *results, int act_field,
 		/* storing the data (after the last array element) */
 		char	   *current_data_location = (char *) &current_string[ntuples + 1];
 
-		for (act_tuple = 0; act_tuple < ntuples && status; act_tuple++)
+		for (act_tuple = start; act_tuple < end && status; act_tuple++, var_index++)
 		{
 			int			len = strlen(PQgetvalue(results, act_tuple, act_field)) + 1;
 
-			if (!ecpg_get_data(results, act_tuple, act_field, stmt->lineno,
+			if (!ecpg_get_data(results, var_index, act_tuple, act_field, stmt->lineno,
 							 var->type, var->ind_type, current_data_location,
 							   var->ind_value, len, 0, var->ind_offset, isarray, stmt->compat, stmt->force_indicator))
 				status = false;
@@ -454,9 +455,9 @@ ecpg_store_result(const PGresult *results, int act_field,
 	}
 	else
 	{
-		for (act_tuple = 0; act_tuple < ntuples && status; act_tuple++)
+		for (act_tuple = start; act_tuple < end && status; act_tuple++, var_index++)
 		{
-			if (!ecpg_get_data(results, act_tuple, act_field, stmt->lineno,
+			if (!ecpg_get_data(results, var_index, act_tuple, act_field, stmt->lineno,
 							   var->type, var->ind_type, var->value,
 							   var->ind_value, var->varcharsize, var->offset, var->ind_offset, isarray, stmt->compat, stmt->force_indicator))
 				status = false;
@@ -1082,18 +1083,26 @@ ecpg_store_input(const int lineno, const bool force_indicator, const struct vari
 	return true;
 }
 
-static void
-free_params(const char **paramValues, int nParams, bool print, int lineno)
+void
+ecpg_free_params(struct statement *stmt, bool print, int lineno)
 {
 	int			n;
 
-	for (n = 0; n < nParams; n++)
+	for (n = 0; n < stmt->nparams; n++)
 	{
 		if (print)
-			ecpg_log("free_params on line %d: parameter %d = %s\n", lineno, n + 1, paramValues[n] ? paramValues[n] : "null");
-		ecpg_free((void *) (paramValues[n]));
+			ecpg_log("free_params on line %d: parameter %d = %s\n", lineno, n + 1, stmt->param_values[n] ? stmt->param_values[n] : "null");
+		ecpg_free((void *) (stmt->param_values[n]));
 	}
-	ecpg_free(paramValues);
+	ecpg_free(stmt->param_values);
+	stmt->nparams = 0;
+	stmt->param_values = NULL;
+
+	for (n = 0; n < stmt->ndollarzero; n++)
+		ecpg_free((void *) (stmt->dollarzero[n]));
+	ecpg_free(stmt->dollarzero);
+	stmt->ndollarzero = 0;
+	stmt->dollarzero = NULL;
 }
 
 
@@ -1129,20 +1138,12 @@ insert_tobeinserted(int position, int ph_len, struct statement * stmt, char *tob
 	return true;
 }
 
-static bool
-ecpg_execute(struct statement * stmt)
+bool
+ecpg_build_params(struct statement * stmt)
 {
-	bool		status = false;
-	char	   *cmdstat;
-	PGresult   *results;
-	PGnotify   *notify;
 	struct variable *var;
 	int			desc_counter = 0;
-	const char **paramValues = NULL;
-	int			nParams = 0;
 	int			position = 0;
-	struct sqlca_t *sqlca = ECPGget_sqlca();
-	bool		clear_result = true;
 
 	/*
 	 * If the type is one of the fill in types then we take the argument and
@@ -1342,7 +1343,7 @@ ecpg_execute(struct statement * stmt)
 			ecpg_raise(stmt->lineno, ECPG_TOO_MANY_ARGUMENTS,
 					   ECPG_SQLSTATE_USING_CLAUSE_DOES_NOT_MATCH_PARAMETERS,
 					   NULL);
-			free_params(paramValues, nParams, false, stmt->lineno);
+			ecpg_free_params(stmt, false, stmt->lineno);
 			return false;
 		}
 
@@ -1357,7 +1358,7 @@ ecpg_execute(struct statement * stmt)
 
 			if (!insert_tobeinserted(position, ph_len, stmt, tobeinserted))
 			{
-				free_params(paramValues, nParams, false, stmt->lineno);
+				ecpg_free_params(stmt, false, stmt->lineno);
 				return false;
 			}
 			tobeinserted = NULL;
@@ -1370,23 +1371,38 @@ ecpg_execute(struct statement * stmt)
 		 */
 		else if (stmt->command[position] == '0')
 		{
+			const char **dollarzero;
+
+			if (!(dollarzero = (const char **) ecpg_realloc(stmt->dollarzero, sizeof(const char *) * (stmt->ndollarzero + 1), stmt->lineno)))
+			{
+				ecpg_free_params(stmt, false, stmt->lineno);
+				return false;
+			}
+			stmt->ndollarzero++;
+			stmt->dollarzero = dollarzero;
+			stmt->dollarzero[stmt->ndollarzero - 1] = strdup(tobeinserted);
+
 			if (!insert_tobeinserted(position, 2, stmt, tobeinserted))
 			{
-				free_params(paramValues, nParams, false, stmt->lineno);
+				ecpg_free_params(stmt, false, stmt->lineno);
 				return false;
 			}
 			tobeinserted = NULL;
 		}
 		else
 		{
-			nParams++;
-			if (!(paramValues = (const char **) ecpg_realloc(paramValues, sizeof(const char *) * nParams, stmt->lineno)))
+			const char **paramValues;
+		
+			if (!(paramValues = (const char **) ecpg_realloc(stmt->param_values, sizeof(const char *) * (stmt->nparams + 1), stmt->lineno)))
 			{
 				ecpg_free(paramValues);
 				return false;
 			}
 
-			paramValues[nParams - 1] = tobeinserted;
+			stmt->nparams++;
+			stmt->param_values = paramValues;
+
+			stmt->param_values[stmt->nparams - 1] = tobeinserted;
 
 			/* let's see if this was an old style placeholder */
 			if (stmt->command[position] == '?')
@@ -1397,7 +1413,7 @@ ecpg_execute(struct statement * stmt)
 
 				if (!(tobeinserted = (char *) ecpg_alloc(buffersize, stmt->lineno)))
 				{
-					free_params(paramValues, nParams, false, stmt->lineno);
+					ecpg_free_params(stmt, false, stmt->lineno);
 					return false;
 				}
 
@@ -1405,7 +1421,7 @@ ecpg_execute(struct statement * stmt)
 
 				if (!insert_tobeinserted(position, 2, stmt, tobeinserted))
 				{
-					free_params(paramValues, nParams, false, stmt->lineno);
+					ecpg_free_params(stmt, false, stmt->lineno);
 					return false;
 				}
 				tobeinserted = NULL;
@@ -1421,58 +1437,76 @@ ecpg_execute(struct statement * stmt)
 	{
 		ecpg_raise(stmt->lineno, ECPG_TOO_FEW_ARGUMENTS,
 				 ECPG_SQLSTATE_USING_CLAUSE_DOES_NOT_MATCH_PARAMETERS, NULL);
-		free_params(paramValues, nParams, false, stmt->lineno);
+		ecpg_free_params(stmt, false, stmt->lineno);
 		return false;
 	}
 
-	/* The request has been build. */
+	return true;
+}
+
 
+static bool
+ecpg_execute(struct statement * stmt)
+{
 	if (PQtransactionStatus(stmt->connection->connection) == PQTRANS_IDLE && !stmt->connection->autocommit)
 	{
-		results = PQexec(stmt->connection->connection, "begin transaction");
-		if (!ecpg_check_PQresult(results, stmt->lineno, stmt->connection->connection, stmt->compat))
+		stmt->results = PQexec(stmt->connection->connection, "begin transaction");
+		if (!ecpg_check_PQresult(stmt->results, stmt->lineno, stmt->connection->connection, stmt->compat))
 		{
-			free_params(paramValues, nParams, false, stmt->lineno);
+			ecpg_free_params(stmt, false, stmt->lineno);
 			return false;
 		}
-		PQclear(results);
+		PQclear(stmt->results);
+		stmt->results = NULL;
 	}
 
-	ecpg_log("ecpg_execute on line %d: query: %s; with %d parameter(s) on connection %s\n", stmt->lineno, stmt->command, nParams, stmt->connection->name);
+	ecpg_log("ecpg_execute on line %d: query: %s; with %d parameter(s) on connection %s\n", stmt->lineno, stmt->command, stmt->nparams, stmt->connection->name);
 	if (stmt->statement_type == ECPGst_execute)
 	{
-		results = PQexecPrepared(stmt->connection->connection, stmt->name, nParams, paramValues, NULL, NULL, 0);
+		stmt->results = PQexecPrepared(stmt->connection->connection, stmt->name, stmt->nparams, stmt->param_values, NULL, NULL, 0);
 		ecpg_log("ecpg_execute on line %d: using PQexecPrepared for \"%s\"\n", stmt->lineno, stmt->command);
 	}
 	else
 	{
-		if (nParams == 0)
+		if (stmt->nparams == 0)
 		{
-			results = PQexec(stmt->connection->connection, stmt->command);
+			stmt->results = PQexec(stmt->connection->connection, stmt->command);
 			ecpg_log("ecpg_execute on line %d: using PQexec\n", stmt->lineno);
 		}
 		else
 		{
-			results = PQexecParams(stmt->connection->connection, stmt->command, nParams, NULL, paramValues, NULL, NULL, 0);
+			stmt->results = PQexecParams(stmt->connection->connection, stmt->command, stmt->nparams, NULL, stmt->param_values, NULL, NULL, 0);
 			ecpg_log("ecpg_execute on line %d: using PQexecParams\n", stmt->lineno);
 		}
 	}
 
-	free_params(paramValues, nParams, true, stmt->lineno);
+	ecpg_free_params(stmt, true, stmt->lineno);
 
-	if (!ecpg_check_PQresult(results, stmt->lineno, stmt->connection->connection, stmt->compat))
+	if (!ecpg_check_PQresult(stmt->results, stmt->lineno, stmt->connection->connection, stmt->compat))
 		return (false);
 
+	return (true);
+}
+
+bool
+ecpg_process_output(struct statement * stmt, int start, int end, int var_index, bool keep_result, bool append_result)
+{
+	char		*cmdstat;
+	PGnotify	*notify;
+	bool		status = false;
+	struct variable *var;
+	struct sqlca_t *sqlca = ECPGget_sqlca();
+
 	var = stmt->outlist;
-	switch (PQresultStatus(results))
+	switch (PQresultStatus(stmt->results))
 	{
 			int			nfields,
 						ntuples,
 						act_field;
 
 		case PGRES_TUPLES_OK:
-			nfields = PQnfields(results);
-			sqlca->sqlerrd[2] = ntuples = PQntuples(results);
+			nfields = PQnfields(stmt->results);
+			sqlca->sqlerrd[2] += ntuples = (end - start);
 			ecpg_log("ecpg_execute on line %d: correctly got %d tuples with %d fields\n", stmt->lineno, ntuples, nfields);
 			status = true;
 
@@ -1494,12 +1528,34 @@ ecpg_execute(struct statement * stmt)
 					status = false;
 				else
 				{
-					if (desc->result)
-						PQclear(desc->result);
-					desc->result = results;
-					clear_result = false;
+					int	row, srcrow, col;
+
+					if (append_result)
+						row = PQntuples(desc->result);
+					else
+					{
+						if (desc->result)
+							PQclear(desc->result);
+						desc->result = PQcopyResult(stmt->results, PG_COPYRES_ATTRS | PG_COPYRES_EVENTS | PG_COPYRES_NOTICEHOOKS);
+						row = 0;
+					}
+
+					for (srcrow = start; srcrow < end; srcrow++, row++)
+						for (col = 0; col < nfields; col++)
+						{
+							bool	isnull = PQgetisnull(stmt->results, srcrow, col);
+							if (!PQsetvalue(desc->result, row, col,
+										isnull ? NULL : PQgetvalue(stmt->results, srcrow, col),
+										isnull ? -1 : PQgetlength(stmt->results, srcrow, col)))
+							{
+								ecpg_raise(stmt->lineno, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
+								status = false;
+								break;
+							}
+						}
+
 					ecpg_log("ecpg_execute on line %d: putting result (%d tuples) into descriptor %s\n",
-							 stmt->lineno, PQntuples(results), (const char *) var->pointer);
+							 stmt->lineno, PQntuples(stmt->results), (const char *) var->pointer);
 				}
 				var = var->next;
 			}
@@ -1509,36 +1565,52 @@ ecpg_execute(struct statement * stmt)
 				{
 					struct sqlda_compat **_sqlda = (struct sqlda_compat **) var->pointer;
 					struct sqlda_compat *sqlda = *_sqlda;
-					struct sqlda_compat *sqlda_new;
+					struct sqlda_compat *sqlda_last, *sqlda_new = NULL;
 					int			i;
 
-					/*
-					 * If we are passed in a previously existing sqlda (chain)
-					 * then free it.
-					 */
-					while (sqlda)
+					if (append_result)
 					{
-						sqlda_new = sqlda->desc_next;
-						free(sqlda);
-						sqlda = sqlda_new;
+						sqlda_last = sqlda;
+						while (sqlda_last && sqlda_last->desc_next)
+							sqlda_last = sqlda_last->desc_next;
 					}
-					*_sqlda = sqlda = sqlda_new = NULL;
-					for (i = ntuples - 1; i >= 0; i--)
+					else
+					{
+						/*
+						 * If we are passed in a previously existing sqlda (chain)
+						 * then free it.
+						 */
+						while (sqlda)
+						{
+							sqlda_last = sqlda->desc_next;
+							free(sqlda);
+							sqlda = sqlda_last;
+						}
+						*_sqlda = sqlda = sqlda_last = NULL;
+					}
+					for (i = end - 1; i >= start; i--)
 					{
+						struct sqlda_compat *tmp;
+
 						/*
-						 * Build a new sqlda structure. Note that only
-						 * fetching 1 record is supported
+						 * Build a new sqlda structure. 
 						 */
-						sqlda_new = ecpg_build_compat_sqlda(stmt->lineno, results, i, stmt->compat);
+						tmp = ecpg_build_compat_sqlda(stmt->lineno, stmt->results, i, stmt->compat);
 
-						if (!sqlda_new)
+						if (!tmp)
 						{
 							/* cleanup all SQLDAs we created up */
+							while (sqlda_new)
+							{
+								tmp = sqlda_new->desc_next;
+								free(sqlda_new);
+								sqlda_new = tmp;
+							}
 							while (sqlda)
 							{
-								sqlda_new = sqlda->desc_next;
+								tmp = sqlda->desc_next;
 								free(sqlda);
-								sqlda = sqlda_new;
+								sqlda = tmp;
 							}
 							*_sqlda = NULL;
 
@@ -1550,51 +1622,74 @@ ecpg_execute(struct statement * stmt)
 						{
 							ecpg_log("ecpg_execute on line %d: new sqlda was built\n", stmt->lineno);
 
-							*_sqlda = sqlda_new;
+							if (sqlda_new == NULL)
+								sqlda_new = tmp;
+							else
+							{
+								tmp->desc_next = sqlda_new;
+								sqlda_new = tmp;
+							}
 
-							ecpg_set_compat_sqlda(stmt->lineno, _sqlda, results, i, stmt->compat);
+							ecpg_set_compat_sqlda(stmt->lineno, &tmp, stmt->results, i, stmt->compat);
 							ecpg_log("ecpg_execute on line %d: putting result (1 tuple %d fields) into sqlda descriptor\n",
-									 stmt->lineno, PQnfields(results));
-
-							sqlda_new->desc_next = sqlda;
-							sqlda = sqlda_new;
+									 stmt->lineno, PQnfields(stmt->results));
 						}
 					}
+					if (sqlda_last)
+						sqlda_last->desc_next = sqlda_new;
+					else
+						*_sqlda = sqlda_new;
 				}
 				else
 				{
 					struct sqlda_struct **_sqlda = (struct sqlda_struct **) var->pointer;
 					struct sqlda_struct *sqlda = *_sqlda;
-					struct sqlda_struct *sqlda_new;
+					struct sqlda_struct *sqlda_last, *sqlda_new = NULL;
 					int			i;
 
-					/*
-					 * If we are passed in a previously existing sqlda (chain)
-					 * then free it.
-					 */
-					while (sqlda)
+					if (append_result)
 					{
-						sqlda_new = sqlda->desc_next;
-						free(sqlda);
-						sqlda = sqlda_new;
+						sqlda_last = sqlda;
+						while (sqlda_last && sqlda_last->desc_next)
+							sqlda_last = sqlda_last->desc_next;
 					}
-					*_sqlda = sqlda = sqlda_new = NULL;
-					for (i = ntuples - 1; i >= 0; i--)
+					else
 					{
 						/*
-						 * Build a new sqlda structure. Note that only
-						 * fetching 1 record is supported
+						 * If we are passed in a previously existing sqlda (chain)
+						 * then free it.
 						 */
-						sqlda_new = ecpg_build_native_sqlda(stmt->lineno, results, i, stmt->compat);
+						while (sqlda)
+						{
+							sqlda_last = sqlda->desc_next;
+							free(sqlda);
+							sqlda = sqlda_last;
+						}
+						*_sqlda = sqlda = sqlda_last = NULL;
+					}
+					for (i = end - 1; i >= start; i--)
+					{
+						struct sqlda_struct *tmp;
+
+						/*
+						 * Build a new sqlda structure. 
+						 */
+						tmp = ecpg_build_native_sqlda(stmt->lineno, stmt->results, i, stmt->compat);
 
-						if (!sqlda_new)
+						if (!tmp)
 						{
 							/* cleanup all SQLDAs we created up */
+							while (sqlda_new)
+							{
+								tmp = sqlda_new->desc_next;
+								free(sqlda_new);
+								sqlda_new = tmp;
+							}
 							while (sqlda)
 							{
-								sqlda_new = sqlda->desc_next;
+								tmp = sqlda->desc_next;
 								free(sqlda);
-								sqlda = sqlda_new;
+								sqlda = tmp;
 							}
 							*_sqlda = NULL;
 
@@ -1606,16 +1701,23 @@ ecpg_execute(struct statement * stmt)
 						{
 							ecpg_log("ecpg_execute on line %d: new sqlda was built\n", stmt->lineno);
 
-							*_sqlda = sqlda_new;
+							if (sqlda_new == NULL)
+								sqlda_new = tmp;
+							else
+							{
+								tmp->desc_next = sqlda_new;
+								sqlda_new = tmp;
+							}
 
-							ecpg_set_native_sqlda(stmt->lineno, _sqlda, results, i, stmt->compat);
+							ecpg_set_native_sqlda(stmt->lineno, &tmp, stmt->results, i, stmt->compat);
 							ecpg_log("ecpg_execute on line %d: putting result (1 tuple %d fields) into sqlda descriptor\n",
-									 stmt->lineno, PQnfields(results));
-
-							sqlda_new->desc_next = sqlda;
-							sqlda = sqlda_new;
+									 stmt->lineno, PQnfields(stmt->results));
 						}
 					}
+					if (sqlda_last)
+						sqlda_last->desc_next = sqlda_new;
+					else
+						*_sqlda = sqlda_new;
 				}
 
 				var = var->next;
@@ -1625,7 +1727,7 @@ ecpg_execute(struct statement * stmt)
 				{
 					if (var != NULL)
 					{
-						status = ecpg_store_result(results, act_field, stmt, var);
+						status = ecpg_store_result(stmt->results, start, end, act_field, stmt, var, var_index);
 						var = var->next;
 					}
 					else if (!INFORMIX_MODE(stmt->compat))
@@ -1644,9 +1746,9 @@ ecpg_execute(struct statement * stmt)
 			break;
 		case PGRES_COMMAND_OK:
 			status = true;
-			cmdstat = PQcmdStatus(results);
-			sqlca->sqlerrd[1] = PQoidValue(results);
-			sqlca->sqlerrd[2] = atol(PQcmdTuples(results));
+			cmdstat = PQcmdStatus(stmt->results);
+			sqlca->sqlerrd[1] = PQoidValue(stmt->results);
+			sqlca->sqlerrd[2] = atol(PQcmdTuples(stmt->results));
 			ecpg_log("ecpg_execute on line %d: OK: %s\n", stmt->lineno, cmdstat);
 			if (stmt->compat != ECPG_COMPAT_INFORMIX_SE &&
 				!sqlca->sqlerrd[2] &&
@@ -1670,12 +1772,12 @@ ecpg_execute(struct statement * stmt)
 				if (res == -1)
 				{
 					/* COPY done */
-					PQclear(results);
-					results = PQgetResult(stmt->connection->connection);
-					if (PQresultStatus(results) == PGRES_COMMAND_OK)
+					PQclear(stmt->results);
+					stmt->results = PQgetResult(stmt->connection->connection);
+					if (PQresultStatus(stmt->results) == PGRES_COMMAND_OK)
 						ecpg_log("ecpg_execute on line %d: got PGRES_COMMAND_OK after PGRES_COPY_OUT\n", stmt->lineno);
 					else
-						ecpg_log("ecpg_execute on line %d: got error after PGRES_COPY_OUT: %s", stmt->lineno, PQresultErrorMessage(results));
+						ecpg_log("ecpg_execute on line %d: got error after PGRES_COPY_OUT: %s", stmt->lineno, PQresultErrorMessage(stmt->results));
 				}
 				break;
 			}
@@ -1687,12 +1789,12 @@ ecpg_execute(struct statement * stmt)
 			 */
 			ecpg_log("ecpg_execute on line %d: unknown execution status type\n",
 					 stmt->lineno);
-			ecpg_raise_backend(stmt->lineno, results, stmt->connection->connection, stmt->compat);
+			ecpg_raise_backend(stmt->lineno, stmt->results, stmt->connection->connection, stmt->compat);
 			status = false;
 			break;
 	}
-	if (clear_result)
-		PQclear(results);
+	if (!keep_result)
+		PQclear(stmt->results);
 
 	/* check for asynchronous returns */
 	notify = PQnotifies(stmt->connection->connection);
@@ -1707,46 +1809,20 @@ ecpg_execute(struct statement * stmt)
 }
 
 bool
-ECPGdo(const int lineno, const int compat, const int force_indicator, const char *connection_name, const bool questionmarks, const int st, const char *query,...)
+ecpg_do_prologue(int lineno, const int compat, const int force_indicator,
+		const char *connection_name, const bool questionmarks,
+		enum ECPG_statement_type statement_type, const char *query,
+		va_list args, struct statement **stmt_out)
 {
-	va_list		args;
 	struct statement *stmt;
 	struct connection *con;
-	bool		status;
-	char	   *oldlocale;
 	enum ECPGttype type;
 	struct variable **list;
-	enum ECPG_statement_type statement_type = (enum ECPG_statement_type) st;
-	char	   *prepname;
-
-	if (!query)
-	{
-		ecpg_raise(lineno, ECPG_EMPTY, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, NULL);
-		return (false);
-	}
-
-	/* Make sure we do NOT honor the locale for numeric input/output */
-	/* since the database wants the standard decimal point */
-	oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
-	setlocale(LC_NUMERIC, "C");
-
-#ifdef ENABLE_THREAD_SAFETY
-	ecpg_pthreads_init();
-#endif
-
-	con = ecpg_get_connection(connection_name);
-
-	if (!ecpg_init(con, connection_name, lineno))
-	{
-		setlocale(LC_NUMERIC, oldlocale);
-		ecpg_free(oldlocale);
-		return (false);
-	}
+	char	*prepname;
 
-	/* construct statement in our own structure */
-	va_start(args, query);
+	*stmt_out = NULL;
 
-	/*
+    	/*
 	 * create a list of variables The variables are listed with input
 	 * variables preceding outputvariables The end of each group is marked by
 	 * an end marker. per variable we list: type - as defined in ecpgtype.h
@@ -1759,11 +1835,24 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
 	 * arraysize of indicator array ind_offset - indicator offset
 	 */
 	if (!(stmt = (struct statement *) ecpg_alloc(sizeof(struct statement), lineno)))
-	{
-		setlocale(LC_NUMERIC, oldlocale);
-		ecpg_free(oldlocale);
-		va_end(args);
 		return false;
+
+	/* Make sure we do NOT honor the locale for numeric input/output */
+	/* since the database wants the standard decimal point */
+	stmt->oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
+	setlocale(LC_NUMERIC, "C");
+
+#ifdef ENABLE_THREAD_SAFETY
+	ecpg_pthreads_init();
+#endif
+
+	con = ecpg_get_connection(connection_name);
+
+	if (!ecpg_init(con, connection_name, lineno))
+	{
+		setlocale(LC_NUMERIC, stmt->oldlocale);
+		free_statement(stmt);
+		return (false);
 	}
 
 	/*
@@ -1774,9 +1863,8 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
 	{
 		if (!ecpg_auto_prepare(lineno, connection_name, compat, &prepname, query))
 		{
-			setlocale(LC_NUMERIC, oldlocale);
-			ecpg_free(oldlocale);
-			va_end(args);
+			setlocale(LC_NUMERIC, stmt->oldlocale);
+			free_statement(stmt);
 			return (false);
 		}
 
@@ -1805,9 +1893,8 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
 		else
 		{
 			ecpg_raise(lineno, ECPG_INVALID_STMT, ECPG_SQLSTATE_INVALID_SQL_STATEMENT_NAME, stmt->command);
-			setlocale(LC_NUMERIC, oldlocale);
-			ecpg_free(oldlocale);
-			va_end(args);
+			setlocale(LC_NUMERIC, stmt->oldlocale);
+			free_statement(stmt);
 			return (false);
 		}
 	}
@@ -1834,10 +1921,8 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
 
 			if (!(var = (struct variable *) ecpg_alloc(sizeof(struct variable), lineno)))
 			{
-				setlocale(LC_NUMERIC, oldlocale);
-				ecpg_free(oldlocale);
+				setlocale(LC_NUMERIC, stmt->oldlocale);
 				free_statement(stmt);
-				va_end(args);
 				return false;
 			}
 
@@ -1892,10 +1977,8 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
 			{
 				ecpg_raise(lineno, ECPG_INVALID_STMT, ECPG_SQLSTATE_INVALID_SQL_STATEMENT_NAME, NULL);
 				ecpg_free(var);
-				setlocale(LC_NUMERIC, oldlocale);
-				ecpg_free(oldlocale);
+				setlocale(LC_NUMERIC, stmt->oldlocale);
 				free_statement(stmt);
-				va_end(args);
 				return false;
 			}
 
@@ -1910,29 +1993,80 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
 		type = va_arg(args, enum ECPGttype);
 	}
 
-	va_end(args);
-
 	/* are we connected? */
 	if (con == NULL || con->connection == NULL)
 	{
-		free_statement(stmt);
 		ecpg_raise(lineno, ECPG_NOT_CONN, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, (con) ? con->name : ecpg_gettext("<empty>"));
-		setlocale(LC_NUMERIC, oldlocale);
-		ecpg_free(oldlocale);
+		setlocale(LC_NUMERIC, stmt->oldlocale);
+		free_statement(stmt);
 		return false;
 	}
 
 	/* initialize auto_mem struct */
 	ecpg_clear_auto_mem();
 
-	status = ecpg_execute(stmt);
+	*stmt_out = stmt;
+
+	return (true);
+}
+
+
+void
+ecpg_do_epilogue(struct statement *stmt)
+{
+	/* reset locale value so our application is not affected */
+	setlocale(LC_NUMERIC, stmt->oldlocale);
 	free_statement(stmt);
+}
+
+bool
+ecpg_do(const int lineno, const int compat, const int force_indicator, const char *connection_name, const bool questionmarks, const int st, const char *query, va_list args)
+{
+	struct statement *stmt;
+
+	if (!query)
+	{
+		ecpg_raise(lineno, ECPG_EMPTY, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, NULL);
+		return false;
+	}
 
-	/* and reset locale value so our application is not affected */
-	setlocale(LC_NUMERIC, oldlocale);
-	ecpg_free(oldlocale);
+	if (!ecpg_do_prologue(lineno, compat, force_indicator, connection_name, questionmarks, (enum ECPG_statement_type) st, query, args, &stmt))
+		return false;
+
+	if (!ecpg_build_params(stmt))
+	{
+		ecpg_do_epilogue(stmt);
+		return false;
+	}
+
+	if (!ecpg_execute(stmt))
+	{
+		ecpg_do_epilogue(stmt);
+		return false;
+	}
+
+	if (!ecpg_process_output(stmt, 0, PQntuples(stmt->results), 0, false, false))
+	{
+		ecpg_do_epilogue(stmt);
+		return false;  
+	}
+
+	ecpg_do_epilogue(stmt);
+
+	return true;
+}
+
+bool
+ECPGdo(const int lineno, const int compat, const int force_indicator, const char *connection_name, const bool questionmarks, const int st, const char *query,...)
+{
+	va_list		args;
+	bool		ret;
+
+	va_start(args, query);
+	ret = ecpg_do(lineno, compat, force_indicator, connection_name, questionmarks, st, query, args);
+	va_end(args);
 
-	return (status);
+	return ret;
 }
 
 /* old descriptor interface */
diff --git a/src/interfaces/ecpg/ecpglib/exports.txt b/src/interfaces/ecpg/ecpglib/exports.txt
index 69e9617..f05e4f1 100644
--- a/src/interfaces/ecpg/ecpglib/exports.txt
+++ b/src/interfaces/ecpg/ecpglib/exports.txt
@@ -29,3 +29,6 @@ ECPGget_PGconn			 26
 ECPGtransactionStatus		 27
 ECPGset_var			 28
 ECPGget_var			 29
+ECPGopen			 30
+ECPGfetch			 31
+ECPGclose			 32
diff --git a/src/interfaces/ecpg/ecpglib/extern.h b/src/interfaces/ecpg/ecpglib/extern.h
index 96d49a4..2920a3c 100644
--- a/src/interfaces/ecpg/ecpglib/extern.h
+++ b/src/interfaces/ecpg/ecpglib/extern.h
@@ -60,6 +60,12 @@ struct statement
 	bool		questionmarks;
 	struct variable *inlist;
 	struct variable *outlist;
+	char		*oldlocale;
+	const char	**dollarzero;
+	int		ndollarzero;
+	const char	**param_values;
+	int		nparams;
+	PGresult	*results;
 };
 
 /* structure to store prepared statements for a connection */
@@ -71,6 +77,17 @@ struct prepared_statement
 	struct prepared_statement *next;
 };
 
+struct cursor_descriptor {
+	char	   *name;
+	PGresult   *res;
+	bool		scrollable;
+	bool		endoftuples;	/* valid if ->scrollable == false and there is no more tuples */
+	int64		n_tuples;	/* valid if ->scrollable == true */
+	int64		start_pos;
+	int64		cache_pos;
+	struct cursor_descriptor *next;
+};
+
 /* structure to store connections */
 struct connection
 {
@@ -79,6 +96,7 @@ struct connection
 	bool		autocommit;
 	struct ECPGtype_information_cache *cache_head;
 	struct prepared_statement *prep_stmts;
+	struct cursor_descriptor *cursor_desc;
 	struct connection *next;
 };
 
@@ -126,7 +144,7 @@ struct variable
 /* Returns a pointer to a string containing a simple type name. */
 void		ecpg_add_mem(void *ptr, int lineno);
 
-bool ecpg_get_data(const PGresult *, int, int, int, enum ECPGttype type,
+bool ecpg_get_data(const PGresult *, int, int, int, int, enum ECPGttype type,
 			  enum ECPGttype, char *, char *, long, long, long,
 			  enum ARRAY_TYPE, enum COMPAT_MODE, bool);
 
@@ -152,9 +170,16 @@ struct descriptor *ecpg_find_desc(int line, const char *name);
 struct prepared_statement *ecpg_find_prepared_statement(const char *,
 						  struct connection *, struct prepared_statement **);
 
-bool ecpg_store_result(const PGresult *results, int act_field,
-				  const struct statement * stmt, struct variable * var);
+bool ecpg_store_result(const PGresult *results, int start, int end, int act_field,
+				  const struct statement * stmt, struct variable * var, int var_index);
 bool		ecpg_store_input(const int, const bool, const struct variable *, char **, bool);
+bool		ecpg_do_prologue(int, const int, const int, const char *, const bool,
+				  enum ECPG_statement_type, const char *, va_list, struct statement **);
+bool		ecpg_build_params(struct statement *);
+bool		ecpg_process_output(struct statement *, int, int, int, bool, bool);
+void		ecpg_free_params(struct statement *, bool, int);
+void		ecpg_do_epilogue(struct statement *);
+bool		ecpg_do(const int, const int, const int, const char *, const bool, const int, const char *, va_list);
 
 bool		ecpg_check_PQresult(PGresult *, int, PGconn *, enum COMPAT_MODE);
 void		ecpg_raise(int line, int code, const char *sqlstate, const char *str);
@@ -191,6 +216,8 @@ void		ecpg_set_native_sqlda(int, struct sqlda_struct **, const PGresult *, int,
 #define ECPG_SQLSTATE_SYNTAX_ERROR			"42601"
 #define ECPG_SQLSTATE_DATATYPE_MISMATCH		"42804"
 #define ECPG_SQLSTATE_DUPLICATE_CURSOR		"42P03"
+#define ECPG_SQLSTATE_INVALID_CURSOR_DEFINITION	"42P11"
+#define ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE	"55000"
 
 /* implementation-defined internal errors of ecpg */
 #define ECPG_SQLSTATE_ECPG_INTERNAL_ERROR	"YE000"
diff --git a/src/interfaces/ecpg/ecpglib/sqlda.c b/src/interfaces/ecpg/ecpglib/sqlda.c
index a1a0e18..aa37dee 100644
--- a/src/interfaces/ecpg/ecpglib/sqlda.c
+++ b/src/interfaces/ecpg/ecpglib/sqlda.c
@@ -389,7 +389,7 @@ ecpg_set_compat_sqlda(int lineno, struct sqlda_compat ** _sqlda, const PGresult
 		if (!isnull)
 		{
 			if (set_data)
-				ecpg_get_data(res, row, i, lineno,
+				ecpg_get_data(res, 0, row, i, lineno,
 							  sqlda->sqlvar[i].sqltype, ECPGt_NO_INDICATOR,
 							  sqlda->sqlvar[i].sqldata, NULL, 0, 0, 0,
 							  ECPG_ARRAY_NONE, compat, false);
@@ -571,7 +571,7 @@ ecpg_set_native_sqlda(int lineno, struct sqlda_struct ** _sqlda, const PGresult
 		if (!isnull)
 		{
 			if (set_data)
-				ecpg_get_data(res, row, i, lineno,
+				ecpg_get_data(res, 0, row, i, lineno,
 							  sqlda->sqlvar[i].sqltype, ECPGt_NO_INDICATOR,
 							  sqlda->sqlvar[i].sqldata, NULL, 0, 0, 0,
 							  ECPG_ARRAY_NONE, compat, false);
diff --git a/src/interfaces/ecpg/include/ecpgerrno.h b/src/interfaces/ecpg/include/ecpgerrno.h
index 36b15b7..f21dad2 100644
--- a/src/interfaces/ecpg/include/ecpgerrno.h
+++ b/src/interfaces/ecpg/include/ecpgerrno.h
@@ -37,6 +37,7 @@
 #define ECPG_NOT_CONN			-221
 
 #define ECPG_INVALID_STMT		-230
+#define ECPG_INVALID_CURSOR		-231
 
 /* dynamic SQL related */
 #define ECPG_UNKNOWN_DESCRIPTOR		-240
diff --git a/src/interfaces/ecpg/include/ecpglib.h b/src/interfaces/ecpg/include/ecpglib.h
index 3b8ed4c..236f797 100644
--- a/src/interfaces/ecpg/include/ecpglib.h
+++ b/src/interfaces/ecpg/include/ecpglib.h
@@ -63,6 +63,13 @@ PGTransactionStatusType ECPGtransactionStatus(const char *);
 
 char	   *ECPGerrmsg(void);
 
+/* Readahead cursor functions */
+bool		ECPGopen(const int, const int, const int, const char *, const bool, const char *, const int, const char *, ...);
+bool		ECPGfetch(const int, const int, const int, const char *, const bool,
+				const char *, enum ECPG_cursor_direction, const char *,
+				const int,const char *, ...);
+bool		ECPGclose(const int, const int, const int, const char *, const bool, const char *, const int, const char *, ...);
+
  /* print an error message */
 void		sqlprint(void);
 
diff --git a/src/interfaces/ecpg/include/ecpgtype.h b/src/interfaces/ecpg/include/ecpgtype.h
index 7cc47e9..dc82457 100644
--- a/src/interfaces/ecpg/include/ecpgtype.h
+++ b/src/interfaces/ecpg/include/ecpgtype.h
@@ -99,6 +99,14 @@ enum ECPG_statement_type
 	ECPGst_prepnormal
 };
 
+enum ECPG_cursor_direction
+{
+	ECPGc_absolute,
+	ECPGc_relative,
+	ECPGc_forward, 
+	ECPGc_backward 
+};
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/interfaces/ecpg/preproc/check_rules.pl b/src/interfaces/ecpg/preproc/check_rules.pl
index 991c40c..8560eb0 100644
--- a/src/interfaces/ecpg/preproc/check_rules.pl
+++ b/src/interfaces/ecpg/preproc/check_rules.pl
@@ -43,7 +43,10 @@ my %replace_line = (
 		'CREATE OptTemp TABLE create_as_target AS EXECUTE prepared_name execute_param_clause',
 
 	'PrepareStmtPREPAREnameprep_type_clauseASPreparableStmt' =>
-		'PREPARE prepared_name prep_type_clause AS PreparableStmt'
+		'PREPARE prepared_name prep_type_clause AS PreparableStmt',
+
+	'DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectStmt' =>
+		'DECLARE cursor_name cursor_options opt_readahead CURSOR opt_hold FOR SelectStmt'
 );
 
 my $block        = '';
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons
index 23421df..40df82f 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -15,7 +15,10 @@ ECPG: stmtClosePortalStmt block
 			}
 		}
 
-		output_statement($1, 0, ECPGst_normal);
+		if (use_fetch_readahead)
+			output_close_statement($1, 0, ECPGst_normal);
+		else
+			output_statement($1, 0, ECPGst_normal);
 	}
 ECPG: stmtDeallocateStmt block
 	{
@@ -27,8 +30,14 @@ ECPG: stmtDeallocateStmt block
 ECPG: stmtDeclareCursorStmt block
 	{ output_simple_statement($1); }
 ECPG: stmtDiscardStmt block
-ECPG: stmtFetchStmt block
 	{ output_statement($1, 1, ECPGst_normal); }
+ECPG: stmtFetchStmt block
+	{
+		if (use_fetch_readahead)
+			output_fetch_statement($1, 1, ECPGst_normal);
+		else
+			output_statement($1, 1, ECPGst_normal);
+	}
 ECPG: stmtDeleteStmt block
 ECPG: stmtInsertStmt block
 ECPG: stmtSelectStmt block
@@ -137,7 +146,10 @@ ECPG: stmtViewStmt rule
 		if ((ptr = add_additional_variables($1, true)) != NULL)
 		{
 			connection = ptr->connection ? mm_strdup(ptr->connection) : NULL;
-			output_statement(mm_strdup(ptr->command), 0, ECPGst_normal);
+			if (use_fetch_readahead)
+				output_open_statement(mm_strdup(ptr->command), 0, ECPGst_normal);
+			else
+				output_statement(mm_strdup(ptr->command), 0, ECPGst_normal);
 			ptr->opened = true;
 		}
 	}
@@ -195,6 +207,21 @@ ECPG: stmtViewStmt rule
 ECPG: where_or_current_clauseWHERECURRENT_POFcursor_name block
 	{
 		char *cursor_marker = $4[0] == ':' ? mm_strdup("$0") : $4;
+		struct cursor *ptr;
+
+		for (ptr = cur; ptr != NULL; ptr = ptr->next)
+		{
+			if (strcmp(ptr->name, $4) == 0)
+				break;
+		}
+		if (!ptr)
+			mmerror(PARSE_ERROR, ET_FATAL, "cursor \"%s\" does not exist", $4);
+
+		if (ptr->fetch_readahead)
+		{
+			mmerror(PARSE_ERROR, ET_ERROR,
+				"\"WHERE CURRENT OF\" is incompatible with a READAHEAD cursor\n");
+		}
 		$$ = cat_str(2,mm_strdup("where current of"), cursor_marker);
 	}
 ECPG: CopyStmtCOPYopt_binaryqualified_nameopt_column_listopt_oidscopy_fromcopy_file_namecopy_delimiteropt_withcopy_options addon
@@ -215,31 +242,77 @@ ECPG: var_valueNumericOnly addon
 		}
 ECPG: fetch_argscursor_name addon
 		add_additional_variables($1, false);
+		set_cursor_readahead($1);
 		if ($1[0] == ':')
 		{
 			free($1);
 			$1 = mm_strdup("$0");
 		}
+		current_cursor_direction = ECPGc_forward;
+		current_cursor_amount = mm_strdup("1");
 ECPG: fetch_argsfrom_incursor_name addon
 		add_additional_variables($2, false);
+		set_cursor_readahead($2);
 		if ($2[0] == ':')
 		{
 			free($2);
 			$2 = mm_strdup("$0");
 		}
+		current_cursor_direction = ECPGc_forward;
+		current_cursor_amount = mm_strdup("1");
 ECPG: fetch_argsNEXTopt_from_incursor_name addon
+		add_additional_variables($3, false);
+		set_cursor_readahead($3);
+		if ($3[0] == ':')
+		{
+			free($3);
+			$3 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_forward;
+		current_cursor_amount = mm_strdup("1");
 ECPG: fetch_argsPRIORopt_from_incursor_name addon
+		add_additional_variables($3, false);
+		set_cursor_readahead($3);
+		if ($3[0] == ':')
+		{
+			free($3);
+			$3 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_backward;
+		current_cursor_amount = mm_strdup("1"); 
 ECPG: fetch_argsFIRST_Popt_from_incursor_name addon
+		add_additional_variables($3, false);
+		set_cursor_readahead($3);
+		if ($3[0] == ':')
+		{
+			free($3);
+			$3 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_absolute;
+		current_cursor_amount = mm_strdup("1");
 ECPG: fetch_argsLAST_Popt_from_incursor_name addon
+		add_additional_variables($3, false);
+		set_cursor_readahead($3);
+		if ($3[0] == ':')
+		{
+			free($3);
+			$3 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_absolute;
+		current_cursor_amount = mm_strdup("-1");
 ECPG: fetch_argsALLopt_from_incursor_name addon
 		add_additional_variables($3, false);
+		set_cursor_readahead($3);
 		if ($3[0] == ':')
 		{
 			free($3);
 			$3 = mm_strdup("$0");
 		}
+		current_cursor_direction = ECPGc_forward;
+		current_cursor_amount = mm_strdup("all");
 ECPG: fetch_argsSignedIconstopt_from_incursor_name addon
 		add_additional_variables($3, false);
+		set_cursor_readahead($3);
 		if ($3[0] == ':')
 		{
 			free($3);
@@ -250,19 +323,76 @@ ECPG: fetch_argsSignedIconstopt_from_incursor_name addon
 			free($1);
 			$1 = mm_strdup("$0");
 		}
+		current_cursor_direction = ECPGc_forward;
+		current_cursor_amount = mm_strdup($1);
 ECPG: fetch_argsFORWARDALLopt_from_incursor_name addon
+		add_additional_variables($4, false);
+		set_cursor_readahead($4);
+		if ($4[0] == ':')
+		{
+			free($4);
+			$4 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_forward;
+		current_cursor_amount = mm_strdup("all");
 ECPG: fetch_argsBACKWARDALLopt_from_incursor_name addon
 		add_additional_variables($4, false);
+		set_cursor_readahead($4);
 		if ($4[0] == ':')
 		{
 			free($4);
 			$4 = mm_strdup("$0");
 		}
+		current_cursor_direction = ECPGc_backward;
+		current_cursor_amount = mm_strdup("all");
 ECPG: fetch_argsABSOLUTE_PSignedIconstopt_from_incursor_name addon
+		add_additional_variables($4, false);
+		set_cursor_readahead($4);
+		if ($4[0] == ':')
+		{
+			free($4);
+			$4 = mm_strdup("$0");
+		}
+		if ($2[0] == '$')
+		{
+			free($2);
+			$2 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_absolute;
+		current_cursor_amount = mm_strdup($2);
 ECPG: fetch_argsRELATIVE_PSignedIconstopt_from_incursor_name addon
+		add_additional_variables($4, false);
+		set_cursor_readahead($4);
+		if ($4[0] == ':')
+		{
+			free($4);
+			$4 = mm_strdup("$0");
+		}
+		if ($2[0] == '$')
+		{
+			free($2);
+			$2 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_relative;
+		current_cursor_amount = mm_strdup($2);
 ECPG: fetch_argsFORWARDSignedIconstopt_from_incursor_name addon
+		add_additional_variables($4, false);
+		set_cursor_readahead($4);
+		if ($4[0] == ':')
+		{
+			free($4);
+			$4 = mm_strdup("$0");
+		}
+		if ($2[0] == '$')
+		{
+			free($2);
+			$2 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_forward;
+		current_cursor_amount = mm_strdup($2);
 ECPG: fetch_argsBACKWARDSignedIconstopt_from_incursor_name addon
 		add_additional_variables($4, false);
+		set_cursor_readahead($4);
 		if ($4[0] == ':')
 		{
 			free($4);
@@ -273,7 +403,15 @@ ECPG: fetch_argsBACKWARDSignedIconstopt_from_incursor_name addon
 			free($2);
 			$2 = mm_strdup("$0");
 		}
-ECPG: cursor_namename rule
+		current_cursor_direction = ECPGc_backward;
+		current_cursor_amount = mm_strdup($2);
+ECPG: cursor_namename block
+		{
+			if (current_cursor)
+				free(current_cursor);
+			current_cursor = make3_str(mm_strdup("\""), mm_strdup($1), mm_strdup("\""));
+			$$ = $1;
+		}
 	| char_civar
 		{
 			char *curname = mm_alloc(strlen($1) + 2);
@@ -296,7 +434,7 @@ ECPG: PrepareStmtPREPAREprepared_nameprep_type_clauseASPreparableStmt block
 	}
 ECPG: ExecuteStmtEXECUTEprepared_nameexecute_param_clauseexecute_rest block
 	{ $$ = $2; }
-ECPG: DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectStmt block
+ECPG: DeclareCursorStmtDECLAREcursor_namecursor_optionsopt_readaheadCURSORopt_holdFORSelectStmt block
 	{
 		struct cursor *ptr, *this;
 		char *cursor_marker = $2[0] == ':' ? mm_strdup("$0") : mm_strdup($2);
@@ -321,7 +459,13 @@ ECPG: DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectSt
 		this->function = (current_function ? mm_strdup(current_function) : NULL);
 		this->connection = connection;
 		this->opened = false;
-		this->command =  cat_str(7, mm_strdup("declare"), cursor_marker, $3, mm_strdup("cursor"), $5, mm_strdup("for"), $7);
+		if (!strcmp($4, "1"))
+			this->fetch_readahead = true;
+		else if (!strcmp($4, "0"))
+			this->fetch_readahead = false;
+		else
+			this->fetch_readahead = fetch_readahead;
+		this->command =  cat_str(7, mm_strdup("declare"), cursor_marker, $3, mm_strdup("cursor"), $6, mm_strdup("for"), $8);
 		this->argsinsert = argsinsert;
 		this->argsinsert_oos = NULL;
 		this->argsresult = argsresult;
@@ -348,6 +492,8 @@ ECPG: DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectSt
 ECPG: ClosePortalStmtCLOSEcursor_name block
 	{
 		char *cursor_marker = $2[0] == ':' ? mm_strdup("$0") : $2;
+		if (!INFORMIX_MODE || pg_strcasecmp($2, "database") != 0)
+			set_cursor_readahead($2);
 		$$ = cat2_str(mm_strdup("close"), cursor_marker);
 	}
 ECPG: opt_hold block
diff --git a/src/interfaces/ecpg/preproc/ecpg.c b/src/interfaces/ecpg/preproc/ecpg.c
index 6f73148..afb898b 100644
--- a/src/interfaces/ecpg/preproc/ecpg.c
+++ b/src/interfaces/ecpg/preproc/ecpg.c
@@ -18,7 +18,8 @@ bool		autocommit = false,
 			force_indicator = true,
 			questionmarks = false,
 			regression_mode = false,
-			auto_prepare = false;
+			auto_prepare = false,
+			fetch_readahead = false;
 
 char	   *output_filename;
 
@@ -51,7 +52,7 @@ help(const char *progname)
 	printf(_("  -I DIRECTORY   search DIRECTORY for include files\n"));
 	printf(_("  -o OUTFILE     write result to OUTFILE\n"));
 	printf(_("  -r OPTION      specify run-time behavior; OPTION can be:\n"
-	 "                 \"no_indicator\", \"prepare\", \"questionmarks\"\n"));
+	 "                 \"no_indicator\", \"prepare\", \"questionmarks\", \"fetch_readahead\"\n"));
 	printf(_("  --regression   run in regression testing mode\n"));
 	printf(_("  -t             turn on autocommit of transactions\n"));
 	printf(_("  --help         show this help, then exit\n"));
@@ -229,6 +230,8 @@ main(int argc, char *const argv[])
 					auto_prepare = true;
 				else if (strcmp(optarg, "questionmarks") == 0)
 					questionmarks = true;
+				else if (strcmp(optarg, "fetch_readahead") == 0)
+					fetch_readahead = true;
 				else
 				{
 					fprintf(stderr, _("Try \"%s --help\" for more information.\n"), argv[0]);
diff --git a/src/interfaces/ecpg/preproc/ecpg.header b/src/interfaces/ecpg/preproc/ecpg.header
index 1ea6ce4..1a0b23b 100644
--- a/src/interfaces/ecpg/preproc/ecpg.header
+++ b/src/interfaces/ecpg/preproc/ecpg.header
@@ -34,7 +34,11 @@
  */
 int struct_level = 0;
 int braces_open; /* brace level counter */
+bool use_fetch_readahead = false;
 char *current_function;
+char *current_cursor = NULL;
+enum ECPG_cursor_direction current_cursor_direction;
+char *current_cursor_amount = NULL;
 int ecpg_internal_var = 0;
 char	*connection = NULL;
 char	*input_filename = NULL;
@@ -111,6 +115,26 @@ mmerror(int error_code, enum errortype type, const char *error, ...)
 }
 
 /*
+ * set use_fetch_readahead based on the current cursor
+ * doesn't return if the cursor is not declared
+ */
+static void
+set_cursor_readahead(const char *curname)
+{
+	struct cursor *ptr;
+
+	for (ptr = cur; ptr != NULL; ptr = ptr->next)
+	{
+		if (strcmp(ptr->name, curname) == 0)
+			break;
+	}
+	if (!ptr)
+		mmerror(PARSE_ERROR, ET_FATAL, "cursor \"%s\" does not exist", curname);
+
+	use_fetch_readahead = ptr->fetch_readahead;
+}
+
+/*
  * string concatenation
  */
 
diff --git a/src/interfaces/ecpg/preproc/ecpg.tokens b/src/interfaces/ecpg/preproc/ecpg.tokens
index b55138a..3995b59 100644
--- a/src/interfaces/ecpg/preproc/ecpg.tokens
+++ b/src/interfaces/ecpg/preproc/ecpg.tokens
@@ -10,7 +10,7 @@
                 SQL_FREE SQL_GET SQL_GO SQL_GOTO SQL_IDENTIFIED
                 SQL_INDICATOR SQL_KEY_MEMBER SQL_LENGTH
                 SQL_LONG SQL_NULLABLE SQL_OCTET_LENGTH
-                SQL_OPEN SQL_OUTPUT SQL_REFERENCE
+                SQL_OPEN SQL_OUTPUT SQL_READAHEAD SQL_REFERENCE
                 SQL_RETURNED_LENGTH SQL_RETURNED_OCTET_LENGTH SQL_SCALE
                 SQL_SECTION SQL_SHORT SQL_SIGNED SQL_SQL SQL_SQLERROR
                 SQL_SQLPRINT SQL_SQLWARNING SQL_START SQL_STOP
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index a362aff..2bf960f 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -287,7 +287,7 @@ prepared_name: name
  * 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
+ECPGCursorStmt:  DECLARE cursor_name cursor_options opt_readahead CURSOR opt_hold FOR prepared_name
 		{
 			struct cursor *ptr, *this;
 			char *cursor_marker = $2[0] == ':' ? mm_strdup("$0") : mm_strdup($2);
@@ -315,15 +315,22 @@ ECPGCursorStmt:  DECLARE cursor_name cursor_options CURSOR opt_hold FOR prepared
 			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->opened = false;
+			if (!strcmp($4, "1"))
+				this->fetch_readahead = true;
+			else if (!strcmp($4, "0"))
+				this->fetch_readahead = false;
+			else
+				this->fetch_readahead = fetch_readahead;
+			this->command =  cat_str(6, mm_strdup("declare"), cursor_marker, $3, mm_strdup("cursor"), $6, 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);
+			thisquery->name = (char *) mm_alloc(sizeof("ECPGprepared_statement(, , __LINE__)") + strlen(con) + strlen($8));
+			sprintf(thisquery->name, "ECPGprepared_statement(%s, %s, __LINE__)", con, $8);
 
 			this->argsinsert = NULL;
 			this->argsinsert_oos = NULL;
@@ -348,6 +355,11 @@ ECPGCursorStmt:  DECLARE cursor_name cursor_options CURSOR opt_hold FOR prepared
 		}
 		;
 
+opt_readahead:	SQL_READAHEAD				{ $$ = mm_strdup("1"); }
+		| NO SQL_READAHEAD			{ $$ = mm_strdup("0"); }
+		| /* EMPTY */			{ $$ = mm_strdup("default"); }
+		;
+
 ECPGExecuteImmediateStmt: EXECUTE IMMEDIATE execstring
 			{
 			  /* execute immediate means prepare the statement and
@@ -988,6 +1000,7 @@ ECPGOpen: SQL_OPEN cursor_name opt_ecpg_using
 		{
 			if ($2[0] == ':')
 				remove_variable_from_list(&argsinsert, find_variable($2 + 1));
+			set_cursor_readahead($2);
 			$$ = $2;
 		}
 		;
@@ -1656,6 +1669,10 @@ char_civar: char_variable
 		{
 			char *ptr = strstr($1, ".arr");
 
+			if (current_cursor)
+				free(current_cursor);
+			current_cursor = mm_strdup($1);
+
 			if (ptr) /* varchar, we need the struct name here, not the struct element */
 				*ptr = '\0';
 			add_variable_to_head(&argsinsert, find_variable($1), &no_indicator);
diff --git a/src/interfaces/ecpg/preproc/ecpg.type b/src/interfaces/ecpg/preproc/ecpg.type
index ac6aa00..2662372 100644
--- a/src/interfaces/ecpg/preproc/ecpg.type
+++ b/src/interfaces/ecpg/preproc/ecpg.type
@@ -85,6 +85,7 @@
 %type <str> opt_output
 %type <str> opt_pointer
 %type <str> opt_port
+%type <str> opt_readahead
 %type <str> opt_reference
 %type <str> opt_scale
 %type <str> opt_server
diff --git a/src/interfaces/ecpg/preproc/ecpg_keywords.c b/src/interfaces/ecpg/preproc/ecpg_keywords.c
index 8032c30..cbd37c6 100644
--- a/src/interfaces/ecpg/preproc/ecpg_keywords.c
+++ b/src/interfaces/ecpg/preproc/ecpg_keywords.c
@@ -56,6 +56,7 @@ static const ScanKeyword ScanECPGKeywords[] = {
 	{"octet_length", SQL_OCTET_LENGTH, 0},
 	{"open", SQL_OPEN, 0},
 	{"output", SQL_OUTPUT, 0},
+	{"readahead", SQL_READAHEAD, 0},
 	{"reference", SQL_REFERENCE, 0},
 	{"returned_length", SQL_RETURNED_LENGTH, 0},
 	{"returned_octet_length", SQL_RETURNED_OCTET_LENGTH, 0},
diff --git a/src/interfaces/ecpg/preproc/extern.h b/src/interfaces/ecpg/preproc/extern.h
index ccf5548..3d22d3a 100644
--- a/src/interfaces/ecpg/preproc/extern.h
+++ b/src/interfaces/ecpg/preproc/extern.h
@@ -24,12 +24,17 @@ extern bool autocommit,
 			force_indicator,
 			questionmarks,
 			regression_mode,
-			auto_prepare;
+			auto_prepare,
+			fetch_readahead;
+extern bool	use_fetch_readahead;
 extern int	braces_open,
 			ret_value,
 			struct_level,
 			ecpg_internal_var;
 extern char *current_function;
+extern char *current_cursor;  
+extern enum ECPG_cursor_direction current_cursor_direction;
+extern char *current_cursor_amount;
 extern char *descriptor_index;
 extern char *descriptor_name;
 extern char *connection;
@@ -67,6 +72,9 @@ extern void output_statement(char *, int, enum ECPG_statement_type);
 extern void output_prepare_statement(char *, char *);
 extern void output_deallocate_prepare_statement(char *);
 extern void output_simple_statement(char *);
+extern void output_open_statement(char *, int, enum ECPG_statement_type);
+extern void output_fetch_statement(char *, int, enum ECPG_statement_type);
+extern void output_close_statement(char *, int, enum ECPG_statement_type);
 extern char *hashline_number(void);
 extern int	base_yyparse(void);
 extern int	base_yylex(void);
diff --git a/src/interfaces/ecpg/preproc/output.c b/src/interfaces/ecpg/preproc/output.c
index 9958a0a..6e60392 100644
--- a/src/interfaces/ecpg/preproc/output.c
+++ b/src/interfaces/ecpg/preproc/output.c
@@ -112,10 +112,16 @@ static char *ecpg_statement_type_name[] = {
 	"ECPGst_prepnormal"
 };
 
-void
-output_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
+static char *ecpg_cursor_direction_name[] = {
+	"ECPGc_absolute",
+	"ECPGc_relative",
+	"ECPGc_forward", 
+	"ECPGc_backward" 
+};
+
+static void
+output_statement_epilogue(char *stmt, int whenever_mode, enum ECPG_statement_type st)
 {
-	fprintf(yyout, "{ ECPGdo(__LINE__, %d, %d, %s, %d, ", compat, force_indicator, connection ? connection : "NULL", questionmarks);
 	if (st == ECPGst_execute || st == ECPGst_exec_immediate)
 	{
 		fprintf(yyout, "%s, %s, ", ecpg_statement_type_name[st], stmt);
@@ -145,6 +151,13 @@ output_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
 }
 
 void
+output_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
+{
+	fprintf(yyout, "{ ECPGdo(__LINE__, %d, %d, %s, %d, ", compat, force_indicator, connection ? connection : "NULL", questionmarks);
+	output_statement_epilogue(stmt, whenever_mode, st);
+}
+
+void
 output_prepare_statement(char *name, char *stmt)
 {
 	fprintf(yyout, "{ ECPGprepare(__LINE__, %s, %d, ", connection ? connection : "NULL", questionmarks);
@@ -178,6 +191,51 @@ output_deallocate_prepare_statement(char *name)
 		free(connection);
 }
 
+void
+output_open_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
+{
+	fprintf(yyout, "{ ECPGopen(__LINE__, %d, %d, %s, %d, %s, ",
+			compat,
+			force_indicator,
+			connection ? connection : "NULL",
+			questionmarks,
+			current_cursor);
+	output_statement_epilogue(stmt, whenever_mode, st);
+}
+
+void
+output_fetch_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
+{
+	char	*amount = mm_alloc(strlen(current_cursor_amount) + 3);
+
+	if (!amount)
+		return;
+
+	sprintf(amount, "\"%s\"", current_cursor_amount);
+	fprintf(yyout, "{ ECPGfetch(__LINE__, %d, %d, %s, %d, %s, %s, %s, ",
+			compat,
+			force_indicator,
+			connection ? connection : "NULL",
+			questionmarks,
+			current_cursor,
+			ecpg_cursor_direction_name[current_cursor_direction],
+			amount);
+	output_statement_epilogue(stmt, whenever_mode, st);
+	free(amount);
+}
+
+void
+output_close_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
+{
+	fprintf(yyout, "{ ECPGclose(__LINE__, %d, %d, %s, %d, %s, ",
+			compat,
+			force_indicator,
+			connection ? connection : "NULL",
+			questionmarks,
+			current_cursor);
+	output_statement_epilogue(stmt, whenever_mode, st);
+}
+
 static void
 output_escaped_str(char *str, bool quoted)
 {
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index 515470e..aebcaf4 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -90,6 +90,8 @@ my %replace_line = (
 	'fetch_argsFORWARDopt_from_incursor_name'      => 'ignore',
 	'fetch_argsBACKWARDopt_from_incursor_name'     => 'ignore',
 	"opt_array_boundsopt_array_bounds'['Iconst']'" => 'ignore',
+	'DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectStmt' =>
+			'DECLARE cursor_name cursor_options opt_readahead CURSOR opt_hold FOR SelectStmt',
 	'VariableShowStmtSHOWvar_name'                 => 'SHOW var_name ecpg_into',
 	'VariableShowStmtSHOWTIMEZONE' => 'SHOW TIME ZONE ecpg_into',
 	'VariableShowStmtSHOWTRANSACTIONISOLATIONLEVEL' => 'SHOW TRANSACTION ISOLATION LEVEL ecpg_into',
diff --git a/src/interfaces/ecpg/preproc/type.h b/src/interfaces/ecpg/preproc/type.h
index 68e0d1a..89c920f 100644
--- a/src/interfaces/ecpg/preproc/type.h
+++ b/src/interfaces/ecpg/preproc/type.h
@@ -130,6 +130,7 @@ struct cursor
 	char	   *command;
 	char	   *connection;
 	bool		opened;
+	bool		fetch_readahead;
 	struct arguments *argsinsert;
 	struct arguments *argsinsert_oos;
 	struct arguments *argsresult;
diff --git a/src/interfaces/ecpg/test/ecpg_schedule b/src/interfaces/ecpg/test/ecpg_schedule
index c07ea93..a15b7d9 100644
--- a/src/interfaces/ecpg/test/ecpg_schedule
+++ b/src/interfaces/ecpg/test/ecpg_schedule
@@ -20,6 +20,7 @@ test: preproc/array_of_struct
 test: preproc/autoprep
 test: preproc/comment
 test: preproc/cursor
+test: preproc/cursor-readahead
 test: preproc/define
 test: preproc/init
 test: preproc/strings
diff --git a/src/interfaces/ecpg/test/ecpg_schedule_tcp b/src/interfaces/ecpg/test/ecpg_schedule_tcp
index 77481b5..60994e0 100644
--- a/src/interfaces/ecpg/test/ecpg_schedule_tcp
+++ b/src/interfaces/ecpg/test/ecpg_schedule_tcp
@@ -20,6 +20,7 @@ test: preproc/array_of_struct
 test: preproc/autoprep
 test: preproc/comment
 test: preproc/cursor
+test: preproc/cursor-readahead
 test: preproc/define
 test: preproc/init
 test: preproc/strings
diff --git a/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.c b/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.c
new file mode 100644
index 0000000..ef39815
--- /dev/null
+++ b/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.c
@@ -0,0 +1,663 @@
+/* Processed by ecpg (regression mode) */
+/* These include files are added by the preprocessor */
+#include <ecpglib.h>
+#include <ecpgerrno.h>
+#include <sqlca.h>
+/* End of automatic include section */
+#define ECPGdebug(X,Y) ECPGdebug((X)+100,(Y))
+
+#line 1 "cursor-readahead.pgc"
+#include <stdio.h>
+#include <malloc.h>
+
+/* test automatic prepare for all statements */
+
+#line 1 "regression.h"
+
+
+
+
+
+
+#line 5 "cursor-readahead.pgc"
+
+
+
+#line 1 "sqlda.h"
+#ifndef ECPG_SQLDA_H
+#define ECPG_SQLDA_H
+
+#ifdef _ECPG_INFORMIX_H
+
+#include "sqlda-compat.h"
+typedef struct sqlvar_compat	sqlvar_t;
+typedef struct sqlda_compat	sqlda_t;
+
+#else
+
+#include "sqlda-native.h"
+typedef struct sqlvar_struct	sqlvar_t;
+typedef struct sqlda_struct	sqlda_t;
+
+#endif
+
+#endif /* ECPG_SQLDA_H */
+
+#line 7 "cursor-readahead.pgc"
+
+
+/* exec sql whenever sqlerror  sqlprint ; */
+#line 9 "cursor-readahead.pgc"
+
+/* exec sql whenever sql_warning  sqlprint ; */
+#line 10 "cursor-readahead.pgc"
+
+
+#define MAXID	(513)
+
+int main(void)
+{
+	int	counts[2] = { 1, 5 };
+	/* exec sql begin declare section */
+		
+		
+		   
+		  
+	
+#line 18 "cursor-readahead.pgc"
+ char * curname ;
+ 
+#line 19 "cursor-readahead.pgc"
+ int maxid ;
+ 
+#line 20 "cursor-readahead.pgc"
+ int i , j , count , rows ;
+ 
+#line 21 "cursor-readahead.pgc"
+ int id , id1 [ 5 ] , id2 [ 5 ] ;
+/* exec sql end declare section */
+#line 22 "cursor-readahead.pgc"
+
+	sqlda_t	*sqlda;
+
+	/*
+	 * Intentionally don't create a 2MB stderr file for this test.
+	 * Enable it manually if you're interested in it.
+	 */
+#if 0
+	ECPGdebug(1, stderr);
+#endif
+
+	{ ECPGconnect(__LINE__, 0, "regress1" , NULL, NULL , NULL, 0); 
+#line 33 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 33 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 33 "cursor-readahead.pgc"
+
+
+	{ ECPGtrans(__LINE__, NULL, "begin");
+#line 35 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 35 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 35 "cursor-readahead.pgc"
+
+
+	maxid = MAXID;
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "create table ra_test ( id integer primary key )", ECPGt_EOIT, ECPGt_EORT);
+#line 39 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 39 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 39 "cursor-readahead.pgc"
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "insert into ra_test select i . i from generate_series ( 1 , $1  ) as i", 
+	ECPGt_int,&(maxid),(long)1,(long)1,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, ECPGt_EORT);
+#line 40 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 40 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 40 "cursor-readahead.pgc"
+
+
+	{ ECPGtrans(__LINE__, NULL, "commit");
+#line 42 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 42 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 42 "cursor-readahead.pgc"
+
+
+	{ ECPGtrans(__LINE__, NULL, "begin");
+#line 44 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 44 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 44 "cursor-readahead.pgc"
+
+
+	curname = "xx";
+	ECPGset_var( 0, &( curname ), __LINE__);\
+ /* declare $0 scroll cursor for select * from ra_test */
+#line 47 "cursor-readahead.pgc"
+
+	/* declare xcur scroll cursor for select * from ra_test */
+#line 48 "cursor-readahead.pgc"
+
+
+	/* exec sql whenever not found  break ; */
+#line 50 "cursor-readahead.pgc"
+
+
+	for (i = 0; i < 2; i++)
+	{
+		count = counts[i];
+
+		{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "declare $0 scroll cursor for select * from ra_test", 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, ECPGt_EORT);
+#line 56 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 56 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 56 "cursor-readahead.pgc"
+
+		{ ECPGopen(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "declare xcur scroll cursor for select * from ra_test", ECPGt_EOIT, ECPGt_EORT);
+#line 57 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 57 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 57 "cursor-readahead.pgc"
+
+
+		id = 0;
+		while (1)
+		{
+			for (j = 0; j < count; j++)
+			{
+				id1[j] = -1;
+				id2[j] = -1;
+			}
+
+			{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "fetch $0 from $0", 
+	ECPGt_int,&(count),(long)1,(long)1,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, 
+	ECPGt_int,(id1),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 68 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode == ECPG_NOT_FOUND) break;
+#line 68 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 68 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 68 "cursor-readahead.pgc"
+
+			{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_forward, "$0", ECPGst_normal, "fetch $0 from xcur", 
+	ECPGt_int,&(count),(long)1,(long)1,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, 
+	ECPGt_int,(id2),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 69 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode == ECPG_NOT_FOUND) break;
+#line 69 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 69 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 69 "cursor-readahead.pgc"
+
+
+			rows = sqlca.sqlerrd[2];
+
+			for (j = 0; j < rows; j++)
+			{
+				id++;
+
+				if (id != id1[j] || id != id2[j] || id1[j] != id2[j])
+				{
+					printf("Reading two cursors forward: ERROR. id = %d id1 = %d id2 = %d\n", id, id1[j], id2[j]);
+					break;
+				}
+			}
+			if (j != rows)
+				break;
+		}
+
+		if (sqlca.sqlwarn[0] == 'W') sqlprint();
+		if (sqlca.sqlcode < 0) sqlprint();
+
+		if (id == maxid)
+			printf("Reading readahead and non-readahead cursors simultaneously forward by %d record: SUCCESS\n", count);
+		else
+			printf("Reading readahead and non-readahead cursors simultaneously forward by %d record: FAILED at %d, id1 %d id2 %d\n", count, id, id1[j], id2[j]);
+
+		{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "close $0", 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, ECPGt_EORT);
+#line 95 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 95 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 95 "cursor-readahead.pgc"
+
+		{ ECPGclose(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "close xcur", ECPGt_EOIT, ECPGt_EORT);
+#line 96 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 96 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 96 "cursor-readahead.pgc"
+
+
+		/* exec sql whenever not found  continue ; */
+#line 98 "cursor-readahead.pgc"
+
+
+		{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "declare $0 scroll cursor for select * from ra_test", 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, ECPGt_EORT);
+#line 100 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 100 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 100 "cursor-readahead.pgc"
+
+		{ ECPGopen(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "declare xcur scroll cursor for select * from ra_test", ECPGt_EOIT, ECPGt_EORT);
+#line 101 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 101 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 101 "cursor-readahead.pgc"
+
+		{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "fetch last from $0", 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, 
+	ECPGt_int,(id1),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 102 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 102 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 102 "cursor-readahead.pgc"
+
+		{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "fetch from $0", 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, 
+	ECPGt_int,(id1),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 103 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 103 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 103 "cursor-readahead.pgc"
+
+		if (sqlca.sqlcode == ECPG_NOT_FOUND)
+			printf("After last record in cursor '%s' (value %d), fetching backwards.\n", curname, id1[0]);
+		else
+			goto err;
+		{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_absolute, "-1", ECPGst_normal, "fetch last from xcur", ECPGt_EOIT, 
+	ECPGt_int,(id2),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 108 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 108 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 108 "cursor-readahead.pgc"
+
+		{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_forward, "1", ECPGst_normal, "fetch from xcur", ECPGt_EOIT, 
+	ECPGt_int,(id2),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 109 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 109 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 109 "cursor-readahead.pgc"
+
+		if (sqlca.sqlcode == ECPG_NOT_FOUND)
+			printf("After last record in cursor 'xcur' (value %d), fetching backwards.\n", id2[0]);
+		else
+			goto err;
+
+		/* exec sql whenever not found  break ; */
+#line 115 "cursor-readahead.pgc"
+
+
+		id = maxid;
+		while (1)
+		{
+			for (j = 0; j < count; j++)
+			{
+				id1[j] = -1;
+				id2[j] = -1;
+			}
+
+			{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "fetch backward $0 from $0", 
+	ECPGt_int,&(count),(long)1,(long)1,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, 
+	ECPGt_int,(id1),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 126 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode == ECPG_NOT_FOUND) break;
+#line 126 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 126 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 126 "cursor-readahead.pgc"
+
+			{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_backward, "$0", ECPGst_normal, "fetch backward $0 from xcur", 
+	ECPGt_int,&(count),(long)1,(long)1,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, 
+	ECPGt_int,(id2),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 127 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode == ECPG_NOT_FOUND) break;
+#line 127 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 127 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 127 "cursor-readahead.pgc"
+
+
+			rows = sqlca.sqlerrd[2];
+
+			for (j = 0; j < rows; j++)
+			{
+				if (id != id1[j] || id != id2[j] || id1[j] != id2[j])
+				{
+					printf("Reading two cursors backward: ERROR. id = %d id1 = %d id2 = %d\n", id, id1[j], id2[j]);
+					break;
+				}
+				id--;
+			}
+			if (j != rows)
+				break;
+		}
+
+		if (id == 0)
+			printf("Reading readahead and non-readahead cursors simultaneously backwards by %d record(s): SUCCESS\n", count);
+		else
+			printf("Reading readahead and non-readahead cursors simultaneously backwards by %d record(s): FAILED at %d, id1 %d id2 %d\n", count, id, id1[j], id2[j]);
+
+		{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "close $0", 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, ECPGt_EORT);
+#line 149 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 149 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 149 "cursor-readahead.pgc"
+
+		{ ECPGclose(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "close xcur", ECPGt_EOIT, ECPGt_EORT);
+#line 150 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 150 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 150 "cursor-readahead.pgc"
+
+
+		/* exec sql whenever not found  continue ; */
+#line 152 "cursor-readahead.pgc"
+
+
+	}
+
+	sqlda = NULL;
+	{ ECPGopen(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "declare xcur scroll cursor for select * from ra_test", ECPGt_EOIT, ECPGt_EORT);
+#line 157 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 157 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 157 "cursor-readahead.pgc"
+
+	{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_forward, "all", ECPGst_normal, "fetch all xcur", ECPGt_EOIT, 
+	ECPGt_sqlda, &sqlda, 0L, 0L, 0L, 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 158 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 158 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 158 "cursor-readahead.pgc"
+
+
+	/* exec sql whenever not found  break ; */
+#line 160 "cursor-readahead.pgc"
+
+
+	id = 0;
+	id1[0] = -1;
+	while (sqlda)
+	{
+		sqlda_t *tmp;
+
+		id++;
+		id1[0] = *(int *)sqlda->sqlvar[0].sqldata;
+
+		if (id != id1[0])
+		{
+			printf("Reading readahead cursors forward into sqlda chain: ERROR. id = %d id1 = %d\n", id, id1[0]);
+			break;
+		}
+		tmp = sqlda->desc_next;
+		free(sqlda);
+		sqlda = tmp;
+	}
+
+	if (id == maxid)
+		printf("Reading all records from a readahead cursor forward into sqlda chain: SUCCESS\n");
+	else
+		printf("Reading all records from a readahead cursor forward into sqlda chain: FAILED. id %d, id1 %d\n", id, id1[0]);
+
+	{ ECPGclose(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "close xcur", ECPGt_EOIT, ECPGt_EORT);
+#line 186 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 186 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 186 "cursor-readahead.pgc"
+
+
+	/* exec sql whenever not found  continue ; */
+#line 188 "cursor-readahead.pgc"
+
+
+	sqlda = NULL;
+	{ ECPGopen(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "declare xcur scroll cursor for select * from ra_test", ECPGt_EOIT, ECPGt_EORT);
+#line 191 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 191 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 191 "cursor-readahead.pgc"
+
+	{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_absolute, "-1", ECPGst_normal, "fetch last from xcur", ECPGt_EOIT, 
+	ECPGt_int,(id1),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 192 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 192 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 192 "cursor-readahead.pgc"
+
+	{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_forward, "1", ECPGst_normal, "fetch from xcur", ECPGt_EOIT, 
+	ECPGt_int,(id1),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 193 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 193 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 193 "cursor-readahead.pgc"
+
+	if (sqlca.sqlcode == ECPG_NOT_FOUND)
+		printf("After last record in cursor 'xcur' (value %d), fetching backwards.\n", id1[0]);
+	else
+		goto err;
+
+	{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_backward, "all", ECPGst_normal, "fetch backward all xcur", ECPGt_EOIT, 
+	ECPGt_sqlda, &sqlda, 0L, 0L, 0L, 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 199 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 199 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 199 "cursor-readahead.pgc"
+
+
+	/* exec sql whenever not found  break ; */
+#line 201 "cursor-readahead.pgc"
+
+
+	id = maxid;
+	id1[0] = -1;
+	while (sqlda)
+	{
+		sqlda_t	*tmp;
+
+		id1[0] = *(int *)sqlda->sqlvar[0].sqldata;
+
+		if (id != id1[0])
+		{
+			printf("Reading readahead cursors backward into sqlda chain: ERROR. id = %d id1 = %d\n", id, id1[0]);
+			break;
+		}
+
+		tmp = sqlda->desc_next;
+		free(sqlda);
+		sqlda = tmp;
+
+		id--;
+	}
+
+	if (id == 0)
+		printf("Reading all records from readahead cursor backwards into sqlda chain: SUCCESS\n");
+	else
+		printf("Reading all records from readahead cursors backwards into sqlda chain: FAILED, id %d, id1 %d\n", id, id1[0]);
+
+	{ ECPGclose(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "close xcur", ECPGt_EOIT, ECPGt_EORT);
+#line 229 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 229 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 229 "cursor-readahead.pgc"
+
+
+err:
+
+	{ ECPGtrans(__LINE__, NULL, "commit");
+#line 233 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 233 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 233 "cursor-readahead.pgc"
+
+
+	{ ECPGtrans(__LINE__, NULL, "begin");
+#line 235 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 235 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 235 "cursor-readahead.pgc"
+
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "drop table ra_test", ECPGt_EOIT, ECPGt_EORT);
+#line 237 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 237 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 237 "cursor-readahead.pgc"
+
+
+	{ ECPGtrans(__LINE__, NULL, "commit");
+#line 239 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 239 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 239 "cursor-readahead.pgc"
+
+
+	{ ECPGdisconnect(__LINE__, "ALL");
+#line 241 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 241 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 241 "cursor-readahead.pgc"
+
+
+	return 0;
+}
diff --git a/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.stderr b/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.stderr
new file mode 100644
index 0000000..e69de29
diff --git a/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.stdout b/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.stdout
new file mode 100644
index 0000000..5e70dd7
--- /dev/null
+++ b/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.stdout
@@ -0,0 +1,11 @@
+Reading readahead and non-readahead cursors simultaneously forward by 1 record: SUCCESS
+After last record in cursor 'xx' (value 513), fetching backwards.
+After last record in cursor 'xcur' (value 513), fetching backwards.
+Reading readahead and non-readahead cursors simultaneously backwards by 1 record(s): SUCCESS
+Reading readahead and non-readahead cursors simultaneously forward by 5 record: SUCCESS
+After last record in cursor 'xx' (value 513), fetching backwards.
+After last record in cursor 'xcur' (value 513), fetching backwards.
+Reading readahead and non-readahead cursors simultaneously backwards by 5 record(s): SUCCESS
+Reading all records from a readahead cursor forward into sqlda chain: SUCCESS
+After last record in cursor 'xcur' (value 513), fetching backwards.
+Reading all records from readahead cursor backwards into sqlda chain: SUCCESS
diff --git a/src/interfaces/ecpg/test/preproc/Makefile b/src/interfaces/ecpg/test/preproc/Makefile
index 3bcb63a..dfe4166 100644
--- a/src/interfaces/ecpg/test/preproc/Makefile
+++ b/src/interfaces/ecpg/test/preproc/Makefile
@@ -8,6 +8,7 @@ TESTS = array_of_struct array_of_struct.c \
 	autoprep autoprep.c \
 	comment comment.c \
 	cursor cursor.c \
+	cursor-readahead cursor-readahead.c \
 	define define.c \
 	init init.c \
 	strings strings.c \
diff --git a/src/interfaces/ecpg/test/preproc/cursor-readahead.pgc b/src/interfaces/ecpg/test/preproc/cursor-readahead.pgc
new file mode 100644
index 0000000..076f9b3
--- /dev/null
+++ b/src/interfaces/ecpg/test/preproc/cursor-readahead.pgc
@@ -0,0 +1,244 @@
+#include <stdio.h>
+#include <malloc.h>
+
+/* test automatic prepare for all statements */
+EXEC SQL INCLUDE ../regression;
+
+EXEC SQL INCLUDE sqlda.h;
+
+EXEC SQL WHENEVER SQLERROR SQLPRINT;
+EXEC SQL WHENEVER SQLWARNING SQLPRINT;
+
+#define MAXID	(513)
+
+int main(void)
+{
+	int	counts[2] = { 1, 5 };
+	EXEC SQL BEGIN DECLARE SECTION;
+	char	*curname;
+	int	maxid;
+	int	i, j, count, rows;
+	int	id, id1[5], id2[5];
+	EXEC SQL END DECLARE SECTION;
+	sqlda_t	*sqlda;
+
+	/*
+	 * Intentionally don't create a 2MB stderr file for this test.
+	 * Enable it manually if you're interested in it.
+	 */
+#if 0
+	ECPGdebug(1, stderr);
+#endif
+
+	EXEC SQL CONNECT TO REGRESSDB1;
+
+	EXEC SQL BEGIN;
+
+	maxid = MAXID;
+
+	EXEC SQL CREATE TABLE ra_test (id INTEGER PRIMARY KEY);
+	EXEC SQL INSERT INTO ra_test SELECT i.i FROM generate_series(1, :maxid) as i;
+
+	EXEC SQL COMMIT;
+
+	EXEC SQL BEGIN;
+
+	curname = "xx";
+	EXEC SQL DECLARE :curname SCROLL CURSOR FOR SELECT * FROM ra_test;
+	EXEC SQL DECLARE xcur SCROLL READAHEAD CURSOR FOR SELECT * FROM ra_test;
+
+	EXEC SQL WHENEVER NOT FOUND DO BREAK;
+
+	for (i = 0; i < 2; i++)
+	{
+		count = counts[i];
+
+		EXEC SQL OPEN :curname;
+		EXEC SQL OPEN xcur;
+
+		id = 0;
+		while (1)
+		{
+			for (j = 0; j < count; j++)
+			{
+				id1[j] = -1;
+				id2[j] = -1;
+			}
+
+			EXEC SQL FETCH :count FROM :curname INTO :id1;
+			EXEC SQL FETCH :count FROM xcur INTO :id2;
+
+			rows = sqlca.sqlerrd[2];
+
+			for (j = 0; j < rows; j++)
+			{
+				id++;
+
+				if (id != id1[j] || id != id2[j] || id1[j] != id2[j])
+				{
+					printf("Reading two cursors forward: ERROR. id = %d id1 = %d id2 = %d\n", id, id1[j], id2[j]);
+					break;
+				}
+			}
+			if (j != rows)
+				break;
+		}
+
+		if (sqlca.sqlwarn[0] == 'W') sqlprint();
+		if (sqlca.sqlcode < 0) sqlprint();
+
+		if (id == maxid)
+			printf("Reading readahead and non-readahead cursors simultaneously forward by %d record: SUCCESS\n", count);
+		else
+			printf("Reading readahead and non-readahead cursors simultaneously forward by %d record: FAILED at %d, id1 %d id2 %d\n", count, id, id1[j], id2[j]);
+
+		EXEC SQL CLOSE :curname;
+		EXEC SQL CLOSE xcur;
+
+		EXEC SQL WHENEVER NOT FOUND CONTINUE;
+
+		EXEC SQL OPEN :curname;
+		EXEC SQL OPEN xcur;
+		EXEC SQL FETCH LAST FROM :curname INTO :id1;
+		EXEC SQL FETCH FROM :curname INTO :id1;
+		if (sqlca.sqlcode == ECPG_NOT_FOUND)
+			printf("After last record in cursor '%s' (value %d), fetching backwards.\n", curname, id1[0]);
+		else
+			goto err;
+		EXEC SQL FETCH LAST FROM xcur INTO :id2;
+		EXEC SQL FETCH FROM xcur INTO :id2;
+		if (sqlca.sqlcode == ECPG_NOT_FOUND)
+			printf("After last record in cursor 'xcur' (value %d), fetching backwards.\n", id2[0]);
+		else
+			goto err;
+
+		EXEC SQL WHENEVER NOT FOUND DO BREAK;
+
+		id = maxid;
+		while (1)
+		{
+			for (j = 0; j < count; j++)
+			{
+				id1[j] = -1;
+				id2[j] = -1;
+			}
+
+			EXEC SQL FETCH BACKWARD :count FROM :curname INTO :id1;
+			EXEC SQL FETCH BACKWARD :count FROM xcur INTO :id2;
+
+			rows = sqlca.sqlerrd[2];
+
+			for (j = 0; j < rows; j++)
+			{
+				if (id != id1[j] || id != id2[j] || id1[j] != id2[j])
+				{
+					printf("Reading two cursors backward: ERROR. id = %d id1 = %d id2 = %d\n", id, id1[j], id2[j]);
+					break;
+				}
+				id--;
+			}
+			if (j != rows)
+				break;
+		}
+
+		if (id == 0)
+			printf("Reading readahead and non-readahead cursors simultaneously backwards by %d record(s): SUCCESS\n", count);
+		else
+			printf("Reading readahead and non-readahead cursors simultaneously backwards by %d record(s): FAILED at %d, id1 %d id2 %d\n", count, id, id1[j], id2[j]);
+
+		EXEC SQL CLOSE :curname;
+		EXEC SQL CLOSE xcur;
+
+		EXEC SQL WHENEVER NOT FOUND CONTINUE;
+
+	}
+
+	sqlda = NULL;
+	EXEC SQL OPEN xcur;
+	EXEC SQL FETCH ALL xcur INTO DESCRIPTOR sqlda;
+
+	EXEC SQL WHENEVER NOT FOUND DO BREAK;
+
+	id = 0;
+	id1[0] = -1;
+	while (sqlda)
+	{
+		sqlda_t *tmp;
+
+		id++;
+		id1[0] = *(int *)sqlda->sqlvar[0].sqldata;
+
+		if (id != id1[0])
+		{
+			printf("Reading readahead cursors forward into sqlda chain: ERROR. id = %d id1 = %d\n", id, id1[0]);
+			break;
+		}
+		tmp = sqlda->desc_next;
+		free(sqlda);
+		sqlda = tmp;
+	}
+
+	if (id == maxid)
+		printf("Reading all records from a readahead cursor forward into sqlda chain: SUCCESS\n");
+	else
+		printf("Reading all records from a readahead cursor forward into sqlda chain: FAILED. id %d, id1 %d\n", id, id1[0]);
+
+	EXEC SQL CLOSE xcur;
+
+	EXEC SQL WHENEVER NOT FOUND CONTINUE;
+
+	sqlda = NULL;
+	EXEC SQL OPEN xcur;
+	EXEC SQL FETCH LAST FROM xcur INTO :id1;
+	EXEC SQL FETCH FROM xcur INTO :id1;
+	if (sqlca.sqlcode == ECPG_NOT_FOUND)
+		printf("After last record in cursor 'xcur' (value %d), fetching backwards.\n", id1[0]);
+	else
+		goto err;
+
+	EXEC SQL FETCH BACKWARD ALL xcur INTO DESCRIPTOR sqlda;
+
+	EXEC SQL WHENEVER NOT FOUND DO BREAK;
+
+	id = maxid;
+	id1[0] = -1;
+	while (sqlda)
+	{
+		sqlda_t	*tmp;
+
+		id1[0] = *(int *)sqlda->sqlvar[0].sqldata;
+
+		if (id != id1[0])
+		{
+			printf("Reading readahead cursors backward into sqlda chain: ERROR. id = %d id1 = %d\n", id, id1[0]);
+			break;
+		}
+
+		tmp = sqlda->desc_next;
+		free(sqlda);
+		sqlda = tmp;
+
+		id--;
+	}
+
+	if (id == 0)
+		printf("Reading all records from readahead cursor backwards into sqlda chain: SUCCESS\n");
+	else
+		printf("Reading all records from readahead cursors backwards into sqlda chain: FAILED, id %d, id1 %d\n", id, id1[0]);
+
+	EXEC SQL CLOSE xcur;
+
+err:
+
+	EXEC SQL COMMIT;
+
+	EXEC SQL BEGIN;
+
+	EXEC SQL DROP TABLE ra_test;
+
+	EXEC SQL COMMIT;
+
+	EXEC SQL DISCONNECT ALL;
+
+	return 0;
+}

Attachments:

  [text/plain] ecpg-cursor-readahead-9.2-git-v2.patch (116.9K, ../4EC41434.7010603@cybertec.at/2-ecpg-cursor-readahead-9.2-git-v2.patch)
  download | inline diff:
diff --git a/doc/src/sgml/ecpg.sgml b/doc/src/sgml/ecpg.sgml
index 68833ca..69b49e0 100644
--- a/doc/src/sgml/ecpg.sgml
+++ b/doc/src/sgml/ecpg.sgml
@@ -454,6 +454,32 @@ EXEC SQL COMMIT;
    details.
   </para>
 
+  <para>
+   ECPG may use cursor readahead to improve performance of programs
+   that use single-row FETCH statements. Option <literal>-r fetch_readahead</literal>
+   option for ECPG modifies the default for all cursors from <literal>NO READAHEAD</literal>
+   to <literal>READAHEAD</literal>. Explicit <literal>READAHEAD</literal> or
+   <literal>NO READAHEAD</literal> turns cursor readahead on or off for the specified cursor,
+   respectively. The default readahead size is 256 rows, this may be modified by setting
+   the <literal>ECPGFETCHSZ</literal> environment variable to a different value.
+  </para>
+
+  <para>
+   Scrolling behaviour differs from the documented one at <xref linkend="sql-declare">
+   when readahead is used for a cursor. ECPG treats cursors as <literal>NO SCROLL</literal>
+   when neither <literal>SCROLL</literal> nor <literal>NO SCROLL</literal> are specified.
+   When backward fetching or positioning is attempted on a <literal>NO SCROLL READAHEAD</literal>
+   cursor, error code 55000 (Object not in prerequisite state) is returned to the application.
+  </para>
+
+  <para>
+   For cursors used in <command>UPDATE</command> or <command>DELETE</command>
+   with the <literal>WHERE CURRENT OF</literal> clause, cursor readahead must be
+   turned off, otherwise <command>ecpg</command> throws an error. This restriction
+   is because when the client side uses readahead, the idea about the cursor position
+   in the dataset may differ between the server and the client.
+  </para>
+
    <note>
     <para>
      The ECPG <command>DECLARE</command> command does not actually
@@ -6583,8 +6609,8 @@ EXEC SQL DEALLOCATE DESCRIPTOR mydesc;
 
    <refsynopsisdiv>
 <synopsis>
-DECLARE <replaceable class="PARAMETER">cursor_name</replaceable> [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ] CURSOR [ { WITH | WITHOUT } HOLD ] FOR <replaceable class="PARAMETER">prepared_name</replaceable>
-DECLARE <replaceable class="PARAMETER">cursor_name</replaceable> [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ] CURSOR [ { WITH | WITHOUT } HOLD ] FOR <replaceable class="PARAMETER">query</replaceable>
+DECLARE <replaceable class="PARAMETER">cursor_name</replaceable> [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ] [ [ NO ] READAHEAD ] CURSOR [ { WITH | WITHOUT } HOLD ] FOR <replaceable class="PARAMETER">prepared_name</replaceable>
+DECLARE <replaceable class="PARAMETER">cursor_name</replaceable> [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ] [ [ NO ] READAHEAD ] CURSOR [ { WITH | WITHOUT } HOLD ] FOR <replaceable class="PARAMETER">query</replaceable>
 </synopsis>
    </refsynopsisdiv>
 
@@ -6639,11 +6665,36 @@ DECLARE <replaceable class="PARAMETER">cursor_name</replaceable> [ BINARY ] [ IN
       </listitem>
      </varlistentry>
     </variablelist>
+   </refsect1>
+
+   <refsect1>
+    <title>Cursor options</title>
 
     <para>
-     For the meaning of the cursor options,
-     see <xref linkend="sql-declare">.
+     For the meaning of other cursor options, see <xref linkend="sql-declare">.
     </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>READAHEAD</literal></term>   
+      <term><literal>NO READAHEAD</literal></term>   
+       <listitem>
+        <para>
+         <literal>READAHEAD</literal> makes the ECPG preprocessor and runtime library
+         use a client-side cursor accounting and data readahead during
+         <command>FETCH</command>. This improves performance for programs that use
+         single-row <command>FETCH</command> statements.
+        </para>
+
+        <para>
+         <literal>NO READAHEAD</literal> disables data readahead in case
+         <parameter>-r fetch_readahead</parameter> is used for compiling the file.
+        </para>
+       </listitem>
+      </varlistentry>
+
+    </variablelist>
+
    </refsect1>
 
    <refsect1>
diff --git a/doc/src/sgml/ref/ecpg-ref.sgml b/doc/src/sgml/ref/ecpg-ref.sgml
index 9c13e93..22dfe44 100644
--- a/doc/src/sgml/ref/ecpg-ref.sgml
+++ b/doc/src/sgml/ref/ecpg-ref.sgml
@@ -166,6 +166,14 @@ PostgreSQL documentation
          </para>
          </listitem>
         </varlistentry>
+        <varlistentry>
+         <term><option>fetch_readahead</option></term>
+         <listitem>
+         <para>
+         Turn on cursor readahead by default.
+         </para>
+         </listitem>
+        </varlistentry>
        </variablelist></para>
      </listitem>
     </varlistentry>
diff --git a/src/interfaces/ecpg/ecpglib/Makefile b/src/interfaces/ecpg/ecpglib/Makefile
index 2b2ffb6..1f46ff6 100644
--- a/src/interfaces/ecpg/ecpglib/Makefile
+++ b/src/interfaces/ecpg/ecpglib/Makefile
@@ -25,7 +25,7 @@ override CFLAGS += $(PTHREAD_CFLAGS)
 LIBS := $(filter-out -lpgport, $(LIBS))
 
 OBJS= execute.o typename.o descriptor.o sqlda.o data.o error.o prepare.o memory.o \
-	connect.o misc.o path.o pgstrcasecmp.o \
+	connect.o misc.o path.o pgstrcasecmp.o cursor.o \
 	$(filter snprintf.o strlcpy.o win32setlocale.o, $(LIBOBJS))
 
 # thread.c is needed only for non-WIN32 implementation of path.c
diff --git a/src/interfaces/ecpg/ecpglib/connect.c b/src/interfaces/ecpg/ecpglib/connect.c
index 997046b..7eda052 100644
--- a/src/interfaces/ecpg/ecpglib/connect.c
+++ b/src/interfaces/ecpg/ecpglib/connect.c
@@ -456,6 +456,7 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p
 
 	this->cache_head = NULL;
 	this->prep_stmts = NULL;
+	this->cursor_desc = NULL;
 
 	if (all_connections == NULL)
 		this->next = NULL;
diff --git a/src/interfaces/ecpg/ecpglib/cursor.c b/src/interfaces/ecpg/ecpglib/cursor.c
new file mode 100644
index 0000000..ff6e40d
--- /dev/null
+++ b/src/interfaces/ecpg/ecpglib/cursor.c
@@ -0,0 +1,708 @@
+/*
+ * FETCH readahead support routines
+ */
+
+#define POSTGRES_ECPG_INTERNAL
+#include "postgres_fe.h"
+
+#include <limits.h>
+
+#include "ecpgtype.h"
+#include "ecpglib.h"
+#include "ecpgerrno.h"
+#include "extern.h"
+
+static struct cursor_descriptor *add_cursor(int lineno, struct connection *con, const char *name, bool scrollable, int64 n_tuples, bool *existing);
+static struct cursor_descriptor *find_cursor(struct connection *con, const char *name);
+static void del_cursor(struct connection *con, const char *name);
+
+/* default fetch size, set on the first call to ECPGopen()  */
+#define DEFAULTFETCHSIZE	(256)
+static int	fetch_size = 0;
+
+/*
+ * Add a new cursor descriptor, maintain alphabetic order
+ */
+static struct cursor_descriptor *
+add_cursor(int lineno, struct connection *con, const char *name, bool scrollable, int64 n_tuples, bool *existing)
+{
+	struct cursor_descriptor *desc,
+				*ptr, *prev = NULL;
+	bool	found = false;
+
+	if (!name || name[0] == '\0')
+	{
+		if (existing)
+			*existing = false;
+		return NULL;
+	}
+
+	ptr = con->cursor_desc;
+	while (ptr)
+	{
+		int ret = strcasecmp(ptr->name, name);
+
+		if (ret == 0)
+		{
+			found = true;
+			break;
+		}
+		if (ret > 0)
+			break;
+
+		prev = ptr;
+		ptr = ptr->next;
+	}
+
+	if (found)
+	{
+		if (existing)
+			*existing = true;
+		return ptr;
+	}
+
+	desc = (struct cursor_descriptor *)ecpg_alloc(sizeof(struct cursor_descriptor), lineno);
+	if (!desc)
+		return NULL;
+	desc->name = ecpg_strdup(name, lineno);
+	if (!desc->name)
+	desc->res = NULL;
+	desc->scrollable = scrollable;
+	desc->n_tuples = n_tuples;
+	desc->start_pos = 0;
+	desc->cache_pos = 0;
+	desc->next = ptr;
+
+	if (prev)
+		prev->next = desc;
+	else
+		con->cursor_desc = desc;
+
+	if (existing)
+		*existing = false;
+	return desc;
+}
+
+static struct cursor_descriptor *
+find_cursor(struct connection *con, const char *name)
+{
+	struct cursor_descriptor *desc = con->cursor_desc;
+	bool	found = false;
+
+	if (!name)
+		return NULL;
+
+	while (desc)
+	{
+		int ret = strcasecmp(desc->name, name);
+
+		if (ret == 0)
+		{
+			found = true;
+			break;
+		}
+		if (ret > 0)
+			break;
+		desc = desc->next;
+	}
+
+	return (found ? desc : NULL);
+}
+
+static void
+del_cursor(struct connection *con, const char *name)
+{
+	struct cursor_descriptor *ptr, *prev = NULL;
+	bool	found = false;
+
+	ptr = con->cursor_desc;
+	while (ptr)
+	{
+		int ret = strcasecmp(ptr->name, name);
+
+		if (ret == 0)
+		{
+			found = true;
+			break;
+		}
+		if (ret > 0)
+			break;
+
+		prev = ptr;
+		ptr = ptr->next;
+	}
+
+	if (found)
+	{
+		if (prev)
+			prev->next = ptr->next;
+		else
+			con->cursor_desc = ptr->next;
+
+		ecpg_free(ptr->name);
+		if (ptr->res)
+			PQclear(ptr->res);
+		ecpg_free(ptr);
+	}
+}
+
+bool
+ECPGopen(const int lineno, const int compat, const int force_indicator,
+		const char *connection_name, const bool questionmarks,
+		const char *curname, const int st, const char *query, ...)
+{
+	va_list		args;
+	bool		ret, scrollable;
+	char	   *new_query, *ptr, *whold, *noscroll, *scroll, *dollar0;
+	struct sqlca_t *sqlca = ECPGget_sqlca();
+
+	if (!query)
+	{
+		ecpg_raise(lineno, ECPG_EMPTY, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, NULL);
+		return false;
+	}
+	ptr = strstr(query, "for ");
+	if (!ptr)
+	{
+		ecpg_raise(lineno, ECPG_INVALID_STMT, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, NULL);
+		return false;
+	}
+	whold = strstr(query, "with hold ");
+	dollar0 = strstr(query, "$0");
+
+	noscroll = strstr(query, "no scroll ");
+	scroll = strstr(query, "scroll ");
+	scrollable = (noscroll == NULL) && (scroll != NULL) && (scroll < ptr);
+
+	new_query = ecpg_alloc(strlen(curname) + strlen(ptr) + (whold ? 10 : 0) + 32, lineno);
+	if (!new_query)
+		return false;
+	sprintf(new_query, "declare %s %s cursor %s%s",
+					(dollar0 && (dollar0 < ptr) ? "$0" : curname),
+					(scrollable ? "scroll" : "no scroll"),
+					(whold ? "with hold " : ""),
+					ptr);
+
+	/* Set the fetch size the first time we are called. */
+	if (fetch_size == 0)
+	{
+		char	   *fsize_str = getenv("ECPGFETCHSZ");
+		char	   *endptr = NULL;
+		int		fsize;
+
+		if (fsize_str)
+		{
+			fsize = strtoul(fsize_str, &endptr, 10);
+			if (endptr || (fsize < 4))
+				fetch_size = DEFAULTFETCHSIZE;
+			else
+				fetch_size = fsize;
+		}
+		else
+			fetch_size = DEFAULTFETCHSIZE;
+	}
+
+	va_start(args, query);
+	ret = ecpg_do(lineno, compat, force_indicator, connection_name, questionmarks, st, new_query, args);
+	va_end(args);
+
+	ecpg_free(new_query);
+
+	/*
+	 * If statement went OK, add the cursor and discover the
+	 * number of rows in the recordset. This will slow down OPEN
+	 * but we gain a lot with caching.
+	 */
+	if (ret /* && sqlca->sqlerrd[2] == 0 */)
+	{
+		struct connection *con = ecpg_get_connection(connection_name);
+		struct cursor_descriptor *cur;
+		bool	existing;
+		int64	n_tuples;
+
+		if (scrollable)
+		{
+			PGresult   *res;
+			char	   *query;
+			char	   *endptr = NULL;
+
+			query = ecpg_alloc(strlen(curname) + strlen("move all in ") + 2, lineno);
+			sprintf(query, "move all in %s", curname);
+			res = PQexec(con->connection, query);
+			n_tuples = strtoull(PQcmdTuples(res), &endptr, 10);
+			PQclear(res);
+			ecpg_free(query);
+
+			/* Go back to the beginning of the resultset. */
+			query = ecpg_alloc(strlen(curname) + strlen("move absolute 0 in ") + 2, lineno);
+			sprintf(query, "move absolute 0 in %s", curname);
+			res = PQexec(con->connection, query);
+			PQclear(res);
+			ecpg_free(query);
+		}
+		else
+		{
+			n_tuples = 0;
+		}
+
+		/* Add the cursor */
+		cur = add_cursor(lineno, con, curname, scrollable, n_tuples, &existing);
+
+		/*
+		 * Report the number of tuples for the [scrollable] cursor.
+		 * The server didn't do it for us.
+		 */
+		sqlca->sqlerrd[2] = (cur->n_tuples < LONG_MAX ? cur->n_tuples : LONG_MAX);
+	}
+
+	return ret;
+}
+
+static bool
+ecpg_cursor_execute(struct statement * stmt, struct cursor_descriptor *cur)
+{
+	char		tmp[64];
+	char	   *query;
+	int64		start_pos;
+
+	if ((cur->cache_pos >= cur->start_pos) && cur->res && (cur->cache_pos < cur->start_pos + PQntuples(cur->res)))
+	{
+		stmt->results = cur->res;
+		ecpg_free_params(stmt, true, stmt->lineno);
+		return true;
+	}
+	else if (!cur->scrollable && cur->res && (PQntuples(cur->res) < fetch_size) && (cur->cache_pos >= cur->start_pos + PQntuples(cur->res)))
+	{
+		cur->endoftuples = true;
+		stmt->results = cur->res;
+		ecpg_free_params(stmt, true, stmt->lineno);
+		return true;
+	}
+
+	if ((PQtransactionStatus(stmt->connection->connection) == PQTRANS_IDLE) && !stmt->connection->autocommit)
+	{
+		stmt->results = PQexec(stmt->connection->connection, "begin transaction");
+		if (!ecpg_check_PQresult(stmt->results, stmt->lineno, stmt->connection->connection, stmt->compat))
+		{
+			ecpg_free_params(stmt, false, stmt->lineno);
+			return false;
+		}
+		PQclear(stmt->results);
+	}
+
+	/*
+	 * Compute the tuple position before the resultset. E.g.:
+	 * MOVE ABSOLUTE 0 + FETCH NEXT <fetch_size> will result
+	 * in a recordset having tuples 1 ... fetch_size
+	 */
+	start_pos = (cur->cache_pos - 1) / fetch_size;
+	start_pos *= fetch_size;
+
+	if (cur->scrollable)
+	{
+		sprintf(tmp, "%lld", (long long)start_pos);
+		query = ecpg_alloc(strlen(tmp) + strlen(cur->name) + 20, stmt->lineno);
+		sprintf(query, "move absolute %s in %s", tmp, cur->name);
+
+		ecpg_log("ecpg_cursor_execute on line %d: query: %s; on connection %s\n", stmt->lineno, query, stmt->connection->name);
+
+		stmt->results = PQexec(stmt->connection->connection, query);
+
+		if (!ecpg_check_PQresult(stmt->results, stmt->lineno, stmt->connection->connection, stmt->compat))
+		{
+			ecpg_free_params(stmt, false, stmt->lineno);
+			return false;
+		}
+
+		PQclear(stmt->results);
+		ecpg_free(query);
+	}
+
+	sprintf(tmp, "%lld", (long long)fetch_size);
+	query = ecpg_alloc(strlen(tmp) + strlen(cur->name) + 24, stmt->lineno);
+	sprintf(query, "fetch forward %s from %s", tmp, cur->name);
+
+	if (cur->res)
+		PQclear(cur->res);
+
+	ecpg_log("ecpg_cursor_execute on line %d: query: %s; with %d parameter(s) on connection %s\n", stmt->lineno, query, stmt->nparams, stmt->connection->name);
+
+	if (stmt->nparams == 0)
+	{
+		cur->res = PQexec(stmt->connection->connection, query);
+		ecpg_log("ecpg_cursor_execute on line %d: using PQexec\n", stmt->lineno);
+	}
+	else
+	{
+		/* shouldn't happen */
+		cur->res = PQexecParams(stmt->connection->connection, query, stmt->nparams, NULL, stmt->param_values, NULL, NULL, 0);
+		ecpg_log("ecpg_cursor_execute on line %d: using PQexecParams\n", stmt->lineno);
+	}
+
+	stmt->results = cur->res;
+
+	ecpg_free_params(stmt, true, stmt->lineno);
+
+	if (!ecpg_check_PQresult(cur->res, stmt->lineno, stmt->connection->connection, stmt->compat))
+	{
+		stmt->results = cur->res = NULL;
+		cur->start_pos = 0;
+		return false;
+	}
+
+	/* The tuple position in the cursor is 1 based. */
+	cur->start_pos = start_pos + 1;
+
+	if (!cur->scrollable && PQntuples(cur->res) == 0)
+		cur->endoftuples = true;
+
+	ecpg_free(query);
+
+	return true;
+}
+
+bool
+ECPGfetch(const int lineno, const int compat, const int force_indicator,
+		const char *connection_name, const bool questionmarks,
+		const char *curname, enum ECPG_cursor_direction direction, const char *amount,
+		const int st, const char *query, ...)
+{
+	struct cursor_descriptor *cur;
+	struct statement *stmt;
+	va_list			args;
+	bool			move, fetchall = false, negate = false;
+	const char	   *amount1 = amount;
+	int			step;
+	int64			n_amount, count, start_idx;
+	struct sqlca_t		   *sqlca = ECPGget_sqlca();
+
+	if (!query)
+	{
+		ecpg_raise(lineno, ECPG_EMPTY, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, NULL);
+		return false;
+	}
+
+	move = (strncmp(query, "move ", 5) == 0);
+
+	cur = find_cursor(ecpg_get_connection(connection_name), curname);
+	if (!cur)
+	{
+		ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_INVALID_CURSOR_NAME, (connection_name) ? connection_name : ecpg_gettext("<empty>"));
+		return false;
+	}
+
+	va_start(args, query);
+
+	if (!ecpg_do_prologue(lineno, compat, force_indicator, connection_name, questionmarks, (enum ECPG_statement_type) st, query, args, &stmt))
+		return false;
+
+	if (!ecpg_build_params(stmt))
+	{
+		ecpg_do_epilogue(stmt);
+		va_end(args);
+		return false;
+	}
+
+	if (amount[0] == '$')
+		amount1 = stmt->dollarzero[0];
+	else if (amount[0] == '-' || amount[0] == '+')
+	{
+		/*
+		 * Handle negative and explicitely positive constants in
+		 * FETCH/MOVE ABSOLUTE/RELATIVE const.
+		 * E.g. '-2' arrives as '- 2', '+2' arrives as '+ 2'.
+		 * strtoll() under Linux stops processing at the space.
+		 */
+		if (amount[0] == '-')
+			negate = true;
+		amount1 = amount + 1;
+		while (*amount1 == ' ')
+			amount1++;
+	}
+
+	if (strcmp(amount, "all") == 0)
+	{
+		fetchall = true;
+		if (cur->scrollable)
+		{
+			switch (direction)
+			{
+				case ECPGc_forward:
+					n_amount = cur->n_tuples - cur->cache_pos;
+					break;
+				case ECPGc_backward:
+					n_amount = cur->cache_pos;
+					break;
+				default:
+					ecpg_raise(lineno, ECPG_EMPTY, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, NULL);
+					return false;
+			}
+		}
+		else
+			n_amount = LONG_MAX;
+	}
+	else
+	{
+		char	   *endptr;
+
+		n_amount = strtoll(amount1, &endptr, 10);
+		if (negate)
+			n_amount = -n_amount;
+	}
+
+	switch (direction)
+	{
+		case ECPGc_absolute:
+			if (cur->scrollable)
+			{
+				if (n_amount < 0)
+					n_amount = 1 + cur->n_tuples + n_amount;
+				else if (n_amount > cur->n_tuples)
+					n_amount = cur->n_tuples + 1;
+			}
+			else
+			{
+				if (n_amount < 0)
+				{
+					ecpg_free_params(stmt, true, stmt->lineno);
+					ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+					break;
+				}
+				if (n_amount < cur->cache_pos)
+				{
+					ecpg_free_params(stmt, true, stmt->lineno);
+					ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+					break;
+				}
+			}
+			cur->cache_pos = n_amount;
+
+			if (!move)
+			{
+				if (cur->cache_pos > 0 && ((cur->scrollable && cur->cache_pos <= cur->n_tuples) || (!cur->scrollable && !cur->endoftuples)))
+				{
+					if (!ecpg_cursor_execute(stmt, cur))
+					{
+						ecpg_do_epilogue(stmt);
+						va_end(args);
+						return false;
+					}
+
+					if (!cur->scrollable && cur->endoftuples)
+					{
+						ecpg_do_epilogue(stmt);
+						va_end(args);
+						ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
+						return false;
+					}
+
+					start_idx = cur->cache_pos - cur->start_pos;
+
+					if (!ecpg_process_output(stmt, start_idx, start_idx + 1, 0, true, false))
+					{
+						ecpg_do_epilogue(stmt);
+						va_end(args);
+						return false;
+					}
+				}
+				else
+				{
+					ecpg_free_params(stmt, true, stmt->lineno);
+					ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
+				}
+			}
+			else
+			{
+				ecpg_free_params(stmt, true, stmt->lineno);
+			}
+			sqlca->sqlerrd[2] = (cur->cache_pos && cur->cache_pos <= cur->n_tuples ? 1 : 0);
+			break;
+
+		case ECPGc_relative:
+relative:
+			if (!cur->scrollable)
+			{
+				if (n_amount < 0)
+				{
+					ecpg_free_params(stmt, true, stmt->lineno);
+					ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+					break;
+				}
+			}
+
+			cur->cache_pos += n_amount;
+			if (cur->cache_pos < 0)
+				cur->cache_pos = 0;
+			else if (cur->cache_pos > cur->n_tuples)
+				cur->cache_pos = cur->n_tuples + 1;
+
+			if (!move)
+			{
+				if (cur->cache_pos > 0 && ((cur->scrollable && cur->cache_pos <= cur->n_tuples) || (!cur->scrollable && !cur->endoftuples)))
+				{
+					if (!ecpg_cursor_execute(stmt, cur))
+					{
+						ecpg_do_epilogue(stmt);
+						va_end(args);
+						return false;
+					}
+
+					if (!cur->scrollable && cur->endoftuples)
+					{
+						ecpg_do_epilogue(stmt);
+						va_end(args);
+						ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
+						return false;
+					}
+
+					start_idx = cur->cache_pos - cur->start_pos;
+
+					if (!ecpg_process_output(stmt, start_idx, start_idx + 1, 0, true, false))
+					{
+						ecpg_do_epilogue(stmt);
+						va_end(args);
+						return false;
+					}
+				}
+				else
+				{
+					ecpg_free_params(stmt, true, stmt->lineno);
+					ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
+				}
+			}
+			else
+			{
+				ecpg_free_params(stmt, true, stmt->lineno);
+			}
+			sqlca->sqlerrd[2] = (cur->cache_pos && cur->cache_pos <= cur->n_tuples ? 1 : 0);
+			break;
+
+		case ECPGc_forward:
+		case ECPGc_backward:
+			if (n_amount == 0)
+				goto relative;
+
+			step = (n_amount > 0 ? 1 : -1);
+			if (direction == ECPGc_backward)
+				step = -step;
+			if (n_amount < 0)
+				n_amount = -n_amount;
+
+			if (!cur->scrollable && step < 0)
+			{
+				ecpg_free_params(stmt, true, stmt->lineno);
+				ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+				break;
+			}
+
+			if (move)
+			{
+				int64 new_pos = cur->cache_pos + step * n_amount;
+				int64 diff;
+
+				if (new_pos < 1)
+					new_pos = 0;
+				if (new_pos > cur->n_tuples)
+					new_pos = cur->n_tuples + 1;
+
+				diff = new_pos - cur->cache_pos;
+				sqlca->sqlerrd[2] = (diff >= 0 ? diff : -diff);
+				cur->cache_pos = new_pos;
+
+				ecpg_free_params(stmt, true, stmt->lineno);
+				break;
+			}
+
+			for (count = 0; (cur->scrollable && count < n_amount) ||
+					(!cur->scrollable && ((fetchall && !cur->endoftuples) || (!fetchall && count < n_amount))); count++)
+			{
+				cur->cache_pos += step;
+				if (cur->cache_pos < 1)
+				{
+					cur->cache_pos = 0;
+					break;
+				}
+				else
+				{
+					if (cur->scrollable && cur->cache_pos > cur->n_tuples)
+					{
+						cur->cache_pos = cur->n_tuples + 1;
+						break;
+					}
+					else if (!cur->scrollable && cur->endoftuples)
+					{
+						break;
+					}
+				}
+
+				if (!ecpg_cursor_execute(stmt, cur))
+				{
+					ecpg_do_epilogue(stmt);
+					va_end(args);
+					return false;
+				}
+
+				if (!cur->scrollable && cur->endoftuples)
+					break;
+
+				start_idx = cur->cache_pos - cur->start_pos;
+
+				if (!ecpg_process_output(stmt, start_idx, start_idx + 1, count, true, (count > 0)))
+				{
+					ecpg_do_epilogue(stmt);
+					va_end(args);
+					return false;
+				}
+			}
+
+			if (count == 0)
+			{
+				ecpg_free_params(stmt, true, stmt->lineno);
+				ecpg_do_epilogue(stmt);
+				va_end(args);
+				ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
+				return false;
+			}
+
+			sqlca->sqlerrd[2] = count;
+
+			break;
+
+		default:
+			ecpg_free_params(stmt, true, stmt->lineno);
+			ecpg_do_epilogue(stmt);
+			va_end(args);
+			ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_INVALID_CURSOR_DEFINITION, (connection_name) ? connection_name : ecpg_gettext("<empty>"));
+			return false;
+	}
+
+	ecpg_do_epilogue(stmt);
+	va_end(args);
+	return true;
+}
+
+bool
+ECPGclose(const int lineno, const int compat, const int force_indicator,
+		const char *connection_name, const bool questionmarks,
+		const char *curname, const int st, const char *query, ...)
+{
+	struct connection  *con;
+	va_list			args;
+	bool			ret;
+
+	con = ecpg_get_connection(connection_name);
+
+	if (!find_cursor(con, curname))
+	{
+		ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_INVALID_CURSOR_NAME, (connection_name) ? connection_name : ecpg_gettext("<empty>"));
+		return false;
+	}
+
+	va_start(args, query);
+	ret = ecpg_do(lineno, compat, force_indicator, connection_name, questionmarks, st, query, args);
+	va_end(args);
+
+	del_cursor(con, curname);
+
+	return ret;
+}
diff --git a/src/interfaces/ecpg/ecpglib/data.c b/src/interfaces/ecpg/ecpglib/data.c
index fc04556..6cbf327 100644
--- a/src/interfaces/ecpg/ecpglib/data.c
+++ b/src/interfaces/ecpg/ecpglib/data.c
@@ -120,7 +120,7 @@ check_special_value(char *ptr, double *retval, char **endptr)
 }
 
 bool
-ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
+ecpg_get_data(const PGresult *results, int var_index, int act_tuple, int act_field, int lineno,
 			  enum ECPGttype type, enum ECPGttype ind_type,
 			  char *var, char *ind, long varcharsize, long offset,
 			  long ind_offset, enum ARRAY_TYPE isarray, enum COMPAT_MODE compat, bool force_indicator)
@@ -167,20 +167,20 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 	{
 		case ECPGt_short:
 		case ECPGt_unsigned_short:
-			*((short *) (ind + ind_offset * act_tuple)) = value_for_indicator;
+			*((short *) (ind + ind_offset * var_index)) = value_for_indicator;
 			break;
 		case ECPGt_int:
 		case ECPGt_unsigned_int:
-			*((int *) (ind + ind_offset * act_tuple)) = value_for_indicator;
+			*((int *) (ind + ind_offset * var_index)) = value_for_indicator;
 			break;
 		case ECPGt_long:
 		case ECPGt_unsigned_long:
-			*((long *) (ind + ind_offset * act_tuple)) = value_for_indicator;
+			*((long *) (ind + ind_offset * var_index)) = value_for_indicator;
 			break;
 #ifdef HAVE_LONG_LONG_INT
 		case ECPGt_long_long:
 		case ECPGt_unsigned_long_long:
-			*((long long int *) (ind + ind_offset * act_tuple)) = value_for_indicator;
+			*((long long int *) (ind + ind_offset * var_index)) = value_for_indicator;
 			break;
 #endif   /* HAVE_LONG_LONG_INT */
 		case ECPGt_NO_INDICATOR:
@@ -192,7 +192,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					 * Informix has an additional way to specify NULLs note
 					 * that this uses special values to denote NULL
 					 */
-					ECPGset_noind_null(type, var + offset * act_tuple);
+					ECPGset_noind_null(type, var + offset * var_index);
 				}
 				else
 				{
@@ -243,10 +243,10 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 		if (binary)
 		{
 			if (varcharsize == 0 || varcharsize * offset >= size)
-				memcpy(var + offset * act_tuple, pval, size);
+				memcpy(var + offset * var_index, pval, size);
 			else
 			{
-				memcpy(var + offset * act_tuple, pval, varcharsize * offset);
+				memcpy(var + offset * var_index, pval, varcharsize * offset);
 
 				if (varcharsize * offset < size)
 				{
@@ -255,20 +255,20 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					{
 						case ECPGt_short:
 						case ECPGt_unsigned_short:
-							*((short *) (ind + ind_offset * act_tuple)) = size;
+							*((short *) (ind + ind_offset * var_index)) = size;
 							break;
 						case ECPGt_int:
 						case ECPGt_unsigned_int:
-							*((int *) (ind + ind_offset * act_tuple)) = size;
+							*((int *) (ind + ind_offset * var_index)) = size;
 							break;
 						case ECPGt_long:
 						case ECPGt_unsigned_long:
-							*((long *) (ind + ind_offset * act_tuple)) = size;
+							*((long *) (ind + ind_offset * var_index)) = size;
 							break;
 #ifdef HAVE_LONG_LONG_INT
 						case ECPGt_long_long:
 						case ECPGt_unsigned_long_long:
-							*((long long int *) (ind + ind_offset * act_tuple)) = size;
+							*((long long int *) (ind + ind_offset * var_index)) = size;
 							break;
 #endif   /* HAVE_LONG_LONG_INT */
 						default:
@@ -307,13 +307,13 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					switch (type)
 					{
 						case ECPGt_short:
-							*((short *) (var + offset * act_tuple)) = (short) res;
+							*((short *) (var + offset * var_index)) = (short) res;
 							break;
 						case ECPGt_int:
-							*((int *) (var + offset * act_tuple)) = (int) res;
+							*((int *) (var + offset * var_index)) = (int) res;
 							break;
 						case ECPGt_long:
-							*((long *) (var + offset * act_tuple)) = (long) res;
+							*((long *) (var + offset * var_index)) = (long) res;
 							break;
 						default:
 							/* Cannot happen */
@@ -336,13 +336,13 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					switch (type)
 					{
 						case ECPGt_unsigned_short:
-							*((unsigned short *) (var + offset * act_tuple)) = (unsigned short) ures;
+							*((unsigned short *) (var + offset * var_index)) = (unsigned short) ures;
 							break;
 						case ECPGt_unsigned_int:
-							*((unsigned int *) (var + offset * act_tuple)) = (unsigned int) ures;
+							*((unsigned int *) (var + offset * var_index)) = (unsigned int) ures;
 							break;
 						case ECPGt_unsigned_long:
-							*((unsigned long *) (var + offset * act_tuple)) = (unsigned long) ures;
+							*((unsigned long *) (var + offset * var_index)) = (unsigned long) ures;
 							break;
 						default:
 							/* Cannot happen */
@@ -353,7 +353,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 #ifdef HAVE_LONG_LONG_INT
 #ifdef HAVE_STRTOLL
 				case ECPGt_long_long:
-					*((long long int *) (var + offset * act_tuple)) = strtoll(pval, &scan_length, 10);
+					*((long long int *) (var + offset * var_index)) = strtoll(pval, &scan_length, 10);
 					if (garbage_left(isarray, scan_length, compat))
 					{
 						ecpg_raise(lineno, ECPG_INT_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
@@ -365,7 +365,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 #endif   /* HAVE_STRTOLL */
 #ifdef HAVE_STRTOULL
 				case ECPGt_unsigned_long_long:
-					*((unsigned long long int *) (var + offset * act_tuple)) = strtoull(pval, &scan_length, 10);
+					*((unsigned long long int *) (var + offset * var_index)) = strtoull(pval, &scan_length, 10);
 					if ((isarray && *scan_length != ',' && *scan_length != '}')
 						|| (!isarray && !(INFORMIX_MODE(compat) && *scan_length == '.') && *scan_length != '\0' && *scan_length != ' '))		/* Garbage left */
 					{
@@ -400,10 +400,10 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					switch (type)
 					{
 						case ECPGt_float:
-							*((float *) (var + offset * act_tuple)) = dres;
+							*((float *) (var + offset * var_index)) = dres;
 							break;
 						case ECPGt_double:
-							*((double *) (var + offset * act_tuple)) = dres;
+							*((double *) (var + offset * var_index)) = dres;
 							break;
 						default:
 							/* Cannot happen */
@@ -415,9 +415,9 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					if (pval[0] == 'f' && pval[1] == '\0')
 					{
 						if (offset == sizeof(char))
-							*((char *) (var + offset * act_tuple)) = false;
+							*((char *) (var + offset * var_index)) = false;
 						else if (offset == sizeof(int))
-							*((int *) (var + offset * act_tuple)) = false;
+							*((int *) (var + offset * var_index)) = false;
 						else
 							ecpg_raise(lineno, ECPG_CONVERT_BOOL,
 									   ECPG_SQLSTATE_DATATYPE_MISMATCH,
@@ -427,16 +427,16 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					else if (pval[0] == 't' && pval[1] == '\0')
 					{
 						if (offset == sizeof(char))
-							*((char *) (var + offset * act_tuple)) = true;
+							*((char *) (var + offset * var_index)) = true;
 						else if (offset == sizeof(int))
-							*((int *) (var + offset * act_tuple)) = true;
+							*((int *) (var + offset * var_index)) = true;
 						else
 							ecpg_raise(lineno, ECPG_CONVERT_BOOL,
 									   ECPG_SQLSTATE_DATATYPE_MISMATCH,
 									   NULL);
 						break;
 					}
-					else if (pval[0] == '\0' && PQgetisnull(results, act_tuple, act_field))
+					else if (pval[0] == '\0' && PQgetisnull(results, var_index, act_field))
 					{
 						/* NULL is valid */
 						break;
@@ -451,7 +451,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 				case ECPGt_unsigned_char:
 				case ECPGt_string:
 					{
-						char	   *str = (char *) (var + offset * act_tuple);
+						char	   *str = (char *) (var + offset * var_index);
 
 						if (varcharsize == 0 || varcharsize > size)
 						{
@@ -479,20 +479,20 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 								{
 									case ECPGt_short:
 									case ECPGt_unsigned_short:
-										*((short *) (ind + ind_offset * act_tuple)) = size;
+										*((short *) (ind + ind_offset * var_index)) = size;
 										break;
 									case ECPGt_int:
 									case ECPGt_unsigned_int:
-										*((int *) (ind + ind_offset * act_tuple)) = size;
+										*((int *) (ind + ind_offset * var_index)) = size;
 										break;
 									case ECPGt_long:
 									case ECPGt_unsigned_long:
-										*((long *) (ind + ind_offset * act_tuple)) = size;
+										*((long *) (ind + ind_offset * var_index)) = size;
 										break;
 #ifdef HAVE_LONG_LONG_INT
 									case ECPGt_long_long:
 									case ECPGt_unsigned_long_long:
-										*((long long int *) (ind + ind_offset * act_tuple)) = size;
+										*((long long int *) (ind + ind_offset * var_index)) = size;
 										break;
 #endif   /* HAVE_LONG_LONG_INT */
 									default:
@@ -508,7 +508,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 				case ECPGt_varchar:
 					{
 						struct ECPGgeneric_varchar *variable =
-						(struct ECPGgeneric_varchar *) (var + offset * act_tuple);
+						(struct ECPGgeneric_varchar *) (var + offset * var_index);
 
 						variable->len = size;
 						if (varcharsize == 0)
@@ -524,20 +524,20 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 								{
 									case ECPGt_short:
 									case ECPGt_unsigned_short:
-										*((short *) (ind + offset * act_tuple)) = variable->len;
+										*((short *) (ind + offset * var_index)) = variable->len;
 										break;
 									case ECPGt_int:
 									case ECPGt_unsigned_int:
-										*((int *) (ind + offset * act_tuple)) = variable->len;
+										*((int *) (ind + offset * var_index)) = variable->len;
 										break;
 									case ECPGt_long:
 									case ECPGt_unsigned_long:
-										*((long *) (ind + offset * act_tuple)) = variable->len;
+										*((long *) (ind + offset * var_index)) = variable->len;
 										break;
 #ifdef HAVE_LONG_LONG_INT
 									case ECPGt_long_long:
 									case ECPGt_unsigned_long_long:
-										*((long long int *) (ind + ind_offset * act_tuple)) = variable->len;
+										*((long long int *) (ind + ind_offset * var_index)) = variable->len;
 										break;
 #endif   /* HAVE_LONG_LONG_INT */
 									default:
@@ -604,9 +604,9 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					pval = scan_length;
 
 					if (type == ECPGt_numeric)
-						PGTYPESnumeric_copy(nres, (numeric *) (var + offset * act_tuple));
+						PGTYPESnumeric_copy(nres, (numeric *) (var + offset * var_index));
 					else
-						PGTYPESnumeric_to_decimal(nres, (decimal *) (var + offset * act_tuple));
+						PGTYPESnumeric_to_decimal(nres, (decimal *) (var + offset * var_index));
 
 					PGTYPESnumeric_free(nres);
 					break;
@@ -657,7 +657,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 					}
 					pval = scan_length;
 
-					PGTYPESinterval_copy(ires, (interval *) (var + offset * act_tuple));
+					PGTYPESinterval_copy(ires, (interval *) (var + offset * var_index));
 					free(ires);
 					break;
 
@@ -701,7 +701,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 						}
 					}
 
-					*((date *) (var + offset * act_tuple)) = ddres;
+					*((date *) (var + offset * var_index)) = ddres;
 					pval = scan_length;
 					break;
 
@@ -745,7 +745,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 						}
 					}
 
-					*((timestamp *) (var + offset * act_tuple)) = tres;
+					*((timestamp *) (var + offset * var_index)) = tres;
 					pval = scan_length;
 					break;
 
@@ -762,6 +762,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 
 				/* set array to next entry */
 				++act_tuple;
+				++var_index;
 
 				/* set pval to the next entry */
 
diff --git a/src/interfaces/ecpg/ecpglib/descriptor.c b/src/interfaces/ecpg/ecpglib/descriptor.c
index 17a956e..2ec4922 100644
--- a/src/interfaces/ecpg/ecpglib/descriptor.c
+++ b/src/interfaces/ecpg/ecpglib/descriptor.c
@@ -438,7 +438,7 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...)
 
 		/* desparate try to guess something sensible */
 		stmt.connection = ecpg_get_connection(NULL);
-		ecpg_store_result(ECPGresult, index, &stmt, &data_var);
+		ecpg_store_result(ECPGresult, 0, PQntuples(ECPGresult), index, &stmt, &data_var, 0);
 
 		setlocale(LC_NUMERIC, oldlocale);
 		ecpg_free(oldlocale);
diff --git a/src/interfaces/ecpg/ecpglib/error.c b/src/interfaces/ecpg/ecpglib/error.c
index ee553fd..7148f50 100644
--- a/src/interfaces/ecpg/ecpglib/error.c
+++ b/src/interfaces/ecpg/ecpglib/error.c
@@ -268,6 +268,20 @@ ecpg_raise(int line, int code, const char *sqlstate, const char *str)
 					 ecpg_gettext("could not connect to database \"%s\" on line %d"), str, line);
 			break;
 
+		case ECPG_INVALID_CURSOR:
+			if (strcmp(sqlstate, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE) == 0)
+				snprintf(sqlca->sqlerrm.sqlerrmc, sizeof(sqlca->sqlerrm.sqlerrmc),
+
+			/*
+			 * translator: this string will be truncated at 149 characters
+			 * expanded.
+			 */
+				ecpg_gettext("cursor can only scan forward"));
+			else
+				snprintf(sqlca->sqlerrm.sqlerrmc, sizeof(sqlca->sqlerrm.sqlerrmc), str);
+
+			break;
+
 		default:
 			snprintf(sqlca->sqlerrm.sqlerrmc, sizeof(sqlca->sqlerrm.sqlerrmc),
 
diff --git a/src/interfaces/ecpg/ecpglib/execute.c b/src/interfaces/ecpg/ecpglib/execute.c
index b8e48a3..12f34f6 100644
--- a/src/interfaces/ecpg/ecpglib/execute.c
+++ b/src/interfaces/ecpg/ecpglib/execute.c
@@ -109,6 +109,7 @@ free_statement(struct statement * stmt)
 	free_variable(stmt->outlist);
 	ecpg_free(stmt->command);
 	ecpg_free(stmt->name);
+	ecpg_free(stmt->oldlocale);
 	ecpg_free(stmt);
 }
 
@@ -311,12 +312,12 @@ ecpg_is_type_an_array(int type, const struct statement * stmt, const struct vari
 
 
 bool
-ecpg_store_result(const PGresult *results, int act_field,
-				  const struct statement * stmt, struct variable * var)
+ecpg_store_result(const PGresult *results, int start, int end, int act_field,
+				  const struct statement * stmt, struct variable * var, int var_index)
 {
 	enum ARRAY_TYPE isarray;
 	int			act_tuple,
-				ntuples = PQntuples(results);
+				ntuples = end - start;
 	bool		status = true;
 
 	if ((isarray = ecpg_is_type_an_array(PQftype(results, act_field), stmt, var)) == ECPG_ARRAY_ERROR)
@@ -367,7 +368,7 @@ ecpg_store_result(const PGresult *results, int act_field,
 					if (!var->varcharsize && !var->arrsize)
 					{
 						/* special mode for handling char**foo=0 */
-						for (act_tuple = 0; act_tuple < ntuples; act_tuple++)
+						for (act_tuple = start; act_tuple < end; act_tuple++)
 							len += strlen(PQgetvalue(results, act_tuple, act_field)) + 1;
 						len *= var->offset;		/* should be 1, but YMNK */
 						len += (ntuples + 1) * sizeof(char *);
@@ -376,7 +377,7 @@ ecpg_store_result(const PGresult *results, int act_field,
 					{
 						var->varcharsize = 0;
 						/* check strlen for each tuple */
-						for (act_tuple = 0; act_tuple < ntuples; act_tuple++)
+						for (act_tuple = start; act_tuple < end; act_tuple++)
 						{
 							int			len = strlen(PQgetvalue(results, act_tuple, act_field)) + 1;
 
@@ -397,7 +398,7 @@ ecpg_store_result(const PGresult *results, int act_field,
 		}
 		else
 		{
-			for (act_tuple = 0; act_tuple < ntuples; act_tuple++)
+			for (act_tuple = start; act_tuple < end; act_tuple++)
 				len += PQgetlength(results, act_tuple, act_field);
 		}
 
@@ -433,11 +434,11 @@ ecpg_store_result(const PGresult *results, int act_field,
 		/* storing the data (after the last array element) */
 		char	   *current_data_location = (char *) &current_string[ntuples + 1];
 
-		for (act_tuple = 0; act_tuple < ntuples && status; act_tuple++)
+		for (act_tuple = start; act_tuple < end && status; act_tuple++, var_index++)
 		{
 			int			len = strlen(PQgetvalue(results, act_tuple, act_field)) + 1;
 
-			if (!ecpg_get_data(results, act_tuple, act_field, stmt->lineno,
+			if (!ecpg_get_data(results, var_index, act_tuple, act_field, stmt->lineno,
 							 var->type, var->ind_type, current_data_location,
 							   var->ind_value, len, 0, var->ind_offset, isarray, stmt->compat, stmt->force_indicator))
 				status = false;
@@ -454,9 +455,9 @@ ecpg_store_result(const PGresult *results, int act_field,
 	}
 	else
 	{
-		for (act_tuple = 0; act_tuple < ntuples && status; act_tuple++)
+		for (act_tuple = start; act_tuple < end && status; act_tuple++, var_index++)
 		{
-			if (!ecpg_get_data(results, act_tuple, act_field, stmt->lineno,
+			if (!ecpg_get_data(results, var_index, act_tuple, act_field, stmt->lineno,
 							   var->type, var->ind_type, var->value,
 							   var->ind_value, var->varcharsize, var->offset, var->ind_offset, isarray, stmt->compat, stmt->force_indicator))
 				status = false;
@@ -1082,18 +1083,26 @@ ecpg_store_input(const int lineno, const bool force_indicator, const struct vari
 	return true;
 }
 
-static void
-free_params(const char **paramValues, int nParams, bool print, int lineno)
+void
+ecpg_free_params(struct statement *stmt, bool print, int lineno)
 {
 	int			n;
 
-	for (n = 0; n < nParams; n++)
+	for (n = 0; n < stmt->nparams; n++)
 	{
 		if (print)
-			ecpg_log("free_params on line %d: parameter %d = %s\n", lineno, n + 1, paramValues[n] ? paramValues[n] : "null");
-		ecpg_free((void *) (paramValues[n]));
+			ecpg_log("free_params on line %d: parameter %d = %s\n", lineno, n + 1, stmt->param_values[n] ? stmt->param_values[n] : "null");
+		ecpg_free((void *) (stmt->param_values[n]));
 	}
-	ecpg_free(paramValues);
+	ecpg_free(stmt->param_values);
+	stmt->nparams = 0;
+	stmt->param_values = NULL;
+
+	for (n = 0; n < stmt->ndollarzero; n++)
+		ecpg_free((void *) (stmt->dollarzero[n]));
+	ecpg_free(stmt->dollarzero);
+	stmt->ndollarzero = 0;
+	stmt->dollarzero = NULL;
 }
 
 
@@ -1129,20 +1138,12 @@ insert_tobeinserted(int position, int ph_len, struct statement * stmt, char *tob
 	return true;
 }
 
-static bool
-ecpg_execute(struct statement * stmt)
+bool
+ecpg_build_params(struct statement * stmt)
 {
-	bool		status = false;
-	char	   *cmdstat;
-	PGresult   *results;
-	PGnotify   *notify;
 	struct variable *var;
 	int			desc_counter = 0;
-	const char **paramValues = NULL;
-	int			nParams = 0;
 	int			position = 0;
-	struct sqlca_t *sqlca = ECPGget_sqlca();
-	bool		clear_result = true;
 
 	/*
 	 * If the type is one of the fill in types then we take the argument and
@@ -1342,7 +1343,7 @@ ecpg_execute(struct statement * stmt)
 			ecpg_raise(stmt->lineno, ECPG_TOO_MANY_ARGUMENTS,
 					   ECPG_SQLSTATE_USING_CLAUSE_DOES_NOT_MATCH_PARAMETERS,
 					   NULL);
-			free_params(paramValues, nParams, false, stmt->lineno);
+			ecpg_free_params(stmt, false, stmt->lineno);
 			return false;
 		}
 
@@ -1357,7 +1358,7 @@ ecpg_execute(struct statement * stmt)
 
 			if (!insert_tobeinserted(position, ph_len, stmt, tobeinserted))
 			{
-				free_params(paramValues, nParams, false, stmt->lineno);
+				ecpg_free_params(stmt, false, stmt->lineno);
 				return false;
 			}
 			tobeinserted = NULL;
@@ -1370,23 +1371,38 @@ ecpg_execute(struct statement * stmt)
 		 */
 		else if (stmt->command[position] == '0')
 		{
+			const char **dollarzero;
+
+			if (!(dollarzero = (const char **) ecpg_realloc(stmt->dollarzero, sizeof(const char *) * (stmt->ndollarzero + 1), stmt->lineno)))
+			{
+				ecpg_free_params(stmt, false, stmt->lineno);
+				return false;
+			}
+			stmt->ndollarzero++;
+			stmt->dollarzero = dollarzero;
+			stmt->dollarzero[stmt->ndollarzero - 1] = strdup(tobeinserted);
+
 			if (!insert_tobeinserted(position, 2, stmt, tobeinserted))
 			{
-				free_params(paramValues, nParams, false, stmt->lineno);
+				ecpg_free_params(stmt, false, stmt->lineno);
 				return false;
 			}
 			tobeinserted = NULL;
 		}
 		else
 		{
-			nParams++;
-			if (!(paramValues = (const char **) ecpg_realloc(paramValues, sizeof(const char *) * nParams, stmt->lineno)))
+			const char **paramValues;
+		
+			if (!(paramValues = (const char **) ecpg_realloc(stmt->param_values, sizeof(const char *) * (stmt->nparams + 1), stmt->lineno)))
 			{
 				ecpg_free(paramValues);
 				return false;
 			}
 
-			paramValues[nParams - 1] = tobeinserted;
+			stmt->nparams++;
+			stmt->param_values = paramValues;
+
+			stmt->param_values[stmt->nparams - 1] = tobeinserted;
 
 			/* let's see if this was an old style placeholder */
 			if (stmt->command[position] == '?')
@@ -1397,7 +1413,7 @@ ecpg_execute(struct statement * stmt)
 
 				if (!(tobeinserted = (char *) ecpg_alloc(buffersize, stmt->lineno)))
 				{
-					free_params(paramValues, nParams, false, stmt->lineno);
+					ecpg_free_params(stmt, false, stmt->lineno);
 					return false;
 				}
 
@@ -1405,7 +1421,7 @@ ecpg_execute(struct statement * stmt)
 
 				if (!insert_tobeinserted(position, 2, stmt, tobeinserted))
 				{
-					free_params(paramValues, nParams, false, stmt->lineno);
+					ecpg_free_params(stmt, false, stmt->lineno);
 					return false;
 				}
 				tobeinserted = NULL;
@@ -1421,58 +1437,76 @@ ecpg_execute(struct statement * stmt)
 	{
 		ecpg_raise(stmt->lineno, ECPG_TOO_FEW_ARGUMENTS,
 				 ECPG_SQLSTATE_USING_CLAUSE_DOES_NOT_MATCH_PARAMETERS, NULL);
-		free_params(paramValues, nParams, false, stmt->lineno);
+		ecpg_free_params(stmt, false, stmt->lineno);
 		return false;
 	}
 
-	/* The request has been build. */
+	return true;
+}
+
 
+static bool
+ecpg_execute(struct statement * stmt)
+{
 	if (PQtransactionStatus(stmt->connection->connection) == PQTRANS_IDLE && !stmt->connection->autocommit)
 	{
-		results = PQexec(stmt->connection->connection, "begin transaction");
-		if (!ecpg_check_PQresult(results, stmt->lineno, stmt->connection->connection, stmt->compat))
+		stmt->results = PQexec(stmt->connection->connection, "begin transaction");
+		if (!ecpg_check_PQresult(stmt->results, stmt->lineno, stmt->connection->connection, stmt->compat))
 		{
-			free_params(paramValues, nParams, false, stmt->lineno);
+			ecpg_free_params(stmt, false, stmt->lineno);
 			return false;
 		}
-		PQclear(results);
+		PQclear(stmt->results);
+		stmt->results = NULL;
 	}
 
-	ecpg_log("ecpg_execute on line %d: query: %s; with %d parameter(s) on connection %s\n", stmt->lineno, stmt->command, nParams, stmt->connection->name);
+	ecpg_log("ecpg_execute on line %d: query: %s; with %d parameter(s) on connection %s\n", stmt->lineno, stmt->command, stmt->nparams, stmt->connection->name);
 	if (stmt->statement_type == ECPGst_execute)
 	{
-		results = PQexecPrepared(stmt->connection->connection, stmt->name, nParams, paramValues, NULL, NULL, 0);
+		stmt->results = PQexecPrepared(stmt->connection->connection, stmt->name, stmt->nparams, stmt->param_values, NULL, NULL, 0);
 		ecpg_log("ecpg_execute on line %d: using PQexecPrepared for \"%s\"\n", stmt->lineno, stmt->command);
 	}
 	else
 	{
-		if (nParams == 0)
+		if (stmt->nparams == 0)
 		{
-			results = PQexec(stmt->connection->connection, stmt->command);
+			stmt->results = PQexec(stmt->connection->connection, stmt->command);
 			ecpg_log("ecpg_execute on line %d: using PQexec\n", stmt->lineno);
 		}
 		else
 		{
-			results = PQexecParams(stmt->connection->connection, stmt->command, nParams, NULL, paramValues, NULL, NULL, 0);
+			stmt->results = PQexecParams(stmt->connection->connection, stmt->command, stmt->nparams, NULL, stmt->param_values, NULL, NULL, 0);
 			ecpg_log("ecpg_execute on line %d: using PQexecParams\n", stmt->lineno);
 		}
 	}
 
-	free_params(paramValues, nParams, true, stmt->lineno);
+	ecpg_free_params(stmt, true, stmt->lineno);
 
-	if (!ecpg_check_PQresult(results, stmt->lineno, stmt->connection->connection, stmt->compat))
+	if (!ecpg_check_PQresult(stmt->results, stmt->lineno, stmt->connection->connection, stmt->compat))
 		return (false);
 
+	return (true);
+}
+
+bool
+ecpg_process_output(struct statement * stmt, int start, int end, int var_index, bool keep_result, bool append_result)
+{
+	char		*cmdstat;
+	PGnotify	*notify;
+	bool		status = false;
+	struct variable *var;
+	struct sqlca_t *sqlca = ECPGget_sqlca();
+
 	var = stmt->outlist;
-	switch (PQresultStatus(results))
+	switch (PQresultStatus(stmt->results))
 	{
 			int			nfields,
 						ntuples,
 						act_field;
 
 		case PGRES_TUPLES_OK:
-			nfields = PQnfields(results);
-			sqlca->sqlerrd[2] = ntuples = PQntuples(results);
+			nfields = PQnfields(stmt->results);
+			sqlca->sqlerrd[2] += ntuples = (end - start);
 			ecpg_log("ecpg_execute on line %d: correctly got %d tuples with %d fields\n", stmt->lineno, ntuples, nfields);
 			status = true;
 
@@ -1494,12 +1528,34 @@ ecpg_execute(struct statement * stmt)
 					status = false;
 				else
 				{
-					if (desc->result)
-						PQclear(desc->result);
-					desc->result = results;
-					clear_result = false;
+					int	row, srcrow, col;
+
+					if (append_result)
+						row = PQntuples(desc->result);
+					else
+					{
+						if (desc->result)
+							PQclear(desc->result);
+						desc->result = PQcopyResult(stmt->results, PG_COPYRES_ATTRS | PG_COPYRES_EVENTS | PG_COPYRES_NOTICEHOOKS);
+						row = 0;
+					}
+
+					for (srcrow = start; srcrow < end; srcrow++, row++)
+						for (col = 0; col < nfields; col++)
+						{
+							bool	isnull = PQgetisnull(stmt->results, srcrow, col);
+							if (!PQsetvalue(desc->result, row, col,
+										isnull ? NULL : PQgetvalue(stmt->results, srcrow, col),
+										isnull ? -1 : PQgetlength(stmt->results, srcrow, col)))
+							{
+								ecpg_raise(stmt->lineno, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
+								status = false;
+								break;
+							}
+						}
+
 					ecpg_log("ecpg_execute on line %d: putting result (%d tuples) into descriptor %s\n",
-							 stmt->lineno, PQntuples(results), (const char *) var->pointer);
+							 stmt->lineno, PQntuples(stmt->results), (const char *) var->pointer);
 				}
 				var = var->next;
 			}
@@ -1509,36 +1565,52 @@ ecpg_execute(struct statement * stmt)
 				{
 					struct sqlda_compat **_sqlda = (struct sqlda_compat **) var->pointer;
 					struct sqlda_compat *sqlda = *_sqlda;
-					struct sqlda_compat *sqlda_new;
+					struct sqlda_compat *sqlda_last, *sqlda_new = NULL;
 					int			i;
 
-					/*
-					 * If we are passed in a previously existing sqlda (chain)
-					 * then free it.
-					 */
-					while (sqlda)
+					if (append_result)
 					{
-						sqlda_new = sqlda->desc_next;
-						free(sqlda);
-						sqlda = sqlda_new;
+						sqlda_last = sqlda;
+						while (sqlda_last && sqlda_last->desc_next)
+							sqlda_last = sqlda_last->desc_next;
 					}
-					*_sqlda = sqlda = sqlda_new = NULL;
-					for (i = ntuples - 1; i >= 0; i--)
+					else
+					{
+						/*
+						 * If we are passed in a previously existing sqlda (chain)
+						 * then free it.
+						 */
+						while (sqlda)
+						{
+							sqlda_last = sqlda->desc_next;
+							free(sqlda);
+							sqlda = sqlda_last;
+						}
+						*_sqlda = sqlda = sqlda_last = NULL;
+					}
+					for (i = end - 1; i >= start; i--)
 					{
+						struct sqlda_compat *tmp;
+
 						/*
-						 * Build a new sqlda structure. Note that only
-						 * fetching 1 record is supported
+						 * Build a new sqlda structure. 
 						 */
-						sqlda_new = ecpg_build_compat_sqlda(stmt->lineno, results, i, stmt->compat);
+						tmp = ecpg_build_compat_sqlda(stmt->lineno, stmt->results, i, stmt->compat);
 
-						if (!sqlda_new)
+						if (!tmp)
 						{
 							/* cleanup all SQLDAs we created up */
+							while (sqlda_new)
+							{
+								tmp = sqlda_new->desc_next;
+								free(sqlda_new);
+								sqlda_new = tmp;
+							}
 							while (sqlda)
 							{
-								sqlda_new = sqlda->desc_next;
+								tmp = sqlda->desc_next;
 								free(sqlda);
-								sqlda = sqlda_new;
+								sqlda = tmp;
 							}
 							*_sqlda = NULL;
 
@@ -1550,51 +1622,74 @@ ecpg_execute(struct statement * stmt)
 						{
 							ecpg_log("ecpg_execute on line %d: new sqlda was built\n", stmt->lineno);
 
-							*_sqlda = sqlda_new;
+							if (sqlda_new == NULL)
+								sqlda_new = tmp;
+							else
+							{
+								tmp->desc_next = sqlda_new;
+								sqlda_new = tmp;
+							}
 
-							ecpg_set_compat_sqlda(stmt->lineno, _sqlda, results, i, stmt->compat);
+							ecpg_set_compat_sqlda(stmt->lineno, &tmp, stmt->results, i, stmt->compat);
 							ecpg_log("ecpg_execute on line %d: putting result (1 tuple %d fields) into sqlda descriptor\n",
-									 stmt->lineno, PQnfields(results));
-
-							sqlda_new->desc_next = sqlda;
-							sqlda = sqlda_new;
+									 stmt->lineno, PQnfields(stmt->results));
 						}
 					}
+					if (sqlda_last)
+						sqlda_last->desc_next = sqlda_new;
+					else
+						*_sqlda = sqlda_new;
 				}
 				else
 				{
 					struct sqlda_struct **_sqlda = (struct sqlda_struct **) var->pointer;
 					struct sqlda_struct *sqlda = *_sqlda;
-					struct sqlda_struct *sqlda_new;
+					struct sqlda_struct *sqlda_last, *sqlda_new = NULL;
 					int			i;
 
-					/*
-					 * If we are passed in a previously existing sqlda (chain)
-					 * then free it.
-					 */
-					while (sqlda)
+					if (append_result)
 					{
-						sqlda_new = sqlda->desc_next;
-						free(sqlda);
-						sqlda = sqlda_new;
+						sqlda_last = sqlda;
+						while (sqlda_last && sqlda_last->desc_next)
+							sqlda_last = sqlda_last->desc_next;
 					}
-					*_sqlda = sqlda = sqlda_new = NULL;
-					for (i = ntuples - 1; i >= 0; i--)
+					else
 					{
 						/*
-						 * Build a new sqlda structure. Note that only
-						 * fetching 1 record is supported
+						 * If we are passed in a previously existing sqlda (chain)
+						 * then free it.
 						 */
-						sqlda_new = ecpg_build_native_sqlda(stmt->lineno, results, i, stmt->compat);
+						while (sqlda)
+						{
+							sqlda_last = sqlda->desc_next;
+							free(sqlda);
+							sqlda = sqlda_last;
+						}
+						*_sqlda = sqlda = sqlda_last = NULL;
+					}
+					for (i = end - 1; i >= start; i--)
+					{
+						struct sqlda_struct *tmp;
+
+						/*
+						 * Build a new sqlda structure. 
+						 */
+						tmp = ecpg_build_native_sqlda(stmt->lineno, stmt->results, i, stmt->compat);
 
-						if (!sqlda_new)
+						if (!tmp)
 						{
 							/* cleanup all SQLDAs we created up */
+							while (sqlda_new)
+							{
+								tmp = sqlda_new->desc_next;
+								free(sqlda_new);
+								sqlda_new = tmp;
+							}
 							while (sqlda)
 							{
-								sqlda_new = sqlda->desc_next;
+								tmp = sqlda->desc_next;
 								free(sqlda);
-								sqlda = sqlda_new;
+								sqlda = tmp;
 							}
 							*_sqlda = NULL;
 
@@ -1606,16 +1701,23 @@ ecpg_execute(struct statement * stmt)
 						{
 							ecpg_log("ecpg_execute on line %d: new sqlda was built\n", stmt->lineno);
 
-							*_sqlda = sqlda_new;
+							if (sqlda_new == NULL)
+								sqlda_new = tmp;
+							else
+							{
+								tmp->desc_next = sqlda_new;
+								sqlda_new = tmp;
+							}
 
-							ecpg_set_native_sqlda(stmt->lineno, _sqlda, results, i, stmt->compat);
+							ecpg_set_native_sqlda(stmt->lineno, &tmp, stmt->results, i, stmt->compat);
 							ecpg_log("ecpg_execute on line %d: putting result (1 tuple %d fields) into sqlda descriptor\n",
-									 stmt->lineno, PQnfields(results));
-
-							sqlda_new->desc_next = sqlda;
-							sqlda = sqlda_new;
+									 stmt->lineno, PQnfields(stmt->results));
 						}
 					}
+					if (sqlda_last)
+						sqlda_last->desc_next = sqlda_new;
+					else
+						*_sqlda = sqlda_new;
 				}
 
 				var = var->next;
@@ -1625,7 +1727,7 @@ ecpg_execute(struct statement * stmt)
 				{
 					if (var != NULL)
 					{
-						status = ecpg_store_result(results, act_field, stmt, var);
+						status = ecpg_store_result(stmt->results, start, end, act_field, stmt, var, var_index);
 						var = var->next;
 					}
 					else if (!INFORMIX_MODE(stmt->compat))
@@ -1644,9 +1746,9 @@ ecpg_execute(struct statement * stmt)
 			break;
 		case PGRES_COMMAND_OK:
 			status = true;
-			cmdstat = PQcmdStatus(results);
-			sqlca->sqlerrd[1] = PQoidValue(results);
-			sqlca->sqlerrd[2] = atol(PQcmdTuples(results));
+			cmdstat = PQcmdStatus(stmt->results);
+			sqlca->sqlerrd[1] = PQoidValue(stmt->results);
+			sqlca->sqlerrd[2] = atol(PQcmdTuples(stmt->results));
 			ecpg_log("ecpg_execute on line %d: OK: %s\n", stmt->lineno, cmdstat);
 			if (stmt->compat != ECPG_COMPAT_INFORMIX_SE &&
 				!sqlca->sqlerrd[2] &&
@@ -1670,12 +1772,12 @@ ecpg_execute(struct statement * stmt)
 				if (res == -1)
 				{
 					/* COPY done */
-					PQclear(results);
-					results = PQgetResult(stmt->connection->connection);
-					if (PQresultStatus(results) == PGRES_COMMAND_OK)
+					PQclear(stmt->results);
+					stmt->results = PQgetResult(stmt->connection->connection);
+					if (PQresultStatus(stmt->results) == PGRES_COMMAND_OK)
 						ecpg_log("ecpg_execute on line %d: got PGRES_COMMAND_OK after PGRES_COPY_OUT\n", stmt->lineno);
 					else
-						ecpg_log("ecpg_execute on line %d: got error after PGRES_COPY_OUT: %s", stmt->lineno, PQresultErrorMessage(results));
+						ecpg_log("ecpg_execute on line %d: got error after PGRES_COPY_OUT: %s", stmt->lineno, PQresultErrorMessage(stmt->results));
 				}
 				break;
 			}
@@ -1687,12 +1789,12 @@ ecpg_execute(struct statement * stmt)
 			 */
 			ecpg_log("ecpg_execute on line %d: unknown execution status type\n",
 					 stmt->lineno);
-			ecpg_raise_backend(stmt->lineno, results, stmt->connection->connection, stmt->compat);
+			ecpg_raise_backend(stmt->lineno, stmt->results, stmt->connection->connection, stmt->compat);
 			status = false;
 			break;
 	}
-	if (clear_result)
-		PQclear(results);
+	if (!keep_result)
+		PQclear(stmt->results);
 
 	/* check for asynchronous returns */
 	notify = PQnotifies(stmt->connection->connection);
@@ -1707,46 +1809,20 @@ ecpg_execute(struct statement * stmt)
 }
 
 bool
-ECPGdo(const int lineno, const int compat, const int force_indicator, const char *connection_name, const bool questionmarks, const int st, const char *query,...)
+ecpg_do_prologue(int lineno, const int compat, const int force_indicator,
+		const char *connection_name, const bool questionmarks,
+		enum ECPG_statement_type statement_type, const char *query,
+		va_list args, struct statement **stmt_out)
 {
-	va_list		args;
 	struct statement *stmt;
 	struct connection *con;
-	bool		status;
-	char	   *oldlocale;
 	enum ECPGttype type;
 	struct variable **list;
-	enum ECPG_statement_type statement_type = (enum ECPG_statement_type) st;
-	char	   *prepname;
-
-	if (!query)
-	{
-		ecpg_raise(lineno, ECPG_EMPTY, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, NULL);
-		return (false);
-	}
-
-	/* Make sure we do NOT honor the locale for numeric input/output */
-	/* since the database wants the standard decimal point */
-	oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
-	setlocale(LC_NUMERIC, "C");
-
-#ifdef ENABLE_THREAD_SAFETY
-	ecpg_pthreads_init();
-#endif
-
-	con = ecpg_get_connection(connection_name);
-
-	if (!ecpg_init(con, connection_name, lineno))
-	{
-		setlocale(LC_NUMERIC, oldlocale);
-		ecpg_free(oldlocale);
-		return (false);
-	}
+	char	*prepname;
 
-	/* construct statement in our own structure */
-	va_start(args, query);
+	*stmt_out = NULL;
 
-	/*
+    	/*
 	 * create a list of variables The variables are listed with input
 	 * variables preceding outputvariables The end of each group is marked by
 	 * an end marker. per variable we list: type - as defined in ecpgtype.h
@@ -1759,11 +1835,24 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
 	 * arraysize of indicator array ind_offset - indicator offset
 	 */
 	if (!(stmt = (struct statement *) ecpg_alloc(sizeof(struct statement), lineno)))
-	{
-		setlocale(LC_NUMERIC, oldlocale);
-		ecpg_free(oldlocale);
-		va_end(args);
 		return false;
+
+	/* Make sure we do NOT honor the locale for numeric input/output */
+	/* since the database wants the standard decimal point */
+	stmt->oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
+	setlocale(LC_NUMERIC, "C");
+
+#ifdef ENABLE_THREAD_SAFETY
+	ecpg_pthreads_init();
+#endif
+
+	con = ecpg_get_connection(connection_name);
+
+	if (!ecpg_init(con, connection_name, lineno))
+	{
+		setlocale(LC_NUMERIC, stmt->oldlocale);
+		free_statement(stmt);
+		return (false);
 	}
 
 	/*
@@ -1774,9 +1863,8 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
 	{
 		if (!ecpg_auto_prepare(lineno, connection_name, compat, &prepname, query))
 		{
-			setlocale(LC_NUMERIC, oldlocale);
-			ecpg_free(oldlocale);
-			va_end(args);
+			setlocale(LC_NUMERIC, stmt->oldlocale);
+			free_statement(stmt);
 			return (false);
 		}
 
@@ -1805,9 +1893,8 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
 		else
 		{
 			ecpg_raise(lineno, ECPG_INVALID_STMT, ECPG_SQLSTATE_INVALID_SQL_STATEMENT_NAME, stmt->command);
-			setlocale(LC_NUMERIC, oldlocale);
-			ecpg_free(oldlocale);
-			va_end(args);
+			setlocale(LC_NUMERIC, stmt->oldlocale);
+			free_statement(stmt);
 			return (false);
 		}
 	}
@@ -1834,10 +1921,8 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
 
 			if (!(var = (struct variable *) ecpg_alloc(sizeof(struct variable), lineno)))
 			{
-				setlocale(LC_NUMERIC, oldlocale);
-				ecpg_free(oldlocale);
+				setlocale(LC_NUMERIC, stmt->oldlocale);
 				free_statement(stmt);
-				va_end(args);
 				return false;
 			}
 
@@ -1892,10 +1977,8 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
 			{
 				ecpg_raise(lineno, ECPG_INVALID_STMT, ECPG_SQLSTATE_INVALID_SQL_STATEMENT_NAME, NULL);
 				ecpg_free(var);
-				setlocale(LC_NUMERIC, oldlocale);
-				ecpg_free(oldlocale);
+				setlocale(LC_NUMERIC, stmt->oldlocale);
 				free_statement(stmt);
-				va_end(args);
 				return false;
 			}
 
@@ -1910,29 +1993,80 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
 		type = va_arg(args, enum ECPGttype);
 	}
 
-	va_end(args);
-
 	/* are we connected? */
 	if (con == NULL || con->connection == NULL)
 	{
-		free_statement(stmt);
 		ecpg_raise(lineno, ECPG_NOT_CONN, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, (con) ? con->name : ecpg_gettext("<empty>"));
-		setlocale(LC_NUMERIC, oldlocale);
-		ecpg_free(oldlocale);
+		setlocale(LC_NUMERIC, stmt->oldlocale);
+		free_statement(stmt);
 		return false;
 	}
 
 	/* initialize auto_mem struct */
 	ecpg_clear_auto_mem();
 
-	status = ecpg_execute(stmt);
+	*stmt_out = stmt;
+
+	return (true);
+}
+
+
+void
+ecpg_do_epilogue(struct statement *stmt)
+{
+	/* reset locale value so our application is not affected */
+	setlocale(LC_NUMERIC, stmt->oldlocale);
 	free_statement(stmt);
+}
+
+bool
+ecpg_do(const int lineno, const int compat, const int force_indicator, const char *connection_name, const bool questionmarks, const int st, const char *query, va_list args)
+{
+	struct statement *stmt;
+
+	if (!query)
+	{
+		ecpg_raise(lineno, ECPG_EMPTY, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, NULL);
+		return false;
+	}
 
-	/* and reset locale value so our application is not affected */
-	setlocale(LC_NUMERIC, oldlocale);
-	ecpg_free(oldlocale);
+	if (!ecpg_do_prologue(lineno, compat, force_indicator, connection_name, questionmarks, (enum ECPG_statement_type) st, query, args, &stmt))
+		return false;
+
+	if (!ecpg_build_params(stmt))
+	{
+		ecpg_do_epilogue(stmt);
+		return false;
+	}
+
+	if (!ecpg_execute(stmt))
+	{
+		ecpg_do_epilogue(stmt);
+		return false;
+	}
+
+	if (!ecpg_process_output(stmt, 0, PQntuples(stmt->results), 0, false, false))
+	{
+		ecpg_do_epilogue(stmt);
+		return false;  
+	}
+
+	ecpg_do_epilogue(stmt);
+
+	return true;
+}
+
+bool
+ECPGdo(const int lineno, const int compat, const int force_indicator, const char *connection_name, const bool questionmarks, const int st, const char *query,...)
+{
+	va_list		args;
+	bool		ret;
+
+	va_start(args, query);
+	ret = ecpg_do(lineno, compat, force_indicator, connection_name, questionmarks, st, query, args);
+	va_end(args);
 
-	return (status);
+	return ret;
 }
 
 /* old descriptor interface */
diff --git a/src/interfaces/ecpg/ecpglib/exports.txt b/src/interfaces/ecpg/ecpglib/exports.txt
index 69e9617..f05e4f1 100644
--- a/src/interfaces/ecpg/ecpglib/exports.txt
+++ b/src/interfaces/ecpg/ecpglib/exports.txt
@@ -29,3 +29,6 @@ ECPGget_PGconn			 26
 ECPGtransactionStatus		 27
 ECPGset_var			 28
 ECPGget_var			 29
+ECPGopen			 30
+ECPGfetch			 31
+ECPGclose			 32
diff --git a/src/interfaces/ecpg/ecpglib/extern.h b/src/interfaces/ecpg/ecpglib/extern.h
index 96d49a4..2920a3c 100644
--- a/src/interfaces/ecpg/ecpglib/extern.h
+++ b/src/interfaces/ecpg/ecpglib/extern.h
@@ -60,6 +60,12 @@ struct statement
 	bool		questionmarks;
 	struct variable *inlist;
 	struct variable *outlist;
+	char		*oldlocale;
+	const char	**dollarzero;
+	int		ndollarzero;
+	const char	**param_values;
+	int		nparams;
+	PGresult	*results;
 };
 
 /* structure to store prepared statements for a connection */
@@ -71,6 +77,17 @@ struct prepared_statement
 	struct prepared_statement *next;
 };
 
+struct cursor_descriptor {
+	char	   *name;
+	PGresult   *res;
+	bool		scrollable;
+	bool		endoftuples;	/* valid if ->scrollable == false and there is no more tuples */
+	int64		n_tuples;	/* valid if ->scrollable == true */
+	int64		start_pos;
+	int64		cache_pos;
+	struct cursor_descriptor *next;
+};
+
 /* structure to store connections */
 struct connection
 {
@@ -79,6 +96,7 @@ struct connection
 	bool		autocommit;
 	struct ECPGtype_information_cache *cache_head;
 	struct prepared_statement *prep_stmts;
+	struct cursor_descriptor *cursor_desc;
 	struct connection *next;
 };
 
@@ -126,7 +144,7 @@ struct variable
 /* Returns a pointer to a string containing a simple type name. */
 void		ecpg_add_mem(void *ptr, int lineno);
 
-bool ecpg_get_data(const PGresult *, int, int, int, enum ECPGttype type,
+bool ecpg_get_data(const PGresult *, int, int, int, int, enum ECPGttype type,
 			  enum ECPGttype, char *, char *, long, long, long,
 			  enum ARRAY_TYPE, enum COMPAT_MODE, bool);
 
@@ -152,9 +170,16 @@ struct descriptor *ecpg_find_desc(int line, const char *name);
 struct prepared_statement *ecpg_find_prepared_statement(const char *,
 						  struct connection *, struct prepared_statement **);
 
-bool ecpg_store_result(const PGresult *results, int act_field,
-				  const struct statement * stmt, struct variable * var);
+bool ecpg_store_result(const PGresult *results, int start, int end, int act_field,
+				  const struct statement * stmt, struct variable * var, int var_index);
 bool		ecpg_store_input(const int, const bool, const struct variable *, char **, bool);
+bool		ecpg_do_prologue(int, const int, const int, const char *, const bool,
+				  enum ECPG_statement_type, const char *, va_list, struct statement **);
+bool		ecpg_build_params(struct statement *);
+bool		ecpg_process_output(struct statement *, int, int, int, bool, bool);
+void		ecpg_free_params(struct statement *, bool, int);
+void		ecpg_do_epilogue(struct statement *);
+bool		ecpg_do(const int, const int, const int, const char *, const bool, const int, const char *, va_list);
 
 bool		ecpg_check_PQresult(PGresult *, int, PGconn *, enum COMPAT_MODE);
 void		ecpg_raise(int line, int code, const char *sqlstate, const char *str);
@@ -191,6 +216,8 @@ void		ecpg_set_native_sqlda(int, struct sqlda_struct **, const PGresult *, int,
 #define ECPG_SQLSTATE_SYNTAX_ERROR			"42601"
 #define ECPG_SQLSTATE_DATATYPE_MISMATCH		"42804"
 #define ECPG_SQLSTATE_DUPLICATE_CURSOR		"42P03"
+#define ECPG_SQLSTATE_INVALID_CURSOR_DEFINITION	"42P11"
+#define ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE	"55000"
 
 /* implementation-defined internal errors of ecpg */
 #define ECPG_SQLSTATE_ECPG_INTERNAL_ERROR	"YE000"
diff --git a/src/interfaces/ecpg/ecpglib/sqlda.c b/src/interfaces/ecpg/ecpglib/sqlda.c
index a1a0e18..aa37dee 100644
--- a/src/interfaces/ecpg/ecpglib/sqlda.c
+++ b/src/interfaces/ecpg/ecpglib/sqlda.c
@@ -389,7 +389,7 @@ ecpg_set_compat_sqlda(int lineno, struct sqlda_compat ** _sqlda, const PGresult
 		if (!isnull)
 		{
 			if (set_data)
-				ecpg_get_data(res, row, i, lineno,
+				ecpg_get_data(res, 0, row, i, lineno,
 							  sqlda->sqlvar[i].sqltype, ECPGt_NO_INDICATOR,
 							  sqlda->sqlvar[i].sqldata, NULL, 0, 0, 0,
 							  ECPG_ARRAY_NONE, compat, false);
@@ -571,7 +571,7 @@ ecpg_set_native_sqlda(int lineno, struct sqlda_struct ** _sqlda, const PGresult
 		if (!isnull)
 		{
 			if (set_data)
-				ecpg_get_data(res, row, i, lineno,
+				ecpg_get_data(res, 0, row, i, lineno,
 							  sqlda->sqlvar[i].sqltype, ECPGt_NO_INDICATOR,
 							  sqlda->sqlvar[i].sqldata, NULL, 0, 0, 0,
 							  ECPG_ARRAY_NONE, compat, false);
diff --git a/src/interfaces/ecpg/include/ecpgerrno.h b/src/interfaces/ecpg/include/ecpgerrno.h
index 36b15b7..f21dad2 100644
--- a/src/interfaces/ecpg/include/ecpgerrno.h
+++ b/src/interfaces/ecpg/include/ecpgerrno.h
@@ -37,6 +37,7 @@
 #define ECPG_NOT_CONN			-221
 
 #define ECPG_INVALID_STMT		-230
+#define ECPG_INVALID_CURSOR		-231
 
 /* dynamic SQL related */
 #define ECPG_UNKNOWN_DESCRIPTOR		-240
diff --git a/src/interfaces/ecpg/include/ecpglib.h b/src/interfaces/ecpg/include/ecpglib.h
index 3b8ed4c..236f797 100644
--- a/src/interfaces/ecpg/include/ecpglib.h
+++ b/src/interfaces/ecpg/include/ecpglib.h
@@ -63,6 +63,13 @@ PGTransactionStatusType ECPGtransactionStatus(const char *);
 
 char	   *ECPGerrmsg(void);
 
+/* Readahead cursor functions */
+bool		ECPGopen(const int, const int, const int, const char *, const bool, const char *, const int, const char *, ...);
+bool		ECPGfetch(const int, const int, const int, const char *, const bool,
+				const char *, enum ECPG_cursor_direction, const char *,
+				const int,const char *, ...);
+bool		ECPGclose(const int, const int, const int, const char *, const bool, const char *, const int, const char *, ...);
+
  /* print an error message */
 void		sqlprint(void);
 
diff --git a/src/interfaces/ecpg/include/ecpgtype.h b/src/interfaces/ecpg/include/ecpgtype.h
index 7cc47e9..dc82457 100644
--- a/src/interfaces/ecpg/include/ecpgtype.h
+++ b/src/interfaces/ecpg/include/ecpgtype.h
@@ -99,6 +99,14 @@ enum ECPG_statement_type
 	ECPGst_prepnormal
 };
 
+enum ECPG_cursor_direction
+{
+	ECPGc_absolute,
+	ECPGc_relative,
+	ECPGc_forward, 
+	ECPGc_backward 
+};
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/interfaces/ecpg/preproc/check_rules.pl b/src/interfaces/ecpg/preproc/check_rules.pl
index 991c40c..8560eb0 100644
--- a/src/interfaces/ecpg/preproc/check_rules.pl
+++ b/src/interfaces/ecpg/preproc/check_rules.pl
@@ -43,7 +43,10 @@ my %replace_line = (
 		'CREATE OptTemp TABLE create_as_target AS EXECUTE prepared_name execute_param_clause',
 
 	'PrepareStmtPREPAREnameprep_type_clauseASPreparableStmt' =>
-		'PREPARE prepared_name prep_type_clause AS PreparableStmt'
+		'PREPARE prepared_name prep_type_clause AS PreparableStmt',
+
+	'DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectStmt' =>
+		'DECLARE cursor_name cursor_options opt_readahead CURSOR opt_hold FOR SelectStmt'
 );
 
 my $block        = '';
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons
index 23421df..40df82f 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -15,7 +15,10 @@ ECPG: stmtClosePortalStmt block
 			}
 		}
 
-		output_statement($1, 0, ECPGst_normal);
+		if (use_fetch_readahead)
+			output_close_statement($1, 0, ECPGst_normal);
+		else
+			output_statement($1, 0, ECPGst_normal);
 	}
 ECPG: stmtDeallocateStmt block
 	{
@@ -27,8 +30,14 @@ ECPG: stmtDeallocateStmt block
 ECPG: stmtDeclareCursorStmt block
 	{ output_simple_statement($1); }
 ECPG: stmtDiscardStmt block
-ECPG: stmtFetchStmt block
 	{ output_statement($1, 1, ECPGst_normal); }
+ECPG: stmtFetchStmt block
+	{
+		if (use_fetch_readahead)
+			output_fetch_statement($1, 1, ECPGst_normal);
+		else
+			output_statement($1, 1, ECPGst_normal);
+	}
 ECPG: stmtDeleteStmt block
 ECPG: stmtInsertStmt block
 ECPG: stmtSelectStmt block
@@ -137,7 +146,10 @@ ECPG: stmtViewStmt rule
 		if ((ptr = add_additional_variables($1, true)) != NULL)
 		{
 			connection = ptr->connection ? mm_strdup(ptr->connection) : NULL;
-			output_statement(mm_strdup(ptr->command), 0, ECPGst_normal);
+			if (use_fetch_readahead)
+				output_open_statement(mm_strdup(ptr->command), 0, ECPGst_normal);
+			else
+				output_statement(mm_strdup(ptr->command), 0, ECPGst_normal);
 			ptr->opened = true;
 		}
 	}
@@ -195,6 +207,21 @@ ECPG: stmtViewStmt rule
 ECPG: where_or_current_clauseWHERECURRENT_POFcursor_name block
 	{
 		char *cursor_marker = $4[0] == ':' ? mm_strdup("$0") : $4;
+		struct cursor *ptr;
+
+		for (ptr = cur; ptr != NULL; ptr = ptr->next)
+		{
+			if (strcmp(ptr->name, $4) == 0)
+				break;
+		}
+		if (!ptr)
+			mmerror(PARSE_ERROR, ET_FATAL, "cursor \"%s\" does not exist", $4);
+
+		if (ptr->fetch_readahead)
+		{
+			mmerror(PARSE_ERROR, ET_ERROR,
+				"\"WHERE CURRENT OF\" is incompatible with a READAHEAD cursor\n");
+		}
 		$$ = cat_str(2,mm_strdup("where current of"), cursor_marker);
 	}
 ECPG: CopyStmtCOPYopt_binaryqualified_nameopt_column_listopt_oidscopy_fromcopy_file_namecopy_delimiteropt_withcopy_options addon
@@ -215,31 +242,77 @@ ECPG: var_valueNumericOnly addon
 		}
 ECPG: fetch_argscursor_name addon
 		add_additional_variables($1, false);
+		set_cursor_readahead($1);
 		if ($1[0] == ':')
 		{
 			free($1);
 			$1 = mm_strdup("$0");
 		}
+		current_cursor_direction = ECPGc_forward;
+		current_cursor_amount = mm_strdup("1");
 ECPG: fetch_argsfrom_incursor_name addon
 		add_additional_variables($2, false);
+		set_cursor_readahead($2);
 		if ($2[0] == ':')
 		{
 			free($2);
 			$2 = mm_strdup("$0");
 		}
+		current_cursor_direction = ECPGc_forward;
+		current_cursor_amount = mm_strdup("1");
 ECPG: fetch_argsNEXTopt_from_incursor_name addon
+		add_additional_variables($3, false);
+		set_cursor_readahead($3);
+		if ($3[0] == ':')
+		{
+			free($3);
+			$3 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_forward;
+		current_cursor_amount = mm_strdup("1");
 ECPG: fetch_argsPRIORopt_from_incursor_name addon
+		add_additional_variables($3, false);
+		set_cursor_readahead($3);
+		if ($3[0] == ':')
+		{
+			free($3);
+			$3 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_backward;
+		current_cursor_amount = mm_strdup("1"); 
 ECPG: fetch_argsFIRST_Popt_from_incursor_name addon
+		add_additional_variables($3, false);
+		set_cursor_readahead($3);
+		if ($3[0] == ':')
+		{
+			free($3);
+			$3 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_absolute;
+		current_cursor_amount = mm_strdup("1");
 ECPG: fetch_argsLAST_Popt_from_incursor_name addon
+		add_additional_variables($3, false);
+		set_cursor_readahead($3);
+		if ($3[0] == ':')
+		{
+			free($3);
+			$3 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_absolute;
+		current_cursor_amount = mm_strdup("-1");
 ECPG: fetch_argsALLopt_from_incursor_name addon
 		add_additional_variables($3, false);
+		set_cursor_readahead($3);
 		if ($3[0] == ':')
 		{
 			free($3);
 			$3 = mm_strdup("$0");
 		}
+		current_cursor_direction = ECPGc_forward;
+		current_cursor_amount = mm_strdup("all");
 ECPG: fetch_argsSignedIconstopt_from_incursor_name addon
 		add_additional_variables($3, false);
+		set_cursor_readahead($3);
 		if ($3[0] == ':')
 		{
 			free($3);
@@ -250,19 +323,76 @@ ECPG: fetch_argsSignedIconstopt_from_incursor_name addon
 			free($1);
 			$1 = mm_strdup("$0");
 		}
+		current_cursor_direction = ECPGc_forward;
+		current_cursor_amount = mm_strdup($1);
 ECPG: fetch_argsFORWARDALLopt_from_incursor_name addon
+		add_additional_variables($4, false);
+		set_cursor_readahead($4);
+		if ($4[0] == ':')
+		{
+			free($4);
+			$4 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_forward;
+		current_cursor_amount = mm_strdup("all");
 ECPG: fetch_argsBACKWARDALLopt_from_incursor_name addon
 		add_additional_variables($4, false);
+		set_cursor_readahead($4);
 		if ($4[0] == ':')
 		{
 			free($4);
 			$4 = mm_strdup("$0");
 		}
+		current_cursor_direction = ECPGc_backward;
+		current_cursor_amount = mm_strdup("all");
 ECPG: fetch_argsABSOLUTE_PSignedIconstopt_from_incursor_name addon
+		add_additional_variables($4, false);
+		set_cursor_readahead($4);
+		if ($4[0] == ':')
+		{
+			free($4);
+			$4 = mm_strdup("$0");
+		}
+		if ($2[0] == '$')
+		{
+			free($2);
+			$2 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_absolute;
+		current_cursor_amount = mm_strdup($2);
 ECPG: fetch_argsRELATIVE_PSignedIconstopt_from_incursor_name addon
+		add_additional_variables($4, false);
+		set_cursor_readahead($4);
+		if ($4[0] == ':')
+		{
+			free($4);
+			$4 = mm_strdup("$0");
+		}
+		if ($2[0] == '$')
+		{
+			free($2);
+			$2 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_relative;
+		current_cursor_amount = mm_strdup($2);
 ECPG: fetch_argsFORWARDSignedIconstopt_from_incursor_name addon
+		add_additional_variables($4, false);
+		set_cursor_readahead($4);
+		if ($4[0] == ':')
+		{
+			free($4);
+			$4 = mm_strdup("$0");
+		}
+		if ($2[0] == '$')
+		{
+			free($2);
+			$2 = mm_strdup("$0");
+		}
+		current_cursor_direction = ECPGc_forward;
+		current_cursor_amount = mm_strdup($2);
 ECPG: fetch_argsBACKWARDSignedIconstopt_from_incursor_name addon
 		add_additional_variables($4, false);
+		set_cursor_readahead($4);
 		if ($4[0] == ':')
 		{
 			free($4);
@@ -273,7 +403,15 @@ ECPG: fetch_argsBACKWARDSignedIconstopt_from_incursor_name addon
 			free($2);
 			$2 = mm_strdup("$0");
 		}
-ECPG: cursor_namename rule
+		current_cursor_direction = ECPGc_backward;
+		current_cursor_amount = mm_strdup($2);
+ECPG: cursor_namename block
+		{
+			if (current_cursor)
+				free(current_cursor);
+			current_cursor = make3_str(mm_strdup("\""), mm_strdup($1), mm_strdup("\""));
+			$$ = $1;
+		}
 	| char_civar
 		{
 			char *curname = mm_alloc(strlen($1) + 2);
@@ -296,7 +434,7 @@ ECPG: PrepareStmtPREPAREprepared_nameprep_type_clauseASPreparableStmt block
 	}
 ECPG: ExecuteStmtEXECUTEprepared_nameexecute_param_clauseexecute_rest block
 	{ $$ = $2; }
-ECPG: DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectStmt block
+ECPG: DeclareCursorStmtDECLAREcursor_namecursor_optionsopt_readaheadCURSORopt_holdFORSelectStmt block
 	{
 		struct cursor *ptr, *this;
 		char *cursor_marker = $2[0] == ':' ? mm_strdup("$0") : mm_strdup($2);
@@ -321,7 +459,13 @@ ECPG: DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectSt
 		this->function = (current_function ? mm_strdup(current_function) : NULL);
 		this->connection = connection;
 		this->opened = false;
-		this->command =  cat_str(7, mm_strdup("declare"), cursor_marker, $3, mm_strdup("cursor"), $5, mm_strdup("for"), $7);
+		if (!strcmp($4, "1"))
+			this->fetch_readahead = true;
+		else if (!strcmp($4, "0"))
+			this->fetch_readahead = false;
+		else
+			this->fetch_readahead = fetch_readahead;
+		this->command =  cat_str(7, mm_strdup("declare"), cursor_marker, $3, mm_strdup("cursor"), $6, mm_strdup("for"), $8);
 		this->argsinsert = argsinsert;
 		this->argsinsert_oos = NULL;
 		this->argsresult = argsresult;
@@ -348,6 +492,8 @@ ECPG: DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectSt
 ECPG: ClosePortalStmtCLOSEcursor_name block
 	{
 		char *cursor_marker = $2[0] == ':' ? mm_strdup("$0") : $2;
+		if (!INFORMIX_MODE || pg_strcasecmp($2, "database") != 0)
+			set_cursor_readahead($2);
 		$$ = cat2_str(mm_strdup("close"), cursor_marker);
 	}
 ECPG: opt_hold block
diff --git a/src/interfaces/ecpg/preproc/ecpg.c b/src/interfaces/ecpg/preproc/ecpg.c
index 6f73148..afb898b 100644
--- a/src/interfaces/ecpg/preproc/ecpg.c
+++ b/src/interfaces/ecpg/preproc/ecpg.c
@@ -18,7 +18,8 @@ bool		autocommit = false,
 			force_indicator = true,
 			questionmarks = false,
 			regression_mode = false,
-			auto_prepare = false;
+			auto_prepare = false,
+			fetch_readahead = false;
 
 char	   *output_filename;
 
@@ -51,7 +52,7 @@ help(const char *progname)
 	printf(_("  -I DIRECTORY   search DIRECTORY for include files\n"));
 	printf(_("  -o OUTFILE     write result to OUTFILE\n"));
 	printf(_("  -r OPTION      specify run-time behavior; OPTION can be:\n"
-	 "                 \"no_indicator\", \"prepare\", \"questionmarks\"\n"));
+	 "                 \"no_indicator\", \"prepare\", \"questionmarks\", \"fetch_readahead\"\n"));
 	printf(_("  --regression   run in regression testing mode\n"));
 	printf(_("  -t             turn on autocommit of transactions\n"));
 	printf(_("  --help         show this help, then exit\n"));
@@ -229,6 +230,8 @@ main(int argc, char *const argv[])
 					auto_prepare = true;
 				else if (strcmp(optarg, "questionmarks") == 0)
 					questionmarks = true;
+				else if (strcmp(optarg, "fetch_readahead") == 0)
+					fetch_readahead = true;
 				else
 				{
 					fprintf(stderr, _("Try \"%s --help\" for more information.\n"), argv[0]);
diff --git a/src/interfaces/ecpg/preproc/ecpg.header b/src/interfaces/ecpg/preproc/ecpg.header
index 1ea6ce4..1a0b23b 100644
--- a/src/interfaces/ecpg/preproc/ecpg.header
+++ b/src/interfaces/ecpg/preproc/ecpg.header
@@ -34,7 +34,11 @@
  */
 int struct_level = 0;
 int braces_open; /* brace level counter */
+bool use_fetch_readahead = false;
 char *current_function;
+char *current_cursor = NULL;
+enum ECPG_cursor_direction current_cursor_direction;
+char *current_cursor_amount = NULL;
 int ecpg_internal_var = 0;
 char	*connection = NULL;
 char	*input_filename = NULL;
@@ -111,6 +115,26 @@ mmerror(int error_code, enum errortype type, const char *error, ...)
 }
 
 /*
+ * set use_fetch_readahead based on the current cursor
+ * doesn't return if the cursor is not declared
+ */
+static void
+set_cursor_readahead(const char *curname)
+{
+	struct cursor *ptr;
+
+	for (ptr = cur; ptr != NULL; ptr = ptr->next)
+	{
+		if (strcmp(ptr->name, curname) == 0)
+			break;
+	}
+	if (!ptr)
+		mmerror(PARSE_ERROR, ET_FATAL, "cursor \"%s\" does not exist", curname);
+
+	use_fetch_readahead = ptr->fetch_readahead;
+}
+
+/*
  * string concatenation
  */
 
diff --git a/src/interfaces/ecpg/preproc/ecpg.tokens b/src/interfaces/ecpg/preproc/ecpg.tokens
index b55138a..3995b59 100644
--- a/src/interfaces/ecpg/preproc/ecpg.tokens
+++ b/src/interfaces/ecpg/preproc/ecpg.tokens
@@ -10,7 +10,7 @@
                 SQL_FREE SQL_GET SQL_GO SQL_GOTO SQL_IDENTIFIED
                 SQL_INDICATOR SQL_KEY_MEMBER SQL_LENGTH
                 SQL_LONG SQL_NULLABLE SQL_OCTET_LENGTH
-                SQL_OPEN SQL_OUTPUT SQL_REFERENCE
+                SQL_OPEN SQL_OUTPUT SQL_READAHEAD SQL_REFERENCE
                 SQL_RETURNED_LENGTH SQL_RETURNED_OCTET_LENGTH SQL_SCALE
                 SQL_SECTION SQL_SHORT SQL_SIGNED SQL_SQL SQL_SQLERROR
                 SQL_SQLPRINT SQL_SQLWARNING SQL_START SQL_STOP
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index a362aff..2bf960f 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -287,7 +287,7 @@ prepared_name: name
  * 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
+ECPGCursorStmt:  DECLARE cursor_name cursor_options opt_readahead CURSOR opt_hold FOR prepared_name
 		{
 			struct cursor *ptr, *this;
 			char *cursor_marker = $2[0] == ':' ? mm_strdup("$0") : mm_strdup($2);
@@ -315,15 +315,22 @@ ECPGCursorStmt:  DECLARE cursor_name cursor_options CURSOR opt_hold FOR prepared
 			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->opened = false;
+			if (!strcmp($4, "1"))
+				this->fetch_readahead = true;
+			else if (!strcmp($4, "0"))
+				this->fetch_readahead = false;
+			else
+				this->fetch_readahead = fetch_readahead;
+			this->command =  cat_str(6, mm_strdup("declare"), cursor_marker, $3, mm_strdup("cursor"), $6, 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);
+			thisquery->name = (char *) mm_alloc(sizeof("ECPGprepared_statement(, , __LINE__)") + strlen(con) + strlen($8));
+			sprintf(thisquery->name, "ECPGprepared_statement(%s, %s, __LINE__)", con, $8);
 
 			this->argsinsert = NULL;
 			this->argsinsert_oos = NULL;
@@ -348,6 +355,11 @@ ECPGCursorStmt:  DECLARE cursor_name cursor_options CURSOR opt_hold FOR prepared
 		}
 		;
 
+opt_readahead:	SQL_READAHEAD				{ $$ = mm_strdup("1"); }
+		| NO SQL_READAHEAD			{ $$ = mm_strdup("0"); }
+		| /* EMPTY */			{ $$ = mm_strdup("default"); }
+		;
+
 ECPGExecuteImmediateStmt: EXECUTE IMMEDIATE execstring
 			{
 			  /* execute immediate means prepare the statement and
@@ -988,6 +1000,7 @@ ECPGOpen: SQL_OPEN cursor_name opt_ecpg_using
 		{
 			if ($2[0] == ':')
 				remove_variable_from_list(&argsinsert, find_variable($2 + 1));
+			set_cursor_readahead($2);
 			$$ = $2;
 		}
 		;
@@ -1656,6 +1669,10 @@ char_civar: char_variable
 		{
 			char *ptr = strstr($1, ".arr");
 
+			if (current_cursor)
+				free(current_cursor);
+			current_cursor = mm_strdup($1);
+
 			if (ptr) /* varchar, we need the struct name here, not the struct element */
 				*ptr = '\0';
 			add_variable_to_head(&argsinsert, find_variable($1), &no_indicator);
diff --git a/src/interfaces/ecpg/preproc/ecpg.type b/src/interfaces/ecpg/preproc/ecpg.type
index ac6aa00..2662372 100644
--- a/src/interfaces/ecpg/preproc/ecpg.type
+++ b/src/interfaces/ecpg/preproc/ecpg.type
@@ -85,6 +85,7 @@
 %type <str> opt_output
 %type <str> opt_pointer
 %type <str> opt_port
+%type <str> opt_readahead
 %type <str> opt_reference
 %type <str> opt_scale
 %type <str> opt_server
diff --git a/src/interfaces/ecpg/preproc/ecpg_keywords.c b/src/interfaces/ecpg/preproc/ecpg_keywords.c
index 8032c30..cbd37c6 100644
--- a/src/interfaces/ecpg/preproc/ecpg_keywords.c
+++ b/src/interfaces/ecpg/preproc/ecpg_keywords.c
@@ -56,6 +56,7 @@ static const ScanKeyword ScanECPGKeywords[] = {
 	{"octet_length", SQL_OCTET_LENGTH, 0},
 	{"open", SQL_OPEN, 0},
 	{"output", SQL_OUTPUT, 0},
+	{"readahead", SQL_READAHEAD, 0},
 	{"reference", SQL_REFERENCE, 0},
 	{"returned_length", SQL_RETURNED_LENGTH, 0},
 	{"returned_octet_length", SQL_RETURNED_OCTET_LENGTH, 0},
diff --git a/src/interfaces/ecpg/preproc/extern.h b/src/interfaces/ecpg/preproc/extern.h
index ccf5548..3d22d3a 100644
--- a/src/interfaces/ecpg/preproc/extern.h
+++ b/src/interfaces/ecpg/preproc/extern.h
@@ -24,12 +24,17 @@ extern bool autocommit,
 			force_indicator,
 			questionmarks,
 			regression_mode,
-			auto_prepare;
+			auto_prepare,
+			fetch_readahead;
+extern bool	use_fetch_readahead;
 extern int	braces_open,
 			ret_value,
 			struct_level,
 			ecpg_internal_var;
 extern char *current_function;
+extern char *current_cursor;  
+extern enum ECPG_cursor_direction current_cursor_direction;
+extern char *current_cursor_amount;
 extern char *descriptor_index;
 extern char *descriptor_name;
 extern char *connection;
@@ -67,6 +72,9 @@ extern void output_statement(char *, int, enum ECPG_statement_type);
 extern void output_prepare_statement(char *, char *);
 extern void output_deallocate_prepare_statement(char *);
 extern void output_simple_statement(char *);
+extern void output_open_statement(char *, int, enum ECPG_statement_type);
+extern void output_fetch_statement(char *, int, enum ECPG_statement_type);
+extern void output_close_statement(char *, int, enum ECPG_statement_type);
 extern char *hashline_number(void);
 extern int	base_yyparse(void);
 extern int	base_yylex(void);
diff --git a/src/interfaces/ecpg/preproc/output.c b/src/interfaces/ecpg/preproc/output.c
index 9958a0a..6e60392 100644
--- a/src/interfaces/ecpg/preproc/output.c
+++ b/src/interfaces/ecpg/preproc/output.c
@@ -112,10 +112,16 @@ static char *ecpg_statement_type_name[] = {
 	"ECPGst_prepnormal"
 };
 
-void
-output_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
+static char *ecpg_cursor_direction_name[] = {
+	"ECPGc_absolute",
+	"ECPGc_relative",
+	"ECPGc_forward", 
+	"ECPGc_backward" 
+};
+
+static void
+output_statement_epilogue(char *stmt, int whenever_mode, enum ECPG_statement_type st)
 {
-	fprintf(yyout, "{ ECPGdo(__LINE__, %d, %d, %s, %d, ", compat, force_indicator, connection ? connection : "NULL", questionmarks);
 	if (st == ECPGst_execute || st == ECPGst_exec_immediate)
 	{
 		fprintf(yyout, "%s, %s, ", ecpg_statement_type_name[st], stmt);
@@ -145,6 +151,13 @@ output_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
 }
 
 void
+output_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
+{
+	fprintf(yyout, "{ ECPGdo(__LINE__, %d, %d, %s, %d, ", compat, force_indicator, connection ? connection : "NULL", questionmarks);
+	output_statement_epilogue(stmt, whenever_mode, st);
+}
+
+void
 output_prepare_statement(char *name, char *stmt)
 {
 	fprintf(yyout, "{ ECPGprepare(__LINE__, %s, %d, ", connection ? connection : "NULL", questionmarks);
@@ -178,6 +191,51 @@ output_deallocate_prepare_statement(char *name)
 		free(connection);
 }
 
+void
+output_open_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
+{
+	fprintf(yyout, "{ ECPGopen(__LINE__, %d, %d, %s, %d, %s, ",
+			compat,
+			force_indicator,
+			connection ? connection : "NULL",
+			questionmarks,
+			current_cursor);
+	output_statement_epilogue(stmt, whenever_mode, st);
+}
+
+void
+output_fetch_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
+{
+	char	*amount = mm_alloc(strlen(current_cursor_amount) + 3);
+
+	if (!amount)
+		return;
+
+	sprintf(amount, "\"%s\"", current_cursor_amount);
+	fprintf(yyout, "{ ECPGfetch(__LINE__, %d, %d, %s, %d, %s, %s, %s, ",
+			compat,
+			force_indicator,
+			connection ? connection : "NULL",
+			questionmarks,
+			current_cursor,
+			ecpg_cursor_direction_name[current_cursor_direction],
+			amount);
+	output_statement_epilogue(stmt, whenever_mode, st);
+	free(amount);
+}
+
+void
+output_close_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
+{
+	fprintf(yyout, "{ ECPGclose(__LINE__, %d, %d, %s, %d, %s, ",
+			compat,
+			force_indicator,
+			connection ? connection : "NULL",
+			questionmarks,
+			current_cursor);
+	output_statement_epilogue(stmt, whenever_mode, st);
+}
+
 static void
 output_escaped_str(char *str, bool quoted)
 {
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index 515470e..aebcaf4 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -90,6 +90,8 @@ my %replace_line = (
 	'fetch_argsFORWARDopt_from_incursor_name'      => 'ignore',
 	'fetch_argsBACKWARDopt_from_incursor_name'     => 'ignore',
 	"opt_array_boundsopt_array_bounds'['Iconst']'" => 'ignore',
+	'DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectStmt' =>
+			'DECLARE cursor_name cursor_options opt_readahead CURSOR opt_hold FOR SelectStmt',
 	'VariableShowStmtSHOWvar_name'                 => 'SHOW var_name ecpg_into',
 	'VariableShowStmtSHOWTIMEZONE' => 'SHOW TIME ZONE ecpg_into',
 	'VariableShowStmtSHOWTRANSACTIONISOLATIONLEVEL' => 'SHOW TRANSACTION ISOLATION LEVEL ecpg_into',
diff --git a/src/interfaces/ecpg/preproc/type.h b/src/interfaces/ecpg/preproc/type.h
index 68e0d1a..89c920f 100644
--- a/src/interfaces/ecpg/preproc/type.h
+++ b/src/interfaces/ecpg/preproc/type.h
@@ -130,6 +130,7 @@ struct cursor
 	char	   *command;
 	char	   *connection;
 	bool		opened;
+	bool		fetch_readahead;
 	struct arguments *argsinsert;
 	struct arguments *argsinsert_oos;
 	struct arguments *argsresult;
diff --git a/src/interfaces/ecpg/test/ecpg_schedule b/src/interfaces/ecpg/test/ecpg_schedule
index c07ea93..a15b7d9 100644
--- a/src/interfaces/ecpg/test/ecpg_schedule
+++ b/src/interfaces/ecpg/test/ecpg_schedule
@@ -20,6 +20,7 @@ test: preproc/array_of_struct
 test: preproc/autoprep
 test: preproc/comment
 test: preproc/cursor
+test: preproc/cursor-readahead
 test: preproc/define
 test: preproc/init
 test: preproc/strings
diff --git a/src/interfaces/ecpg/test/ecpg_schedule_tcp b/src/interfaces/ecpg/test/ecpg_schedule_tcp
index 77481b5..60994e0 100644
--- a/src/interfaces/ecpg/test/ecpg_schedule_tcp
+++ b/src/interfaces/ecpg/test/ecpg_schedule_tcp
@@ -20,6 +20,7 @@ test: preproc/array_of_struct
 test: preproc/autoprep
 test: preproc/comment
 test: preproc/cursor
+test: preproc/cursor-readahead
 test: preproc/define
 test: preproc/init
 test: preproc/strings
diff --git a/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.c b/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.c
new file mode 100644
index 0000000..ef39815
--- /dev/null
+++ b/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.c
@@ -0,0 +1,663 @@
+/* Processed by ecpg (regression mode) */
+/* These include files are added by the preprocessor */
+#include <ecpglib.h>
+#include <ecpgerrno.h>
+#include <sqlca.h>
+/* End of automatic include section */
+#define ECPGdebug(X,Y) ECPGdebug((X)+100,(Y))
+
+#line 1 "cursor-readahead.pgc"
+#include <stdio.h>
+#include <malloc.h>
+
+/* test automatic prepare for all statements */
+
+#line 1 "regression.h"
+
+
+
+
+
+
+#line 5 "cursor-readahead.pgc"
+
+
+
+#line 1 "sqlda.h"
+#ifndef ECPG_SQLDA_H
+#define ECPG_SQLDA_H
+
+#ifdef _ECPG_INFORMIX_H
+
+#include "sqlda-compat.h"
+typedef struct sqlvar_compat	sqlvar_t;
+typedef struct sqlda_compat	sqlda_t;
+
+#else
+
+#include "sqlda-native.h"
+typedef struct sqlvar_struct	sqlvar_t;
+typedef struct sqlda_struct	sqlda_t;
+
+#endif
+
+#endif /* ECPG_SQLDA_H */
+
+#line 7 "cursor-readahead.pgc"
+
+
+/* exec sql whenever sqlerror  sqlprint ; */
+#line 9 "cursor-readahead.pgc"
+
+/* exec sql whenever sql_warning  sqlprint ; */
+#line 10 "cursor-readahead.pgc"
+
+
+#define MAXID	(513)
+
+int main(void)
+{
+	int	counts[2] = { 1, 5 };
+	/* exec sql begin declare section */
+		
+		
+		   
+		  
+	
+#line 18 "cursor-readahead.pgc"
+ char * curname ;
+ 
+#line 19 "cursor-readahead.pgc"
+ int maxid ;
+ 
+#line 20 "cursor-readahead.pgc"
+ int i , j , count , rows ;
+ 
+#line 21 "cursor-readahead.pgc"
+ int id , id1 [ 5 ] , id2 [ 5 ] ;
+/* exec sql end declare section */
+#line 22 "cursor-readahead.pgc"
+
+	sqlda_t	*sqlda;
+
+	/*
+	 * Intentionally don't create a 2MB stderr file for this test.
+	 * Enable it manually if you're interested in it.
+	 */
+#if 0
+	ECPGdebug(1, stderr);
+#endif
+
+	{ ECPGconnect(__LINE__, 0, "regress1" , NULL, NULL , NULL, 0); 
+#line 33 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 33 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 33 "cursor-readahead.pgc"
+
+
+	{ ECPGtrans(__LINE__, NULL, "begin");
+#line 35 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 35 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 35 "cursor-readahead.pgc"
+
+
+	maxid = MAXID;
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "create table ra_test ( id integer primary key )", ECPGt_EOIT, ECPGt_EORT);
+#line 39 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 39 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 39 "cursor-readahead.pgc"
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "insert into ra_test select i . i from generate_series ( 1 , $1  ) as i", 
+	ECPGt_int,&(maxid),(long)1,(long)1,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, ECPGt_EORT);
+#line 40 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 40 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 40 "cursor-readahead.pgc"
+
+
+	{ ECPGtrans(__LINE__, NULL, "commit");
+#line 42 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 42 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 42 "cursor-readahead.pgc"
+
+
+	{ ECPGtrans(__LINE__, NULL, "begin");
+#line 44 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 44 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 44 "cursor-readahead.pgc"
+
+
+	curname = "xx";
+	ECPGset_var( 0, &( curname ), __LINE__);\
+ /* declare $0 scroll cursor for select * from ra_test */
+#line 47 "cursor-readahead.pgc"
+
+	/* declare xcur scroll cursor for select * from ra_test */
+#line 48 "cursor-readahead.pgc"
+
+
+	/* exec sql whenever not found  break ; */
+#line 50 "cursor-readahead.pgc"
+
+
+	for (i = 0; i < 2; i++)
+	{
+		count = counts[i];
+
+		{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "declare $0 scroll cursor for select * from ra_test", 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, ECPGt_EORT);
+#line 56 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 56 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 56 "cursor-readahead.pgc"
+
+		{ ECPGopen(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "declare xcur scroll cursor for select * from ra_test", ECPGt_EOIT, ECPGt_EORT);
+#line 57 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 57 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 57 "cursor-readahead.pgc"
+
+
+		id = 0;
+		while (1)
+		{
+			for (j = 0; j < count; j++)
+			{
+				id1[j] = -1;
+				id2[j] = -1;
+			}
+
+			{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "fetch $0 from $0", 
+	ECPGt_int,&(count),(long)1,(long)1,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, 
+	ECPGt_int,(id1),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 68 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode == ECPG_NOT_FOUND) break;
+#line 68 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 68 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 68 "cursor-readahead.pgc"
+
+			{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_forward, "$0", ECPGst_normal, "fetch $0 from xcur", 
+	ECPGt_int,&(count),(long)1,(long)1,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, 
+	ECPGt_int,(id2),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 69 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode == ECPG_NOT_FOUND) break;
+#line 69 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 69 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 69 "cursor-readahead.pgc"
+
+
+			rows = sqlca.sqlerrd[2];
+
+			for (j = 0; j < rows; j++)
+			{
+				id++;
+
+				if (id != id1[j] || id != id2[j] || id1[j] != id2[j])
+				{
+					printf("Reading two cursors forward: ERROR. id = %d id1 = %d id2 = %d\n", id, id1[j], id2[j]);
+					break;
+				}
+			}
+			if (j != rows)
+				break;
+		}
+
+		if (sqlca.sqlwarn[0] == 'W') sqlprint();
+		if (sqlca.sqlcode < 0) sqlprint();
+
+		if (id == maxid)
+			printf("Reading readahead and non-readahead cursors simultaneously forward by %d record: SUCCESS\n", count);
+		else
+			printf("Reading readahead and non-readahead cursors simultaneously forward by %d record: FAILED at %d, id1 %d id2 %d\n", count, id, id1[j], id2[j]);
+
+		{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "close $0", 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, ECPGt_EORT);
+#line 95 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 95 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 95 "cursor-readahead.pgc"
+
+		{ ECPGclose(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "close xcur", ECPGt_EOIT, ECPGt_EORT);
+#line 96 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 96 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 96 "cursor-readahead.pgc"
+
+
+		/* exec sql whenever not found  continue ; */
+#line 98 "cursor-readahead.pgc"
+
+
+		{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "declare $0 scroll cursor for select * from ra_test", 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, ECPGt_EORT);
+#line 100 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 100 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 100 "cursor-readahead.pgc"
+
+		{ ECPGopen(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "declare xcur scroll cursor for select * from ra_test", ECPGt_EOIT, ECPGt_EORT);
+#line 101 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 101 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 101 "cursor-readahead.pgc"
+
+		{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "fetch last from $0", 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, 
+	ECPGt_int,(id1),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 102 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 102 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 102 "cursor-readahead.pgc"
+
+		{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "fetch from $0", 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, 
+	ECPGt_int,(id1),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 103 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 103 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 103 "cursor-readahead.pgc"
+
+		if (sqlca.sqlcode == ECPG_NOT_FOUND)
+			printf("After last record in cursor '%s' (value %d), fetching backwards.\n", curname, id1[0]);
+		else
+			goto err;
+		{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_absolute, "-1", ECPGst_normal, "fetch last from xcur", ECPGt_EOIT, 
+	ECPGt_int,(id2),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 108 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 108 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 108 "cursor-readahead.pgc"
+
+		{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_forward, "1", ECPGst_normal, "fetch from xcur", ECPGt_EOIT, 
+	ECPGt_int,(id2),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 109 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 109 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 109 "cursor-readahead.pgc"
+
+		if (sqlca.sqlcode == ECPG_NOT_FOUND)
+			printf("After last record in cursor 'xcur' (value %d), fetching backwards.\n", id2[0]);
+		else
+			goto err;
+
+		/* exec sql whenever not found  break ; */
+#line 115 "cursor-readahead.pgc"
+
+
+		id = maxid;
+		while (1)
+		{
+			for (j = 0; j < count; j++)
+			{
+				id1[j] = -1;
+				id2[j] = -1;
+			}
+
+			{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "fetch backward $0 from $0", 
+	ECPGt_int,&(count),(long)1,(long)1,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, 
+	ECPGt_int,(id1),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 126 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode == ECPG_NOT_FOUND) break;
+#line 126 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 126 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 126 "cursor-readahead.pgc"
+
+			{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_backward, "$0", ECPGst_normal, "fetch backward $0 from xcur", 
+	ECPGt_int,&(count),(long)1,(long)1,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, 
+	ECPGt_int,(id2),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 127 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode == ECPG_NOT_FOUND) break;
+#line 127 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 127 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 127 "cursor-readahead.pgc"
+
+
+			rows = sqlca.sqlerrd[2];
+
+			for (j = 0; j < rows; j++)
+			{
+				if (id != id1[j] || id != id2[j] || id1[j] != id2[j])
+				{
+					printf("Reading two cursors backward: ERROR. id = %d id1 = %d id2 = %d\n", id, id1[j], id2[j]);
+					break;
+				}
+				id--;
+			}
+			if (j != rows)
+				break;
+		}
+
+		if (id == 0)
+			printf("Reading readahead and non-readahead cursors simultaneously backwards by %d record(s): SUCCESS\n", count);
+		else
+			printf("Reading readahead and non-readahead cursors simultaneously backwards by %d record(s): FAILED at %d, id1 %d id2 %d\n", count, id, id1[j], id2[j]);
+
+		{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "close $0", 
+	ECPGt_char,&(curname),(long)0,(long)1,(1)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, ECPGt_EORT);
+#line 149 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 149 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 149 "cursor-readahead.pgc"
+
+		{ ECPGclose(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "close xcur", ECPGt_EOIT, ECPGt_EORT);
+#line 150 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 150 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 150 "cursor-readahead.pgc"
+
+
+		/* exec sql whenever not found  continue ; */
+#line 152 "cursor-readahead.pgc"
+
+
+	}
+
+	sqlda = NULL;
+	{ ECPGopen(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "declare xcur scroll cursor for select * from ra_test", ECPGt_EOIT, ECPGt_EORT);
+#line 157 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 157 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 157 "cursor-readahead.pgc"
+
+	{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_forward, "all", ECPGst_normal, "fetch all xcur", ECPGt_EOIT, 
+	ECPGt_sqlda, &sqlda, 0L, 0L, 0L, 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 158 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 158 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 158 "cursor-readahead.pgc"
+
+
+	/* exec sql whenever not found  break ; */
+#line 160 "cursor-readahead.pgc"
+
+
+	id = 0;
+	id1[0] = -1;
+	while (sqlda)
+	{
+		sqlda_t *tmp;
+
+		id++;
+		id1[0] = *(int *)sqlda->sqlvar[0].sqldata;
+
+		if (id != id1[0])
+		{
+			printf("Reading readahead cursors forward into sqlda chain: ERROR. id = %d id1 = %d\n", id, id1[0]);
+			break;
+		}
+		tmp = sqlda->desc_next;
+		free(sqlda);
+		sqlda = tmp;
+	}
+
+	if (id == maxid)
+		printf("Reading all records from a readahead cursor forward into sqlda chain: SUCCESS\n");
+	else
+		printf("Reading all records from a readahead cursor forward into sqlda chain: FAILED. id %d, id1 %d\n", id, id1[0]);
+
+	{ ECPGclose(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "close xcur", ECPGt_EOIT, ECPGt_EORT);
+#line 186 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 186 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 186 "cursor-readahead.pgc"
+
+
+	/* exec sql whenever not found  continue ; */
+#line 188 "cursor-readahead.pgc"
+
+
+	sqlda = NULL;
+	{ ECPGopen(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "declare xcur scroll cursor for select * from ra_test", ECPGt_EOIT, ECPGt_EORT);
+#line 191 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 191 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 191 "cursor-readahead.pgc"
+
+	{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_absolute, "-1", ECPGst_normal, "fetch last from xcur", ECPGt_EOIT, 
+	ECPGt_int,(id1),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 192 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 192 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 192 "cursor-readahead.pgc"
+
+	{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_forward, "1", ECPGst_normal, "fetch from xcur", ECPGt_EOIT, 
+	ECPGt_int,(id1),(long)1,(long)5,sizeof(int), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 193 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 193 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 193 "cursor-readahead.pgc"
+
+	if (sqlca.sqlcode == ECPG_NOT_FOUND)
+		printf("After last record in cursor 'xcur' (value %d), fetching backwards.\n", id1[0]);
+	else
+		goto err;
+
+	{ ECPGfetch(__LINE__, 0, 1, NULL, 0, "xcur", ECPGc_backward, "all", ECPGst_normal, "fetch backward all xcur", ECPGt_EOIT, 
+	ECPGt_sqlda, &sqlda, 0L, 0L, 0L, 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 199 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 199 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 199 "cursor-readahead.pgc"
+
+
+	/* exec sql whenever not found  break ; */
+#line 201 "cursor-readahead.pgc"
+
+
+	id = maxid;
+	id1[0] = -1;
+	while (sqlda)
+	{
+		sqlda_t	*tmp;
+
+		id1[0] = *(int *)sqlda->sqlvar[0].sqldata;
+
+		if (id != id1[0])
+		{
+			printf("Reading readahead cursors backward into sqlda chain: ERROR. id = %d id1 = %d\n", id, id1[0]);
+			break;
+		}
+
+		tmp = sqlda->desc_next;
+		free(sqlda);
+		sqlda = tmp;
+
+		id--;
+	}
+
+	if (id == 0)
+		printf("Reading all records from readahead cursor backwards into sqlda chain: SUCCESS\n");
+	else
+		printf("Reading all records from readahead cursors backwards into sqlda chain: FAILED, id %d, id1 %d\n", id, id1[0]);
+
+	{ ECPGclose(__LINE__, 0, 1, NULL, 0, "xcur", ECPGst_normal, "close xcur", ECPGt_EOIT, ECPGt_EORT);
+#line 229 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 229 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 229 "cursor-readahead.pgc"
+
+
+err:
+
+	{ ECPGtrans(__LINE__, NULL, "commit");
+#line 233 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 233 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 233 "cursor-readahead.pgc"
+
+
+	{ ECPGtrans(__LINE__, NULL, "begin");
+#line 235 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 235 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 235 "cursor-readahead.pgc"
+
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "drop table ra_test", ECPGt_EOIT, ECPGt_EORT);
+#line 237 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 237 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 237 "cursor-readahead.pgc"
+
+
+	{ ECPGtrans(__LINE__, NULL, "commit");
+#line 239 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 239 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 239 "cursor-readahead.pgc"
+
+
+	{ ECPGdisconnect(__LINE__, "ALL");
+#line 241 "cursor-readahead.pgc"
+
+if (sqlca.sqlwarn[0] == 'W') sqlprint();
+#line 241 "cursor-readahead.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 241 "cursor-readahead.pgc"
+
+
+	return 0;
+}
diff --git a/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.stderr b/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.stderr
new file mode 100644
index 0000000..e69de29
diff --git a/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.stdout b/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.stdout
new file mode 100644
index 0000000..5e70dd7
--- /dev/null
+++ b/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.stdout
@@ -0,0 +1,11 @@
+Reading readahead and non-readahead cursors simultaneously forward by 1 record: SUCCESS
+After last record in cursor 'xx' (value 513), fetching backwards.
+After last record in cursor 'xcur' (value 513), fetching backwards.
+Reading readahead and non-readahead cursors simultaneously backwards by 1 record(s): SUCCESS
+Reading readahead and non-readahead cursors simultaneously forward by 5 record: SUCCESS
+After last record in cursor 'xx' (value 513), fetching backwards.
+After last record in cursor 'xcur' (value 513), fetching backwards.
+Reading readahead and non-readahead cursors simultaneously backwards by 5 record(s): SUCCESS
+Reading all records from a readahead cursor forward into sqlda chain: SUCCESS
+After last record in cursor 'xcur' (value 513), fetching backwards.
+Reading all records from readahead cursor backwards into sqlda chain: SUCCESS
diff --git a/src/interfaces/ecpg/test/preproc/Makefile b/src/interfaces/ecpg/test/preproc/Makefile
index 3bcb63a..dfe4166 100644
--- a/src/interfaces/ecpg/test/preproc/Makefile
+++ b/src/interfaces/ecpg/test/preproc/Makefile
@@ -8,6 +8,7 @@ TESTS = array_of_struct array_of_struct.c \
 	autoprep autoprep.c \
 	comment comment.c \
 	cursor cursor.c \
+	cursor-readahead cursor-readahead.c \
 	define define.c \
 	init init.c \
 	strings strings.c \
diff --git a/src/interfaces/ecpg/test/preproc/cursor-readahead.pgc b/src/interfaces/ecpg/test/preproc/cursor-readahead.pgc
new file mode 100644
index 0000000..076f9b3
--- /dev/null
+++ b/src/interfaces/ecpg/test/preproc/cursor-readahead.pgc
@@ -0,0 +1,244 @@
+#include <stdio.h>
+#include <malloc.h>
+
+/* test automatic prepare for all statements */
+EXEC SQL INCLUDE ../regression;
+
+EXEC SQL INCLUDE sqlda.h;
+
+EXEC SQL WHENEVER SQLERROR SQLPRINT;
+EXEC SQL WHENEVER SQLWARNING SQLPRINT;
+
+#define MAXID	(513)
+
+int main(void)
+{
+	int	counts[2] = { 1, 5 };
+	EXEC SQL BEGIN DECLARE SECTION;
+	char	*curname;
+	int	maxid;
+	int	i, j, count, rows;
+	int	id, id1[5], id2[5];
+	EXEC SQL END DECLARE SECTION;
+	sqlda_t	*sqlda;
+
+	/*
+	 * Intentionally don't create a 2MB stderr file for this test.
+	 * Enable it manually if you're interested in it.
+	 */
+#if 0
+	ECPGdebug(1, stderr);
+#endif
+
+	EXEC SQL CONNECT TO REGRESSDB1;
+
+	EXEC SQL BEGIN;
+
+	maxid = MAXID;
+
+	EXEC SQL CREATE TABLE ra_test (id INTEGER PRIMARY KEY);
+	EXEC SQL INSERT INTO ra_test SELECT i.i FROM generate_series(1, :maxid) as i;
+
+	EXEC SQL COMMIT;
+
+	EXEC SQL BEGIN;
+
+	curname = "xx";
+	EXEC SQL DECLARE :curname SCROLL CURSOR FOR SELECT * FROM ra_test;
+	EXEC SQL DECLARE xcur SCROLL READAHEAD CURSOR FOR SELECT * FROM ra_test;
+
+	EXEC SQL WHENEVER NOT FOUND DO BREAK;
+
+	for (i = 0; i < 2; i++)
+	{
+		count = counts[i];
+
+		EXEC SQL OPEN :curname;
+		EXEC SQL OPEN xcur;
+
+		id = 0;
+		while (1)
+		{
+			for (j = 0; j < count; j++)
+			{
+				id1[j] = -1;
+				id2[j] = -1;
+			}
+
+			EXEC SQL FETCH :count FROM :curname INTO :id1;
+			EXEC SQL FETCH :count FROM xcur INTO :id2;
+
+			rows = sqlca.sqlerrd[2];
+
+			for (j = 0; j < rows; j++)
+			{
+				id++;
+
+				if (id != id1[j] || id != id2[j] || id1[j] != id2[j])
+				{
+					printf("Reading two cursors forward: ERROR. id = %d id1 = %d id2 = %d\n", id, id1[j], id2[j]);
+					break;
+				}
+			}
+			if (j != rows)
+				break;
+		}
+
+		if (sqlca.sqlwarn[0] == 'W') sqlprint();
+		if (sqlca.sqlcode < 0) sqlprint();
+
+		if (id == maxid)
+			printf("Reading readahead and non-readahead cursors simultaneously forward by %d record: SUCCESS\n", count);
+		else
+			printf("Reading readahead and non-readahead cursors simultaneously forward by %d record: FAILED at %d, id1 %d id2 %d\n", count, id, id1[j], id2[j]);
+
+		EXEC SQL CLOSE :curname;
+		EXEC SQL CLOSE xcur;
+
+		EXEC SQL WHENEVER NOT FOUND CONTINUE;
+
+		EXEC SQL OPEN :curname;
+		EXEC SQL OPEN xcur;
+		EXEC SQL FETCH LAST FROM :curname INTO :id1;
+		EXEC SQL FETCH FROM :curname INTO :id1;
+		if (sqlca.sqlcode == ECPG_NOT_FOUND)
+			printf("After last record in cursor '%s' (value %d), fetching backwards.\n", curname, id1[0]);
+		else
+			goto err;
+		EXEC SQL FETCH LAST FROM xcur INTO :id2;
+		EXEC SQL FETCH FROM xcur INTO :id2;
+		if (sqlca.sqlcode == ECPG_NOT_FOUND)
+			printf("After last record in cursor 'xcur' (value %d), fetching backwards.\n", id2[0]);
+		else
+			goto err;
+
+		EXEC SQL WHENEVER NOT FOUND DO BREAK;
+
+		id = maxid;
+		while (1)
+		{
+			for (j = 0; j < count; j++)
+			{
+				id1[j] = -1;
+				id2[j] = -1;
+			}
+
+			EXEC SQL FETCH BACKWARD :count FROM :curname INTO :id1;
+			EXEC SQL FETCH BACKWARD :count FROM xcur INTO :id2;
+
+			rows = sqlca.sqlerrd[2];
+
+			for (j = 0; j < rows; j++)
+			{
+				if (id != id1[j] || id != id2[j] || id1[j] != id2[j])
+				{
+					printf("Reading two cursors backward: ERROR. id = %d id1 = %d id2 = %d\n", id, id1[j], id2[j]);
+					break;
+				}
+				id--;
+			}
+			if (j != rows)
+				break;
+		}
+
+		if (id == 0)
+			printf("Reading readahead and non-readahead cursors simultaneously backwards by %d record(s): SUCCESS\n", count);
+		else
+			printf("Reading readahead and non-readahead cursors simultaneously backwards by %d record(s): FAILED at %d, id1 %d id2 %d\n", count, id, id1[j], id2[j]);
+
+		EXEC SQL CLOSE :curname;
+		EXEC SQL CLOSE xcur;
+
+		EXEC SQL WHENEVER NOT FOUND CONTINUE;
+
+	}
+
+	sqlda = NULL;
+	EXEC SQL OPEN xcur;
+	EXEC SQL FETCH ALL xcur INTO DESCRIPTOR sqlda;
+
+	EXEC SQL WHENEVER NOT FOUND DO BREAK;
+
+	id = 0;
+	id1[0] = -1;
+	while (sqlda)
+	{
+		sqlda_t *tmp;
+
+		id++;
+		id1[0] = *(int *)sqlda->sqlvar[0].sqldata;
+
+		if (id != id1[0])
+		{
+			printf("Reading readahead cursors forward into sqlda chain: ERROR. id = %d id1 = %d\n", id, id1[0]);
+			break;
+		}
+		tmp = sqlda->desc_next;
+		free(sqlda);
+		sqlda = tmp;
+	}
+
+	if (id == maxid)
+		printf("Reading all records from a readahead cursor forward into sqlda chain: SUCCESS\n");
+	else
+		printf("Reading all records from a readahead cursor forward into sqlda chain: FAILED. id %d, id1 %d\n", id, id1[0]);
+
+	EXEC SQL CLOSE xcur;
+
+	EXEC SQL WHENEVER NOT FOUND CONTINUE;
+
+	sqlda = NULL;
+	EXEC SQL OPEN xcur;
+	EXEC SQL FETCH LAST FROM xcur INTO :id1;
+	EXEC SQL FETCH FROM xcur INTO :id1;
+	if (sqlca.sqlcode == ECPG_NOT_FOUND)
+		printf("After last record in cursor 'xcur' (value %d), fetching backwards.\n", id1[0]);
+	else
+		goto err;
+
+	EXEC SQL FETCH BACKWARD ALL xcur INTO DESCRIPTOR sqlda;
+
+	EXEC SQL WHENEVER NOT FOUND DO BREAK;
+
+	id = maxid;
+	id1[0] = -1;
+	while (sqlda)
+	{
+		sqlda_t	*tmp;
+
+		id1[0] = *(int *)sqlda->sqlvar[0].sqldata;
+
+		if (id != id1[0])
+		{
+			printf("Reading readahead cursors backward into sqlda chain: ERROR. id = %d id1 = %d\n", id, id1[0]);
+			break;
+		}
+
+		tmp = sqlda->desc_next;
+		free(sqlda);
+		sqlda = tmp;
+
+		id--;
+	}
+
+	if (id == 0)
+		printf("Reading all records from readahead cursor backwards into sqlda chain: SUCCESS\n");
+	else
+		printf("Reading all records from readahead cursors backwards into sqlda chain: FAILED, id %d, id1 %d\n", id, id1[0]);
+
+	EXEC SQL CLOSE xcur;
+
+err:
+
+	EXEC SQL COMMIT;
+
+	EXEC SQL BEGIN;
+
+	EXEC SQL DROP TABLE ra_test;
+
+	EXEC SQL COMMIT;
+
+	EXEC SQL DISCONNECT ALL;
+
+	return 0;
+}

view thread (69+ messages)  latest in thread

Message-ID: <4EC41434.7010603@cybertec.at>
Permalink:  ../4EC41434.7010603@cybertec.at/
Also on:    postgresql.org/message-id/4EC41434.7010603@cybertec.at

 ·  · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-hackers@postgresql.org
  Cc: zb@cybertec.at, meskes@postgresql.org, robertmhaas@gmail.com, heikki.linnakangas@enterprisedb.com, bruce@momjian.us
  Subject: Re: ECPG FETCH readahead
  In-Reply-To: <4EC41434.7010603@cybertec.at>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox