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; +}