ECPG FETCH readahead
Hi,
we improved ECPG quite a lot in 9.0 because we worked and
still working with an Informix to PostgreSQL migration project.
We came across a pretty big performance problem that can be seen in
every "naive" application that uses only FETCH 1, FETCH RELATIVE
or FETCH ABSOLUTE. These are almost the only FETCH variations
usable in Informix, i.e. it doesn't have the grammar for fetching N rows
at once. Instead, the Client SDK libraries do caching themselves
behind the scenes to reduce network turnaround time.
This is what we implemented for ECPG, so by default it fetches 256 rows
at once if possible and serves the application from memory. The number
of cached rows can be changed using the ECPGFETCHSZ environment
variable. The cursor readahead is activated by "ecpg -r fetch_readahead".
The implementation splits ECPGdo() and ecpg_execute() in ecpglib/execute.c
so the different parts are callable from the newly introduced cursor.c code.
Three new API calls are introduced: ECPGopen(), ECPGfetch() and
ECPGclose(). Obviously, OPEN and CLOSE use ECPGopen() and
ECPGclose(), respectively. They build and tear down the cache structures
besides calling the main ECPGdo() behind the scenes.
ECPGopen() also discovers the total number of records in the recordset,
so the previous ECPG "deficiency" (backend limitation) that sqlca.sqlerrd[2]
didn't report the (possibly estimated) number of rows in the resultset
is now
overcome. This slows down OPEN for cursors serving larger datasets
but it makes possible to position the readahead window using MOVE
ABSOLUTE no matter what FORWARD/BACKWARD/ABSOLUTE/RELATIVE
variants are used by the application. And the caching is more than
overweighs
the slowdown in OPEN it seems.
ECPGfetch() is the more interesting one, this handles FETCH and MOVE
statements and follows the absolute position of the cursor in the
client, too.
In Informix, the DECLARE statement is used for creating a cursor descriptor,
it can be OPENed/CLOSEd several times and the "FREE curname" statement
tears down the cursor descriptor. In our implementation, OPEN and CLOSE
sets up and tears down the caching structure, The DECLARE statement
didn't lose its declarative nature and the FREE statement is still only
usable
only for prepared statements. I chose this path because this way this
feature
can be used in native mode as well. It is usable even if the application
itself
uses FETCH N. The readahead window can be set externally to the
application to squeeze out more performance in batch programs.
The patch size is over 2MB because I introduced a new regression test
called fetch2.pgc that does a lot of work on a recordset having 400 rows.
It browses the recordset back and forth with:
- FETCH FORWARD 1/FETCH BACKWARD 1
- FETCH FORWARD 5/FETCH BACKWARD 5
- FETCH ABSOLUTE +N/FETCH ABSOLUTE -N
- FETCH FORWARD 3+MOVE FORWARD 7, also backwards
- FETCH RELATIVE +2/FETCH RELATIVE -2
This test is compiled both with and without "-r fetch_readahead", so
I was able to verify that the two runs produce the same output.
Also, fetch.pgc, dyntest.pgc and sqlda.pgc are also compiled with and
without "-r fetch_readahead", for verifying that both SQL and SQLDA
descriptors are working the same way as before. E.g. PGresult for
SQL descriptors are not simply assigned anymore, they are copied
using PQcopyResult() without tuples and a bunch of PQsetvalue() calls
to copy only the proper rows from the cache or all rows if no cache.
The split parts of ecpg_execute() are intentionally kept the original
wording (especially the "ecpg_execute" function name) in ecpg_log()
messages to eliminate any impact on other regression tests. If this is
not desired, a patch for this can come later.
Because of the patch size, the compressed version is attached.
Comments?
Best regards,
Zolt�n B�sz�rm�nyi
--
Bible has answers for everything. Proof:
"But let your communication be, Yea, yea; Nay, nay: for whatsoever is more
than these cometh of evil." (Matthew 5:37) - basics of digital technology.
"May your kingdom come" - superficial description of plate tectonics
----------------------------------
Zolt�n B�sz�rm�nyi
Cybertec Sch�nig & Sch�nig GmbH
http://www.postgresql.at/
Attachments:
Boszormenyi Zoltan wrote:
Hi,
we improved ECPG quite a lot in 9.0 because we worked and
still working with an Informix to PostgreSQL migration project.We came across a pretty big performance problem that can be seen in
every "naive" application that uses only FETCH 1, FETCH RELATIVE
or FETCH ABSOLUTE. These are almost the only FETCH variations
usable in Informix, i.e. it doesn't have the grammar for fetching N rows
at once. Instead, the Client SDK libraries do caching themselves
behind the scenes to reduce network turnaround time.
I assume our ecpg version supports >1 fetch values, even in Informix
mode. Does it make sense to add lots of code to our ecpg then?
--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com
+ None of us is going to be here forever. +
Hi,
2010-06-23 22:42 keltez�ssel, Bruce Momjian �rta:
Boszormenyi Zoltan wrote:
Hi,
we improved ECPG quite a lot in 9.0 because we worked and
still working with an Informix to PostgreSQL migration project.We came across a pretty big performance problem that can be seen in
every "naive" application that uses only FETCH 1, FETCH RELATIVE
or FETCH ABSOLUTE. These are almost the only FETCH variations
usable in Informix, i.e. it doesn't have the grammar for fetching N rows
at once. Instead, the Client SDK libraries do caching themselves
behind the scenes to reduce network turnaround time.I assume our ecpg version supports>1 fetch values, even in Informix
mode. Does it make sense to add lots of code to our ecpg then?
I think, yes, it does make sense. Because we are talking
about porting a whole lot of COBOL applications.
The ESQL/C or ECPG connector was already written
the Informix quirks in mind, so it fetches only one record
at a time passing it to the application. And similar performance
is expected from ECPG - which excpectation is not fulfilled
currently because libecpg doesn't do the same caching as
ESQL/C does.
And FYI, I haven't added a whole lot of code, most of the
code changes in the patch is execute.c refactoring.
ECPGdo() was split into several functions, the new parts
are still doing the same things.
I can make the test case much smaller, I only needed
to test crossing the readahead window boundary.
This would also make the patch much smaller.
And this readahead is not on by default, it's only activated
by "ecpg -r fetch_readahead".
Best regards,
Zolt�n B�sz�rm�nyi
On 24/06/10 10:27, B�sz�rm�nyi Zolt�n wrote:
And this readahead is not on by default, it's only activated
by "ecpg -r fetch_readahead".
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.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
2010-06-24 11:04 keltez�ssel, Heikki Linnakangas �rta:
On 24/06/10 10:27, B�sz�rm�nyi Zolt�n wrote:
And this readahead is not on by default, it's only activated
by "ecpg -r fetch_readahead".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.
Because in the first step I wanted to minimize the impact
on regression test stderr results. This is what I mentioned
in the initial mail, I stuck to the original wording of ecpg_log()
messages in the split-up parts of the original ECPGdo() and
ecpg_execute() exactly for this reason. The usual policy for
ecpg_log() is to report the function name where it was issued.
I was also thinking about a new feature for pg_regress,
to compare stdout results of two regression tests automatically
so a difference can be reported as an error. It would be good
for automated testing of features in ECPG that can be toggled,
like auto-prepare and fetch readahead. It might come in handy
in other subsystems, too.
Best regards,
Zolt�n B�sz�rm�nyi
On Wed, Jun 23, 2010 at 04:42:37PM -0400, Bruce Momjian wrote:
I assume our ecpg version supports >1 fetch values, even in Informix
mode. Does it make sense to add lots of code to our ecpg then?
Yes, it does. The big question that zoltan and I haven't figured out yet, is
whether it makes sense to add all this even for native ecpg mode.
Michael
--
Michael Meskes
Michael at Fam-Meskes dot De, Michael at Meskes dot (De|Com|Net|Org)
Michael at BorussiaFan dot De, Meskes at (Debian|Postgresql) dot Org
ICQ 179140304, AIM/Yahoo/Skype michaelmeskes, Jabber meskes@jabber.org
VfL Borussia! Força Barça! Go SF 49ers! Use Debian GNU/Linux, PostgreSQL
I think, yes, it does make sense. Because we are talking
about porting a whole lot of COBOL applications.
COBOL???
The ESQL/C or ECPG connector was already written
the Informix quirks in mind, so it fetches only one record
at a time passing it to the application. And similar performance
is expected from ECPG - which excpectation is not fulfilled
currently because libecpg doesn't do the same caching as
ESQL/C does.
Eh, you are talking about a program you wrote for your customer or they wrote
themselves, right? I simply refuse to add this stuff only to fix this situation
for that one customer of yours if it only hits them. Now the thing to discuss
is how common is this situation.
Michael
--
Michael Meskes
Michael at Fam-Meskes dot De, Michael at Meskes dot (De|Com|Net|Org)
Michael at BorussiaFan dot De, Meskes at (Debian|Postgresql) dot Org
ICQ 179140304, AIM/Yahoo/Skype michaelmeskes, Jabber meskes@jabber.org
VfL Borussia! Força Barça! Go SF 49ers! Use Debian GNU/Linux, PostgreSQL
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.
Michael
--
Michael Meskes
Michael at Fam-Meskes dot De, Michael at Meskes dot (De|Com|Net|Org)
Michael at BorussiaFan dot De, Meskes at (Debian|Postgresql) dot Org
ICQ 179140304, AIM/Yahoo/Skype michaelmeskes, Jabber meskes@jabber.org
VfL Borussia! Força Barça! Go SF 49ers! Use Debian GNU/Linux, PostgreSQL
On Jun 24, 2010, at 2:13 PM, Michael Meskes wrote:
I think, yes, it does make sense. Because we are talking
about porting a whole lot of COBOL applications.COBOL???
yes, COBOL :).
it is much more common than people think.
it is not the first COBOL request for PostgreSQL hitting my desk.
in our concrete example we are using a C module written with ECPG which is magically attached to tons of COBOL code ...
The ESQL/C or ECPG connector was already written
the Informix quirks in mind, so it fetches only one record
at a time passing it to the application. And similar performance
is expected from ECPG - which excpectation is not fulfilled
currently because libecpg doesn't do the same caching as
ESQL/C does.Eh, you are talking about a program you wrote for your customer or they wrote
themselves, right? I simply refuse to add this stuff only to fix this situation
for that one customer of yours if it only hits them. Now the thing to discuss
is how common is this situation.Michael
i think that this cursor issue is a pretty common thing for many codes.
people are usually not aware of the fact that network round trips and parsing which are naturally related to "FETCH 1" are a lot more expensive than fetching one row somewhere deep inside the DB engine.
out there there are many applications which fetch data row by row. if an app fetches data row by row in PostgreSQL it will be A LOT slower than in, say, Informix because most commercial database clients will cache data inside a cursor behind the scenes to avoid the problem we try to solve.
currently we are talking about a performance penalty of factor 5 or so. so - it is not a small thing; it is a big difference.
regards,
hans
--
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt, Austria
Web: http://www.postgresql-support.de
2010-06-24 14:13 keltezéssel, Michael Meskes írta:
I think, yes, it does make sense. Because we are talking
about porting a whole lot of COBOL applications.COBOL???
Yes, OpenCOBOL...
The ESQL/C or ECPG connector was already written
the Informix quirks in mind, so it fetches only one record
at a time passing it to the application. And similar performance
is expected from ECPG - which excpectation is not fulfilled
currently because libecpg doesn't do the same caching as
ESQL/C does.Eh, you are talking about a program you wrote for your customer or they wrote
themselves, right? I simply refuse to add this stuff only to fix this situation
for that one customer of yours if it only hits them. Now the thing to discuss
is how common is this situation.
The OpenCOBOL database connector was written by them
but the problem is more generic. There are many "naive"
applications (elsewhere, too) using cursors but fetching
one record at a time perhaps for portability reasons.
This patch provides a big performance boost for those.
Best regards,
Zoltán Böszörményi
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?
Thanks,
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
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.
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/
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/
Attachments:
ecpg-cursor-readahead-9.2-git-v2.patchtext/plain; name=ecpg-cursor-readahead-9.2-git-v2.patchDownload
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 *) ¤t_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;
+}
Hi,
2011-11-16 20:51 keltezéssel, Boszormenyi Zoltan írta:
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.
New patch attached against yesterday's GIT tree. Changes:
- documented the new ECPG_INVALID_CURSOR error code
- consistently free everything in error paths in cursor.c
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/
Attachments:
ecpg-cursor-readahead-9.2-git-v5.patchtext/x-patch; name=ecpg-cursor-readahead-9.2-git-v5.patchDownload
diff --git a/doc/src/sgml/ecpg.sgml b/doc/src/sgml/ecpg.sgml
index 68833ca..55feac2 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
@@ -5289,6 +5315,17 @@ while (1)
</varlistentry>
<varlistentry>
+ <term>-231 (<symbol>ECPG_INVALID_CURSOR</symbol>)</term>
+ <listitem>
+ <para>
+ The cursor you are trying to use with readahead has not been opened yet (SQLSTATE 34000),
+ invalid values were passed to libecpg (SQLSTATE 42P11) hor not in prerequisite state, i.e.
+ FETCH or MOVE BACKWARD was attempted on a non-SCROLL cursor (SQLSTATE 55000).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term>-239 (<symbol>ECPG_INFORMIX_DUPLICATE_KEY</symbol>)</term>
<listitem>
<para>
@@ -6583,8 +6620,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 +6676,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..bcaf913
--- /dev/null
+++ b/src/interfaces/ecpg/ecpglib/cursor.c
@@ -0,0 +1,730 @@
+/*
+ * 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))
+ {
+ va_end(args);
+ 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_free_params(stmt, true, stmt->lineno);
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ 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_do_epilogue(stmt);
+ va_end(args);
+ ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+ return false;
+ }
+ if (n_amount < cur->cache_pos)
+ {
+ ecpg_free_params(stmt, true, stmt->lineno);
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+ return false;
+ }
+ }
+ 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_free_params(stmt, true, stmt->lineno);
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ return false;
+ }
+
+ if (!cur->scrollable && cur->endoftuples)
+ {
+ 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;
+ }
+
+ start_idx = cur->cache_pos - cur->start_pos;
+
+ if (!ecpg_process_output(stmt, start_idx, start_idx + 1, 0, true, false))
+ {
+ ecpg_free_params(stmt, true, stmt->lineno);
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ return false;
+ }
+ }
+ else
+ {
+ 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] = (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_do_epilogue(stmt);
+ va_end(args);
+ ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+ return false;
+ }
+ }
+
+ 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_free_params(stmt, true, stmt->lineno);
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ return false;
+ }
+
+ if (!cur->scrollable && cur->endoftuples)
+ {
+ 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;
+ }
+
+ start_idx = cur->cache_pos - cur->start_pos;
+
+ if (!ecpg_process_output(stmt, start_idx, start_idx + 1, 0, true, false))
+ {
+ ecpg_free_params(stmt, true, stmt->lineno);
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ return false;
+ }
+ }
+ else
+ {
+ 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] = (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_do_epilogue(stmt);
+ va_end(args);
+ ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+ return false;
+ }
+
+ 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;
+
+ 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_free_params(stmt, true, stmt->lineno);
+ 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_free_params(stmt, true, stmt->lineno);
+ 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_free_params(stmt, true, stmt->lineno);
+ 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 db97503..467be57 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 1dde52c..a2a4a9c 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 f468147..cfe5a5e 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 *) ¤t_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 343a793..9577ee7 100644
--- a/src/interfaces/ecpg/ecpglib/sqlda.c
+++ b/src/interfaces/ecpg/ecpglib/sqlda.c
@@ -393,7 +393,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);
@@ -578,7 +578,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 5c5adf7..e80044e 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
{
@@ -24,8 +27,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
@@ -132,7 +141,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;
}
}
@@ -190,6 +202,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
@@ -210,31 +237,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);
@@ -245,19 +318,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);
@@ -268,7 +398,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);
@@ -291,7 +429,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);
@@ -316,7 +454,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;
@@ -343,6 +487,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 94c45c8..6ca3d3e 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 389a527..68ad718 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;
+}
On Thu, Dec 29, 2011 at 10:46:23AM +0100, Boszormenyi Zoltan wrote:
2011-11-16 20:51 keltez?ssel, Boszormenyi Zoltan ?rta:
2010-10-14 11:56 keltez?ssel, Boszormenyi Zoltan ?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.
We yet lack a consensus on whether native ECPG apps should have access to the
feature. My 2c is to make it available, because it's useful syntactic sugar.
If your program independently processes each row of an arbitrary-length result
set, current facilities force you to add an extra outer loop to batch the
FETCHes for every such code site. Applications could define macros to
abstract that pattern, but this seems common-enough to justify bespoke
handling. Besides, minimalists already use libpq directly.
I suggest enabling the feature by default but drastically reducing the default
readahead chunk size from 256 to, say, 5. That still reduces the FETCH round
trip overhead by 80%, but it's small enough not to attract pathological
behavior on a workload where each row is a 10 MiB document. I would not offer
an ecpg-time option to disable the feature per se. Instead, let the user set
the default chunk size at ecpg time. A setting of 1 effectively disables the
feature, though one could later re-enable it with ECPGFETCHSZ.
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.
Avoid the empty file with fprintf(STDERR, "Dummy non-empty error output\n");
- *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.
Likewise, this may as well take a chunk size rather than a yes/no.
The patch adds warnings:
error.c: In function `ecpg_raise':
error.c:281: warning: format not a string literal and no format arguments
error.c:281: warning: format not a string literal and no format arguments
The patch adds few comments and no larger comments explaining its higher-level
ideas. That makes it much harder to review. In this regard it follows the
tradition of the ECPG code, but let's depart from that tradition for new work.
I mention a few cases below where the need for commentary is acute.
I tested full reads of various "SELECT * FROM generate_series(1, $1)" commands
over a 50ms link, and the patch gives a sound performance improvement. With
no readahead, a mere N=100 takes 5s to drain. With readahead enabled, N=10000
takes only 3s. Performance was quite similar to that of implementing my own
batching with "FETCH 256 FROM cur". When I kicked up ECPGFETCHSZ (after
fixing its implementation -- see below) past the result set size, performance
was comparable to that of simply passing the query through psql.
--- a/doc/src/sgml/ecpg.sgml +++ b/doc/src/sgml/ecpg.sgml
@@ -5289,6 +5315,17 @@ while (1)
</varlistentry><varlistentry> + <term>-231 (<symbol>ECPG_INVALID_CURSOR</symbol>)</term> + <listitem> + <para> + The cursor you are trying to use with readahead has not been opened yet (SQLSTATE 34000), + invalid values were passed to libecpg (SQLSTATE 42P11) hor not in prerequisite state, i.e.
Typo.
--- /dev/null +++ b/src/interfaces/ecpg/ecpglib/cursor.c @@ -0,0 +1,730 @@
cursor.c contains various >78-col lines. pgindent has limited ability to
improve those, so please split them.
+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; + }
Any reason not to use find_cursor() here?
+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; + }
Any reason not to use find_cursor() here?
+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 ");
A query like 'SELECT 1 AS "with hold "' fools these lexical tests. Capture
that information in the parser rather than attempting to reconstruct it here.
+ 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;
"endptr" will never be NULL; use "*endptr". As it stands, the code always
ignores ECPGFETCHSZ. An unusable ECPGFETCHSZ should procedure an error, not
silently give no effect. Why a minimum of 4?
+ 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 */)
Why is the commented code there?
+ { + 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; + }
You give this rationale for the above code:
On Thu, Jun 17, 2010 at 02:09:47PM +0200, Boszormenyi Zoltan wrote:
ECPGopen() also discovers the total number of records in the recordset,
so the previous ECPG "deficiency" (backend limitation) that sqlca.sqlerrd[2]
didn't report the (possibly estimated) number of rows in the resultset
is now
overcome. This slows down OPEN for cursors serving larger datasets
but it makes possible to position the readahead window using MOVE
ABSOLUTE no matter what FORWARD/BACKWARD/ABSOLUTE/RELATIVE
variants are used by the application. And the caching is more than
overweighs
the slowdown in OPEN it seems.
From the documentation for Informix and Oracle, those databases do not
populate sqlerrd[2] this way:
http://publib.boulder.ibm.com/infocenter/idshelp/v10/index.jsp?topic=/com.ibm.sqlt.doc/sqltmst189.htm
http://docs.oracle.com/cd/A57673_01/DOC/api/doc/PC_22/ch10.htm#toc139
The performance impact will vary widely depending on the query cost per row
and the fraction of rows the application will actually retrieve. Consider a
complex aggregate returning only a handful of rows. Consider SELECT * on a
1B-row table with the application ceasing reads after 1000 rows. Performance
aside, this will yield double execution of any volatile functions involved.
So, I think we ought to diligently avoid this step. (Failing that, the
documentation must warn about the extra full cursor scan and this feature must
stay disabled by default.)
+ + /* 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; + }
Why does ecpg_cursor_execute() also call ecpg_free_params()? Offhand, it
seems that ECPGfetch() always takes care of that and is the more appropriate
place, seeing it's the one calling ecpg_build_params().
--- 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)
This function could sure use a block comment such as would be customary in
src/backend. Compare the one at heap_update(), for example.
--- 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"));
Every other message works in the line number somehow; this should do the same.
--- a/src/interfaces/ecpg/ecpglib/execute.c +++ b/src/interfaces/ecpg/ecpglib/execute.c
@@ -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)
A block comment would especially help this function, considering the name
tells one so little.
--- 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; };
Please comment the members of this struct like we do in most of src/include.
dollarzero has something to do with dynamic cursor names, right? Does it have
other roles?
--- a/src/interfaces/ecpg/preproc/ecpg.header +++ b/src/interfaces/ecpg/preproc/ecpg.header
@@ -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; +}
Following add_additional_variables(), use strcasecmp() for literal cursor
names and strcmp() for cursor name host variables.
--- 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;
The names of the last two variables don't make clear the difference between
them. I suggest default_fetch_readahead and current_fetch_readahead.
--- /dev/null +++ b/src/interfaces/ecpg/test/preproc/cursor-readahead.pgc @@ -0,0 +1,244 @@ +#include <stdio.h> +#include <malloc.h>
Why <malloc.h>? I only see this calling free(); use <stdlib.h> instead.
Thanks,
nm
Hi,
first, thank you for answering and for the review.
2012-03-02 17:41 keltezéssel, Noah Misch írta:
On Thu, Dec 29, 2011 at 10:46:23AM +0100, Boszormenyi Zoltan wrote:
2011-11-16 20:51 keltez?ssel, Boszormenyi Zoltan ?rta:
2010-10-14 11:56 keltez?ssel, Boszormenyi Zoltan ?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.We yet lack a consensus on whether native ECPG apps should have access to the
feature.
I don't even remember about any opinion on this matter.
So, at this point don't know whether it's lack of interest.
We also have a saying "silence means agreement". :-)
My 2c is to make it available, because it's useful syntactic sugar.
Thanks, we thought the same.
If your program independently processes each row of an arbitrary-length result
set, current facilities force you to add an extra outer loop to batch the
FETCHes for every such code site. Applications could define macros to
abstract that pattern, but this seems common-enough to justify bespoke
handling.
We have similar opinions.
Besides, minimalists already use libpq directly.
Indeed. On the other hand, ECPG provides a safety net with syntax checking
so it's useful for not minimalist types. :-)
I suggest enabling the feature by default but drastically reducing the default
readahead chunk size from 256 to, say, 5.
That still reduces the FETCH round
trip overhead by 80%, but it's small enough not to attract pathological
behavior on a workload where each row is a 10 MiB document.
I see. How about 8? Nice "round" power of 2 value, still small and avoids
87.5% of overhead.
BTW, the default disabled behaviour was to avoid "make check" breakage,
see below.
I would not offer
an ecpg-time option to disable the feature per se. Instead, let the user set
the default chunk size at ecpg time. A setting of 1 effectively disables the
feature, though one could later re-enable it with ECPGFETCHSZ.
This means all code previously going through ECPGdo() would go through
ECPGopen()/ECPGfetch()/ECPGclose(). This is more intrusive and all
regression tests that were only testing certain features would also
test the readahead feature, too.
Also, the test for WHERE CURRENT OF at ecpg time would have to be done
at runtime, possibly making previously working code fail if ECPGFETCHSZ is enabled.
How about still allowing "NO READAHEAD" cursors that compile into plain ECPGdo()?
This way, ECPGFETCHSZ don't interfere with WHERE CURRENT OF. But this would
mean code changes everywhere where WHERE CURRENT OF is used.
Or how about a new feature in the backend, so ECPG can do
UPDATE/DELETE ... WHERE OFFSET N OF cursor
and the offset of computed from the actual cursor position and the position known
by the application? This way an app can do readahead and do work on rows collected
by the cursor with WHERE CURRENT OF which gets converted to WHERE OFFSET OF
behind the scenes.
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.Avoid the empty file with fprintf(STDERR, "Dummy non-empty error output\n");
Fixed.
- *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.Likewise, this may as well take a chunk size rather than a yes/no.
Done.
The patch adds warnings:
error.c: In function `ecpg_raise':
error.c:281: warning: format not a string literal and no format arguments
error.c:281: warning: format not a string literal and no format arguments
Fixed.
The patch adds few comments and no larger comments explaining its higher-level
ideas. That makes it much harder to review. In this regard it follows the
tradition of the ECPG code, but let's depart from that tradition for new work.
I mention a few cases below where the need for commentary is acute.
Understood. Adding comments as I go over that code again.
I tested full reads of various "SELECT * FROM generate_series(1, $1)" commands
over a 50ms link, and the patch gives a sound performance improvement. With
no readahead, a mere N=100 takes 5s to drain. With readahead enabled, N=10000
takes only 3s. Performance was quite similar to that of implementing my own
batching with "FETCH 256 FROM cur". When I kicked up ECPGFETCHSZ (after
fixing its implementation -- see below) past the result set size, performance
was comparable to that of simply passing the query through psql.--- a/doc/src/sgml/ecpg.sgml +++ b/doc/src/sgml/ecpg.sgml @@ -5289,6 +5315,17 @@ while (1) </varlistentry><varlistentry> + <term>-231 (<symbol>ECPG_INVALID_CURSOR</symbol>)</term> + <listitem> + <para> + The cursor you are trying to use with readahead has not been opened yet (SQLSTATE 34000), + invalid values were passed to libecpg (SQLSTATE 42P11) hor not in prerequisite state, i.e.Typo.
Fixed.
--- /dev/null +++ b/src/interfaces/ecpg/ecpglib/cursor.c @@ -0,0 +1,730 @@cursor.c contains various >78-col lines. pgindent has limited ability to
improve those, so please split them.+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; + }Any reason not to use find_cursor() here?
Because both add_cursor() and del_cursor() needs the "prev" pointer.
I now modified find_cursor() and they use it.
+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; + }Any reason not to use find_cursor() here?
+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 ");A query like 'SELECT 1 AS "with hold "' fools these lexical tests.
But SELECT 1 AS "with hold" doesn't go through ECPGopen(), it's run by ECPGdo()
so no breakage there. ecpglib functions are not intended to be called from manually
constructed C code.
Capture
that information in the parser rather than attempting to reconstruct it here.
Okay, this makes sense anyway.
+ 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;"endptr" will never be NULL; use "*endptr". As it stands, the code always
ignores ECPGFETCHSZ.
You're right.
An unusable ECPGFETCHSZ should procedure an error, not
silently give no effect.
Point taken. Which error handling do imagine? abort() or simply returning false
and raise and error in SQLCA?
Why a minimum of 4?
I forgot.
+ 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 */)Why is the commented code there?
Some leftover from testing, it shouldn't be there.
+ { + 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; + }You give this rationale for the above code:
On Thu, Jun 17, 2010 at 02:09:47PM +0200, Boszormenyi Zoltan wrote:
ECPGopen() also discovers the total number of records in the recordset,
so the previous ECPG "deficiency" (backend limitation) that sqlca.sqlerrd[2]
didn't report the (possibly estimated) number of rows in the resultset
is now
overcome. This slows down OPEN for cursors serving larger datasets
but it makes possible to position the readahead window using MOVE
ABSOLUTE no matter what FORWARD/BACKWARD/ABSOLUTE/RELATIVE
variants are used by the application. And the caching is more than
overweighs
the slowdown in OPEN it seems.From the documentation for Informix and Oracle, those databases do not
populate sqlerrd[2] this way:
http://publib.boulder.ibm.com/infocenter/idshelp/v10/index.jsp?topic=/com.ibm.sqlt.doc/sqltmst189.htm
http://docs.oracle.com/cd/A57673_01/DOC/api/doc/PC_22/ch10.htm#toc139
The problem here is that Informix in the field in fact returns the number of rows
in the cursor and the customer we developed this readahead code for relied on this.
Maybe this was eliminated in newer versions of Informix to make it faster.
The performance impact will vary widely depending on the query cost per row
and the fraction of rows the application will actually retrieve. Consider a
complex aggregate returning only a handful of rows.
Indeed.
Consider SELECT * on a
1B-row table with the application ceasing reads after 1000 rows. Performance
aside, this will yield double execution of any volatile functions involved.
So, I think we ought to diligently avoid this step. (Failing that, the
documentation must warn about the extra full cursor scan and this feature must
stay disabled by default.)
OK, how about enabling it for Informix-compat mode only, or only via an
environment variable? I agree it should be documented.
+ + /* 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; + }Why does ecpg_cursor_execute() also call ecpg_free_params()? Offhand, it
seems that ECPGfetch() always takes care of that and is the more appropriate
place, seeing it's the one calling ecpg_build_params().
I will look at it.
--- 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)This function could sure use a block comment such as would be customary in
src/backend. Compare the one at heap_update(), for example.
OK.
--- 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"));Every other message works in the line number somehow; this should do the same.
Fixed.
--- a/src/interfaces/ecpg/ecpglib/execute.c +++ b/src/interfaces/ecpg/ecpglib/execute.c @@ -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)A block comment would especially help this function, considering the name
tells one so little.
OK.
--- 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; };Please comment the members of this struct like we do in most of src/include.
OK.
dollarzero has something to do with dynamic cursor names, right? Does it have
other roles?
Yes, it had other roles. ECPG supports user variables in cases where the
PostgreSQL grammar doesn't. There's this rule:
ECPG: var_valueNumericOnly addon
if ($1[0] == '$')
{
free($1);
$1 = mm_strdup("$0");
}
The "var_value: NumericOnly" case in gram.y can show up in a lot of cases.
This feature was there before the dynamic cursor. You can even use them together
which means more than one $0 placeholders in the statement. E.g.:
FETCH :amount FROM :curname;
gets translated to
FETCH $0 FROM $0;
by ecpg, and both the amount and the cursor name is passed in in user variables.
The value is needed by cursor.c, this is why this "dollarzero" pointer is needed.
--- a/src/interfaces/ecpg/preproc/ecpg.header +++ b/src/interfaces/ecpg/preproc/ecpg.header @@ -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; +}Following add_additional_variables(), use strcasecmp() for literal cursor
names and strcmp() for cursor name host variables.
After modifying the grammar to use numeric values for readahead window size,
this function and the "use_fetch_readahead" variable are not needed anymore.
--- 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;The names of the last two variables don't make clear the difference between
them. I suggest default_fetch_readahead and current_fetch_readahead.
Now fetch_readahead is int, the other one is no more.
--- /dev/null +++ b/src/interfaces/ecpg/test/preproc/cursor-readahead.pgc @@ -0,0 +1,244 @@ +#include <stdio.h> +#include <malloc.h>Why <malloc.h>? I only see this calling free(); use <stdlib.h> instead.
Fixed.
I had to update the code, the previous patch didn't apply cleanly to current GIT.
I will send the new patch some time next week after fixing the "make check"
breakage.
Thanks,
nm
--
----------------------------------
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/
On Fri, Mar 02, 2012 at 11:41:05AM -0500, Noah Misch wrote:
We yet lack a consensus on whether native ECPG apps should have access to the
feature. My 2c is to make it available, because it's useful syntactic sugar.
If your program independently processes each row of an arbitrary-length result
set, current facilities force you to add an extra outer loop to batch the
FETCHes for every such code site. Applications could define macros to
abstract that pattern, but this seems common-enough to justify bespoke
handling. Besides, minimalists already use libpq directly.
Sorry, I don't really understand what you're saying here. The program logic
won't change at all when using this feature or what do I misunderstand?
I suggest enabling the feature by default but drastically reducing the default
readahead chunk size from 256 to, say, 5. That still reduces the FETCH round
trip overhead by 80%, but it's small enough not to attract pathological
behavior on a workload where each row is a 10 MiB document. I would not offer
an ecpg-time option to disable the feature per se. Instead, let the user set
the default chunk size at ecpg time. A setting of 1 effectively disables the
feature, though one could later re-enable it with ECPGFETCHSZ.
Using 1 to effectively disable the feature is fine with me, but I strongly
object any default enabling this feature. It's farily easy to create cases with
pathological behaviour and this features is not standard by any means. I figure
a normal programmer would expect only one row being transfered when fetching
one.
Other than that, thanks for the great review.
Michael
--
Michael Meskes
Michael at Fam-Meskes dot De, Michael at Meskes dot (De|Com|Net|Org)
Michael at BorussiaFan dot De, Meskes at (Debian|Postgresql) dot Org
Jabber: michael.meskes at googlemail dot com
VfL Borussia! Força Barça! Go SF 49ers! Use Debian GNU/Linux, PostgreSQL
2012-03-04 17:16 keltezéssel, Michael Meskes írta:
On Fri, Mar 02, 2012 at 11:41:05AM -0500, Noah Misch wrote:
We yet lack a consensus on whether native ECPG apps should have access to the
feature. My 2c is to make it available, because it's useful syntactic sugar.
If your program independently processes each row of an arbitrary-length result
set, current facilities force you to add an extra outer loop to batch the
FETCHes for every such code site. Applications could define macros to
abstract that pattern, but this seems common-enough to justify bespoke
handling. Besides, minimalists already use libpq directly.Sorry, I don't really understand what you're saying here. The program logic
won't change at all when using this feature or what do I misunderstand?
The program logic shouldn't change at all. He meant that extra coding effort
is needed if you want manual caching. It requires 2 loops instead of 1 if you use
FETCH N (N>1).
I suggest enabling the feature by default but drastically reducing the default
readahead chunk size from 256 to, say, 5. That still reduces the FETCH round
trip overhead by 80%, but it's small enough not to attract pathological
behavior on a workload where each row is a 10 MiB document. I would not offer
an ecpg-time option to disable the feature per se. Instead, let the user set
the default chunk size at ecpg time. A setting of 1 effectively disables the
feature, though one could later re-enable it with ECPGFETCHSZ.Using 1 to effectively disable the feature is fine with me,
Something else would be needed. For DML with WHERE CURRENT OF cursor,
the feature should stay disabled even with the environment variable is set
without adding any decoration to the DECLARE statement. The safe thing
would be to distinguish between uncached (but cachable) and uncachable
cases. A single value cannot work.
but I strongly
object any default enabling this feature. It's farily easy to create cases with
pathological behaviour and this features is not standard by any means. I figure
a normal programmer would expect only one row being transfered when fetching
one.Other than that, thanks for the great review.
Michael
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/
On Sun, Mar 04, 2012 at 05:34:50PM +0100, Boszormenyi Zoltan wrote:
The program logic shouldn't change at all. He meant that extra coding effort
is needed if you want manual caching. It requires 2 loops instead of 1 if you use
FETCH N (N>1).
Ah, thanks for the explanation.
Michael
--
Michael Meskes
Michael at Fam-Meskes dot De, Michael at Meskes dot (De|Com|Net|Org)
Michael at BorussiaFan dot De, Meskes at (Debian|Postgresql) dot Org
Jabber: michael.meskes at googlemail dot com
VfL Borussia! Força Barça! Go SF 49ers! Use Debian GNU/Linux, PostgreSQL
On Sun, Mar 04, 2012 at 05:16:06PM +0100, Michael Meskes wrote:
On Fri, Mar 02, 2012 at 11:41:05AM -0500, Noah Misch wrote:
I suggest enabling the feature by default but drastically reducing the default
readahead chunk size from 256 to, say, 5. That still reduces the FETCH round
trip overhead by 80%, but it's small enough not to attract pathological
behavior on a workload where each row is a 10 MiB document. I would not offer
an ecpg-time option to disable the feature per se. Instead, let the user set
the default chunk size at ecpg time. A setting of 1 effectively disables the
feature, though one could later re-enable it with ECPGFETCHSZ.Using 1 to effectively disable the feature is fine with me, but I strongly
object any default enabling this feature. It's farily easy to create cases with
pathological behaviour and this features is not standard by any means. I figure
a normal programmer would expect only one row being transfered when fetching
one.
On further reflection, I agree with you here. The prospect for queries that
call volatile functions changed my mind; they would exhibit different
functional behavior under readahead. We mustn't silently give affected
programs different semantics.
Thanks,
nm
On Sun, Mar 04, 2012 at 04:33:32PM +0100, Boszormenyi Zoltan wrote:
2012-03-02 17:41 keltez?ssel, Noah Misch ?rta:
On Thu, Dec 29, 2011 at 10:46:23AM +0100, Boszormenyi Zoltan wrote:
I suggest enabling the feature by default but drastically reducing the default
readahead chunk size from 256 to, say, 5.That still reduces the FETCH round
trip overhead by 80%, but it's small enough not to attract pathological
behavior on a workload where each row is a 10 MiB document.I see. How about 8? Nice "round" power of 2 value, still small and avoids
87.5% of overhead.
Having pondered the matter further, I now agree with Michael that the feature
should stay disabled by default. See my response to him for rationale.
Assuming that conclusion holds, we can recommended a higher value to users who
enable the feature at all. Your former proposal of 256 seems fine.
BTW, the default disabled behaviour was to avoid "make check" breakage,
see below.I would not offer
an ecpg-time option to disable the feature per se. Instead, let the user set
the default chunk size at ecpg time. A setting of 1 effectively disables the
feature, though one could later re-enable it with ECPGFETCHSZ.This means all code previously going through ECPGdo() would go through
ECPGopen()/ECPGfetch()/ECPGclose(). This is more intrusive and all
regression tests that were only testing certain features would also
test the readahead feature, too.
It's a good sort of intrusiveness, reducing the likelihood of introducing bugs
basically unrelated to readahead that happen to afflict only ECPGdo() or only
the cursor.c interfaces. Let's indeed not have any preexisting test cases use
readahead per se, but having them use the cursor.c interfaces anyway will
build confidence in the new code. The churn in expected debug output isn't
ideal, but I don't prefer the alternative of segmenting the implementation for
the sake of the test cases.
Also, the test for WHERE CURRENT OF at ecpg time would have to be done
at runtime, possibly making previously working code fail if ECPGFETCHSZ is enabled.
Good point.
How about still allowing "NO READAHEAD" cursors that compile into plain ECPGdo()?
This way, ECPGFETCHSZ don't interfere with WHERE CURRENT OF. But this would
mean code changes everywhere where WHERE CURRENT OF is used.
ECPGFETCHSZ should only affect cursors that make no explicit mention of
READAHEAD. I'm not sure whether that should mean actually routing READHEAD 1
cursors through ECPGdo() or simply making sure that cursor.c achieves the same
outcome; see later for a possible reason to still do the latter.
Or how about a new feature in the backend, so ECPG can do
UPDATE/DELETE ... WHERE OFFSET N OF cursor
and the offset of computed from the actual cursor position and the position known
by the application? This way an app can do readahead and do work on rows collected
by the cursor with WHERE CURRENT OF which gets converted to WHERE OFFSET OF
behind the scenes.
That's a neat idea, but I would expect obstacles threatening our ability to
use it automatically for readahead. You would have to make the cursor a
SCROLL cursor. We'll often pass a negative offset, making the operation fail
if the cursor query used FOR UPDATE. Volatile functions in the query will get
more calls. That's assuming the operation will map internally to something
like MOVE N; UPDATE ... WHERE CURRENT OF; MOVE -N. You might come up with
innovations to mitigate those obstacles, but those innovations would probably
also apply to MOVE/FETCH. In any event, this would constitute a substantive
patch in its own right.
One way out of trouble here is to make WHERE CURRENT OF imply READHEAD
1/READHEAD 0 (incidentally, perhaps those two should be synonyms) on the
affected cursor. If the cursor has some other readahead quantity declared
explicitly, throw an error during preprocessing.
Failing a reasonable resolution, I'm prepared to withdraw my suggestion of
making ECPGFETCHSZ always-usable. It's nice to have, not critical.
+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 ");A query like 'SELECT 1 AS "with hold "' fools these lexical tests.
But SELECT 1 AS "with hold" doesn't go through ECPGopen(), it's run by ECPGdo()
so no breakage there. ecpglib functions are not intended to be called from manually
constructed C code.
I tried something like
EXEC SQL DECLARE cur CURSOR FOR SELECT * FROM generate_series(1 , $1) AS t("with hold ");
It wrongly generated this backend command:
declare cur no scroll cursor with hold for select * from generate_series ( 1 , $1 ) as t ( "with hold " )
An unusable ECPGFETCHSZ should procedure an error, not
silently give no effect.Point taken. Which error handling do imagine? abort() or simply returning false
and raise and error in SQLCA?
The latter.
+ /* + * 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 */)Why is the commented code there?
Some leftover from testing, it shouldn't be there.
+ { + 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; + }You give this rationale for the above code:
On Thu, Jun 17, 2010 at 02:09:47PM +0200, Boszormenyi Zoltan wrote:
ECPGopen() also discovers the total number of records in the recordset,
so the previous ECPG "deficiency" (backend limitation) that sqlca.sqlerrd[2]
didn't report the (possibly estimated) number of rows in the resultset
is now
overcome. This slows down OPEN for cursors serving larger datasets
but it makes possible to position the readahead window using MOVE
ABSOLUTE no matter what FORWARD/BACKWARD/ABSOLUTE/RELATIVE
variants are used by the application. And the caching is more than
overweighs
the slowdown in OPEN it seems.From the documentation for Informix and Oracle, those databases do not
populate sqlerrd[2] this way:
http://publib.boulder.ibm.com/infocenter/idshelp/v10/index.jsp?topic=/com.ibm.sqlt.doc/sqltmst189.htm
http://docs.oracle.com/cd/A57673_01/DOC/api/doc/PC_22/ch10.htm#toc139The problem here is that Informix in the field in fact returns the number of rows
in the cursor and the customer we developed this readahead code for relied on this.
Maybe this was eliminated in newer versions of Informix to make it faster.The performance impact will vary widely depending on the query cost per row
and the fraction of rows the application will actually retrieve. Consider a
complex aggregate returning only a handful of rows.Indeed.
Consider SELECT * on a
1B-row table with the application ceasing reads after 1000 rows. Performance
aside, this will yield double execution of any volatile functions involved.
So, I think we ought to diligently avoid this step. (Failing that, the
documentation must warn about the extra full cursor scan and this feature must
stay disabled by default.)OK, how about enabling it for Informix-compat mode only, or only via an
environment variable? I agree it should be documented.
For a query where backend execution cost dominates the cost of transferring
rows to the client, does Informix take roughly twice the normal time to
execute the query via an ESQL/C cursor? Is that acceptable overhead for every
"ecpg -C" user? (FWIW, I've never used Informix-compat mode.) If not, the
feature deserves its own option.
Whatever the trigger condition, shouldn't this apply independent of whether
readahead is in use for a given cursor? (This could constitute a reason to
use the cursor.c interfaces for every cursor.)
Does some vendor-neutral standard define semantics for sqlerrd, or has it
propagated by imitation?
This reminds me to mention: your documentation should note that the use of
readahead or the option that enables sqlerrd[2] calculation may change the
outcome of queries calling volatile functions. See how the DECLARE
documentation page discusses this hazard for SCROLL/WITH HOLD cursors.
--- 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; };Please comment the members of this struct like we do in most of src/include.
OK.
dollarzero has something to do with dynamic cursor names, right? Does it have
other roles?Yes, it had other roles. ECPG supports user variables in cases where the
PostgreSQL grammar doesn't. There's this rule:ECPG: var_valueNumericOnly addon
if ($1[0] == '$')
{
free($1);
$1 = mm_strdup("$0");
}The "var_value: NumericOnly" case in gram.y can show up in a lot of cases.
This feature was there before the dynamic cursor. You can even use them together
which means more than one $0 placeholders in the statement. E.g.:
FETCH :amount FROM :curname;
gets translated to
FETCH $0 FROM $0;
by ecpg, and both the amount and the cursor name is passed in in user variables.
The value is needed by cursor.c, this is why this "dollarzero" pointer is needed.
Thanks for that explanation; the situation is clearer to me now.
nm
2012-03-05 19:56 keltezéssel, Noah Misch írta:
Having pondered the matter further, I now agree with Michael that the feature
should stay disabled by default. See my response to him for rationale.
Assuming that conclusion holds, we can recommended a higher value to users who
enable the feature at all. Your former proposal of 256 seems fine.
OK.
BTW, the default disabled behaviour was to avoid "make check" breakage,
see below.I would not offer
an ecpg-time option to disable the feature per se. Instead, let the user set
the default chunk size at ecpg time. A setting of 1 effectively disables the
feature, though one could later re-enable it with ECPGFETCHSZ.This means all code previously going through ECPGdo() would go through
ECPGopen()/ECPGfetch()/ECPGclose(). This is more intrusive and all
regression tests that were only testing certain features would also
test the readahead feature, too.It's a good sort of intrusiveness, reducing the likelihood of introducing bugs
basically unrelated to readahead that happen to afflict only ECPGdo() or only
the cursor.c interfaces. Let's indeed not have any preexisting test cases use
readahead per se, but having them use the cursor.c interfaces anyway will
build confidence in the new code. The churn in expected debug output isn't
ideal, but I don't prefer the alternative of segmenting the implementation for
the sake of the test cases.
I see.
Also, the test for WHERE CURRENT OF at ecpg time would have to be done
at runtime, possibly making previously working code fail if ECPGFETCHSZ is enabled.Good point.
How about still allowing "NO READAHEAD" cursors that compile into plain ECPGdo()?
This way, ECPGFETCHSZ don't interfere with WHERE CURRENT OF. But this would
mean code changes everywhere where WHERE CURRENT OF is used.ECPGFETCHSZ should only affect cursors that make no explicit mention of
READAHEAD. I'm not sure whether that should mean actually routing READHEAD 1
cursors through ECPGdo() or simply making sure that cursor.c achieves the same
outcome; see later for a possible reason to still do the latter.Or how about a new feature in the backend, so ECPG can do
UPDATE/DELETE ... WHERE OFFSET N OF cursor
and the offset of computed from the actual cursor position and the position known
by the application? This way an app can do readahead and do work on rows collected
by the cursor with WHERE CURRENT OF which gets converted to WHERE OFFSET OF
behind the scenes.That's a neat idea, but I would expect obstacles threatening our ability to
use it automatically for readahead. You would have to make the cursor a
SCROLL cursor. We'll often pass a negative offset, making the operation fail
if the cursor query used FOR UPDATE. Volatile functions in the query will get
more calls. That's assuming the operation will map internally to something
like MOVE N; UPDATE ... WHERE CURRENT OF; MOVE -N. You might come up with
innovations to mitigate those obstacles, but those innovations would probably
also apply to MOVE/FETCH. In any event, this would constitute a substantive
patch in its own right.
I was thinking along the lines of a Portal keeping the ItemPointerData
for each tuple in the last FETCH statement. The WHERE OFFSET N OF cursor
would treat the offset value relative to the tuple order returned by FETCH.
So, OFFSET 0 OF == CURRENT OF and other values of N are negative.
This way, it doesn't matter if the cursor is SCROLL, NO SCROLL or have
the default behaviour with "SCROLL in some cases". Then ECPGopen()
doesn't have to play games with the DECLARE statement. Only ECPGfetch()
needs to play with MOVE statements, passing different offsets to the backend,
not what the application passed.
One way out of trouble here is to make WHERE CURRENT OF imply READHEAD
1/READHEAD 0 (incidentally, perhaps those two should be synonyms) on the
affected cursor. If the cursor has some other readahead quantity declared
explicitly, throw an error during preprocessing.
I played with this idea a while ago, from a different point of view.
If the ECPG code had the DECLARE mycur, DML ... WHERE CURRENT OF mycur
and OPEN mycur in exactly this order, i.e. WHERE CURRENT OF appears in
a standalone function between DECLARE and the first OPEN for the cursor,
then ECPG disabled readahead automatically for that cursor and for that
cursor only. But this requires effort on the user of ECPG and can be very
fragile. Code cleanup with reordering functions can break previously
working code.
Failing a reasonable resolution, I'm prepared to withdraw my suggestion of
making ECPGFETCHSZ always-usable. It's nice to have, not critical.+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 ");A query like 'SELECT 1 AS "with hold "' fools these lexical tests.
But SELECT 1 AS "with hold" doesn't go through ECPGopen(), it's run by ECPGdo()
so no breakage there. ecpglib functions are not intended to be called from manually
constructed C code.I tried something like
EXEC SQL DECLARE cur CURSOR FOR SELECT * FROM generate_series(1 , $1) AS t("with hold ");
It wrongly generated this backend command:
declare cur no scroll cursor with hold for select * from generate_series ( 1 , $1 ) as t ( "with hold " )
Ah, ok. The grammar test in ecpg is better.
An unusable ECPGFETCHSZ should procedure an error, not
silently give no effect.Point taken. Which error handling do imagine? abort() or simply returning false
and raise and error in SQLCA?The latter.
+ /* + * 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 */)Why is the commented code there?
Some leftover from testing, it shouldn't be there.
Actually, now I remember. It was a wishful thinking on my part
that whenever PG supports returning a number of rows at opening
the cursor, correct or estimated, this code shouldn't be executed,
just accept what the backend has given us.
+ { + 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; + }You give this rationale for the above code:
On Thu, Jun 17, 2010 at 02:09:47PM +0200, Boszormenyi Zoltan wrote:
ECPGopen() also discovers the total number of records in the recordset,
so the previous ECPG "deficiency" (backend limitation) that sqlca.sqlerrd[2]
didn't report the (possibly estimated) number of rows in the resultset
is now
overcome. This slows down OPEN for cursors serving larger datasets
but it makes possible to position the readahead window using MOVE
ABSOLUTE no matter what FORWARD/BACKWARD/ABSOLUTE/RELATIVE
variants are used by the application. And the caching is more than
overweighs
the slowdown in OPEN it seems.From the documentation for Informix and Oracle, those databases do not
populate sqlerrd[2] this way:
http://publib.boulder.ibm.com/infocenter/idshelp/v10/index.jsp?topic=/com.ibm.sqlt.doc/sqltmst189.htm
http://docs.oracle.com/cd/A57673_01/DOC/api/doc/PC_22/ch10.htm#toc139The problem here is that Informix in the field in fact returns the number of rows
in the cursor and the customer we developed this readahead code for relied on this.
Maybe this was eliminated in newer versions of Informix to make it faster.The performance impact will vary widely depending on the query cost per row
and the fraction of rows the application will actually retrieve. Consider a
complex aggregate returning only a handful of rows.Indeed.
Consider SELECT * on a
1B-row table with the application ceasing reads after 1000 rows. Performance
aside, this will yield double execution of any volatile functions involved.
So, I think we ought to diligently avoid this step. (Failing that, the
documentation must warn about the extra full cursor scan and this feature must
stay disabled by default.)OK, how about enabling it for Informix-compat mode only, or only via an
environment variable? I agree it should be documented.For a query where backend execution cost dominates the cost of transferring
rows to the client, does Informix take roughly twice the normal time to
execute the query via an ESQL/C cursor? Is that acceptable overhead for every
"ecpg -C" user? (FWIW, I've never used Informix-compat mode.) If not, the
feature deserves its own option.Whatever the trigger condition, shouldn't this apply independent of whether
readahead is in use for a given cursor?
I guess so.
(This could constitute a reason to
use the cursor.c interfaces for every cursor.)
Indeed.
Does some vendor-neutral standard define semantics for sqlerrd, or has it
propagated by imitation?
No idea.
This reminds me to mention: your documentation should note that the use of
readahead or the option that enables sqlerrd[2] calculation may change the
outcome of queries calling volatile functions. See how the DECLARE
documentation page discusses this hazard for SCROLL/WITH HOLD cursors.
OK.
--- 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; };Please comment the members of this struct like we do in most of src/include.
OK.
dollarzero has something to do with dynamic cursor names, right? Does it have
other roles?Yes, it had other roles. ECPG supports user variables in cases where the
PostgreSQL grammar doesn't. There's this rule:ECPG: var_valueNumericOnly addon
if ($1[0] == '$')
{
free($1);
$1 = mm_strdup("$0");
}The "var_value: NumericOnly" case in gram.y can show up in a lot of cases.
This feature was there before the dynamic cursor. You can even use them together
which means more than one $0 placeholders in the statement. E.g.:
FETCH :amount FROM :curname;
gets translated to
FETCH $0 FROM $0;
by ecpg, and both the amount and the cursor name is passed in in user variables.
The value is needed by cursor.c, this is why this "dollarzero" pointer is needed.Thanks for that explanation; the situation is clearer to me now.
nm
--
----------------------------------
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/
On Tue, Mar 06, 2012 at 07:07:41AM +0100, Boszormenyi Zoltan wrote:
2012-03-05 19:56 keltez?ssel, Noah Misch ?rta:
Or how about a new feature in the backend, so ECPG can do
UPDATE/DELETE ... WHERE OFFSET N OF cursor
and the offset of computed from the actual cursor position and the position known
by the application? This way an app can do readahead and do work on rows collected
by the cursor with WHERE CURRENT OF which gets converted to WHERE OFFSET OF
behind the scenes.That's a neat idea, but I would expect obstacles threatening our ability to
use it automatically for readahead. You would have to make the cursor a
SCROLL cursor. We'll often pass a negative offset, making the operation fail
if the cursor query used FOR UPDATE. Volatile functions in the query will get
more calls. That's assuming the operation will map internally to something
like MOVE N; UPDATE ... WHERE CURRENT OF; MOVE -N. You might come up with
innovations to mitigate those obstacles, but those innovations would probably
also apply to MOVE/FETCH. In any event, this would constitute a substantive
patch in its own right.I was thinking along the lines of a Portal keeping the ItemPointerData
for each tuple in the last FETCH statement. The WHERE OFFSET N OF cursor
would treat the offset value relative to the tuple order returned by FETCH.
So, OFFSET 0 OF == CURRENT OF and other values of N are negative.
This way, it doesn't matter if the cursor is SCROLL, NO SCROLL or have
the default behaviour with "SCROLL in some cases". Then ECPGopen()
doesn't have to play games with the DECLARE statement. Only ECPGfetch()
needs to play with MOVE statements, passing different offsets to the backend,
not what the application passed.
That broad approach sounds promising. The main other consideration that comes
to mind is a plan to limit resource usage for a cursor that reads, say, 1B
rows. However, I think attempting to implement this now will significantly
decrease the chance of getting the core patch features committed now.
One way out of trouble here is to make WHERE CURRENT OF imply READHEAD
1/READHEAD 0 (incidentally, perhaps those two should be synonyms) on the
affected cursor. If the cursor has some other readahead quantity declared
explicitly, throw an error during preprocessing.I played with this idea a while ago, from a different point of view.
If the ECPG code had the DECLARE mycur, DML ... WHERE CURRENT OF mycur
and OPEN mycur in exactly this order, i.e. WHERE CURRENT OF appears in
a standalone function between DECLARE and the first OPEN for the cursor,
then ECPG disabled readahead automatically for that cursor and for that
cursor only. But this requires effort on the user of ECPG and can be very
fragile. Code cleanup with reordering functions can break previously
working code.
Don't the same challenges apply to accurately reporting an error when the user
specifies WHERE CURRENT OF for a readahead cursor?
On Tue, Mar 6, 2012 at 6:06 AM, Noah Misch <noah@leadboat.com> wrote:
On Tue, Mar 06, 2012 at 07:07:41AM +0100, Boszormenyi Zoltan wrote:
2012-03-05 19:56 keltez?ssel, Noah Misch ?rta:
Or how about a new feature in the backend, so ECPG can do
UPDATE/DELETE ... WHERE OFFSET N OF cursor
and the offset of computed from the actual cursor position and the position known
by the application? This way an app can do readahead and do work on rows collected
by the cursor with WHERE CURRENT OF which gets converted to WHERE OFFSET OF
behind the scenes.That's a neat idea, but I would expect obstacles threatening our ability to
use it automatically for readahead. You would have to make the cursor a
SCROLL cursor. We'll often pass a negative offset, making the operation fail
if the cursor query used FOR UPDATE. Volatile functions in the query will get
more calls. That's assuming the operation will map internally to something
like MOVE N; UPDATE ... WHERE CURRENT OF; MOVE -N. You might come up with
innovations to mitigate those obstacles, but those innovations would probably
also apply to MOVE/FETCH. In any event, this would constitute a substantive
patch in its own right.I was thinking along the lines of a Portal keeping the ItemPointerData
for each tuple in the last FETCH statement. The WHERE OFFSET N OF cursor
would treat the offset value relative to the tuple order returned by FETCH.
So, OFFSET 0 OF == CURRENT OF and other values of N are negative.
This way, it doesn't matter if the cursor is SCROLL, NO SCROLL or have
the default behaviour with "SCROLL in some cases". Then ECPGopen()
doesn't have to play games with the DECLARE statement. Only ECPGfetch()
needs to play with MOVE statements, passing different offsets to the backend,
not what the application passed.That broad approach sounds promising. The main other consideration that comes
to mind is a plan to limit resource usage for a cursor that reads, say, 1B
rows. However, I think attempting to implement this now will significantly
decrease the chance of getting the core patch features committed now.One way out of trouble here is to make WHERE CURRENT OF imply READHEAD
1/READHEAD 0 (incidentally, perhaps those two should be synonyms) on the
affected cursor. If the cursor has some other readahead quantity declared
explicitly, throw an error during preprocessing.I played with this idea a while ago, from a different point of view.
If the ECPG code had the DECLARE mycur, DML ... WHERE CURRENT OF mycur
and OPEN mycur in exactly this order, i.e. WHERE CURRENT OF appears in
a standalone function between DECLARE and the first OPEN for the cursor,
then ECPG disabled readahead automatically for that cursor and for that
cursor only. But this requires effort on the user of ECPG and can be very
fragile. Code cleanup with reordering functions can break previously
working code.Don't the same challenges apply to accurately reporting an error when the user
specifies WHERE CURRENT OF for a readahead cursor?
I think we need either an updated version of this patch that's ready
for commit real soon now, or we need to postpone it to 9.3.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
2012-03-15 21:59 keltezéssel, Robert Haas írta:
I think we need either an updated version of this patch that's ready for commit real
soon now, or we need to postpone it to 9.3.
Sorry for the delay, I had been busy with other tasks and I rewrote this code
to better cope with unknown result size, scrollable cursors and negative
cursor positions.
I think all points raised by Noah is addressed: per-cursor readahead window size,
extensive comments, documentation and not enabling result set size discovery.
Also, I noticed this in passing:
static void
free_variable(struct variable * var)
{
struct variable *var_next;
if (var == NULL)
return;
var_next = var->next;
ecpg_free(var);
while (var_next)
{
var = var_next;
var_next = var->next;
ecpg_free(var);
}
}
I rewrote this as below to eliminate manual unrolling of the loop,
they are equivalent:
static void
free_variable(struct variable * var)
{
struct variable *var_next;
while (var)
{
var_next = var->next;
ecpg_free(var);
var = var_next;
}
}
The problem with WHERE CURRENT OF is solved by a little more grammar
and ecpglib code, which effectively does a MOVE ABSOLUTE N before
executing the DML with WHERE CURRENT OF clause. No patching of the
backend. This way, the new ECPG caching code is compatible with older
servers but obviously reduces the efficiency of caching.
Attached are two patches, the first one is the feature patch.
The second patch makes all cursor statements go through the new
caching functions with 1 tuple readahead window size. It only changes
the preprocessed code and stderr logs of the regression tests affected,
not their results.
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/
Attachments:
ecpg-cursor-readahead-all-cursors.patch.gzapplication/x-tar; name=ecpg-cursor-readahead-all-cursors.patch.gzDownload
�z�lO ecpg-cursor-readahead-all-cursors.patch �]s�6�����6M$� �?���d�=O];�����v4E;|�D���f3�� )��H�� �����,��������%8���Aw��k0���!p����a��z��
�m�
O]g�p: �I�;��{0��a��T��o���+�u���
�b��CjO!'&�����)HQ��v��lZ)�=d���a*��iI���_������@�����o� ��������w~��D�*`�������?���g��w��4�q����~�\{`��:��#M��SVE?�������
�6�Q�#{�9�����]:w�Y�&f��Ao\{H�@OK����#�� }��lT9�K���o\gh�9�p��Ue��S�����J 2�f6X��{7r>��z��-������������
��n-5~��;w�:QId��J��x�uH��'{D����|����x��t���%
�h�@���( � �\}��E�?����v����n�k������g���OWW~��x�:�xLA������K��w0�������Y�xa1�0{FO��S���0��A���/�~�{�2Sgx�.����n��������f�;�81<��n�(�7�������w��|��)O1�����t��s3~<s�L����_���@;x�����������Mi�P&t��KJ����9�E��z^�^.u�}�����
aH�S!������%��$����e���8��.z��Z���
����e��E��ip�Fp��hO������c�R�T!]_����� Q
+R+���l�������Vym���(V����^���,��A-V^#b�t
�^,���������o���jRJ�bx�5#���xB�u���hi�.�V^+b�L��Y�n\������N����a���+�be���^0f���{������0�#j�Fy�C-�-5U1���2�%2gm���������h�LM�nL�fR�ot�y-�C0\
�|�����5��MU
z�����aZ����������V�(����tl�y�����U-'_��Z��������xT��E�^�b�4J>����/quG�h�,�C^�b��XS����f�����T��abm���������5�j��sW����n�]������h�p�0���\6A��v^��i=WZl6�9�Qa�~����]�����?��� /^$�\�v�c��3��+�������
Z-]�t�C7_�?NMr���R�D->���@�d���=��8H���@Su�i�|�����/$[L��uM�w#�d��L��[C/�O���9�{?�d���7\-��w���1$����+y��\}����<u�z��nD^����
�R����������%��v�u�y��o;�I�Vt.����n�j��7t���qR�o&��Q��Q�l� ��a��I~�g�t�:}�P�����Th(��d�l�7�^�!W�-�
��[���R�����i�5v���@��$�|�7S�0x���*���&St��n�'�,_�j:��S����f"�WN'�,�F'�F���$�N'��
����C��;�}�����s=�{�����8=���Y�p���)=xNa�����S�l���c��$�����89��e �����0�>O���)���K��8������?\= t8�-:@5��a>��|�b�^v����"M������ ��6�wn� �� j~�a��dt������p�2��:��Oe8{!��s���S���jx��{�W�c�lN|a�$�����8%
Er<�;z7+���'����.�FZ��h��Y���f��?a�K25�te7��7,���J����u���'�J�i4�W���c��� AD��N���7�>p����^������9d��G_ ���qLnZ���A-�V�
u=���y{q�.���0;����l2�V`�('��(pY$��x�p��Q��O��������w ufe'�����]FuN�A��|(�s�������#���'��T�������i�!���5��q(>���T�q�QS�����$���!g��&���#�YF��b����v���A��D�JS��Y.$ViFn��P������&T~ycz�1�����������)B�B�����z�<�T��3$��KW��M�>Qf_y��: z�c�����J�v��6���$D�U�j�H���L>�G�-6Zq���������.�/�}��K��JZ�es�qD���Rh�i��s���2>��Ys���M6��H�k���h
���?����m6N:h�?���]������+���_^��<}ws��W�����������;��2��.����=��g�&X&����do����n���.����&�?}�z�}���]�"���>�@�xj�+D��e��. �m���M���F��+ Z�$M���hW������2Q��P��,�����3,�Mm�KM�+�T~v6V����7�,n���p�$m�������)��4�Z��n�����X�����\sB3,eFJ�-R~�Wd���{������U}�� ��$.'��VE�i�rQW�%W��n��U�������YNlO
|
F7(0eY�,QW�qi�[�*V#�N{��S��kl,��~Qg���x�(���1������_{�wh���1�HE_���������L#�M��.�z���H���O��nn���l{
V�>u��I
���?���P ��������;9u�g�2�C$�*>����}(S������g��G���t2t������� ,^'w8
���p���y�[���K�����c�9�p6��&����-�L��7B-�b���i/�)y7��L��K��ty����N@�
i�>��o��S�W�W:r�� ;�E������T�<�?�9s0���Q@�TC��|��~�Cv����M��$��,�[V��r�\r���u�*w����wgU��������_���o���#KK��x-Am�L���E�72Y[?���V�|�X�)5��^�a��0!R�x��o�4����e)@`��D�X�%��%��%BX��e��CKmdI#K6���.Q]�����K�t]"K�d������K*��)�r����F��P&��^���#m�������"EY���?uU�� �Q~}��E�x��t"Ki��W��D�Yw-�J�B��V�"�zZ�"�
U��������m���Ol�A*�H�jDL�(��:,u�R7�&Y�.�l��.XQ!��p�!��3�C��H�F�}bED�JQ�)��J����>a��>I9����G ��+3$D9�)��=H��+T���S�G�L%a�-�j}
�S�!DnhbC.7�N�F�<)M"?�dY�Hy�'�&��&�O����I,bEo4I�I6h�=���$�&y��D�B��&��z���4����DV��W�I����_qi4�MR��%���sU�l� QHt��"7*��*H��uWA2N�U�,��%��QA5VA�N8���p",��x�}:�k�MdW���L�}9���|S�R#W�v��e>�s���`���L�p~����N��%^����Z_���=��������c[:p������'��*�
�c4{E���m1���a��]��Ub-��s/�C �F����5���]}�F'��`����q�c�|����<��W�9�u��������7d�&�hm��Bq��u�0K�e����8Fs��bh������S��
��P'f���2E�����W�J�#[�������W= ~p?�0_����lW��l�0
O8T:�=ici��d5#G�;��aIZbEo��#�V�h����Y���~���6�1��H��u�M���L<�D��d��z�&��|QFN��� �\���o���R��?�!�fE�������PYJ4��R��_>C�n�������z���_��}�F=�Eg��3��i�EM���� B���b�dh4�����^�5��P����h�Zh����>q�a�2�j�_��[�����*��j�^����� "�H9���&�\�HH�X9�TT~m#rj(r$��r��=aDT����6y`Z����������D2)�J�Y������^�Y��X�714�������4�X�� ���� j'3��l��T ����'[e��+W�K�R��[D��1��[+����q�=�n����+��������z�Y���-�|����2n8�������S�t�����Cf�K1U2�����y�K<��Q�T�g�(��'aZ�y�����Y����\2s����mK!us�������^2:
���c �GJ�����c���a@�DjB M��<t��s�����^HYO+��s���o \���������)*���=�#f/�EG�z���3a,��{p�;5�:;0Fc#��w�8}!ah.��Sq#;����k09����Y������ab�I���zb?Ab<��������
�Y�Q��:DPHE�b�#�k�Q��"�����k��cG:�|.V{�o��0�^���xq{y��~}
�Z4�@�#?b���)bs�y�,� �GX
��,��!�s�����/��Z�U����\c�y�x +P�c[�*�V��8�8��-y��DJ�$�vU��"�}}���LH�"�6 �N0�D������Ad��A����0�C�����O\I(��(?e@&������H��!K&h����b{c�xqy�����v����H�]n�4�%������O@H�ebY���Y�L4��.�`/�M|���TY�h���������F T��n7X����
�PO� ����W �h��?<����>���$�~R��H��d
�j#IK��\�H�B{Y�H�<�h7����D���� wA��
�R��WU����O�z�f������� ;,|ICd�g��O�+�m���`p�r��'�����+:t���W��DXs���jE���nP�#D�TPM���g-�7�!��v�=8��yj�����XOo����H�' %�o*�$%���CgH4RB:�����������A.�����>7z����zBS!6v^��;Q��D��M�D�$�R���FTH���������a����G�*L��KUR����''+$dF,�
I��&����N�',+L����h�=�� H����OD���H ��]�-%$i�L)�6R��R�"�����t����-)���1!�wN�l�D#%$�w]����U2���H�K��T{�R+&�Hn2f���h���Cb���l�:Q!���E�$��)*�FT�XTT��l���7�nD��!*,��I�lDE�E�)[THR-���hDE�EEE�=aQA0�j����
�*T���6i�������d�
I�%SV�������H�',+TbMn.E#+$���N�}���j��GM#��e���j
T
������)cZXs�$mz��1��R���ScS�kE*�����"F��!�
��C����a�Ht ���4��?�aj����E��z�cP�V ��g��� �u���� ����&�w!$���7��|�O'�{�5�P�GJ�P�P�I�"u�{�C|���H����`�����F?��45C��D&4z�I� �;p.� �1<{�FO�EO�e��XO`"��FO<:=a*PW��e4z��"]��M����$��q��FO�SOH�~�+���&>�����D�����j�^Y3����bB���M��/��/M��~��s��~�$�6�����z�I�|r��=CT�x���;p��5)b<�}x��7�\��7<e=|�at��1��q����������^�8��OP��j���#� ��X]� "=M�)��2���f�����&�UH���{*Y� ��B���6���Y��(�K*��v��O�""b���_x�
���O���}? |��~��������~2n!�������a����B���������f��2yH�.#w����V|j�����[�����-,32-[j-��_�_��W���^.�9~�j��D�m�?q���N��[�F�2YT���J�lc*�%/|��|
Z�Q�7� �h{������z�u��6���>�:���7����nn����j������0���e�l���+�9e�"���2hTk|*�J�!�� y� ~�}���W��������"����k�e6L��L���&����,��5�r�
D���9T]F�aZ4�c��a��:�&
�����]�fo�����>W,�����
�Q�N��YRm�9N�+�����/3�%h.���h��`�
A�+��2�-bE����+:��3���oH�:�����n9�S������(���s���oo��V����<�8��&@�1��n9�Omj��s���:��Tc�8�]Y��'���� �>��A .��%
��hv��~]/�}D)����)��,r�&�c^���n��zQ��%�O�� W��/��d�q��T�L��8�%�U�J���c^��\�]�w�],���'��1 ���A�N�{r}��r� ���"��/� rqGX�b�0�H��/�"<#Z�|�g��217�yo�2e��6����1�qG���+:���C��"S��
[X7���(���0��m���~Af�'�S/���DQ��1+B����*���}�}��o7�6��FR��n#�FZ��n�}(qp�>l������&�4<6�)Epv�<��q��Ap7b��h�����zX/����/4L�u��
�<9e�B�x���t\s���{�����������;{��l��#_8{o�=BuN��#9[�����W6g���w��D(���{$�bK��������=n����{@�,7���G�'Js������k*[�#LY
��2���O�D�i%��4���\*>g.����T|���R�i�|:s��.a������t)�n#�F���n�=�\�0f>K!p��.v�r�].����.
�5g����w����OK���w�����\|��?1{�r���w�����\��a�].��1�{o�].>��w�����\�!��c2����+>�?x�xBy I�itN�'�����Tt�N�[�����&��G�Ofai���`�]_�~8}����N�X���/?94�D,@L����|(F����m3��mN��?;����Vd7�$04v'N�����F���S��KK9��,���b�wV��Z:K|�����!�Z�iR��#����P���)@�\�LTT�ET��FeiU�������ROQ)�>%�n�Q1R���{��&�qqm��z�}��� ;�������4Pt��<!�j*2�f$(r~"��dr�C�s.@}uV G��'aM�f�O���[�~~)�O
������#CU��7���UK*p3q�m�T�\\{(�/�!�+�5u�jjl�4�����,Z
:�)�EG���E�8�B�2�*��W�Bd��T�;9�jjo�7"���J����Z���A�A ���2��Og���G���{(�������Q���b���0�0��9z9Gx� ����f��g/�����RR�mnW���LS)2���Vy���P&:�� �"���0�Cf.Xp���.�J=�E�$���U'&CM���F�&$d����Lzi��d���17�d�v�<ka� p�`@/m���5�:�X-����n)U8��`�Bq-t���� �Y0 ��}�[�N;&0���LH���L��h`Ch�.qJe�����&$=Me�q���
!�
�����QS;Y��H��e"4�i������@!&���]�$d������������Y�s��|����Z�����:�����e-���t�����B�_�����i3��h���� c<��4������?n�*����o{C"��}Cr��o����!��EnS���(�����g�NN���p3-����B0��}]O}�D�� 1
�$���4�#�y�f6~�j5��x�~4#��,�%]Me��`d*:�D.:�H�^*== k*uD�(J�w���FJ��e4@�P\�t����q�G �@��>����XWv���vJ�_��:��=H�
�8���T�\8X���~B��T��� �����>��@\oo���3&��Ibo ��J��W/�d:��kLBf��8�ci"��":����E����BZ�#��u@5�W����j���������e�@����8w�/�^��b4xI��a�������|�v[ �!������/����=~ +��X�VRo���Td������gCbe��kS�T�\|���zk�De���I� �8f6�/�9R����O[��
O���{O��R�s����(�u������0�fg�m;�<�S���+dg8�VQ�a�7�%�����I4Rn*�O*o.(������455f~���x�f*^���%VM�M9�K�������T�r������9ON�@�������7����G�'�����QH���QHm��"q�x�S�������~~&WI�>�;�H�F
}���*}j6�vzPb�?l7!B����6����+E��G��o=�R�n����{�����������M6���L��#�C������_��{�]le#����������k��?����Q���>;����J���r.���v�����R�M���4���|I��t�*���j���[ ��\�������A��7�� �7�f����v~��y������_b���K�N�{��7�^z�-���CD|�o/�K$)�'�o�M���@��3��=b<y����z�5�w`J[����wq��4,����>9���&����u.b��K]���R����Y�v�G�dW�zoE���m�Bc������Gt��zoI�CT� �����)�r��S?���zJ��|�*o�6 k���Q�pw��� bH5�Ur�6���g�>����O�\&7�a����������,N|�`k9n*�����;��#�m���9�����t�m�+�h�����M��B�C��=�!� }��0 �CvOH�0��1�
�������u���'�����_�����&���\h�,���'~U~P0x��sy)r�n�=��qr�}������^|���+�������uw;F ���z�\8��C���y����K����7/�n�k��\���������\���AN`��o�M������T<��nh����z����}{��d������j��NR��S?�%����;�R��%��eF�r�-��r~u��"MM�y�w2e�x��:�pz@k>
Y<�����;����W����
���I�i�m��[������my���s3�����d�1���B"�y��+���3�g�U��$%X�4�y�@�}����uRY"LN��D�,%
�99E�B��j9_�E���h�^�x����yf��P!����������b1��+��~�����p����}�Yn��!����s���7�����A�!�HS�:C���d��L0,0�#��8�)�{P1��y�2�����G�8��N��#�Vsr����q�0T8"��G���x��#�����^�2�#RA�I���,=Px�G�;�F���tT�v0�!��J�(��-(�Gu+i����)Z��Q��P���I� #����D�ih�!��h���F`�A�
�����8b���,�(��$��! ���<�w_��I @��y�"��4�hJ��D�nNN���6�$�]�
I��C �C �}�B�P"��P$�����E������+O��)<�Q�x���e��P2LH�d68�����@IO@V�c����%�AY��&(d��"�c���� g�1D 7m�&D 1�l����H�A3����5�A
�f�n���)Z��14��Wbh����c�����Yd����_��f�O)|b�
(\`{"�K8@����q��]�|��=9�am�uk99E�B�8v wJ� B8,��I"A�\�D����$?H�A>4�0�i@W$��}��Ia�e���$o�����:��xh!���$a����A��Aa�BMc9=m�D�4h@�J�q�
F
���d���!ud="|��r��F�1����f�J�r�}o� H�p� !F�p��l�I���l�F�sh�F� ��[Tm�D�<�0s���Q��E"dfY��
I�P�D��!BY3�.������$���I��1q�l��$Ix�Y�T6R#W%5��u��n>��m������C�+�+\13Y�Dh� �����5K����!a~�P���
�H�6L1[�$���DI29"��]zY )I��*����L�i2m�
:=m�D�8��wJh�rh1c���2��J���(�D�=T�=�X���@��I��,��d��,��dp��7J�-�v��T���zt��[�^���5X�$���r%����J��u8VQ��F��F��n�������u"nP��{%���>
uh6�(�mB�1"
�QP`B��jO
T1��|I�fq���oL,��O$������D
i�����i['�����Pa JS������G��)J�$!ShV���x6<v`4���u�./����L�q������c���0An�W�0��C��}�����2&`1h &`j['��a�P�����+E�_�5��W�e��1b�)F�=1�#���
�[\[_����
b��A��� F�RZv��0����N��#
x��D�#�Q�f�q������$:F)�f �F$MT^�$��)���/d��-���*����c{
���:\a�@S�:7�+�H����W�-�nLEW4����4p�+f�!�w���WL�v�7\���w�G(dJFm���5�I�U<xI�z����n�L��E��NO�:7-���Z�Z�Z$������c25��?��)��]�LaM�P�4��2?��'a��Q*l����t!S���|�B���,dJ 3�@���"�B�d���d���=�(��*�Z����LV��u"nP��*J�Og�:1L�E(P0����P`�Q��R��
�(Dci!�`��B&���XB��q?A7�W2Lc��6�6,������N��c x���!I�p�4�:�4���h�h�9j�O�� ���D�~�Od�������4Y�fu��p<��m�7�0�
D� �|���v�������u"n5 x
%j�5/n=%��n ( m��$Y�����!�~���)��
��W-���4Z27"��[6���$�z�P�G#_<4M�m�a0��m�����B !B!J�%���)i���|A��Y���D��q
�p��*��}1A�"��&������m, �f������M{i�0���m����F�#B�qB��d�\�1�����q�����F�)�� ~"�.(�"���F#MZ�Qs%�=a"�$������J���4��A�����u"nZ��%����0��������Di[���$SH�L��(�L���#��Jf� JJB?a�)�xo�
[���o��I28�B&���B&��62I��-d��� ��J#��iD%�2vQ��Y���aj['����4���!Bg ���4��9=}<�
N��F@���I��H��
�N�!��t@,�:.=��1�����t�X2Lc�5l�mX�a7��m����Nc�z��{��\��C�k��z���f}�tu�����r�y*�y*�����[N|�xz�^��o.������\��.�\����h>C����<�����1
=������O"�DQ�}��ap\�E�s�3�$(~��j�������~�����W������������o�)��U���AR�;���K��������p-����/u��k����X��^\|��?^]\�b���������l/�o���W�w�X^^�������_�o�����e�eNN��n/^}����)>���e�vz����W�=:;��W{�gg����zus�����Y�����S����G���x,��X����X������1;{�?X<H<Y�R�M9���M���U�}"w���������[�T��'�On8�����-�\m�Sr�������OC�~-'�N���_�R=����W/�
��wd+�x�w����p5�:Q��n�0�36�i����������')�����?�}����$�R��n�<�����/Jjl�(��^��nG�g���r�],�k ��=_f��/x�S���<
����(����4�zY���1�����y���z)����dxav��8�k=�1�xU�=o^_1���H�`��2������D6R �a�Y�����`23���E�k?Lu�B����wP!�(�:z�*Q��/�(�C�CB}�Ih��������A�*��u�]C�O��'�D�.��N$
P��C�*�@$`>���X*���=B�{U��Oz6p�����{����4�g��]�w]�T�`�j�p=_I���# �
e��r��r��(}�������0�I_G��|�@�g9�A�pPE0H�g�`�@0H-��-���r�o�~�^L���Y.�s�6s5 sz�����3�����:�Lw�#��'�]������v}y�����W�;��'g_K�V��^s3������5�k����;��W�����VW���:31o��VW��b��w�^�L�B 9��$� ����'!%��� �)��\>x�|�m��J�e�Q���]��_���$����[��x�V7B��|'�3�%�r��Y����MlC�������������I����|��^E��������w4�T|7���j�����������3����%�cY�}s����h�=����jZ|T�%���m��|�����l��-S�d��K�=����,8p3,��� �c&yWAN+u7�~<8�V?Y�!r�nR�7��W��k39�+M/r�~��]�o^�$}k�h��I��i���Q�h&����I�I��&B�n��DS&��bj����L
�89U�B�t��+(l?��%�f!zd��}���>!�a*4p�������*h�����\�
F�r�.hy5��<h�
h 4� \�P�C
��!����`I{F�C
���l���H �����e��`��
!aC�|����`)�C
�5 d�VQ,i��u��R���{�Q�N#��H�P�TPC����W �� j��Q,Q��P���AS��1j`�'�C
���N���WPVp%�o�/������C)((�9�QJ��R�`��D�X�R4U�m���P�A� ����7����C����g����?�U�R���������|��D|��������W��h/�_^nD��F��y�������%��jq�������x���������P���+���w�(�?�D������G�N���_�x��w�3�9����mub���I'��/Z�e�c��rm"���������K������������V���,
L���H
�����������
v_�*����5�a�6S���L��*��6�K��:!����i�n�|+SO��5��=,�W,����c���^�x��|�G\���_Y�}�._���_��B�0�.���m�X/7s��w�/������������$
U�y������ ���E&$�:�ef��a3�����<�k�kb#Z�VSE�����pm&��3{~����;����r_V��Q��r�?3����R�c�:��c,'�z7�sq���e���A����u���z�������f�=u���x1RLY0���&}(� ��D��������f�R*���)�����3��x]�[#t�t7��tJ6G�M���pn������l!
{��\L5�B�t��7P���{p4�����\����C=4��lp����M�������t�]����=�]�l��J���}�`�@���R�:�i����4��#��oNezd���S��2E`<