diff --git a/doc/src/sgml/ecpg.sgml b/doc/src/sgml/ecpg.sgml
index 68833ca..69b49e0 100644
--- a/doc/src/sgml/ecpg.sgml
+++ b/doc/src/sgml/ecpg.sgml
@@ -454,6 +454,32 @@ EXEC SQL COMMIT;
details.
+
+ 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
@@ -6583,8 +6609,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 +6665,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..ff6e40d
--- /dev/null
+++ b/src/interfaces/ecpg/ecpglib/cursor.c
@@ -0,0 +1,708 @@
+/*
+ * FETCH readahead support routines
+ */
+
+#define POSTGRES_ECPG_INTERNAL
+#include "postgres_fe.h"
+
+#include
+
+#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))
+ return false;
+
+ if (!ecpg_build_params(stmt))
+ {
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ return false;
+ }
+
+ if (amount[0] == '$')
+ amount1 = stmt->dollarzero[0];
+ else if (amount[0] == '-' || amount[0] == '+')
+ {
+ /*
+ * Handle negative and explicitely positive constants in
+ * FETCH/MOVE ABSOLUTE/RELATIVE const.
+ * E.g. '-2' arrives as '- 2', '+2' arrives as '+ 2'.
+ * strtoll() under Linux stops processing at the space.
+ */
+ if (amount[0] == '-')
+ negate = true;
+ amount1 = amount + 1;
+ while (*amount1 == ' ')
+ amount1++;
+ }
+
+ if (strcmp(amount, "all") == 0)
+ {
+ fetchall = true;
+ if (cur->scrollable)
+ {
+ switch (direction)
+ {
+ case ECPGc_forward:
+ n_amount = cur->n_tuples - cur->cache_pos;
+ break;
+ case ECPGc_backward:
+ n_amount = cur->cache_pos;
+ break;
+ default:
+ ecpg_raise(lineno, ECPG_EMPTY, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, NULL);
+ return false;
+ }
+ }
+ else
+ n_amount = LONG_MAX;
+ }
+ else
+ {
+ char *endptr;
+
+ n_amount = strtoll(amount1, &endptr, 10);
+ if (negate)
+ n_amount = -n_amount;
+ }
+
+ switch (direction)
+ {
+ case ECPGc_absolute:
+ if (cur->scrollable)
+ {
+ if (n_amount < 0)
+ n_amount = 1 + cur->n_tuples + n_amount;
+ else if (n_amount > cur->n_tuples)
+ n_amount = cur->n_tuples + 1;
+ }
+ else
+ {
+ if (n_amount < 0)
+ {
+ ecpg_free_params(stmt, true, stmt->lineno);
+ ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+ break;
+ }
+ if (n_amount < cur->cache_pos)
+ {
+ ecpg_free_params(stmt, true, stmt->lineno);
+ ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+ break;
+ }
+ }
+ cur->cache_pos = n_amount;
+
+ if (!move)
+ {
+ if (cur->cache_pos > 0 && ((cur->scrollable && cur->cache_pos <= cur->n_tuples) || (!cur->scrollable && !cur->endoftuples)))
+ {
+ if (!ecpg_cursor_execute(stmt, cur))
+ {
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ return false;
+ }
+
+ if (!cur->scrollable && cur->endoftuples)
+ {
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
+ return false;
+ }
+
+ start_idx = cur->cache_pos - cur->start_pos;
+
+ if (!ecpg_process_output(stmt, start_idx, start_idx + 1, 0, true, false))
+ {
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ return false;
+ }
+ }
+ else
+ {
+ ecpg_free_params(stmt, true, stmt->lineno);
+ ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
+ }
+ }
+ else
+ {
+ ecpg_free_params(stmt, true, stmt->lineno);
+ }
+ sqlca->sqlerrd[2] = (cur->cache_pos && cur->cache_pos <= cur->n_tuples ? 1 : 0);
+ break;
+
+ case ECPGc_relative:
+relative:
+ if (!cur->scrollable)
+ {
+ if (n_amount < 0)
+ {
+ ecpg_free_params(stmt, true, stmt->lineno);
+ ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+ break;
+ }
+ }
+
+ cur->cache_pos += n_amount;
+ if (cur->cache_pos < 0)
+ cur->cache_pos = 0;
+ else if (cur->cache_pos > cur->n_tuples)
+ cur->cache_pos = cur->n_tuples + 1;
+
+ if (!move)
+ {
+ if (cur->cache_pos > 0 && ((cur->scrollable && cur->cache_pos <= cur->n_tuples) || (!cur->scrollable && !cur->endoftuples)))
+ {
+ if (!ecpg_cursor_execute(stmt, cur))
+ {
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ return false;
+ }
+
+ if (!cur->scrollable && cur->endoftuples)
+ {
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
+ return false;
+ }
+
+ start_idx = cur->cache_pos - cur->start_pos;
+
+ if (!ecpg_process_output(stmt, start_idx, start_idx + 1, 0, true, false))
+ {
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ return false;
+ }
+ }
+ else
+ {
+ ecpg_free_params(stmt, true, stmt->lineno);
+ ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
+ }
+ }
+ else
+ {
+ ecpg_free_params(stmt, true, stmt->lineno);
+ }
+ sqlca->sqlerrd[2] = (cur->cache_pos && cur->cache_pos <= cur->n_tuples ? 1 : 0);
+ break;
+
+ case ECPGc_forward:
+ case ECPGc_backward:
+ if (n_amount == 0)
+ goto relative;
+
+ step = (n_amount > 0 ? 1 : -1);
+ if (direction == ECPGc_backward)
+ step = -step;
+ if (n_amount < 0)
+ n_amount = -n_amount;
+
+ if (!cur->scrollable && step < 0)
+ {
+ ecpg_free_params(stmt, true, stmt->lineno);
+ ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE, NULL);
+ break;
+ }
+
+ if (move)
+ {
+ int64 new_pos = cur->cache_pos + step * n_amount;
+ int64 diff;
+
+ if (new_pos < 1)
+ new_pos = 0;
+ if (new_pos > cur->n_tuples)
+ new_pos = cur->n_tuples + 1;
+
+ diff = new_pos - cur->cache_pos;
+ sqlca->sqlerrd[2] = (diff >= 0 ? diff : -diff);
+ cur->cache_pos = new_pos;
+
+ ecpg_free_params(stmt, true, stmt->lineno);
+ break;
+ }
+
+ for (count = 0; (cur->scrollable && count < n_amount) ||
+ (!cur->scrollable && ((fetchall && !cur->endoftuples) || (!fetchall && count < n_amount))); count++)
+ {
+ cur->cache_pos += step;
+ if (cur->cache_pos < 1)
+ {
+ cur->cache_pos = 0;
+ break;
+ }
+ else
+ {
+ if (cur->scrollable && cur->cache_pos > cur->n_tuples)
+ {
+ cur->cache_pos = cur->n_tuples + 1;
+ break;
+ }
+ else if (!cur->scrollable && cur->endoftuples)
+ {
+ break;
+ }
+ }
+
+ if (!ecpg_cursor_execute(stmt, cur))
+ {
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ return false;
+ }
+
+ if (!cur->scrollable && cur->endoftuples)
+ break;
+
+ start_idx = cur->cache_pos - cur->start_pos;
+
+ if (!ecpg_process_output(stmt, start_idx, start_idx + 1, count, true, (count > 0)))
+ {
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ return false;
+ }
+ }
+
+ if (count == 0)
+ {
+ ecpg_free_params(stmt, true, stmt->lineno);
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
+ return false;
+ }
+
+ sqlca->sqlerrd[2] = count;
+
+ break;
+
+ default:
+ ecpg_free_params(stmt, true, stmt->lineno);
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_INVALID_CURSOR_DEFINITION, (connection_name) ? connection_name : ecpg_gettext(""));
+ return false;
+ }
+
+ ecpg_do_epilogue(stmt);
+ va_end(args);
+ return true;
+}
+
+bool
+ECPGclose(const int lineno, const int compat, const int force_indicator,
+ const char *connection_name, const bool questionmarks,
+ const char *curname, const int st, const char *query, ...)
+{
+ struct connection *con;
+ va_list args;
+ bool ret;
+
+ con = ecpg_get_connection(connection_name);
+
+ if (!find_cursor(con, curname))
+ {
+ ecpg_raise(lineno, ECPG_INVALID_CURSOR, ECPG_SQLSTATE_INVALID_CURSOR_NAME, (connection_name) ? connection_name : ecpg_gettext(""));
+ return false;
+ }
+
+ va_start(args, query);
+ ret = ecpg_do(lineno, compat, force_indicator, connection_name, questionmarks, st, query, args);
+ va_end(args);
+
+ del_cursor(con, curname);
+
+ return ret;
+}
diff --git a/src/interfaces/ecpg/ecpglib/data.c b/src/interfaces/ecpg/ecpglib/data.c
index fc04556..6cbf327 100644
--- a/src/interfaces/ecpg/ecpglib/data.c
+++ b/src/interfaces/ecpg/ecpglib/data.c
@@ -120,7 +120,7 @@ check_special_value(char *ptr, double *retval, char **endptr)
}
bool
-ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
+ecpg_get_data(const PGresult *results, int var_index, int act_tuple, int act_field, int lineno,
enum ECPGttype type, enum ECPGttype ind_type,
char *var, char *ind, long varcharsize, long offset,
long ind_offset, enum ARRAY_TYPE isarray, enum COMPAT_MODE compat, bool force_indicator)
@@ -167,20 +167,20 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
{
case ECPGt_short:
case ECPGt_unsigned_short:
- *((short *) (ind + ind_offset * act_tuple)) = value_for_indicator;
+ *((short *) (ind + ind_offset * var_index)) = value_for_indicator;
break;
case ECPGt_int:
case ECPGt_unsigned_int:
- *((int *) (ind + ind_offset * act_tuple)) = value_for_indicator;
+ *((int *) (ind + ind_offset * var_index)) = value_for_indicator;
break;
case ECPGt_long:
case ECPGt_unsigned_long:
- *((long *) (ind + ind_offset * act_tuple)) = value_for_indicator;
+ *((long *) (ind + ind_offset * var_index)) = value_for_indicator;
break;
#ifdef HAVE_LONG_LONG_INT
case ECPGt_long_long:
case ECPGt_unsigned_long_long:
- *((long long int *) (ind + ind_offset * act_tuple)) = value_for_indicator;
+ *((long long int *) (ind + ind_offset * var_index)) = value_for_indicator;
break;
#endif /* HAVE_LONG_LONG_INT */
case ECPGt_NO_INDICATOR:
@@ -192,7 +192,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
* Informix has an additional way to specify NULLs note
* that this uses special values to denote NULL
*/
- ECPGset_noind_null(type, var + offset * act_tuple);
+ ECPGset_noind_null(type, var + offset * var_index);
}
else
{
@@ -243,10 +243,10 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
if (binary)
{
if (varcharsize == 0 || varcharsize * offset >= size)
- memcpy(var + offset * act_tuple, pval, size);
+ memcpy(var + offset * var_index, pval, size);
else
{
- memcpy(var + offset * act_tuple, pval, varcharsize * offset);
+ memcpy(var + offset * var_index, pval, varcharsize * offset);
if (varcharsize * offset < size)
{
@@ -255,20 +255,20 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
{
case ECPGt_short:
case ECPGt_unsigned_short:
- *((short *) (ind + ind_offset * act_tuple)) = size;
+ *((short *) (ind + ind_offset * var_index)) = size;
break;
case ECPGt_int:
case ECPGt_unsigned_int:
- *((int *) (ind + ind_offset * act_tuple)) = size;
+ *((int *) (ind + ind_offset * var_index)) = size;
break;
case ECPGt_long:
case ECPGt_unsigned_long:
- *((long *) (ind + ind_offset * act_tuple)) = size;
+ *((long *) (ind + ind_offset * var_index)) = size;
break;
#ifdef HAVE_LONG_LONG_INT
case ECPGt_long_long:
case ECPGt_unsigned_long_long:
- *((long long int *) (ind + ind_offset * act_tuple)) = size;
+ *((long long int *) (ind + ind_offset * var_index)) = size;
break;
#endif /* HAVE_LONG_LONG_INT */
default:
@@ -307,13 +307,13 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
switch (type)
{
case ECPGt_short:
- *((short *) (var + offset * act_tuple)) = (short) res;
+ *((short *) (var + offset * var_index)) = (short) res;
break;
case ECPGt_int:
- *((int *) (var + offset * act_tuple)) = (int) res;
+ *((int *) (var + offset * var_index)) = (int) res;
break;
case ECPGt_long:
- *((long *) (var + offset * act_tuple)) = (long) res;
+ *((long *) (var + offset * var_index)) = (long) res;
break;
default:
/* Cannot happen */
@@ -336,13 +336,13 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
switch (type)
{
case ECPGt_unsigned_short:
- *((unsigned short *) (var + offset * act_tuple)) = (unsigned short) ures;
+ *((unsigned short *) (var + offset * var_index)) = (unsigned short) ures;
break;
case ECPGt_unsigned_int:
- *((unsigned int *) (var + offset * act_tuple)) = (unsigned int) ures;
+ *((unsigned int *) (var + offset * var_index)) = (unsigned int) ures;
break;
case ECPGt_unsigned_long:
- *((unsigned long *) (var + offset * act_tuple)) = (unsigned long) ures;
+ *((unsigned long *) (var + offset * var_index)) = (unsigned long) ures;
break;
default:
/* Cannot happen */
@@ -353,7 +353,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
#ifdef HAVE_LONG_LONG_INT
#ifdef HAVE_STRTOLL
case ECPGt_long_long:
- *((long long int *) (var + offset * act_tuple)) = strtoll(pval, &scan_length, 10);
+ *((long long int *) (var + offset * var_index)) = strtoll(pval, &scan_length, 10);
if (garbage_left(isarray, scan_length, compat))
{
ecpg_raise(lineno, ECPG_INT_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
@@ -365,7 +365,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
#endif /* HAVE_STRTOLL */
#ifdef HAVE_STRTOULL
case ECPGt_unsigned_long_long:
- *((unsigned long long int *) (var + offset * act_tuple)) = strtoull(pval, &scan_length, 10);
+ *((unsigned long long int *) (var + offset * var_index)) = strtoull(pval, &scan_length, 10);
if ((isarray && *scan_length != ',' && *scan_length != '}')
|| (!isarray && !(INFORMIX_MODE(compat) && *scan_length == '.') && *scan_length != '\0' && *scan_length != ' ')) /* Garbage left */
{
@@ -400,10 +400,10 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
switch (type)
{
case ECPGt_float:
- *((float *) (var + offset * act_tuple)) = dres;
+ *((float *) (var + offset * var_index)) = dres;
break;
case ECPGt_double:
- *((double *) (var + offset * act_tuple)) = dres;
+ *((double *) (var + offset * var_index)) = dres;
break;
default:
/* Cannot happen */
@@ -415,9 +415,9 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
if (pval[0] == 'f' && pval[1] == '\0')
{
if (offset == sizeof(char))
- *((char *) (var + offset * act_tuple)) = false;
+ *((char *) (var + offset * var_index)) = false;
else if (offset == sizeof(int))
- *((int *) (var + offset * act_tuple)) = false;
+ *((int *) (var + offset * var_index)) = false;
else
ecpg_raise(lineno, ECPG_CONVERT_BOOL,
ECPG_SQLSTATE_DATATYPE_MISMATCH,
@@ -427,16 +427,16 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
else if (pval[0] == 't' && pval[1] == '\0')
{
if (offset == sizeof(char))
- *((char *) (var + offset * act_tuple)) = true;
+ *((char *) (var + offset * var_index)) = true;
else if (offset == sizeof(int))
- *((int *) (var + offset * act_tuple)) = true;
+ *((int *) (var + offset * var_index)) = true;
else
ecpg_raise(lineno, ECPG_CONVERT_BOOL,
ECPG_SQLSTATE_DATATYPE_MISMATCH,
NULL);
break;
}
- else if (pval[0] == '\0' && PQgetisnull(results, act_tuple, act_field))
+ else if (pval[0] == '\0' && PQgetisnull(results, var_index, act_field))
{
/* NULL is valid */
break;
@@ -451,7 +451,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
case ECPGt_unsigned_char:
case ECPGt_string:
{
- char *str = (char *) (var + offset * act_tuple);
+ char *str = (char *) (var + offset * var_index);
if (varcharsize == 0 || varcharsize > size)
{
@@ -479,20 +479,20 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
{
case ECPGt_short:
case ECPGt_unsigned_short:
- *((short *) (ind + ind_offset * act_tuple)) = size;
+ *((short *) (ind + ind_offset * var_index)) = size;
break;
case ECPGt_int:
case ECPGt_unsigned_int:
- *((int *) (ind + ind_offset * act_tuple)) = size;
+ *((int *) (ind + ind_offset * var_index)) = size;
break;
case ECPGt_long:
case ECPGt_unsigned_long:
- *((long *) (ind + ind_offset * act_tuple)) = size;
+ *((long *) (ind + ind_offset * var_index)) = size;
break;
#ifdef HAVE_LONG_LONG_INT
case ECPGt_long_long:
case ECPGt_unsigned_long_long:
- *((long long int *) (ind + ind_offset * act_tuple)) = size;
+ *((long long int *) (ind + ind_offset * var_index)) = size;
break;
#endif /* HAVE_LONG_LONG_INT */
default:
@@ -508,7 +508,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
case ECPGt_varchar:
{
struct ECPGgeneric_varchar *variable =
- (struct ECPGgeneric_varchar *) (var + offset * act_tuple);
+ (struct ECPGgeneric_varchar *) (var + offset * var_index);
variable->len = size;
if (varcharsize == 0)
@@ -524,20 +524,20 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
{
case ECPGt_short:
case ECPGt_unsigned_short:
- *((short *) (ind + offset * act_tuple)) = variable->len;
+ *((short *) (ind + offset * var_index)) = variable->len;
break;
case ECPGt_int:
case ECPGt_unsigned_int:
- *((int *) (ind + offset * act_tuple)) = variable->len;
+ *((int *) (ind + offset * var_index)) = variable->len;
break;
case ECPGt_long:
case ECPGt_unsigned_long:
- *((long *) (ind + offset * act_tuple)) = variable->len;
+ *((long *) (ind + offset * var_index)) = variable->len;
break;
#ifdef HAVE_LONG_LONG_INT
case ECPGt_long_long:
case ECPGt_unsigned_long_long:
- *((long long int *) (ind + ind_offset * act_tuple)) = variable->len;
+ *((long long int *) (ind + ind_offset * var_index)) = variable->len;
break;
#endif /* HAVE_LONG_LONG_INT */
default:
@@ -604,9 +604,9 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
pval = scan_length;
if (type == ECPGt_numeric)
- PGTYPESnumeric_copy(nres, (numeric *) (var + offset * act_tuple));
+ PGTYPESnumeric_copy(nres, (numeric *) (var + offset * var_index));
else
- PGTYPESnumeric_to_decimal(nres, (decimal *) (var + offset * act_tuple));
+ PGTYPESnumeric_to_decimal(nres, (decimal *) (var + offset * var_index));
PGTYPESnumeric_free(nres);
break;
@@ -657,7 +657,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
}
pval = scan_length;
- PGTYPESinterval_copy(ires, (interval *) (var + offset * act_tuple));
+ PGTYPESinterval_copy(ires, (interval *) (var + offset * var_index));
free(ires);
break;
@@ -701,7 +701,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
}
}
- *((date *) (var + offset * act_tuple)) = ddres;
+ *((date *) (var + offset * var_index)) = ddres;
pval = scan_length;
break;
@@ -745,7 +745,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
}
}
- *((timestamp *) (var + offset * act_tuple)) = tres;
+ *((timestamp *) (var + offset * var_index)) = tres;
pval = scan_length;
break;
@@ -762,6 +762,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
/* set array to next entry */
++act_tuple;
+ ++var_index;
/* set pval to the next entry */
diff --git a/src/interfaces/ecpg/ecpglib/descriptor.c b/src/interfaces/ecpg/ecpglib/descriptor.c
index 17a956e..2ec4922 100644
--- a/src/interfaces/ecpg/ecpglib/descriptor.c
+++ b/src/interfaces/ecpg/ecpglib/descriptor.c
@@ -438,7 +438,7 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...)
/* desparate try to guess something sensible */
stmt.connection = ecpg_get_connection(NULL);
- ecpg_store_result(ECPGresult, index, &stmt, &data_var);
+ ecpg_store_result(ECPGresult, 0, PQntuples(ECPGresult), index, &stmt, &data_var, 0);
setlocale(LC_NUMERIC, oldlocale);
ecpg_free(oldlocale);
diff --git a/src/interfaces/ecpg/ecpglib/error.c b/src/interfaces/ecpg/ecpglib/error.c
index ee553fd..7148f50 100644
--- a/src/interfaces/ecpg/ecpglib/error.c
+++ b/src/interfaces/ecpg/ecpglib/error.c
@@ -268,6 +268,20 @@ ecpg_raise(int line, int code, const char *sqlstate, const char *str)
ecpg_gettext("could not connect to database \"%s\" on line %d"), str, line);
break;
+ case ECPG_INVALID_CURSOR:
+ if (strcmp(sqlstate, ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE) == 0)
+ snprintf(sqlca->sqlerrm.sqlerrmc, sizeof(sqlca->sqlerrm.sqlerrmc),
+
+ /*
+ * translator: this string will be truncated at 149 characters
+ * expanded.
+ */
+ ecpg_gettext("cursor can only scan forward"));
+ else
+ snprintf(sqlca->sqlerrm.sqlerrmc, sizeof(sqlca->sqlerrm.sqlerrmc), str);
+
+ break;
+
default:
snprintf(sqlca->sqlerrm.sqlerrmc, sizeof(sqlca->sqlerrm.sqlerrmc),
diff --git a/src/interfaces/ecpg/ecpglib/execute.c b/src/interfaces/ecpg/ecpglib/execute.c
index b8e48a3..12f34f6 100644
--- a/src/interfaces/ecpg/ecpglib/execute.c
+++ b/src/interfaces/ecpg/ecpglib/execute.c
@@ -109,6 +109,7 @@ free_statement(struct statement * stmt)
free_variable(stmt->outlist);
ecpg_free(stmt->command);
ecpg_free(stmt->name);
+ ecpg_free(stmt->oldlocale);
ecpg_free(stmt);
}
@@ -311,12 +312,12 @@ ecpg_is_type_an_array(int type, const struct statement * stmt, const struct vari
bool
-ecpg_store_result(const PGresult *results, int act_field,
- const struct statement * stmt, struct variable * var)
+ecpg_store_result(const PGresult *results, int start, int end, int act_field,
+ const struct statement * stmt, struct variable * var, int var_index)
{
enum ARRAY_TYPE isarray;
int act_tuple,
- ntuples = PQntuples(results);
+ ntuples = end - start;
bool status = true;
if ((isarray = ecpg_is_type_an_array(PQftype(results, act_field), stmt, var)) == ECPG_ARRAY_ERROR)
@@ -367,7 +368,7 @@ ecpg_store_result(const PGresult *results, int act_field,
if (!var->varcharsize && !var->arrsize)
{
/* special mode for handling char**foo=0 */
- for (act_tuple = 0; act_tuple < ntuples; act_tuple++)
+ for (act_tuple = start; act_tuple < end; act_tuple++)
len += strlen(PQgetvalue(results, act_tuple, act_field)) + 1;
len *= var->offset; /* should be 1, but YMNK */
len += (ntuples + 1) * sizeof(char *);
@@ -376,7 +377,7 @@ ecpg_store_result(const PGresult *results, int act_field,
{
var->varcharsize = 0;
/* check strlen for each tuple */
- for (act_tuple = 0; act_tuple < ntuples; act_tuple++)
+ for (act_tuple = start; act_tuple < end; act_tuple++)
{
int len = strlen(PQgetvalue(results, act_tuple, act_field)) + 1;
@@ -397,7 +398,7 @@ ecpg_store_result(const PGresult *results, int act_field,
}
else
{
- for (act_tuple = 0; act_tuple < ntuples; act_tuple++)
+ for (act_tuple = start; act_tuple < end; act_tuple++)
len += PQgetlength(results, act_tuple, act_field);
}
@@ -433,11 +434,11 @@ ecpg_store_result(const PGresult *results, int act_field,
/* storing the data (after the last array element) */
char *current_data_location = (char *) ¤t_string[ntuples + 1];
- for (act_tuple = 0; act_tuple < ntuples && status; act_tuple++)
+ for (act_tuple = start; act_tuple < end && status; act_tuple++, var_index++)
{
int len = strlen(PQgetvalue(results, act_tuple, act_field)) + 1;
- if (!ecpg_get_data(results, act_tuple, act_field, stmt->lineno,
+ if (!ecpg_get_data(results, var_index, act_tuple, act_field, stmt->lineno,
var->type, var->ind_type, current_data_location,
var->ind_value, len, 0, var->ind_offset, isarray, stmt->compat, stmt->force_indicator))
status = false;
@@ -454,9 +455,9 @@ ecpg_store_result(const PGresult *results, int act_field,
}
else
{
- for (act_tuple = 0; act_tuple < ntuples && status; act_tuple++)
+ for (act_tuple = start; act_tuple < end && status; act_tuple++, var_index++)
{
- if (!ecpg_get_data(results, act_tuple, act_field, stmt->lineno,
+ if (!ecpg_get_data(results, var_index, act_tuple, act_field, stmt->lineno,
var->type, var->ind_type, var->value,
var->ind_value, var->varcharsize, var->offset, var->ind_offset, isarray, stmt->compat, stmt->force_indicator))
status = false;
@@ -1082,18 +1083,26 @@ ecpg_store_input(const int lineno, const bool force_indicator, const struct vari
return true;
}
-static void
-free_params(const char **paramValues, int nParams, bool print, int lineno)
+void
+ecpg_free_params(struct statement *stmt, bool print, int lineno)
{
int n;
- for (n = 0; n < nParams; n++)
+ for (n = 0; n < stmt->nparams; n++)
{
if (print)
- ecpg_log("free_params on line %d: parameter %d = %s\n", lineno, n + 1, paramValues[n] ? paramValues[n] : "null");
- ecpg_free((void *) (paramValues[n]));
+ ecpg_log("free_params on line %d: parameter %d = %s\n", lineno, n + 1, stmt->param_values[n] ? stmt->param_values[n] : "null");
+ ecpg_free((void *) (stmt->param_values[n]));
}
- ecpg_free(paramValues);
+ ecpg_free(stmt->param_values);
+ stmt->nparams = 0;
+ stmt->param_values = NULL;
+
+ for (n = 0; n < stmt->ndollarzero; n++)
+ ecpg_free((void *) (stmt->dollarzero[n]));
+ ecpg_free(stmt->dollarzero);
+ stmt->ndollarzero = 0;
+ stmt->dollarzero = NULL;
}
@@ -1129,20 +1138,12 @@ insert_tobeinserted(int position, int ph_len, struct statement * stmt, char *tob
return true;
}
-static bool
-ecpg_execute(struct statement * stmt)
+bool
+ecpg_build_params(struct statement * stmt)
{
- bool status = false;
- char *cmdstat;
- PGresult *results;
- PGnotify *notify;
struct variable *var;
int desc_counter = 0;
- const char **paramValues = NULL;
- int nParams = 0;
int position = 0;
- struct sqlca_t *sqlca = ECPGget_sqlca();
- bool clear_result = true;
/*
* If the type is one of the fill in types then we take the argument and
@@ -1342,7 +1343,7 @@ ecpg_execute(struct statement * stmt)
ecpg_raise(stmt->lineno, ECPG_TOO_MANY_ARGUMENTS,
ECPG_SQLSTATE_USING_CLAUSE_DOES_NOT_MATCH_PARAMETERS,
NULL);
- free_params(paramValues, nParams, false, stmt->lineno);
+ ecpg_free_params(stmt, false, stmt->lineno);
return false;
}
@@ -1357,7 +1358,7 @@ ecpg_execute(struct statement * stmt)
if (!insert_tobeinserted(position, ph_len, stmt, tobeinserted))
{
- free_params(paramValues, nParams, false, stmt->lineno);
+ ecpg_free_params(stmt, false, stmt->lineno);
return false;
}
tobeinserted = NULL;
@@ -1370,23 +1371,38 @@ ecpg_execute(struct statement * stmt)
*/
else if (stmt->command[position] == '0')
{
+ const char **dollarzero;
+
+ if (!(dollarzero = (const char **) ecpg_realloc(stmt->dollarzero, sizeof(const char *) * (stmt->ndollarzero + 1), stmt->lineno)))
+ {
+ ecpg_free_params(stmt, false, stmt->lineno);
+ return false;
+ }
+ stmt->ndollarzero++;
+ stmt->dollarzero = dollarzero;
+ stmt->dollarzero[stmt->ndollarzero - 1] = strdup(tobeinserted);
+
if (!insert_tobeinserted(position, 2, stmt, tobeinserted))
{
- free_params(paramValues, nParams, false, stmt->lineno);
+ ecpg_free_params(stmt, false, stmt->lineno);
return false;
}
tobeinserted = NULL;
}
else
{
- nParams++;
- if (!(paramValues = (const char **) ecpg_realloc(paramValues, sizeof(const char *) * nParams, stmt->lineno)))
+ const char **paramValues;
+
+ if (!(paramValues = (const char **) ecpg_realloc(stmt->param_values, sizeof(const char *) * (stmt->nparams + 1), stmt->lineno)))
{
ecpg_free(paramValues);
return false;
}
- paramValues[nParams - 1] = tobeinserted;
+ stmt->nparams++;
+ stmt->param_values = paramValues;
+
+ stmt->param_values[stmt->nparams - 1] = tobeinserted;
/* let's see if this was an old style placeholder */
if (stmt->command[position] == '?')
@@ -1397,7 +1413,7 @@ ecpg_execute(struct statement * stmt)
if (!(tobeinserted = (char *) ecpg_alloc(buffersize, stmt->lineno)))
{
- free_params(paramValues, nParams, false, stmt->lineno);
+ ecpg_free_params(stmt, false, stmt->lineno);
return false;
}
@@ -1405,7 +1421,7 @@ ecpg_execute(struct statement * stmt)
if (!insert_tobeinserted(position, 2, stmt, tobeinserted))
{
- free_params(paramValues, nParams, false, stmt->lineno);
+ ecpg_free_params(stmt, false, stmt->lineno);
return false;
}
tobeinserted = NULL;
@@ -1421,58 +1437,76 @@ ecpg_execute(struct statement * stmt)
{
ecpg_raise(stmt->lineno, ECPG_TOO_FEW_ARGUMENTS,
ECPG_SQLSTATE_USING_CLAUSE_DOES_NOT_MATCH_PARAMETERS, NULL);
- free_params(paramValues, nParams, false, stmt->lineno);
+ ecpg_free_params(stmt, false, stmt->lineno);
return false;
}
- /* The request has been build. */
+ return true;
+}
+
+static bool
+ecpg_execute(struct statement * stmt)
+{
if (PQtransactionStatus(stmt->connection->connection) == PQTRANS_IDLE && !stmt->connection->autocommit)
{
- results = PQexec(stmt->connection->connection, "begin transaction");
- if (!ecpg_check_PQresult(results, stmt->lineno, stmt->connection->connection, stmt->compat))
+ stmt->results = PQexec(stmt->connection->connection, "begin transaction");
+ if (!ecpg_check_PQresult(stmt->results, stmt->lineno, stmt->connection->connection, stmt->compat))
{
- free_params(paramValues, nParams, false, stmt->lineno);
+ ecpg_free_params(stmt, false, stmt->lineno);
return false;
}
- PQclear(results);
+ PQclear(stmt->results);
+ stmt->results = NULL;
}
- ecpg_log("ecpg_execute on line %d: query: %s; with %d parameter(s) on connection %s\n", stmt->lineno, stmt->command, nParams, stmt->connection->name);
+ ecpg_log("ecpg_execute on line %d: query: %s; with %d parameter(s) on connection %s\n", stmt->lineno, stmt->command, stmt->nparams, stmt->connection->name);
if (stmt->statement_type == ECPGst_execute)
{
- results = PQexecPrepared(stmt->connection->connection, stmt->name, nParams, paramValues, NULL, NULL, 0);
+ stmt->results = PQexecPrepared(stmt->connection->connection, stmt->name, stmt->nparams, stmt->param_values, NULL, NULL, 0);
ecpg_log("ecpg_execute on line %d: using PQexecPrepared for \"%s\"\n", stmt->lineno, stmt->command);
}
else
{
- if (nParams == 0)
+ if (stmt->nparams == 0)
{
- results = PQexec(stmt->connection->connection, stmt->command);
+ stmt->results = PQexec(stmt->connection->connection, stmt->command);
ecpg_log("ecpg_execute on line %d: using PQexec\n", stmt->lineno);
}
else
{
- results = PQexecParams(stmt->connection->connection, stmt->command, nParams, NULL, paramValues, NULL, NULL, 0);
+ stmt->results = PQexecParams(stmt->connection->connection, stmt->command, stmt->nparams, NULL, stmt->param_values, NULL, NULL, 0);
ecpg_log("ecpg_execute on line %d: using PQexecParams\n", stmt->lineno);
}
}
- free_params(paramValues, nParams, true, stmt->lineno);
+ ecpg_free_params(stmt, true, stmt->lineno);
- if (!ecpg_check_PQresult(results, stmt->lineno, stmt->connection->connection, stmt->compat))
+ if (!ecpg_check_PQresult(stmt->results, stmt->lineno, stmt->connection->connection, stmt->compat))
return (false);
+ return (true);
+}
+
+bool
+ecpg_process_output(struct statement * stmt, int start, int end, int var_index, bool keep_result, bool append_result)
+{
+ char *cmdstat;
+ PGnotify *notify;
+ bool status = false;
+ struct variable *var;
+ struct sqlca_t *sqlca = ECPGget_sqlca();
+
var = stmt->outlist;
- switch (PQresultStatus(results))
+ switch (PQresultStatus(stmt->results))
{
int nfields,
ntuples,
act_field;
case PGRES_TUPLES_OK:
- nfields = PQnfields(results);
- sqlca->sqlerrd[2] = ntuples = PQntuples(results);
+ nfields = PQnfields(stmt->results);
+ sqlca->sqlerrd[2] += ntuples = (end - start);
ecpg_log("ecpg_execute on line %d: correctly got %d tuples with %d fields\n", stmt->lineno, ntuples, nfields);
status = true;
@@ -1494,12 +1528,34 @@ ecpg_execute(struct statement * stmt)
status = false;
else
{
- if (desc->result)
- PQclear(desc->result);
- desc->result = results;
- clear_result = false;
+ int row, srcrow, col;
+
+ if (append_result)
+ row = PQntuples(desc->result);
+ else
+ {
+ if (desc->result)
+ PQclear(desc->result);
+ desc->result = PQcopyResult(stmt->results, PG_COPYRES_ATTRS | PG_COPYRES_EVENTS | PG_COPYRES_NOTICEHOOKS);
+ row = 0;
+ }
+
+ for (srcrow = start; srcrow < end; srcrow++, row++)
+ for (col = 0; col < nfields; col++)
+ {
+ bool isnull = PQgetisnull(stmt->results, srcrow, col);
+ if (!PQsetvalue(desc->result, row, col,
+ isnull ? NULL : PQgetvalue(stmt->results, srcrow, col),
+ isnull ? -1 : PQgetlength(stmt->results, srcrow, col)))
+ {
+ ecpg_raise(stmt->lineno, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
+ status = false;
+ break;
+ }
+ }
+
ecpg_log("ecpg_execute on line %d: putting result (%d tuples) into descriptor %s\n",
- stmt->lineno, PQntuples(results), (const char *) var->pointer);
+ stmt->lineno, PQntuples(stmt->results), (const char *) var->pointer);
}
var = var->next;
}
@@ -1509,36 +1565,52 @@ ecpg_execute(struct statement * stmt)
{
struct sqlda_compat **_sqlda = (struct sqlda_compat **) var->pointer;
struct sqlda_compat *sqlda = *_sqlda;
- struct sqlda_compat *sqlda_new;
+ struct sqlda_compat *sqlda_last, *sqlda_new = NULL;
int i;
- /*
- * If we are passed in a previously existing sqlda (chain)
- * then free it.
- */
- while (sqlda)
+ if (append_result)
{
- sqlda_new = sqlda->desc_next;
- free(sqlda);
- sqlda = sqlda_new;
+ sqlda_last = sqlda;
+ while (sqlda_last && sqlda_last->desc_next)
+ sqlda_last = sqlda_last->desc_next;
}
- *_sqlda = sqlda = sqlda_new = NULL;
- for (i = ntuples - 1; i >= 0; i--)
+ else
+ {
+ /*
+ * If we are passed in a previously existing sqlda (chain)
+ * then free it.
+ */
+ while (sqlda)
+ {
+ sqlda_last = sqlda->desc_next;
+ free(sqlda);
+ sqlda = sqlda_last;
+ }
+ *_sqlda = sqlda = sqlda_last = NULL;
+ }
+ for (i = end - 1; i >= start; i--)
{
+ struct sqlda_compat *tmp;
+
/*
- * Build a new sqlda structure. Note that only
- * fetching 1 record is supported
+ * Build a new sqlda structure.
*/
- sqlda_new = ecpg_build_compat_sqlda(stmt->lineno, results, i, stmt->compat);
+ tmp = ecpg_build_compat_sqlda(stmt->lineno, stmt->results, i, stmt->compat);
- if (!sqlda_new)
+ if (!tmp)
{
/* cleanup all SQLDAs we created up */
+ while (sqlda_new)
+ {
+ tmp = sqlda_new->desc_next;
+ free(sqlda_new);
+ sqlda_new = tmp;
+ }
while (sqlda)
{
- sqlda_new = sqlda->desc_next;
+ tmp = sqlda->desc_next;
free(sqlda);
- sqlda = sqlda_new;
+ sqlda = tmp;
}
*_sqlda = NULL;
@@ -1550,51 +1622,74 @@ ecpg_execute(struct statement * stmt)
{
ecpg_log("ecpg_execute on line %d: new sqlda was built\n", stmt->lineno);
- *_sqlda = sqlda_new;
+ if (sqlda_new == NULL)
+ sqlda_new = tmp;
+ else
+ {
+ tmp->desc_next = sqlda_new;
+ sqlda_new = tmp;
+ }
- ecpg_set_compat_sqlda(stmt->lineno, _sqlda, results, i, stmt->compat);
+ ecpg_set_compat_sqlda(stmt->lineno, &tmp, stmt->results, i, stmt->compat);
ecpg_log("ecpg_execute on line %d: putting result (1 tuple %d fields) into sqlda descriptor\n",
- stmt->lineno, PQnfields(results));
-
- sqlda_new->desc_next = sqlda;
- sqlda = sqlda_new;
+ stmt->lineno, PQnfields(stmt->results));
}
}
+ if (sqlda_last)
+ sqlda_last->desc_next = sqlda_new;
+ else
+ *_sqlda = sqlda_new;
}
else
{
struct sqlda_struct **_sqlda = (struct sqlda_struct **) var->pointer;
struct sqlda_struct *sqlda = *_sqlda;
- struct sqlda_struct *sqlda_new;
+ struct sqlda_struct *sqlda_last, *sqlda_new = NULL;
int i;
- /*
- * If we are passed in a previously existing sqlda (chain)
- * then free it.
- */
- while (sqlda)
+ if (append_result)
{
- sqlda_new = sqlda->desc_next;
- free(sqlda);
- sqlda = sqlda_new;
+ sqlda_last = sqlda;
+ while (sqlda_last && sqlda_last->desc_next)
+ sqlda_last = sqlda_last->desc_next;
}
- *_sqlda = sqlda = sqlda_new = NULL;
- for (i = ntuples - 1; i >= 0; i--)
+ else
{
/*
- * Build a new sqlda structure. Note that only
- * fetching 1 record is supported
+ * If we are passed in a previously existing sqlda (chain)
+ * then free it.
*/
- sqlda_new = ecpg_build_native_sqlda(stmt->lineno, results, i, stmt->compat);
+ while (sqlda)
+ {
+ sqlda_last = sqlda->desc_next;
+ free(sqlda);
+ sqlda = sqlda_last;
+ }
+ *_sqlda = sqlda = sqlda_last = NULL;
+ }
+ for (i = end - 1; i >= start; i--)
+ {
+ struct sqlda_struct *tmp;
+
+ /*
+ * Build a new sqlda structure.
+ */
+ tmp = ecpg_build_native_sqlda(stmt->lineno, stmt->results, i, stmt->compat);
- if (!sqlda_new)
+ if (!tmp)
{
/* cleanup all SQLDAs we created up */
+ while (sqlda_new)
+ {
+ tmp = sqlda_new->desc_next;
+ free(sqlda_new);
+ sqlda_new = tmp;
+ }
while (sqlda)
{
- sqlda_new = sqlda->desc_next;
+ tmp = sqlda->desc_next;
free(sqlda);
- sqlda = sqlda_new;
+ sqlda = tmp;
}
*_sqlda = NULL;
@@ -1606,16 +1701,23 @@ ecpg_execute(struct statement * stmt)
{
ecpg_log("ecpg_execute on line %d: new sqlda was built\n", stmt->lineno);
- *_sqlda = sqlda_new;
+ if (sqlda_new == NULL)
+ sqlda_new = tmp;
+ else
+ {
+ tmp->desc_next = sqlda_new;
+ sqlda_new = tmp;
+ }
- ecpg_set_native_sqlda(stmt->lineno, _sqlda, results, i, stmt->compat);
+ ecpg_set_native_sqlda(stmt->lineno, &tmp, stmt->results, i, stmt->compat);
ecpg_log("ecpg_execute on line %d: putting result (1 tuple %d fields) into sqlda descriptor\n",
- stmt->lineno, PQnfields(results));
-
- sqlda_new->desc_next = sqlda;
- sqlda = sqlda_new;
+ stmt->lineno, PQnfields(stmt->results));
}
}
+ if (sqlda_last)
+ sqlda_last->desc_next = sqlda_new;
+ else
+ *_sqlda = sqlda_new;
}
var = var->next;
@@ -1625,7 +1727,7 @@ ecpg_execute(struct statement * stmt)
{
if (var != NULL)
{
- status = ecpg_store_result(results, act_field, stmt, var);
+ status = ecpg_store_result(stmt->results, start, end, act_field, stmt, var, var_index);
var = var->next;
}
else if (!INFORMIX_MODE(stmt->compat))
@@ -1644,9 +1746,9 @@ ecpg_execute(struct statement * stmt)
break;
case PGRES_COMMAND_OK:
status = true;
- cmdstat = PQcmdStatus(results);
- sqlca->sqlerrd[1] = PQoidValue(results);
- sqlca->sqlerrd[2] = atol(PQcmdTuples(results));
+ cmdstat = PQcmdStatus(stmt->results);
+ sqlca->sqlerrd[1] = PQoidValue(stmt->results);
+ sqlca->sqlerrd[2] = atol(PQcmdTuples(stmt->results));
ecpg_log("ecpg_execute on line %d: OK: %s\n", stmt->lineno, cmdstat);
if (stmt->compat != ECPG_COMPAT_INFORMIX_SE &&
!sqlca->sqlerrd[2] &&
@@ -1670,12 +1772,12 @@ ecpg_execute(struct statement * stmt)
if (res == -1)
{
/* COPY done */
- PQclear(results);
- results = PQgetResult(stmt->connection->connection);
- if (PQresultStatus(results) == PGRES_COMMAND_OK)
+ PQclear(stmt->results);
+ stmt->results = PQgetResult(stmt->connection->connection);
+ if (PQresultStatus(stmt->results) == PGRES_COMMAND_OK)
ecpg_log("ecpg_execute on line %d: got PGRES_COMMAND_OK after PGRES_COPY_OUT\n", stmt->lineno);
else
- ecpg_log("ecpg_execute on line %d: got error after PGRES_COPY_OUT: %s", stmt->lineno, PQresultErrorMessage(results));
+ ecpg_log("ecpg_execute on line %d: got error after PGRES_COPY_OUT: %s", stmt->lineno, PQresultErrorMessage(stmt->results));
}
break;
}
@@ -1687,12 +1789,12 @@ ecpg_execute(struct statement * stmt)
*/
ecpg_log("ecpg_execute on line %d: unknown execution status type\n",
stmt->lineno);
- ecpg_raise_backend(stmt->lineno, results, stmt->connection->connection, stmt->compat);
+ ecpg_raise_backend(stmt->lineno, stmt->results, stmt->connection->connection, stmt->compat);
status = false;
break;
}
- if (clear_result)
- PQclear(results);
+ if (!keep_result)
+ PQclear(stmt->results);
/* check for asynchronous returns */
notify = PQnotifies(stmt->connection->connection);
@@ -1707,46 +1809,20 @@ ecpg_execute(struct statement * stmt)
}
bool
-ECPGdo(const int lineno, const int compat, const int force_indicator, const char *connection_name, const bool questionmarks, const int st, const char *query,...)
+ecpg_do_prologue(int lineno, const int compat, const int force_indicator,
+ const char *connection_name, const bool questionmarks,
+ enum ECPG_statement_type statement_type, const char *query,
+ va_list args, struct statement **stmt_out)
{
- va_list args;
struct statement *stmt;
struct connection *con;
- bool status;
- char *oldlocale;
enum ECPGttype type;
struct variable **list;
- enum ECPG_statement_type statement_type = (enum ECPG_statement_type) st;
- char *prepname;
-
- if (!query)
- {
- ecpg_raise(lineno, ECPG_EMPTY, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, NULL);
- return (false);
- }
-
- /* Make sure we do NOT honor the locale for numeric input/output */
- /* since the database wants the standard decimal point */
- oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
- setlocale(LC_NUMERIC, "C");
-
-#ifdef ENABLE_THREAD_SAFETY
- ecpg_pthreads_init();
-#endif
-
- con = ecpg_get_connection(connection_name);
-
- if (!ecpg_init(con, connection_name, lineno))
- {
- setlocale(LC_NUMERIC, oldlocale);
- ecpg_free(oldlocale);
- return (false);
- }
+ char *prepname;
- /* construct statement in our own structure */
- va_start(args, query);
+ *stmt_out = NULL;
- /*
+ /*
* create a list of variables The variables are listed with input
* variables preceding outputvariables The end of each group is marked by
* an end marker. per variable we list: type - as defined in ecpgtype.h
@@ -1759,11 +1835,24 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
* arraysize of indicator array ind_offset - indicator offset
*/
if (!(stmt = (struct statement *) ecpg_alloc(sizeof(struct statement), lineno)))
- {
- setlocale(LC_NUMERIC, oldlocale);
- ecpg_free(oldlocale);
- va_end(args);
return false;
+
+ /* Make sure we do NOT honor the locale for numeric input/output */
+ /* since the database wants the standard decimal point */
+ stmt->oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
+ setlocale(LC_NUMERIC, "C");
+
+#ifdef ENABLE_THREAD_SAFETY
+ ecpg_pthreads_init();
+#endif
+
+ con = ecpg_get_connection(connection_name);
+
+ if (!ecpg_init(con, connection_name, lineno))
+ {
+ setlocale(LC_NUMERIC, stmt->oldlocale);
+ free_statement(stmt);
+ return (false);
}
/*
@@ -1774,9 +1863,8 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
{
if (!ecpg_auto_prepare(lineno, connection_name, compat, &prepname, query))
{
- setlocale(LC_NUMERIC, oldlocale);
- ecpg_free(oldlocale);
- va_end(args);
+ setlocale(LC_NUMERIC, stmt->oldlocale);
+ free_statement(stmt);
return (false);
}
@@ -1805,9 +1893,8 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
else
{
ecpg_raise(lineno, ECPG_INVALID_STMT, ECPG_SQLSTATE_INVALID_SQL_STATEMENT_NAME, stmt->command);
- setlocale(LC_NUMERIC, oldlocale);
- ecpg_free(oldlocale);
- va_end(args);
+ setlocale(LC_NUMERIC, stmt->oldlocale);
+ free_statement(stmt);
return (false);
}
}
@@ -1834,10 +1921,8 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
if (!(var = (struct variable *) ecpg_alloc(sizeof(struct variable), lineno)))
{
- setlocale(LC_NUMERIC, oldlocale);
- ecpg_free(oldlocale);
+ setlocale(LC_NUMERIC, stmt->oldlocale);
free_statement(stmt);
- va_end(args);
return false;
}
@@ -1892,10 +1977,8 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
{
ecpg_raise(lineno, ECPG_INVALID_STMT, ECPG_SQLSTATE_INVALID_SQL_STATEMENT_NAME, NULL);
ecpg_free(var);
- setlocale(LC_NUMERIC, oldlocale);
- ecpg_free(oldlocale);
+ setlocale(LC_NUMERIC, stmt->oldlocale);
free_statement(stmt);
- va_end(args);
return false;
}
@@ -1910,29 +1993,80 @@ ECPGdo(const int lineno, const int compat, const int force_indicator, const char
type = va_arg(args, enum ECPGttype);
}
- va_end(args);
-
/* are we connected? */
if (con == NULL || con->connection == NULL)
{
- free_statement(stmt);
ecpg_raise(lineno, ECPG_NOT_CONN, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, (con) ? con->name : ecpg_gettext(""));
- setlocale(LC_NUMERIC, oldlocale);
- ecpg_free(oldlocale);
+ setlocale(LC_NUMERIC, stmt->oldlocale);
+ free_statement(stmt);
return false;
}
/* initialize auto_mem struct */
ecpg_clear_auto_mem();
- status = ecpg_execute(stmt);
+ *stmt_out = stmt;
+
+ return (true);
+}
+
+
+void
+ecpg_do_epilogue(struct statement *stmt)
+{
+ /* reset locale value so our application is not affected */
+ setlocale(LC_NUMERIC, stmt->oldlocale);
free_statement(stmt);
+}
+
+bool
+ecpg_do(const int lineno, const int compat, const int force_indicator, const char *connection_name, const bool questionmarks, const int st, const char *query, va_list args)
+{
+ struct statement *stmt;
+
+ if (!query)
+ {
+ ecpg_raise(lineno, ECPG_EMPTY, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, NULL);
+ return false;
+ }
- /* and reset locale value so our application is not affected */
- setlocale(LC_NUMERIC, oldlocale);
- ecpg_free(oldlocale);
+ if (!ecpg_do_prologue(lineno, compat, force_indicator, connection_name, questionmarks, (enum ECPG_statement_type) st, query, args, &stmt))
+ return false;
+
+ if (!ecpg_build_params(stmt))
+ {
+ ecpg_do_epilogue(stmt);
+ return false;
+ }
+
+ if (!ecpg_execute(stmt))
+ {
+ ecpg_do_epilogue(stmt);
+ return false;
+ }
+
+ if (!ecpg_process_output(stmt, 0, PQntuples(stmt->results), 0, false, false))
+ {
+ ecpg_do_epilogue(stmt);
+ return false;
+ }
+
+ ecpg_do_epilogue(stmt);
+
+ return true;
+}
+
+bool
+ECPGdo(const int lineno, const int compat, const int force_indicator, const char *connection_name, const bool questionmarks, const int st, const char *query,...)
+{
+ va_list args;
+ bool ret;
+
+ va_start(args, query);
+ ret = ecpg_do(lineno, compat, force_indicator, connection_name, questionmarks, st, query, args);
+ va_end(args);
- return (status);
+ return ret;
}
/* old descriptor interface */
diff --git a/src/interfaces/ecpg/ecpglib/exports.txt b/src/interfaces/ecpg/ecpglib/exports.txt
index 69e9617..f05e4f1 100644
--- a/src/interfaces/ecpg/ecpglib/exports.txt
+++ b/src/interfaces/ecpg/ecpglib/exports.txt
@@ -29,3 +29,6 @@ ECPGget_PGconn 26
ECPGtransactionStatus 27
ECPGset_var 28
ECPGget_var 29
+ECPGopen 30
+ECPGfetch 31
+ECPGclose 32
diff --git a/src/interfaces/ecpg/ecpglib/extern.h b/src/interfaces/ecpg/ecpglib/extern.h
index 96d49a4..2920a3c 100644
--- a/src/interfaces/ecpg/ecpglib/extern.h
+++ b/src/interfaces/ecpg/ecpglib/extern.h
@@ -60,6 +60,12 @@ struct statement
bool questionmarks;
struct variable *inlist;
struct variable *outlist;
+ char *oldlocale;
+ const char **dollarzero;
+ int ndollarzero;
+ const char **param_values;
+ int nparams;
+ PGresult *results;
};
/* structure to store prepared statements for a connection */
@@ -71,6 +77,17 @@ struct prepared_statement
struct prepared_statement *next;
};
+struct cursor_descriptor {
+ char *name;
+ PGresult *res;
+ bool scrollable;
+ bool endoftuples; /* valid if ->scrollable == false and there is no more tuples */
+ int64 n_tuples; /* valid if ->scrollable == true */
+ int64 start_pos;
+ int64 cache_pos;
+ struct cursor_descriptor *next;
+};
+
/* structure to store connections */
struct connection
{
@@ -79,6 +96,7 @@ struct connection
bool autocommit;
struct ECPGtype_information_cache *cache_head;
struct prepared_statement *prep_stmts;
+ struct cursor_descriptor *cursor_desc;
struct connection *next;
};
@@ -126,7 +144,7 @@ struct variable
/* Returns a pointer to a string containing a simple type name. */
void ecpg_add_mem(void *ptr, int lineno);
-bool ecpg_get_data(const PGresult *, int, int, int, enum ECPGttype type,
+bool ecpg_get_data(const PGresult *, int, int, int, int, enum ECPGttype type,
enum ECPGttype, char *, char *, long, long, long,
enum ARRAY_TYPE, enum COMPAT_MODE, bool);
@@ -152,9 +170,16 @@ struct descriptor *ecpg_find_desc(int line, const char *name);
struct prepared_statement *ecpg_find_prepared_statement(const char *,
struct connection *, struct prepared_statement **);
-bool ecpg_store_result(const PGresult *results, int act_field,
- const struct statement * stmt, struct variable * var);
+bool ecpg_store_result(const PGresult *results, int start, int end, int act_field,
+ const struct statement * stmt, struct variable * var, int var_index);
bool ecpg_store_input(const int, const bool, const struct variable *, char **, bool);
+bool ecpg_do_prologue(int, const int, const int, const char *, const bool,
+ enum ECPG_statement_type, const char *, va_list, struct statement **);
+bool ecpg_build_params(struct statement *);
+bool ecpg_process_output(struct statement *, int, int, int, bool, bool);
+void ecpg_free_params(struct statement *, bool, int);
+void ecpg_do_epilogue(struct statement *);
+bool ecpg_do(const int, const int, const int, const char *, const bool, const int, const char *, va_list);
bool ecpg_check_PQresult(PGresult *, int, PGconn *, enum COMPAT_MODE);
void ecpg_raise(int line, int code, const char *sqlstate, const char *str);
@@ -191,6 +216,8 @@ void ecpg_set_native_sqlda(int, struct sqlda_struct **, const PGresult *, int,
#define ECPG_SQLSTATE_SYNTAX_ERROR "42601"
#define ECPG_SQLSTATE_DATATYPE_MISMATCH "42804"
#define ECPG_SQLSTATE_DUPLICATE_CURSOR "42P03"
+#define ECPG_SQLSTATE_INVALID_CURSOR_DEFINITION "42P11"
+#define ECPG_SQLSTATE_OBJECT_NOT_IN_PREREQUISITE_STATE "55000"
/* implementation-defined internal errors of ecpg */
#define ECPG_SQLSTATE_ECPG_INTERNAL_ERROR "YE000"
diff --git a/src/interfaces/ecpg/ecpglib/sqlda.c b/src/interfaces/ecpg/ecpglib/sqlda.c
index a1a0e18..aa37dee 100644
--- a/src/interfaces/ecpg/ecpglib/sqlda.c
+++ b/src/interfaces/ecpg/ecpglib/sqlda.c
@@ -389,7 +389,7 @@ ecpg_set_compat_sqlda(int lineno, struct sqlda_compat ** _sqlda, const PGresult
if (!isnull)
{
if (set_data)
- ecpg_get_data(res, row, i, lineno,
+ ecpg_get_data(res, 0, row, i, lineno,
sqlda->sqlvar[i].sqltype, ECPGt_NO_INDICATOR,
sqlda->sqlvar[i].sqldata, NULL, 0, 0, 0,
ECPG_ARRAY_NONE, compat, false);
@@ -571,7 +571,7 @@ ecpg_set_native_sqlda(int lineno, struct sqlda_struct ** _sqlda, const PGresult
if (!isnull)
{
if (set_data)
- ecpg_get_data(res, row, i, lineno,
+ ecpg_get_data(res, 0, row, i, lineno,
sqlda->sqlvar[i].sqltype, ECPGt_NO_INDICATOR,
sqlda->sqlvar[i].sqldata, NULL, 0, 0, 0,
ECPG_ARRAY_NONE, compat, false);
diff --git a/src/interfaces/ecpg/include/ecpgerrno.h b/src/interfaces/ecpg/include/ecpgerrno.h
index 36b15b7..f21dad2 100644
--- a/src/interfaces/ecpg/include/ecpgerrno.h
+++ b/src/interfaces/ecpg/include/ecpgerrno.h
@@ -37,6 +37,7 @@
#define ECPG_NOT_CONN -221
#define ECPG_INVALID_STMT -230
+#define ECPG_INVALID_CURSOR -231
/* dynamic SQL related */
#define ECPG_UNKNOWN_DESCRIPTOR -240
diff --git a/src/interfaces/ecpg/include/ecpglib.h b/src/interfaces/ecpg/include/ecpglib.h
index 3b8ed4c..236f797 100644
--- a/src/interfaces/ecpg/include/ecpglib.h
+++ b/src/interfaces/ecpg/include/ecpglib.h
@@ -63,6 +63,13 @@ PGTransactionStatusType ECPGtransactionStatus(const char *);
char *ECPGerrmsg(void);
+/* Readahead cursor functions */
+bool ECPGopen(const int, const int, const int, const char *, const bool, const char *, const int, const char *, ...);
+bool ECPGfetch(const int, const int, const int, const char *, const bool,
+ const char *, enum ECPG_cursor_direction, const char *,
+ const int,const char *, ...);
+bool ECPGclose(const int, const int, const int, const char *, const bool, const char *, const int, const char *, ...);
+
/* print an error message */
void sqlprint(void);
diff --git a/src/interfaces/ecpg/include/ecpgtype.h b/src/interfaces/ecpg/include/ecpgtype.h
index 7cc47e9..dc82457 100644
--- a/src/interfaces/ecpg/include/ecpgtype.h
+++ b/src/interfaces/ecpg/include/ecpgtype.h
@@ -99,6 +99,14 @@ enum ECPG_statement_type
ECPGst_prepnormal
};
+enum ECPG_cursor_direction
+{
+ ECPGc_absolute,
+ ECPGc_relative,
+ ECPGc_forward,
+ ECPGc_backward
+};
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/ecpg/preproc/check_rules.pl b/src/interfaces/ecpg/preproc/check_rules.pl
index 991c40c..8560eb0 100644
--- a/src/interfaces/ecpg/preproc/check_rules.pl
+++ b/src/interfaces/ecpg/preproc/check_rules.pl
@@ -43,7 +43,10 @@ my %replace_line = (
'CREATE OptTemp TABLE create_as_target AS EXECUTE prepared_name execute_param_clause',
'PrepareStmtPREPAREnameprep_type_clauseASPreparableStmt' =>
- 'PREPARE prepared_name prep_type_clause AS PreparableStmt'
+ 'PREPARE prepared_name prep_type_clause AS PreparableStmt',
+
+ 'DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectStmt' =>
+ 'DECLARE cursor_name cursor_options opt_readahead CURSOR opt_hold FOR SelectStmt'
);
my $block = '';
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons
index 23421df..40df82f 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -15,7 +15,10 @@ ECPG: stmtClosePortalStmt block
}
}
- output_statement($1, 0, ECPGst_normal);
+ if (use_fetch_readahead)
+ output_close_statement($1, 0, ECPGst_normal);
+ else
+ output_statement($1, 0, ECPGst_normal);
}
ECPG: stmtDeallocateStmt block
{
@@ -27,8 +30,14 @@ ECPG: stmtDeallocateStmt block
ECPG: stmtDeclareCursorStmt block
{ output_simple_statement($1); }
ECPG: stmtDiscardStmt block
-ECPG: stmtFetchStmt block
{ output_statement($1, 1, ECPGst_normal); }
+ECPG: stmtFetchStmt block
+ {
+ if (use_fetch_readahead)
+ output_fetch_statement($1, 1, ECPGst_normal);
+ else
+ output_statement($1, 1, ECPGst_normal);
+ }
ECPG: stmtDeleteStmt block
ECPG: stmtInsertStmt block
ECPG: stmtSelectStmt block
@@ -137,7 +146,10 @@ ECPG: stmtViewStmt rule
if ((ptr = add_additional_variables($1, true)) != NULL)
{
connection = ptr->connection ? mm_strdup(ptr->connection) : NULL;
- output_statement(mm_strdup(ptr->command), 0, ECPGst_normal);
+ if (use_fetch_readahead)
+ output_open_statement(mm_strdup(ptr->command), 0, ECPGst_normal);
+ else
+ output_statement(mm_strdup(ptr->command), 0, ECPGst_normal);
ptr->opened = true;
}
}
@@ -195,6 +207,21 @@ ECPG: stmtViewStmt rule
ECPG: where_or_current_clauseWHERECURRENT_POFcursor_name block
{
char *cursor_marker = $4[0] == ':' ? mm_strdup("$0") : $4;
+ struct cursor *ptr;
+
+ for (ptr = cur; ptr != NULL; ptr = ptr->next)
+ {
+ if (strcmp(ptr->name, $4) == 0)
+ break;
+ }
+ if (!ptr)
+ mmerror(PARSE_ERROR, ET_FATAL, "cursor \"%s\" does not exist", $4);
+
+ if (ptr->fetch_readahead)
+ {
+ mmerror(PARSE_ERROR, ET_ERROR,
+ "\"WHERE CURRENT OF\" is incompatible with a READAHEAD cursor\n");
+ }
$$ = cat_str(2,mm_strdup("where current of"), cursor_marker);
}
ECPG: CopyStmtCOPYopt_binaryqualified_nameopt_column_listopt_oidscopy_fromcopy_file_namecopy_delimiteropt_withcopy_options addon
@@ -215,31 +242,77 @@ ECPG: var_valueNumericOnly addon
}
ECPG: fetch_argscursor_name addon
add_additional_variables($1, false);
+ set_cursor_readahead($1);
if ($1[0] == ':')
{
free($1);
$1 = mm_strdup("$0");
}
+ current_cursor_direction = ECPGc_forward;
+ current_cursor_amount = mm_strdup("1");
ECPG: fetch_argsfrom_incursor_name addon
add_additional_variables($2, false);
+ set_cursor_readahead($2);
if ($2[0] == ':')
{
free($2);
$2 = mm_strdup("$0");
}
+ current_cursor_direction = ECPGc_forward;
+ current_cursor_amount = mm_strdup("1");
ECPG: fetch_argsNEXTopt_from_incursor_name addon
+ add_additional_variables($3, false);
+ set_cursor_readahead($3);
+ if ($3[0] == ':')
+ {
+ free($3);
+ $3 = mm_strdup("$0");
+ }
+ current_cursor_direction = ECPGc_forward;
+ current_cursor_amount = mm_strdup("1");
ECPG: fetch_argsPRIORopt_from_incursor_name addon
+ add_additional_variables($3, false);
+ set_cursor_readahead($3);
+ if ($3[0] == ':')
+ {
+ free($3);
+ $3 = mm_strdup("$0");
+ }
+ current_cursor_direction = ECPGc_backward;
+ current_cursor_amount = mm_strdup("1");
ECPG: fetch_argsFIRST_Popt_from_incursor_name addon
+ add_additional_variables($3, false);
+ set_cursor_readahead($3);
+ if ($3[0] == ':')
+ {
+ free($3);
+ $3 = mm_strdup("$0");
+ }
+ current_cursor_direction = ECPGc_absolute;
+ current_cursor_amount = mm_strdup("1");
ECPG: fetch_argsLAST_Popt_from_incursor_name addon
+ add_additional_variables($3, false);
+ set_cursor_readahead($3);
+ if ($3[0] == ':')
+ {
+ free($3);
+ $3 = mm_strdup("$0");
+ }
+ current_cursor_direction = ECPGc_absolute;
+ current_cursor_amount = mm_strdup("-1");
ECPG: fetch_argsALLopt_from_incursor_name addon
add_additional_variables($3, false);
+ set_cursor_readahead($3);
if ($3[0] == ':')
{
free($3);
$3 = mm_strdup("$0");
}
+ current_cursor_direction = ECPGc_forward;
+ current_cursor_amount = mm_strdup("all");
ECPG: fetch_argsSignedIconstopt_from_incursor_name addon
add_additional_variables($3, false);
+ set_cursor_readahead($3);
if ($3[0] == ':')
{
free($3);
@@ -250,19 +323,76 @@ ECPG: fetch_argsSignedIconstopt_from_incursor_name addon
free($1);
$1 = mm_strdup("$0");
}
+ current_cursor_direction = ECPGc_forward;
+ current_cursor_amount = mm_strdup($1);
ECPG: fetch_argsFORWARDALLopt_from_incursor_name addon
+ add_additional_variables($4, false);
+ set_cursor_readahead($4);
+ if ($4[0] == ':')
+ {
+ free($4);
+ $4 = mm_strdup("$0");
+ }
+ current_cursor_direction = ECPGc_forward;
+ current_cursor_amount = mm_strdup("all");
ECPG: fetch_argsBACKWARDALLopt_from_incursor_name addon
add_additional_variables($4, false);
+ set_cursor_readahead($4);
if ($4[0] == ':')
{
free($4);
$4 = mm_strdup("$0");
}
+ current_cursor_direction = ECPGc_backward;
+ current_cursor_amount = mm_strdup("all");
ECPG: fetch_argsABSOLUTE_PSignedIconstopt_from_incursor_name addon
+ add_additional_variables($4, false);
+ set_cursor_readahead($4);
+ if ($4[0] == ':')
+ {
+ free($4);
+ $4 = mm_strdup("$0");
+ }
+ if ($2[0] == '$')
+ {
+ free($2);
+ $2 = mm_strdup("$0");
+ }
+ current_cursor_direction = ECPGc_absolute;
+ current_cursor_amount = mm_strdup($2);
ECPG: fetch_argsRELATIVE_PSignedIconstopt_from_incursor_name addon
+ add_additional_variables($4, false);
+ set_cursor_readahead($4);
+ if ($4[0] == ':')
+ {
+ free($4);
+ $4 = mm_strdup("$0");
+ }
+ if ($2[0] == '$')
+ {
+ free($2);
+ $2 = mm_strdup("$0");
+ }
+ current_cursor_direction = ECPGc_relative;
+ current_cursor_amount = mm_strdup($2);
ECPG: fetch_argsFORWARDSignedIconstopt_from_incursor_name addon
+ add_additional_variables($4, false);
+ set_cursor_readahead($4);
+ if ($4[0] == ':')
+ {
+ free($4);
+ $4 = mm_strdup("$0");
+ }
+ if ($2[0] == '$')
+ {
+ free($2);
+ $2 = mm_strdup("$0");
+ }
+ current_cursor_direction = ECPGc_forward;
+ current_cursor_amount = mm_strdup($2);
ECPG: fetch_argsBACKWARDSignedIconstopt_from_incursor_name addon
add_additional_variables($4, false);
+ set_cursor_readahead($4);
if ($4[0] == ':')
{
free($4);
@@ -273,7 +403,15 @@ ECPG: fetch_argsBACKWARDSignedIconstopt_from_incursor_name addon
free($2);
$2 = mm_strdup("$0");
}
-ECPG: cursor_namename rule
+ current_cursor_direction = ECPGc_backward;
+ current_cursor_amount = mm_strdup($2);
+ECPG: cursor_namename block
+ {
+ if (current_cursor)
+ free(current_cursor);
+ current_cursor = make3_str(mm_strdup("\""), mm_strdup($1), mm_strdup("\""));
+ $$ = $1;
+ }
| char_civar
{
char *curname = mm_alloc(strlen($1) + 2);
@@ -296,7 +434,7 @@ ECPG: PrepareStmtPREPAREprepared_nameprep_type_clauseASPreparableStmt block
}
ECPG: ExecuteStmtEXECUTEprepared_nameexecute_param_clauseexecute_rest block
{ $$ = $2; }
-ECPG: DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectStmt block
+ECPG: DeclareCursorStmtDECLAREcursor_namecursor_optionsopt_readaheadCURSORopt_holdFORSelectStmt block
{
struct cursor *ptr, *this;
char *cursor_marker = $2[0] == ':' ? mm_strdup("$0") : mm_strdup($2);
@@ -321,7 +459,13 @@ ECPG: DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectSt
this->function = (current_function ? mm_strdup(current_function) : NULL);
this->connection = connection;
this->opened = false;
- this->command = cat_str(7, mm_strdup("declare"), cursor_marker, $3, mm_strdup("cursor"), $5, mm_strdup("for"), $7);
+ if (!strcmp($4, "1"))
+ this->fetch_readahead = true;
+ else if (!strcmp($4, "0"))
+ this->fetch_readahead = false;
+ else
+ this->fetch_readahead = fetch_readahead;
+ this->command = cat_str(7, mm_strdup("declare"), cursor_marker, $3, mm_strdup("cursor"), $6, mm_strdup("for"), $8);
this->argsinsert = argsinsert;
this->argsinsert_oos = NULL;
this->argsresult = argsresult;
@@ -348,6 +492,8 @@ ECPG: DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectSt
ECPG: ClosePortalStmtCLOSEcursor_name block
{
char *cursor_marker = $2[0] == ':' ? mm_strdup("$0") : $2;
+ if (!INFORMIX_MODE || pg_strcasecmp($2, "database") != 0)
+ set_cursor_readahead($2);
$$ = cat2_str(mm_strdup("close"), cursor_marker);
}
ECPG: opt_hold block
diff --git a/src/interfaces/ecpg/preproc/ecpg.c b/src/interfaces/ecpg/preproc/ecpg.c
index 6f73148..afb898b 100644
--- a/src/interfaces/ecpg/preproc/ecpg.c
+++ b/src/interfaces/ecpg/preproc/ecpg.c
@@ -18,7 +18,8 @@ bool autocommit = false,
force_indicator = true,
questionmarks = false,
regression_mode = false,
- auto_prepare = false;
+ auto_prepare = false,
+ fetch_readahead = false;
char *output_filename;
@@ -51,7 +52,7 @@ help(const char *progname)
printf(_(" -I DIRECTORY search DIRECTORY for include files\n"));
printf(_(" -o OUTFILE write result to OUTFILE\n"));
printf(_(" -r OPTION specify run-time behavior; OPTION can be:\n"
- " \"no_indicator\", \"prepare\", \"questionmarks\"\n"));
+ " \"no_indicator\", \"prepare\", \"questionmarks\", \"fetch_readahead\"\n"));
printf(_(" --regression run in regression testing mode\n"));
printf(_(" -t turn on autocommit of transactions\n"));
printf(_(" --help show this help, then exit\n"));
@@ -229,6 +230,8 @@ main(int argc, char *const argv[])
auto_prepare = true;
else if (strcmp(optarg, "questionmarks") == 0)
questionmarks = true;
+ else if (strcmp(optarg, "fetch_readahead") == 0)
+ fetch_readahead = true;
else
{
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), argv[0]);
diff --git a/src/interfaces/ecpg/preproc/ecpg.header b/src/interfaces/ecpg/preproc/ecpg.header
index 1ea6ce4..1a0b23b 100644
--- a/src/interfaces/ecpg/preproc/ecpg.header
+++ b/src/interfaces/ecpg/preproc/ecpg.header
@@ -34,7 +34,11 @@
*/
int struct_level = 0;
int braces_open; /* brace level counter */
+bool use_fetch_readahead = false;
char *current_function;
+char *current_cursor = NULL;
+enum ECPG_cursor_direction current_cursor_direction;
+char *current_cursor_amount = NULL;
int ecpg_internal_var = 0;
char *connection = NULL;
char *input_filename = NULL;
@@ -111,6 +115,26 @@ mmerror(int error_code, enum errortype type, const char *error, ...)
}
/*
+ * set use_fetch_readahead based on the current cursor
+ * doesn't return if the cursor is not declared
+ */
+static void
+set_cursor_readahead(const char *curname)
+{
+ struct cursor *ptr;
+
+ for (ptr = cur; ptr != NULL; ptr = ptr->next)
+ {
+ if (strcmp(ptr->name, curname) == 0)
+ break;
+ }
+ if (!ptr)
+ mmerror(PARSE_ERROR, ET_FATAL, "cursor \"%s\" does not exist", curname);
+
+ use_fetch_readahead = ptr->fetch_readahead;
+}
+
+/*
* string concatenation
*/
diff --git a/src/interfaces/ecpg/preproc/ecpg.tokens b/src/interfaces/ecpg/preproc/ecpg.tokens
index b55138a..3995b59 100644
--- a/src/interfaces/ecpg/preproc/ecpg.tokens
+++ b/src/interfaces/ecpg/preproc/ecpg.tokens
@@ -10,7 +10,7 @@
SQL_FREE SQL_GET SQL_GO SQL_GOTO SQL_IDENTIFIED
SQL_INDICATOR SQL_KEY_MEMBER SQL_LENGTH
SQL_LONG SQL_NULLABLE SQL_OCTET_LENGTH
- SQL_OPEN SQL_OUTPUT SQL_REFERENCE
+ SQL_OPEN SQL_OUTPUT SQL_READAHEAD SQL_REFERENCE
SQL_RETURNED_LENGTH SQL_RETURNED_OCTET_LENGTH SQL_SCALE
SQL_SECTION SQL_SHORT SQL_SIGNED SQL_SQL SQL_SQLERROR
SQL_SQLPRINT SQL_SQLWARNING SQL_START SQL_STOP
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index a362aff..2bf960f 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -287,7 +287,7 @@ prepared_name: name
* Declare a prepared cursor. The syntax is different from the standard
* declare statement, so we create a new rule.
*/
-ECPGCursorStmt: DECLARE cursor_name cursor_options CURSOR opt_hold FOR prepared_name
+ECPGCursorStmt: DECLARE cursor_name cursor_options opt_readahead CURSOR opt_hold FOR prepared_name
{
struct cursor *ptr, *this;
char *cursor_marker = $2[0] == ':' ? mm_strdup("$0") : mm_strdup($2);
@@ -315,15 +315,22 @@ ECPGCursorStmt: DECLARE cursor_name cursor_options CURSOR opt_hold FOR prepared
this->name = $2;
this->function = (current_function ? mm_strdup(current_function) : NULL);
this->connection = connection;
- this->command = cat_str(6, mm_strdup("declare"), cursor_marker, $3, mm_strdup("cursor"), $5, mm_strdup("for $1"));
+ this->opened = false;
+ if (!strcmp($4, "1"))
+ this->fetch_readahead = true;
+ else if (!strcmp($4, "0"))
+ this->fetch_readahead = false;
+ else
+ this->fetch_readahead = fetch_readahead;
+ this->command = cat_str(6, mm_strdup("declare"), cursor_marker, $3, mm_strdup("cursor"), $6, mm_strdup("for $1"));
this->argsresult = NULL;
this->argsresult_oos = NULL;
thisquery->type = &ecpg_query;
thisquery->brace_level = 0;
thisquery->next = NULL;
- thisquery->name = (char *) mm_alloc(sizeof("ECPGprepared_statement(, , __LINE__)") + strlen(con) + strlen($7));
- sprintf(thisquery->name, "ECPGprepared_statement(%s, %s, __LINE__)", con, $7);
+ thisquery->name = (char *) mm_alloc(sizeof("ECPGprepared_statement(, , __LINE__)") + strlen(con) + strlen($8));
+ sprintf(thisquery->name, "ECPGprepared_statement(%s, %s, __LINE__)", con, $8);
this->argsinsert = NULL;
this->argsinsert_oos = NULL;
@@ -348,6 +355,11 @@ ECPGCursorStmt: DECLARE cursor_name cursor_options CURSOR opt_hold FOR prepared
}
;
+opt_readahead: SQL_READAHEAD { $$ = mm_strdup("1"); }
+ | NO SQL_READAHEAD { $$ = mm_strdup("0"); }
+ | /* EMPTY */ { $$ = mm_strdup("default"); }
+ ;
+
ECPGExecuteImmediateStmt: EXECUTE IMMEDIATE execstring
{
/* execute immediate means prepare the statement and
@@ -988,6 +1000,7 @@ ECPGOpen: SQL_OPEN cursor_name opt_ecpg_using
{
if ($2[0] == ':')
remove_variable_from_list(&argsinsert, find_variable($2 + 1));
+ set_cursor_readahead($2);
$$ = $2;
}
;
@@ -1656,6 +1669,10 @@ char_civar: char_variable
{
char *ptr = strstr($1, ".arr");
+ if (current_cursor)
+ free(current_cursor);
+ current_cursor = mm_strdup($1);
+
if (ptr) /* varchar, we need the struct name here, not the struct element */
*ptr = '\0';
add_variable_to_head(&argsinsert, find_variable($1), &no_indicator);
diff --git a/src/interfaces/ecpg/preproc/ecpg.type b/src/interfaces/ecpg/preproc/ecpg.type
index ac6aa00..2662372 100644
--- a/src/interfaces/ecpg/preproc/ecpg.type
+++ b/src/interfaces/ecpg/preproc/ecpg.type
@@ -85,6 +85,7 @@
%type 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 9958a0a..6e60392 100644
--- a/src/interfaces/ecpg/preproc/output.c
+++ b/src/interfaces/ecpg/preproc/output.c
@@ -112,10 +112,16 @@ static char *ecpg_statement_type_name[] = {
"ECPGst_prepnormal"
};
-void
-output_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
+static char *ecpg_cursor_direction_name[] = {
+ "ECPGc_absolute",
+ "ECPGc_relative",
+ "ECPGc_forward",
+ "ECPGc_backward"
+};
+
+static void
+output_statement_epilogue(char *stmt, int whenever_mode, enum ECPG_statement_type st)
{
- fprintf(yyout, "{ ECPGdo(__LINE__, %d, %d, %s, %d, ", compat, force_indicator, connection ? connection : "NULL", questionmarks);
if (st == ECPGst_execute || st == ECPGst_exec_immediate)
{
fprintf(yyout, "%s, %s, ", ecpg_statement_type_name[st], stmt);
@@ -145,6 +151,13 @@ output_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
}
void
+output_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
+{
+ fprintf(yyout, "{ ECPGdo(__LINE__, %d, %d, %s, %d, ", compat, force_indicator, connection ? connection : "NULL", questionmarks);
+ output_statement_epilogue(stmt, whenever_mode, st);
+}
+
+void
output_prepare_statement(char *name, char *stmt)
{
fprintf(yyout, "{ ECPGprepare(__LINE__, %s, %d, ", connection ? connection : "NULL", questionmarks);
@@ -178,6 +191,51 @@ output_deallocate_prepare_statement(char *name)
free(connection);
}
+void
+output_open_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
+{
+ fprintf(yyout, "{ ECPGopen(__LINE__, %d, %d, %s, %d, %s, ",
+ compat,
+ force_indicator,
+ connection ? connection : "NULL",
+ questionmarks,
+ current_cursor);
+ output_statement_epilogue(stmt, whenever_mode, st);
+}
+
+void
+output_fetch_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
+{
+ char *amount = mm_alloc(strlen(current_cursor_amount) + 3);
+
+ if (!amount)
+ return;
+
+ sprintf(amount, "\"%s\"", current_cursor_amount);
+ fprintf(yyout, "{ ECPGfetch(__LINE__, %d, %d, %s, %d, %s, %s, %s, ",
+ compat,
+ force_indicator,
+ connection ? connection : "NULL",
+ questionmarks,
+ current_cursor,
+ ecpg_cursor_direction_name[current_cursor_direction],
+ amount);
+ output_statement_epilogue(stmt, whenever_mode, st);
+ free(amount);
+}
+
+void
+output_close_statement(char *stmt, int whenever_mode, enum ECPG_statement_type st)
+{
+ fprintf(yyout, "{ ECPGclose(__LINE__, %d, %d, %s, %d, %s, ",
+ compat,
+ force_indicator,
+ connection ? connection : "NULL",
+ questionmarks,
+ current_cursor);
+ output_statement_epilogue(stmt, whenever_mode, st);
+}
+
static void
output_escaped_str(char *str, bool quoted)
{
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index 515470e..aebcaf4 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -90,6 +90,8 @@ my %replace_line = (
'fetch_argsFORWARDopt_from_incursor_name' => 'ignore',
'fetch_argsBACKWARDopt_from_incursor_name' => 'ignore',
"opt_array_boundsopt_array_bounds'['Iconst']'" => 'ignore',
+ 'DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectStmt' =>
+ 'DECLARE cursor_name cursor_options opt_readahead CURSOR opt_hold FOR SelectStmt',
'VariableShowStmtSHOWvar_name' => 'SHOW var_name ecpg_into',
'VariableShowStmtSHOWTIMEZONE' => 'SHOW TIME ZONE ecpg_into',
'VariableShowStmtSHOWTRANSACTIONISOLATIONLEVEL' => 'SHOW TRANSACTION ISOLATION LEVEL ecpg_into',
diff --git a/src/interfaces/ecpg/preproc/type.h b/src/interfaces/ecpg/preproc/type.h
index 68e0d1a..89c920f 100644
--- a/src/interfaces/ecpg/preproc/type.h
+++ b/src/interfaces/ecpg/preproc/type.h
@@ -130,6 +130,7 @@ struct cursor
char *command;
char *connection;
bool opened;
+ bool fetch_readahead;
struct arguments *argsinsert;
struct arguments *argsinsert_oos;
struct arguments *argsresult;
diff --git a/src/interfaces/ecpg/test/ecpg_schedule b/src/interfaces/ecpg/test/ecpg_schedule
index c07ea93..a15b7d9 100644
--- a/src/interfaces/ecpg/test/ecpg_schedule
+++ b/src/interfaces/ecpg/test/ecpg_schedule
@@ -20,6 +20,7 @@ test: preproc/array_of_struct
test: preproc/autoprep
test: preproc/comment
test: preproc/cursor
+test: preproc/cursor-readahead
test: preproc/define
test: preproc/init
test: preproc/strings
diff --git a/src/interfaces/ecpg/test/ecpg_schedule_tcp b/src/interfaces/ecpg/test/ecpg_schedule_tcp
index 77481b5..60994e0 100644
--- a/src/interfaces/ecpg/test/ecpg_schedule_tcp
+++ b/src/interfaces/ecpg/test/ecpg_schedule_tcp
@@ -20,6 +20,7 @@ test: preproc/array_of_struct
test: preproc/autoprep
test: preproc/comment
test: preproc/cursor
+test: preproc/cursor-readahead
test: preproc/define
test: preproc/init
test: preproc/strings
diff --git a/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.c b/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.c
new file mode 100644
index 0000000..ef39815
--- /dev/null
+++ b/src/interfaces/ecpg/test/expected/preproc-cursor-readahead.c
@@ -0,0 +1,663 @@
+/* Processed by ecpg (regression mode) */
+/* These include files are added by the preprocessor */
+#include
+#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;
+}