Received: from makus.postgresql.org (makus.postgresql.org [98.129.198.125]) by mail.postgresql.org (Postfix) with ESMTP id 02EFF275B9; Thu, 29 Dec 2011 05:46:49 -0400 (AST) Received: from mail.cybertec.at ([87.118.110.48]) by makus.postgresql.org with esmtp (Exim 4.72) (envelope-from ) id 1RgCZK-00080c-RM; Thu, 29 Dec 2011 09:46:50 +0000 Received: from localhost.localdomain (host-87-242-63-140.prtelecom.hu [87.242.63.140]) by mail.cybertec.at (Postfix) with ESMTPSA id 697E15FC282; Thu, 29 Dec 2011 10:46:05 +0100 (CET) Message-ID: <4EFC36EF.1060308@cybertec.at> Date: Thu, 29 Dec 2011 10:46:23 +0100 From: Boszormenyi Zoltan User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0) Gecko/20111222 Thunderbird/9.0 MIME-Version: 1.0 To: Michael Meskes , PG Hackers CC: Robert Haas , Heikki Linnakangas , Bruce Momjian Subject: Re: ECPG FETCH readahead References: <201006232042.o5NKgb503695@momjian.us> <4C2308FD.1090300@cybertec.at> <4C231F9E.6000102@enterprisedb.com> <20100624121922.GD24137@feivel.credativ.lan> <4CB6D3C8.3020709@cybertec.at> <4EC41434.7010603@cybertec.at> In-Reply-To: <4EC41434.7010603@cybertec.at> Content-Type: multipart/mixed; boundary="------------070802090507050107040507" X-Pg-Spam-Score: -3.2 (---) X-Archive-Number: 201112/1294 X-Sequence-Number: 200546 This is a multi-part message in MIME format. --------------070802090507050107040507 Content-Type: multipart/alternative; boundary="------------040604080606000809050208" --------------040604080606000809050208 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Hi, 2011-11-16 20:51 keltez=E9ssel, Boszormenyi Zoltan =EDrta: > 2010-10-14 11:56 keltez=E9ssel, Boszormenyi Zoltan =EDrta: >> Hi, >> >> Robert Haas =EDrta: >>> On Thu, Jun 24, 2010 at 8:19 AM, Michael Meskes wrote: >>> =20 >>>> On Thu, Jun 24, 2010 at 12:04:30PM +0300, Heikki Linnakangas wrote: >>>> =20 >>>>> 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. >>>>> =20 >>>> 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 restrictio= n. I still >>>> do not really see that this feature gives us an advantage in native = mode >>>> though. >>>> =20 >>> So, who has the next action on this patch? Does Zoltan need to revis= e >>> it, or does Michael need to review it, or where are we? >>> =20 >> 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 empt= y > 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 environm= ent > 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 manda= tory. > > The regression test creates a table with 513 rows, so it spans 2 full a= nd > 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 o= f > 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=E1n B=F6sz=F6rm=E9nyi > > > --=20 ---------------------------------- Zolt=E1n B=F6sz=F6rm=E9nyi Cybertec Sch=F6nig & Sch=F6nig GmbH Gr=F6hrm=FChlgasse 26 A-2700 Wiener Neustadt, Austria Web: http://www.postgresql-support.de http://www.postgresql.at/ --------------040604080606000809050208 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit 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/
--------------040604080606000809050208-- --------------070802090507050107040507 Content-Type: text/x-patch; name="ecpg-cursor-readahead-9.2-git-v5.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="ecpg-cursor-readahead-9.2-git-v5.patch" 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. + + ECPG may use cursor readahead to improve performance of programs + that use single-row FETCH statements. Option -r fetch_readahead + option for ECPG modifies the default for all cursors from NO READAHEAD + to READAHEAD. Explicit READAHEAD or + NO READAHEAD 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 ECPGFETCHSZ environment variable to a different value. + + + + Scrolling behaviour differs from the documented one at + when readahead is used for a cursor. ECPG treats cursors as NO SCROLL + when neither SCROLL nor NO SCROLL are specified. + When backward fetching or positioning is attempted on a NO SCROLL READAHEAD + cursor, error code 55000 (Object not in prerequisite state) is returned to the application. + + + + For cursors used in UPDATE or DELETE + with the WHERE CURRENT OF clause, cursor readahead must be + turned off, otherwise ecpg 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. + + The ECPG DECLARE command does not actually @@ -5289,6 +5315,17 @@ while (1) + -231 (ECPG_INVALID_CURSOR) + + + 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). + + + + + -239 (ECPG_INFORMIX_DUPLICATE_KEY) @@ -6583,8 +6620,8 @@ EXEC SQL DEALLOCATE DESCRIPTOR mydesc; -DECLARE cursor_name [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ] CURSOR [ { WITH | WITHOUT } HOLD ] FOR prepared_name -DECLARE cursor_name [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ] CURSOR [ { WITH | WITHOUT } HOLD ] FOR query +DECLARE cursor_name [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ] [ [ NO ] READAHEAD ] CURSOR [ { WITH | WITHOUT } HOLD ] FOR prepared_name +DECLARE cursor_name [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ] [ [ NO ] READAHEAD ] CURSOR [ { WITH | WITHOUT } HOLD ] FOR query @@ -6639,11 +6676,36 @@ DECLARE cursor_name [ BINARY ] [ IN + + + + Cursor options - For the meaning of the cursor options, - see . + For the meaning of other cursor options, see . + + + + READAHEAD + NO READAHEAD + + + READAHEAD makes the ECPG preprocessor and runtime library + use a client-side cursor accounting and data readahead during + FETCH. This improves performance for programs that use + single-row FETCH statements. + + + + NO READAHEAD disables data readahead in case + -r fetch_readahead is used for compiling the file. + + + + + + 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 + + + + + Turn on cursor readahead by default. + + + 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 + +#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 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("")); + 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("")); + 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("")); + 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("")); - 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 opt_output %type opt_pointer %type opt_port +%type opt_readahead %type opt_reference %type opt_scale %type 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 +#include +#include +/* End of automatic include section */ +#define ECPGdebug(X,Y) ECPGdebug((X)+100,(Y)) + +#line 1 "cursor-readahead.pgc" +#include +#include + +/* 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 +#include + +/* 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; +} --------------070802090507050107040507--