From f43b5de649cdf8a36f7d90a96932800cb0e34162 Mon Sep 17 00:00:00 2001 From: Hiroshi Inoue Date: Fri, 11 Jan 2002 02:50:01 +0000 Subject: [PATCH] Add a directory to save the changes until 7.3-tree is branched. --- src/interfaces/odbc/windev/README.txt | 9 + src/interfaces/odbc/windev/bind.c | 487 ++++ src/interfaces/odbc/windev/bind.h | 55 + src/interfaces/odbc/windev/columninfo.c | 191 ++ src/interfaces/odbc/windev/columninfo.h | 42 + src/interfaces/odbc/windev/connection.c | 1839 ++++++++++++ src/interfaces/odbc/windev/connection.h | 312 +++ src/interfaces/odbc/windev/convert.c | 2655 ++++++++++++++++++ src/interfaces/odbc/windev/convert.h | 52 + src/interfaces/odbc/windev/dlg_specific.c | 1138 ++++++++ src/interfaces/odbc/windev/dlg_specific.h | 147 + src/interfaces/odbc/windev/drvconn.c | 435 +++ src/interfaces/odbc/windev/environ.c | 588 ++++ src/interfaces/odbc/windev/environ.h | 31 + src/interfaces/odbc/windev/execute.c | 955 +++++++ src/interfaces/odbc/windev/info.c | 3714 +++++++++++++++++++++++++ src/interfaces/odbc/windev/iodbc.h | 68 + src/interfaces/odbc/windev/license.txt | 962 +++++++ src/interfaces/odbc/windev/lobj.c | 186 ++ src/interfaces/odbc/windev/lobj.h | 47 + src/interfaces/odbc/windev/md5.c | 351 +++ src/interfaces/odbc/windev/md5.h | 49 + src/interfaces/odbc/windev/misc.c | 306 ++ src/interfaces/odbc/windev/misc.h | 98 + src/interfaces/odbc/windev/multibyte.c | 138 + src/interfaces/odbc/windev/multibyte.h | 39 + src/interfaces/odbc/windev/notice.txt | 35 + src/interfaces/odbc/windev/odbcapi.c | 661 +++++ src/interfaces/odbc/windev/odbcapi30.c | 752 +++++ src/interfaces/odbc/windev/options.c | 668 +++++ src/interfaces/odbc/windev/parse.c | 954 +++++++ src/interfaces/odbc/windev/pgapifunc.h | 244 ++ src/interfaces/odbc/windev/pgtypes.c | 1109 ++++++++ src/interfaces/odbc/windev/pgtypes.h | 99 + src/interfaces/odbc/windev/psqlodbc.c | 132 + src/interfaces/odbc/windev/psqlodbc.h | 268 ++ src/interfaces/odbc/windev/psqlodbc.rc | 425 +++ src/interfaces/odbc/windev/psqlodbc.reg | 17 + src/interfaces/odbc/windev/psqlodbc_win32.def | 60 + src/interfaces/odbc/windev/qresult.c | 712 +++++ src/interfaces/odbc/windev/qresult.h | 131 + src/interfaces/odbc/windev/resource.h | 66 + src/interfaces/odbc/windev/results.c | 1937 +++++++++++++ src/interfaces/odbc/windev/setup.c | 425 +++ src/interfaces/odbc/windev/setup.rul | 495 ++++ src/interfaces/odbc/windev/socket.c | 355 +++ src/interfaces/odbc/windev/socket.h | 94 + src/interfaces/odbc/windev/statement.c | 1161 ++++++++ src/interfaces/odbc/windev/statement.h | 254 ++ src/interfaces/odbc/windev/tuple.c | 66 + src/interfaces/odbc/windev/tuple.h | 46 + src/interfaces/odbc/windev/tuplelist.c | 216 ++ src/interfaces/odbc/windev/tuplelist.h | 35 + src/interfaces/odbc/windev/win32.mak | 507 ++++ src/interfaces/odbc/windev/win_md5.c | 15 + 55 files changed, 26833 insertions(+) create mode 100644 src/interfaces/odbc/windev/README.txt create mode 100644 src/interfaces/odbc/windev/bind.c create mode 100644 src/interfaces/odbc/windev/bind.h create mode 100644 src/interfaces/odbc/windev/columninfo.c create mode 100644 src/interfaces/odbc/windev/columninfo.h create mode 100644 src/interfaces/odbc/windev/connection.c create mode 100644 src/interfaces/odbc/windev/connection.h create mode 100644 src/interfaces/odbc/windev/convert.c create mode 100644 src/interfaces/odbc/windev/convert.h create mode 100644 src/interfaces/odbc/windev/dlg_specific.c create mode 100644 src/interfaces/odbc/windev/dlg_specific.h create mode 100644 src/interfaces/odbc/windev/drvconn.c create mode 100644 src/interfaces/odbc/windev/environ.c create mode 100644 src/interfaces/odbc/windev/environ.h create mode 100644 src/interfaces/odbc/windev/execute.c create mode 100644 src/interfaces/odbc/windev/info.c create mode 100644 src/interfaces/odbc/windev/iodbc.h create mode 100644 src/interfaces/odbc/windev/license.txt create mode 100644 src/interfaces/odbc/windev/lobj.c create mode 100644 src/interfaces/odbc/windev/lobj.h create mode 100644 src/interfaces/odbc/windev/md5.c create mode 100644 src/interfaces/odbc/windev/md5.h create mode 100644 src/interfaces/odbc/windev/misc.c create mode 100644 src/interfaces/odbc/windev/misc.h create mode 100644 src/interfaces/odbc/windev/multibyte.c create mode 100644 src/interfaces/odbc/windev/multibyte.h create mode 100644 src/interfaces/odbc/windev/notice.txt create mode 100644 src/interfaces/odbc/windev/odbcapi.c create mode 100644 src/interfaces/odbc/windev/odbcapi30.c create mode 100644 src/interfaces/odbc/windev/options.c create mode 100644 src/interfaces/odbc/windev/parse.c create mode 100644 src/interfaces/odbc/windev/pgapifunc.h create mode 100644 src/interfaces/odbc/windev/pgtypes.c create mode 100644 src/interfaces/odbc/windev/pgtypes.h create mode 100644 src/interfaces/odbc/windev/psqlodbc.c create mode 100644 src/interfaces/odbc/windev/psqlodbc.h create mode 100644 src/interfaces/odbc/windev/psqlodbc.rc create mode 100644 src/interfaces/odbc/windev/psqlodbc.reg create mode 100644 src/interfaces/odbc/windev/psqlodbc_win32.def create mode 100644 src/interfaces/odbc/windev/qresult.c create mode 100644 src/interfaces/odbc/windev/qresult.h create mode 100644 src/interfaces/odbc/windev/resource.h create mode 100644 src/interfaces/odbc/windev/results.c create mode 100644 src/interfaces/odbc/windev/setup.c create mode 100644 src/interfaces/odbc/windev/setup.rul create mode 100644 src/interfaces/odbc/windev/socket.c create mode 100644 src/interfaces/odbc/windev/socket.h create mode 100644 src/interfaces/odbc/windev/statement.c create mode 100644 src/interfaces/odbc/windev/statement.h create mode 100644 src/interfaces/odbc/windev/tuple.c create mode 100644 src/interfaces/odbc/windev/tuple.h create mode 100644 src/interfaces/odbc/windev/tuplelist.c create mode 100644 src/interfaces/odbc/windev/tuplelist.h create mode 100644 src/interfaces/odbc/windev/win32.mak create mode 100644 src/interfaces/odbc/windev/win_md5.c diff --git a/src/interfaces/odbc/windev/README.txt b/src/interfaces/odbc/windev/README.txt new file mode 100644 index 0000000000..1070c0d2c7 --- /dev/null +++ b/src/interfaces/odbc/windev/README.txt @@ -0,0 +1,9 @@ +This directory is to save the changes about psqlodbc driver under +win32 while the main development tree(the parent directory) is +nearly freezed(i.e. during beta, a while after the release until +the next branch is made etc). The changes would be reflected +to the main directory later after all. However note that the +trial binary version would be sometimes made using the source +and put on the ftp server for download. + + Hiroshi Inoue inoue@postgresql.org diff --git a/src/interfaces/odbc/windev/bind.c b/src/interfaces/odbc/windev/bind.c new file mode 100644 index 0000000000..e9f5cc3482 --- /dev/null +++ b/src/interfaces/odbc/windev/bind.c @@ -0,0 +1,487 @@ +/*------- + * Module: bind.c + * + * Description: This module contains routines related to binding + * columns and parameters. + * + * Classes: BindInfoClass, ParameterInfoClass + * + * API functions: SQLBindParameter, SQLBindCol, SQLDescribeParam, SQLNumParams, + * SQLParamOptions(NI) + * + * Comments: See "notice.txt" for copyright and license information. + *------- + */ + +#include "bind.h" + +#include "environ.h" +#include "statement.h" +#include "qresult.h" +#include "pgtypes.h" +#include +#include + +#include "pgapifunc.h" + + +/* Bind parameters on a statement handle */ +RETCODE SQL_API +PGAPI_BindParameter( + HSTMT hstmt, + UWORD ipar, + SWORD fParamType, + SWORD fCType, + SWORD fSqlType, + UDWORD cbColDef, + SWORD ibScale, + PTR rgbValue, + SDWORD cbValueMax, + SDWORD FAR * pcbValue) +{ + StatementClass *stmt = (StatementClass *) hstmt; + static char *func = "PGAPI_BindParameter"; + + mylog("%s: entering...\n", func); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + SC_clear_error(stmt); + + if (stmt->parameters_allocated < ipar) + { + ParameterInfoClass *old_parameters; + int i, + old_parameters_allocated; + + old_parameters = stmt->parameters; + old_parameters_allocated = stmt->parameters_allocated; + + stmt->parameters = (ParameterInfoClass *) malloc(sizeof(ParameterInfoClass) * (ipar)); + if (!stmt->parameters) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Could not allocate memory for statement parameters"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + stmt->parameters_allocated = ipar; + + /* copy the old parameters over */ + for (i = 0; i < old_parameters_allocated; i++) + { + /* a structure copy should work */ + stmt->parameters[i] = old_parameters[i]; + } + + /* get rid of the old parameters, if there were any */ + if (old_parameters) + free(old_parameters); + + /* + * zero out the newly allocated parameters (in case they skipped + * some, + */ + /* so we don't accidentally try to use them later) */ + for (; i < stmt->parameters_allocated; i++) + { + stmt->parameters[i].buflen = 0; + stmt->parameters[i].buffer = 0; + stmt->parameters[i].used = 0; + stmt->parameters[i].paramType = 0; + stmt->parameters[i].CType = 0; + stmt->parameters[i].SQLType = 0; + stmt->parameters[i].precision = 0; + stmt->parameters[i].scale = 0; + stmt->parameters[i].data_at_exec = FALSE; + stmt->parameters[i].lobj_oid = 0; + stmt->parameters[i].EXEC_used = NULL; + stmt->parameters[i].EXEC_buffer = NULL; + } + } + + /* use zero based column numbers for the below part */ + ipar--; + + /* store the given info */ + stmt->parameters[ipar].buflen = cbValueMax; + stmt->parameters[ipar].buffer = rgbValue; + stmt->parameters[ipar].used = pcbValue; + stmt->parameters[ipar].paramType = fParamType; + stmt->parameters[ipar].CType = fCType; + stmt->parameters[ipar].SQLType = fSqlType; + stmt->parameters[ipar].precision = cbColDef; + stmt->parameters[ipar].scale = ibScale; + + /* + * If rebinding a parameter that had data-at-exec stuff in it, then + * free that stuff + */ + if (stmt->parameters[ipar].EXEC_used) + { + free(stmt->parameters[ipar].EXEC_used); + stmt->parameters[ipar].EXEC_used = NULL; + } + + if (stmt->parameters[ipar].EXEC_buffer) + { + if (stmt->parameters[ipar].SQLType != SQL_LONGVARBINARY) + free(stmt->parameters[ipar].EXEC_buffer); + stmt->parameters[ipar].EXEC_buffer = NULL; + } + + /* Data at exec macro only valid for C char/binary data */ + if (pcbValue && (*pcbValue == SQL_DATA_AT_EXEC || + *pcbValue <= SQL_LEN_DATA_AT_EXEC_OFFSET)) + stmt->parameters[ipar].data_at_exec = TRUE; + else + stmt->parameters[ipar].data_at_exec = FALSE; + + /* Clear premature result */ + if (stmt->status == STMT_PREMATURE) + SC_recycle_statement(stmt); + + mylog("PGAPI_BindParamater: ipar=%d, paramType=%d, fCType=%d, fSqlType=%d, cbColDef=%d, ibScale=%d, rgbValue=%d, *pcbValue = %d, data_at_exec = %d\n", ipar, fParamType, fCType, fSqlType, cbColDef, ibScale, rgbValue, pcbValue ? *pcbValue : -777, stmt->parameters[ipar].data_at_exec); + + return SQL_SUCCESS; +} + + +/* Associate a user-supplied buffer with a database column. */ +RETCODE SQL_API +PGAPI_BindCol( + HSTMT hstmt, + UWORD icol, + SWORD fCType, + PTR rgbValue, + SDWORD cbValueMax, + SDWORD FAR * pcbValue) +{ + StatementClass *stmt = (StatementClass *) hstmt; + static char *func = "PGAPI_BindCol"; + + mylog("%s: entering...\n", func); + + mylog("**** PGAPI_BindCol: stmt = %u, icol = %d\n", stmt, icol); + mylog("**** : fCType=%d rgb=%x valusMax=%d pcb=%x\n", fCType, rgbValue, cbValueMax, pcbValue); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + + SC_clear_error(stmt); + + if (stmt->status == STMT_EXECUTING) + { + stmt->errormsg = "Can't bind columns while statement is still executing."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* If the bookmark column is being bound, then just save it */ + if (icol == 0) + { + if (rgbValue == NULL) + { + stmt->bookmark.buffer = NULL; + stmt->bookmark.used = NULL; + } + else + { + /* Make sure it is the bookmark data type */ + if (fCType != SQL_C_BOOKMARK) + { + stmt->errormsg = "Column 0 is not of type SQL_C_BOOKMARK"; + stmt->errornumber = STMT_PROGRAM_TYPE_OUT_OF_RANGE; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + stmt->bookmark.buffer = rgbValue; + stmt->bookmark.used = pcbValue; + } + return SQL_SUCCESS; + } + + /* + * Allocate enough bindings if not already done. Most likely, + * execution of a statement would have setup the necessary bindings. + * But some apps call BindCol before any statement is executed. + */ + if (icol > stmt->bindings_allocated) + extend_bindings(stmt, icol); + + /* check to see if the bindings were allocated */ + if (!stmt->bindings) + { + stmt->errormsg = "Could not allocate memory for bindings."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* use zero based col numbers from here out */ + icol--; + + /* Reset for SQLGetData */ + stmt->bindings[icol].data_left = -1; + + if (rgbValue == NULL) + { + /* we have to unbind the column */ + stmt->bindings[icol].buflen = 0; + stmt->bindings[icol].buffer = NULL; + stmt->bindings[icol].used = NULL; + stmt->bindings[icol].returntype = SQL_C_CHAR; + } + else + { + /* ok, bind that column */ + stmt->bindings[icol].buflen = cbValueMax; + stmt->bindings[icol].buffer = rgbValue; + stmt->bindings[icol].used = pcbValue; + stmt->bindings[icol].returntype = fCType; + + mylog(" bound buffer[%d] = %u\n", icol, stmt->bindings[icol].buffer); + } + + return SQL_SUCCESS; +} + + +/* + * Returns the description of a parameter marker. + * This function is listed as not being supported by SQLGetFunctions() because it is + * used to describe "parameter markers" (not bound parameters), in which case, + * the dbms should return info on the markers. Since Postgres doesn't support that, + * it is best to say this function is not supported and let the application assume a + * data type (most likely varchar). + */ +RETCODE SQL_API +PGAPI_DescribeParam( + HSTMT hstmt, + UWORD ipar, + SWORD FAR * pfSqlType, + UDWORD FAR * pcbColDef, + SWORD FAR * pibScale, + SWORD FAR * pfNullable) +{ + StatementClass *stmt = (StatementClass *) hstmt; + static char *func = "PGAPI_DescribeParam"; + + mylog("%s: entering...\n", func); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + SC_clear_error(stmt); + + if ((ipar < 1) || (ipar > stmt->parameters_allocated)) + { + stmt->errormsg = "Invalid parameter number for PGAPI_DescribeParam."; + stmt->errornumber = STMT_BAD_PARAMETER_NUMBER_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + ipar--; + + /* + * This implementation is not very good, since it is supposed to + * describe + */ + /* parameter markers, not bound parameters. */ + if (pfSqlType) + *pfSqlType = stmt->parameters[ipar].SQLType; + + if (pcbColDef) + *pcbColDef = stmt->parameters[ipar].precision; + + if (pibScale) + *pibScale = stmt->parameters[ipar].scale; + + if (pfNullable) + *pfNullable = pgtype_nullable(stmt, stmt->parameters[ipar].paramType); + + return SQL_SUCCESS; +} + + +/* Sets multiple values (arrays) for the set of parameter markers. */ +RETCODE SQL_API +PGAPI_ParamOptions( + HSTMT hstmt, + UDWORD crow, + UDWORD FAR * pirow) +{ + static char *func = "PGAPI_ParamOptions"; + StatementClass *stmt = (StatementClass *) hstmt; + + mylog("%s: entering... %d %x\n", func, crow, pirow); + + if (crow == 1) /* temporary solution and must be + * rewritten later */ + { + if (pirow) + *pirow = 1; + return SQL_SUCCESS; + } + stmt->errornumber = CONN_UNSUPPORTED_OPTION; + stmt->errormsg = "Function not implemented"; + SC_log_error(func, "Function not implemented", (StatementClass *) hstmt); + return SQL_ERROR; +} + + +/* + * This function should really talk to the dbms to determine the number of + * "parameter markers" (not bound parameters) in the statement. But, since + * Postgres doesn't support that, the driver should just count the number of markers + * and return that. The reason the driver just can't say this function is unsupported + * like it does for SQLDescribeParam is that some applications don't care and try + * to call it anyway. + * If the statement does not have parameters, it should just return 0. + */ +RETCODE SQL_API +PGAPI_NumParams( + HSTMT hstmt, + SWORD FAR * pcpar) +{ + StatementClass *stmt = (StatementClass *) hstmt; + char in_quote = FALSE; + unsigned int i; + static char *func = "PGAPI_NumParams"; + + mylog("%s: entering...\n", func); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + SC_clear_error(stmt); + + if (pcpar) + *pcpar = 0; + else + { + SC_log_error(func, "pcpar was null", stmt); + return SQL_ERROR; + } + + + if (!stmt->statement) + { + /* no statement has been allocated */ + stmt->errormsg = "PGAPI_NumParams called with no statement ready."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + else + { + for (i = 0; i < strlen(stmt->statement); i++) + { + if (stmt->statement[i] == '?' && !in_quote) + (*pcpar)++; + else + { + if (stmt->statement[i] == '\'') + in_quote = (in_quote ? FALSE : TRUE); + } + } + return SQL_SUCCESS; + } +} + + +/* + * Bindings Implementation + */ +BindInfoClass * +create_empty_bindings(int num_columns) +{ + BindInfoClass *new_bindings; + int i; + + new_bindings = (BindInfoClass *) malloc(num_columns * sizeof(BindInfoClass)); + if (!new_bindings) + return 0; + + for (i = 0; i < num_columns; i++) + { + new_bindings[i].buflen = 0; + new_bindings[i].buffer = NULL; + new_bindings[i].used = NULL; + new_bindings[i].data_left = -1; + new_bindings[i].ttlbuf = NULL; + new_bindings[i].ttlbuflen = 0; + } + + return new_bindings; +} + + +void +extend_bindings(StatementClass *stmt, int num_columns) +{ + static char *func = "extend_bindings"; + BindInfoClass *new_bindings; + int i; + + mylog("%s: entering ... stmt=%u, bindings_allocated=%d, num_columns=%d\n", func, stmt, stmt->bindings_allocated, num_columns); + + /* + * if we have too few, allocate room for more, and copy the old + * entries into the new structure + */ + if (stmt->bindings_allocated < num_columns) + { + new_bindings = create_empty_bindings(num_columns); + if (!new_bindings) + { + mylog("%s: unable to create %d new bindings from %d old bindings\n", func, num_columns, stmt->bindings_allocated); + + if (stmt->bindings) + { + free(stmt->bindings); + stmt->bindings = NULL; + } + stmt->bindings_allocated = 0; + return; + } + + if (stmt->bindings) + { + for (i = 0; i < stmt->bindings_allocated; i++) + new_bindings[i] = stmt->bindings[i]; + + free(stmt->bindings); + } + + stmt->bindings = new_bindings; + stmt->bindings_allocated = num_columns; + } + + /* + * There is no reason to zero out extra bindings if there are more + * than needed. If an app has allocated extra bindings, let it worry + * about it by unbinding those columns. + */ + + /* SQLBindCol(1..) ... SQLBindCol(10...) # got 10 bindings */ + /* SQLExecDirect(...) # returns 5 cols */ + /* SQLExecDirect(...) # returns 10 cols (now OK) */ + + mylog("exit extend_bindings\n"); +} diff --git a/src/interfaces/odbc/windev/bind.h b/src/interfaces/odbc/windev/bind.h new file mode 100644 index 0000000000..34b9e1d399 --- /dev/null +++ b/src/interfaces/odbc/windev/bind.h @@ -0,0 +1,55 @@ +/* File: bind.h + * + * Description: See "bind.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __BIND_H__ +#define __BIND_H__ + +#include "psqlodbc.h" + +/* + * BindInfoClass -- stores information about a bound column + */ +struct BindInfoClass_ +{ + Int4 buflen; /* size of buffer */ + Int4 data_left; /* amount of data left to read + * (SQLGetData) */ + char *buffer; /* pointer to the buffer */ + Int4 *used; /* used space in the buffer (for strings + * not counting the '\0') */ + char *ttlbuf; /* to save the large result */ + Int4 ttlbuflen; /* the buffer length */ + Int2 returntype; /* kind of conversion to be applied when + * returning (SQL_C_DEFAULT, + * SQL_C_CHAR...) */ +}; + +/* + * ParameterInfoClass -- stores information about a bound parameter + */ +struct ParameterInfoClass_ +{ + Int4 buflen; + char *buffer; + Int4 *used; + Int2 paramType; + Int2 CType; + Int2 SQLType; + UInt4 precision; + Int2 scale; + Oid lobj_oid; + Int4 *EXEC_used; /* amount of data OR the oid of the large + * object */ + char *EXEC_buffer; /* the data or the FD of the large object */ + char data_at_exec; +}; + +BindInfoClass *create_empty_bindings(int num_columns); +void extend_bindings(StatementClass *stmt, int num_columns); + +#endif diff --git a/src/interfaces/odbc/windev/columninfo.c b/src/interfaces/odbc/windev/columninfo.c new file mode 100644 index 0000000000..7fe72d3f6d --- /dev/null +++ b/src/interfaces/odbc/windev/columninfo.c @@ -0,0 +1,191 @@ +/*------- + * Module: columninfo.c + * + * Description: This module contains routines related to + * reading and storing the field information from a query. + * + * Classes: ColumnInfoClass (Functions prefix: "CI_") + * + * API functions: none + * + * Comments: See "notice.txt" for copyright and license information. + *------- + */ + +#include "pgtypes.h" +#include "columninfo.h" + +#include "connection.h" +#include "socket.h" +#include +#include +#include "pgapifunc.h" + +ColumnInfoClass * +CI_Constructor() +{ + ColumnInfoClass *rv; + + rv = (ColumnInfoClass *) malloc(sizeof(ColumnInfoClass)); + + if (rv) + { + rv->num_fields = 0; + rv->name = NULL; + rv->adtid = NULL; + rv->adtsize = NULL; + rv->display_size = NULL; + rv->atttypmod = NULL; + } + + return rv; +} + + +void +CI_Destructor(ColumnInfoClass *self) +{ + CI_free_memory(self); + + free(self); +} + + +/* + * Read in field descriptions. + * If self is not null, then also store the information. + * If self is null, then just read, don't store. + */ +char +CI_read_fields(ColumnInfoClass *self, ConnectionClass *conn) +{ + Int2 lf; + int new_num_fields; + Oid new_adtid; + Int2 new_adtsize; + Int4 new_atttypmod = -1; + + /* MAX_COLUMN_LEN may be sufficient but for safety */ + char new_field_name[2 * MAX_COLUMN_LEN + 1]; + SocketClass *sock; + ConnInfo *ci; + + sock = CC_get_socket(conn); + ci = &conn->connInfo; + + /* at first read in the number of fields that are in the query */ + new_num_fields = (Int2) SOCK_get_int(sock, sizeof(Int2)); + + mylog("num_fields = %d\n", new_num_fields); + + if (self) + /* according to that allocate memory */ + CI_set_num_fields(self, new_num_fields); + + /* now read in the descriptions */ + for (lf = 0; lf < new_num_fields; lf++) + { + SOCK_get_string(sock, new_field_name, 2 * MAX_COLUMN_LEN); + new_adtid = (Oid) SOCK_get_int(sock, 4); + new_adtsize = (Int2) SOCK_get_int(sock, 2); + + /* If 6.4 protocol, then read the atttypmod field */ + if (PG_VERSION_GE(conn, 6.4)) + { + mylog("READING ATTTYPMOD\n"); + new_atttypmod = (Int4) SOCK_get_int(sock, 4); + + /* Subtract the header length */ + switch (new_adtid) + { + case PG_TYPE_DATETIME: + case PG_TYPE_TIMESTAMP_NO_TMZONE: + case PG_TYPE_TIME: + case PG_TYPE_TIME_WITH_TMZONE: + break; + default: + new_atttypmod -= 4; + } + if (new_atttypmod < 0) + new_atttypmod = -1; + + } + + mylog("CI_read_fields: fieldname='%s', adtid=%d, adtsize=%d, atttypmod=%d\n", new_field_name, new_adtid, new_adtsize, new_atttypmod); + + if (self) + CI_set_field_info(self, lf, new_field_name, new_adtid, new_adtsize, new_atttypmod); + } + + return (SOCK_get_errcode(sock) == 0); +} + + +void +CI_free_memory(ColumnInfoClass *self) +{ + register Int2 lf; + int num_fields = self->num_fields; + + for (lf = 0; lf < num_fields; lf++) + { + if (self->name[lf]) + { + free(self->name[lf]); + self->name[lf] = NULL; + } + } + + /* Safe to call even if null */ + self->num_fields = 0; + if (self->name) + free(self->name); + self->name = NULL; + if (self->adtid) + free(self->adtid); + self->adtid = NULL; + if (self->adtsize) + free(self->adtsize); + self->adtsize = NULL; + if (self->display_size) + free(self->display_size); + self->display_size = NULL; + + if (self->atttypmod) + free(self->atttypmod); + self->atttypmod = NULL; +} + + +void +CI_set_num_fields(ColumnInfoClass *self, int new_num_fields) +{ + CI_free_memory(self); /* always safe to call */ + + self->num_fields = new_num_fields; + + self->name = (char **) malloc(sizeof(char *) * self->num_fields); + memset(self->name, 0, sizeof(char *) * self->num_fields); + self->adtid = (Oid *) malloc(sizeof(Oid) * self->num_fields); + self->adtsize = (Int2 *) malloc(sizeof(Int2) * self->num_fields); + self->display_size = (Int2 *) malloc(sizeof(Int2) * self->num_fields); + self->atttypmod = (Int4 *) malloc(sizeof(Int4) * self->num_fields); +} + + +void +CI_set_field_info(ColumnInfoClass *self, int field_num, char *new_name, + Oid new_adtid, Int2 new_adtsize, Int4 new_atttypmod) +{ + /* check bounds */ + if ((field_num < 0) || (field_num >= self->num_fields)) + return; + + /* store the info */ + self->name[field_num] = strdup(new_name); + self->adtid[field_num] = new_adtid; + self->adtsize[field_num] = new_adtsize; + self->atttypmod[field_num] = new_atttypmod; + + self->display_size[field_num] = 0; +} diff --git a/src/interfaces/odbc/windev/columninfo.h b/src/interfaces/odbc/windev/columninfo.h new file mode 100644 index 0000000000..41e9400dce --- /dev/null +++ b/src/interfaces/odbc/windev/columninfo.h @@ -0,0 +1,42 @@ +/* File: columninfo.h + * + * Description: See "columninfo.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __COLUMNINFO_H__ +#define __COLUMNINFO_H__ + +#include "psqlodbc.h" + +struct ColumnInfoClass_ +{ + Int2 num_fields; + char **name; /* list of type names */ + Oid *adtid; /* list of type ids */ + Int2 *adtsize; /* list type sizes */ + Int2 *display_size; /* the display size (longest row) */ + Int4 *atttypmod; /* the length of bpchar/varchar */ +}; + +#define CI_get_num_fields(self) (self->num_fields) +#define CI_get_oid(self, col) (self->adtid[col]) +#define CI_get_fieldname(self, col) (self->name[col]) +#define CI_get_fieldsize(self, col) (self->adtsize[col]) +#define CI_get_display_size(self, col) (self->display_size[col]) +#define CI_get_atttypmod(self, col) (self->atttypmod[col]) + +ColumnInfoClass *CI_Constructor(void); +void CI_Destructor(ColumnInfoClass *self); +void CI_free_memory(ColumnInfoClass *self); +char CI_read_fields(ColumnInfoClass *self, ConnectionClass *conn); + +/* functions for setting up the fields from within the program, */ +/* without reading from a socket */ +void CI_set_num_fields(ColumnInfoClass *self, int new_num_fields); +void CI_set_field_info(ColumnInfoClass *self, int field_num, char *new_name, + Oid new_adtid, Int2 new_adtsize, Int4 atttypmod); + +#endif diff --git a/src/interfaces/odbc/windev/connection.c b/src/interfaces/odbc/windev/connection.c new file mode 100644 index 0000000000..e057d7b73f --- /dev/null +++ b/src/interfaces/odbc/windev/connection.c @@ -0,0 +1,1839 @@ +/*------ + * Module: connection.c + * + * Description: This module contains routines related to + * connecting to and disconnecting from the Postgres DBMS. + * + * Classes: ConnectionClass (Functions prefix: "CC_") + * + * API functions: SQLAllocConnect, SQLConnect, SQLDisconnect, SQLFreeConnect, + * SQLBrowseConnect(NI) + * + * Comments: See "notice.txt" for copyright and license information. + *------- + */ +/* Multibyte support Eiji Tokuya 2001-03-15 */ + +#include "connection.h" + +#include +#include +#include + +#include "environ.h" +#include "socket.h" +#include "statement.h" +#include "qresult.h" +#include "lobj.h" +#include "dlg_specific.h" + +#ifdef MULTIBYTE +#include "multibyte.h" +#endif + +#include "pgapifunc.h" +#include "md5.h" + +#define STMT_INCREMENT 16 /* how many statement holders to allocate + * at a time */ + +#define PRN_NULLCHECK + +extern GLOBAL_VALUES globals; + + +RETCODE SQL_API +PGAPI_AllocConnect( + HENV henv, + HDBC FAR * phdbc) +{ + EnvironmentClass *env = (EnvironmentClass *) henv; + ConnectionClass *conn; + static char *func = "PGAPI_AllocConnect"; + + mylog("%s: entering...\n", func); + + conn = CC_Constructor(); + mylog("**** %s: henv = %u, conn = %u\n", func, henv, conn); + + if (!conn) + { + env->errormsg = "Couldn't allocate memory for Connection object."; + env->errornumber = ENV_ALLOC_ERROR; + *phdbc = SQL_NULL_HDBC; + EN_log_error(func, "", env); + return SQL_ERROR; + } + + if (!EN_add_connection(env, conn)) + { + env->errormsg = "Maximum number of connections exceeded."; + env->errornumber = ENV_ALLOC_ERROR; + CC_Destructor(conn); + *phdbc = SQL_NULL_HDBC; + EN_log_error(func, "", env); + return SQL_ERROR; + } + + *phdbc = (HDBC) conn; + + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_Connect( + HDBC hdbc, + UCHAR FAR * szDSN, + SWORD cbDSN, + UCHAR FAR * szUID, + SWORD cbUID, + UCHAR FAR * szAuthStr, + SWORD cbAuthStr) +{ + ConnectionClass *conn = (ConnectionClass *) hdbc; + ConnInfo *ci; + static char *func = "PGAPI_Connect"; + + mylog("%s: entering...\n", func); + + if (!conn) + { + CC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + ci = &conn->connInfo; + + make_string(szDSN, cbDSN, ci->dsn); + + /* get the values for the DSN from the registry */ + getDSNinfo(ci, CONN_OVERWRITE); + logs_on_off(1, ci->drivers.debug, ci->drivers.commlog); + /* initialize pg_version from connInfo.protocol */ + CC_initialize_pg_version(conn); + + /* + * override values from DSN info with UID and authStr(pwd) This only + * occurs if the values are actually there. + */ + make_string(szUID, cbUID, ci->username); + make_string(szAuthStr, cbAuthStr, ci->password); + + /* fill in any defaults */ + getDSNdefaults(ci); + + qlog("conn = %u, %s(DSN='%s', UID='%s', PWD='%s')\n", conn, func, ci->dsn, ci->username, ci->password); + + if (CC_connect(conn, FALSE) <= 0) + { + /* Error messages are filled in */ + CC_log_error(func, "Error on CC_connect", conn); + return SQL_ERROR; + } + + mylog("%s: returning...\n", func); + + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_BrowseConnect( + HDBC hdbc, + UCHAR FAR * szConnStrIn, + SWORD cbConnStrIn, + UCHAR FAR * szConnStrOut, + SWORD cbConnStrOutMax, + SWORD FAR * pcbConnStrOut) +{ + static char *func = "PGAPI_BrowseConnect"; + + mylog("%s: entering...\n", func); + + return SQL_SUCCESS; +} + + +/* Drop any hstmts open on hdbc and disconnect from database */ +RETCODE SQL_API +PGAPI_Disconnect( + HDBC hdbc) +{ + ConnectionClass *conn = (ConnectionClass *) hdbc; + static char *func = "PGAPI_Disconnect"; + + + mylog("%s: entering...\n", func); + + if (!conn) + { + CC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + qlog("conn=%u, %s\n", conn, func); + + if (conn->status == CONN_EXECUTING) + { + conn->errornumber = CONN_IN_USE; + conn->errormsg = "A transaction is currently being executed"; + CC_log_error(func, "", conn); + return SQL_ERROR; + } + + logs_on_off(-1, conn->connInfo.drivers.debug, conn->connInfo.drivers.commlog); + mylog("%s: about to CC_cleanup\n", func); + + /* Close the connection and free statements */ + CC_cleanup(conn); + + mylog("%s: done CC_cleanup\n", func); + mylog("%s: returning...\n", func); + + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_FreeConnect( + HDBC hdbc) +{ + ConnectionClass *conn = (ConnectionClass *) hdbc; + static char *func = "PGAPI_FreeConnect"; + + mylog("%s: entering...\n", func); + mylog("**** in %s: hdbc=%u\n", func, hdbc); + + if (!conn) + { + CC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + /* Remove the connection from the environment */ + if (!EN_remove_connection(conn->henv, conn)) + { + conn->errornumber = CONN_IN_USE; + conn->errormsg = "A transaction is currently being executed"; + CC_log_error(func, "", conn); + return SQL_ERROR; + } + + CC_Destructor(conn); + + mylog("%s: returning...\n", func); + + return SQL_SUCCESS; +} + + +/* + * IMPLEMENTATION CONNECTION CLASS + */ +ConnectionClass * +CC_Constructor() +{ + ConnectionClass *rv; + + rv = (ConnectionClass *) malloc(sizeof(ConnectionClass)); + + if (rv != NULL) + { + rv->henv = NULL; /* not yet associated with an environment */ + + rv->errormsg = NULL; + rv->errornumber = 0; + rv->errormsg_created = FALSE; + + rv->status = CONN_NOT_CONNECTED; + rv->transact_status = CONN_IN_AUTOCOMMIT; /* autocommit by default */ + + memset(&rv->connInfo, 0, sizeof(ConnInfo)); +#ifdef DRIVER_CURSOR_IMPLEMENT + rv->connInfo.updatable_cursors = 1; +#endif /* DRIVER_CURSOR_IMPLEMENT */ + memcpy(&(rv->connInfo.drivers), &globals, sizeof(globals)); + rv->sock = SOCK_Constructor(rv); + if (!rv->sock) + return NULL; + + rv->stmts = (StatementClass **) malloc(sizeof(StatementClass *) * STMT_INCREMENT); + if (!rv->stmts) + return NULL; + memset(rv->stmts, 0, sizeof(StatementClass *) * STMT_INCREMENT); + + rv->num_stmts = STMT_INCREMENT; + + rv->lobj_type = PG_TYPE_LO; + + rv->ntables = 0; + rv->col_info = NULL; + + rv->translation_option = 0; + rv->translation_handle = NULL; + rv->DataSourceToDriver = NULL; + rv->DriverToDataSource = NULL; + rv->driver_version = ODBCVER; + memset(rv->pg_version, 0, sizeof(rv->pg_version)); + rv->pg_version_number = .0; + rv->pg_version_major = 0; + rv->pg_version_minor = 0; + rv->ms_jet = 0; +#ifdef MULTIBYTE + rv->client_encoding = NULL; + rv->server_encoding = NULL; +#endif /* MULTIBYTE */ + + + /* Initialize statement options to defaults */ + /* Statements under this conn will inherit these options */ + + InitializeStatementOptions(&rv->stmtOptions); + + + } + return rv; +} + + +char +CC_Destructor(ConnectionClass *self) +{ + mylog("enter CC_Destructor, self=%u\n", self); + + if (self->status == CONN_EXECUTING) + return 0; + + CC_cleanup(self); /* cleanup socket and statements */ + + mylog("after CC_Cleanup\n"); + +#ifdef MULTIBYTE + if (self->client_encoding) + free(self->client_encoding); + if (self->server_encoding) + free(self->server_encoding); +#endif /* MULTIBYTE */ + /* Free up statement holders */ + if (self->stmts) + { + free(self->stmts); + self->stmts = NULL; + } + mylog("after free statement holders\n"); + + /* Free cached table info */ + if (self->col_info) + { + int i; + + for (i = 0; i < self->ntables; i++) + { + if (self->col_info[i]->result) /* Free the SQLColumns + * result structure */ + QR_Destructor(self->col_info[i]->result); + + free(self->col_info[i]); + } + free(self->col_info); + } + + + free(self); + + mylog("exit CC_Destructor\n"); + + return 1; +} + + +/* Return how many cursors are opened on this connection */ +int +CC_cursor_count(ConnectionClass *self) +{ + StatementClass *stmt; + int i, + count = 0; + + mylog("CC_cursor_count: self=%u, num_stmts=%d\n", self, self->num_stmts); + + for (i = 0; i < self->num_stmts; i++) + { + stmt = self->stmts[i]; + if (stmt && stmt->result && stmt->result->cursor) + count++; + } + + mylog("CC_cursor_count: returning %d\n", count); + + return count; +} + + +void +CC_clear_error(ConnectionClass *self) +{ + self->errornumber = 0; + self->errormsg = NULL; + self->errormsg_created = FALSE; +} + + +/* + * Used to cancel a transaction. + * We are almost always in the middle of a transaction. + */ +char +CC_abort(ConnectionClass *self) +{ + QResultClass *res; + + if (CC_is_in_trans(self)) + { + res = NULL; + + mylog("CC_abort: sending ABORT!\n"); + + res = CC_send_query(self, "ABORT", NULL); + CC_set_no_trans(self); + + if (res != NULL) + QR_Destructor(res); + else + return FALSE; + + } + + return TRUE; +} + + +/* This is called by SQLDisconnect also */ +char +CC_cleanup(ConnectionClass *self) +{ + int i; + StatementClass *stmt; + + if (self->status == CONN_EXECUTING) + return FALSE; + + mylog("in CC_Cleanup, self=%u\n", self); + + /* Cancel an ongoing transaction */ + /* We are always in the middle of a transaction, */ + /* even if we are in auto commit. */ + if (self->sock) + CC_abort(self); + + mylog("after CC_abort\n"); + + /* This actually closes the connection to the dbase */ + if (self->sock) + { + SOCK_Destructor(self->sock); + self->sock = NULL; + } + + mylog("after SOCK destructor\n"); + + /* Free all the stmts on this connection */ + for (i = 0; i < self->num_stmts; i++) + { + stmt = self->stmts[i]; + if (stmt) + { + stmt->hdbc = NULL; /* prevent any more dbase interactions */ + + SC_Destructor(stmt); + + self->stmts[i] = NULL; + } + } + + /* Check for translation dll */ +#ifdef WIN32 + if (self->translation_handle) + { + FreeLibrary(self->translation_handle); + self->translation_handle = NULL; + } +#endif + + mylog("exit CC_Cleanup\n"); + return TRUE; +} + + +int +CC_set_translation(ConnectionClass *self) +{ + +#ifdef WIN32 + + if (self->translation_handle != NULL) + { + FreeLibrary(self->translation_handle); + self->translation_handle = NULL; + } + + if (self->connInfo.translation_dll[0] == 0) + return TRUE; + + self->translation_option = atoi(self->connInfo.translation_option); + self->translation_handle = LoadLibrary(self->connInfo.translation_dll); + + if (self->translation_handle == NULL) + { + self->errornumber = CONN_UNABLE_TO_LOAD_DLL; + self->errormsg = "Could not load the translation DLL."; + return FALSE; + } + + self->DataSourceToDriver + = (DataSourceToDriverProc) GetProcAddress(self->translation_handle, + "SQLDataSourceToDriver"); + + self->DriverToDataSource + = (DriverToDataSourceProc) GetProcAddress(self->translation_handle, + "SQLDriverToDataSource"); + + if (self->DataSourceToDriver == NULL || self->DriverToDataSource == NULL) + { + self->errornumber = CONN_UNABLE_TO_LOAD_DLL; + self->errormsg = "Could not find translation DLL functions."; + return FALSE; + } +#endif + return TRUE; +} + +static int +md5_auth_send(ConnectionClass *self, const char *salt) +{ + char *pwd1 = NULL, *pwd2 = NULL; + ConnInfo *ci = &(self->connInfo); + SocketClass *sock = self->sock; + +mylog("MD5 user=%s password=%s\n", ci->username, ci->password); + if (!(pwd1 = malloc(MD5_PASSWD_LEN + 1))) + return 1; + if (!EncryptMD5(ci->password, ci->username, strlen(ci->username), pwd1)) + { + free(pwd1); + return 1; + } + if (!(pwd2 = malloc(MD5_PASSWD_LEN + 1))) + { + free(pwd1); + return 1; + } + if (!EncryptMD5(pwd1 + strlen("md5"), salt, 4, pwd2)) + { + free(pwd2); + free(pwd1); + return 1; + } + free(pwd1); + SOCK_put_int(sock, 4 + strlen(pwd2) + 1, 4); + SOCK_put_n_char(sock, pwd2, strlen(pwd2) + 1); + SOCK_flush_output(sock); + free(pwd2); + return 0; +} + +char +CC_connect(ConnectionClass *self, char do_password) +{ + StartupPacket sp; + StartupPacket6_2 sp62; + QResultClass *res; + SocketClass *sock; + ConnInfo *ci = &(self->connInfo); + int areq = -1; + int beresp; + char msgbuffer[ERROR_MSG_LENGTH]; + char salt[5]; + static char *func = "CC_connect"; + +#ifdef MULTIBYTE + char *encoding; +#endif /* MULTIBYTE */ + + mylog("%s: entering...\n", func); + + if (do_password) + + sock = self->sock; /* already connected, just authenticate */ + + else + { + qlog("Global Options: Version='%s', fetch=%d, socket=%d, unknown_sizes=%d, max_varchar_size=%d, max_longvarchar_size=%d\n", + POSTGRESDRIVERVERSION, + ci->drivers.fetch_max, + ci->drivers.socket_buffersize, + ci->drivers.unknown_sizes, + ci->drivers.max_varchar_size, + ci->drivers.max_longvarchar_size); + qlog(" disable_optimizer=%d, ksqo=%d, unique_index=%d, use_declarefetch=%d\n", + ci->drivers.disable_optimizer, + ci->drivers.ksqo, + ci->drivers.unique_index, + ci->drivers.use_declarefetch); + qlog(" text_as_longvarchar=%d, unknowns_as_longvarchar=%d, bools_as_char=%d\n", + ci->drivers.text_as_longvarchar, + ci->drivers.unknowns_as_longvarchar, + ci->drivers.bools_as_char); + +#ifdef MULTIBYTE + encoding = check_client_encoding(ci->conn_settings); + if (encoding && strcmp(encoding, "OTHER")) + self->client_encoding = strdup(encoding); + else + { + encoding = check_client_encoding(ci->drivers.conn_settings); + if (encoding && strcmp(encoding, "OTHER")) + self->client_encoding = strdup(encoding); + } + qlog(" extra_systable_prefixes='%s', conn_settings='%s' conn_encoding='%s'\n", + ci->drivers.extra_systable_prefixes, + ci->drivers.conn_settings, + encoding ? encoding : ""); +#else + qlog(" extra_systable_prefixes='%s', conn_settings='%s'\n", + ci->drivers.extra_systable_prefixes, + ci->drivers.conn_settings); +#endif + + if (self->status != CONN_NOT_CONNECTED) + { + self->errormsg = "Already connected."; + self->errornumber = CONN_OPENDB_ERROR; + return 0; + } + + if (ci->server[0] == '\0' || ci->port[0] == '\0' || ci->database[0] == '\0') + { + self->errornumber = CONN_INIREAD_ERROR; + self->errormsg = "Missing server name, port, or database name in call to CC_connect."; + return 0; + } + + mylog("CC_connect(): DSN = '%s', server = '%s', port = '%s', database = '%s', username = '%s', password='%s'\n", ci->dsn, ci->server, ci->port, ci->database, ci->username, ci->password); + +another_version_retry: + + /* + * If the socket was closed for some reason (like a SQLDisconnect, + * but no SQLFreeConnect then create a socket now. + */ + if (!self->sock) + { + self->sock = SOCK_Constructor(self); + if (!self->sock) + { + self->errornumber = CONNECTION_SERVER_NOT_REACHED; + self->errormsg = "Could not open a socket to the server"; + return 0; + } + } + + sock = self->sock; + + mylog("connecting to the server socket...\n"); + + SOCK_connect_to(sock, (short) atoi(ci->port), ci->server); + if (SOCK_get_errcode(sock) != 0) + { + mylog("connection to the server socket failed.\n"); + self->errornumber = CONNECTION_SERVER_NOT_REACHED; + self->errormsg = "Could not connect to the server"; + return 0; + } + mylog("connection to the server socket succeeded.\n"); + + if (PROTOCOL_62(ci)) + { + sock->reverse = TRUE; /* make put_int and get_int work + * for 6.2 */ + + memset(&sp62, 0, sizeof(StartupPacket6_2)); + SOCK_put_int(sock, htonl(4 + sizeof(StartupPacket6_2)), 4); + sp62.authtype = htonl(NO_AUTHENTICATION); + strncpy(sp62.database, ci->database, PATH_SIZE); + strncpy(sp62.user, ci->username, NAMEDATALEN); + SOCK_put_n_char(sock, (char *) &sp62, sizeof(StartupPacket6_2)); + SOCK_flush_output(sock); + } + else + { + memset(&sp, 0, sizeof(StartupPacket)); + + mylog("sizeof startup packet = %d\n", sizeof(StartupPacket)); + + /* Send length of Authentication Block */ + SOCK_put_int(sock, 4 + sizeof(StartupPacket), 4); + + if (PROTOCOL_63(ci)) + sp.protoVersion = (ProtocolVersion) htonl(PG_PROTOCOL_63); + else + sp.protoVersion = (ProtocolVersion) htonl(PG_PROTOCOL_LATEST); + + strncpy(sp.database, ci->database, SM_DATABASE); + strncpy(sp.user, ci->username, SM_USER); + + SOCK_put_n_char(sock, (char *) &sp, sizeof(StartupPacket)); + SOCK_flush_output(sock); + } + + mylog("sent the authentication block.\n"); + + if (sock->errornumber != 0) + { + mylog("couldn't send the authentication block properly.\n"); + self->errornumber = CONN_INVALID_AUTHENTICATION; + self->errormsg = "Sending the authentication packet failed"; + return 0; + } + mylog("sent the authentication block successfully.\n"); + } + + + mylog("gonna do authentication\n"); + + + /* + * Now get the authentication request from backend + */ + + if (!PROTOCOL_62(ci)) + { + BOOL before_64 = PG_VERSION_LT(self, 6.4), + ReadyForQuery = FALSE; + + do + { + if (do_password) + beresp = 'R'; + else + { + beresp = SOCK_get_char(sock); + mylog("auth got '%c'\n", beresp); + } + + switch (beresp) + { + case 'E': + + SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH); + self->errornumber = CONN_INVALID_AUTHENTICATION; + self->errormsg = msgbuffer; + qlog("ERROR from backend during authentication: '%s'\n", self->errormsg); + if (strncmp(msgbuffer, "Unsupported frontend protocol", 29) == 0) + { /* retry older version */ + if (PROTOCOL_63(ci)) + strcpy(ci->protocol, PG62); + else + strcpy(ci->protocol, PG63); + SOCK_Destructor(sock); + self->sock = (SocketClass *) 0; + CC_initialize_pg_version(self); + goto another_version_retry; + } + + return 0; + case 'R': + + if (do_password) + { + mylog("in 'R' do_password\n"); + areq = AUTH_REQ_PASSWORD; + do_password = FALSE; + } + else + { + + areq = SOCK_get_int(sock, 4); + if (areq == AUTH_REQ_MD5) + SOCK_get_n_char(sock, salt, 4); + if (areq == AUTH_REQ_CRYPT) + SOCK_get_n_char(sock, salt, 2); + + mylog("areq = %d\n", areq); + } + switch (areq) + { + case AUTH_REQ_OK: + break; + + case AUTH_REQ_KRB4: + self->errormsg = "Kerberos 4 authentication not supported"; + self->errornumber = CONN_AUTH_TYPE_UNSUPPORTED; + return 0; + + case AUTH_REQ_KRB5: + self->errormsg = "Kerberos 5 authentication not supported"; + self->errornumber = CONN_AUTH_TYPE_UNSUPPORTED; + return 0; + + case AUTH_REQ_PASSWORD: + mylog("in AUTH_REQ_PASSWORD\n"); + + if (ci->password[0] == '\0') + { + self->errornumber = CONNECTION_NEED_PASSWORD; + self->errormsg = "A password is required for this connection."; + return -1; /* need password */ + } + + mylog("past need password\n"); + + SOCK_put_int(sock, 4 + strlen(ci->password) + 1, 4); + SOCK_put_n_char(sock, ci->password, strlen(ci->password) + 1); + SOCK_flush_output(sock); + + mylog("past flush\n"); + break; + + case AUTH_REQ_CRYPT: + self->errormsg = "Password crypt authentication not supported"; + self->errornumber = CONN_AUTH_TYPE_UNSUPPORTED; + return 0; + case AUTH_REQ_MD5: + mylog("in AUTH_REQ_MD5\n"); + if (ci->password[0] == '\0') + { + self->errornumber = CONNECTION_NEED_PASSWORD; + self->errormsg = "A password is required for this connection."; + return -1; /* need password */ + } + if (md5_auth_send(self, salt)) + { + self->errormsg = "md5 hashing failed"; + self->errornumber = CONN_INVALID_AUTHENTICATION; + return 0; + } + break; + + case AUTH_REQ_SCM_CREDS: + self->errormsg = "Unix socket credential authentication not supported"; + self->errornumber = CONN_AUTH_TYPE_UNSUPPORTED; + return 0; + + default: + self->errormsg = "Unknown authentication type"; + self->errornumber = CONN_AUTH_TYPE_UNSUPPORTED; + return 0; + } + break; + case 'K': /* Secret key (6.4 protocol) */ + (void) SOCK_get_int(sock, 4); /* pid */ + (void) SOCK_get_int(sock, 4); /* key */ + + break; + case 'Z': /* Backend is ready for new query (6.4) */ + ReadyForQuery = TRUE; + break; + default: + self->errormsg = "Unexpected protocol character during authentication"; + self->errornumber = CONN_INVALID_AUTHENTICATION; + return 0; + } + + /* + * There were no ReadyForQuery responce before 6.4. + */ + if (before_64 && areq == AUTH_REQ_OK) + ReadyForQuery = TRUE; + } while (!ReadyForQuery); + } + + + CC_clear_error(self); /* clear any password error */ + + /* + * send an empty query in order to find out whether the specified + * database really exists on the server machine + */ + mylog("sending an empty query...\n"); + + res = CC_send_query(self, " ", NULL); + if (res == NULL || QR_get_status(res) != PGRES_EMPTY_QUERY) + { + mylog("got no result from the empty query. (probably database does not exist)\n"); + self->errornumber = CONNECTION_NO_SUCH_DATABASE; + self->errormsg = "The database does not exist on the server\nor user authentication failed."; + if (res != NULL) + QR_Destructor(res); + return 0; + } + if (res) + QR_Destructor(res); + + mylog("empty query seems to be OK.\n"); + + CC_set_translation(self); + + /* + * Send any initial settings + */ + + /* + * Since these functions allocate statements, and since the connection + * is not established yet, it would violate odbc state transition + * rules. Therefore, these functions call the corresponding local + * function instead. + */ + CC_send_settings(self); + CC_lookup_lo(self); /* a hack to get the oid of our large + * object oid type */ + CC_lookup_pg_version(self); /* Get PostgreSQL version for SQLGetInfo + * use */ + + CC_clear_error(self); /* clear any initial command errors */ + self->status = CONN_CONNECTED; + + mylog("%s: returning...\n", func); + + return 1; + +} + + +char +CC_add_statement(ConnectionClass *self, StatementClass *stmt) +{ + int i; + + mylog("CC_add_statement: self=%u, stmt=%u\n", self, stmt); + + for (i = 0; i < self->num_stmts; i++) + { + if (!self->stmts[i]) + { + stmt->hdbc = self; + self->stmts[i] = stmt; + return TRUE; + } + } + + /* no more room -- allocate more memory */ + self->stmts = (StatementClass **) realloc(self->stmts, sizeof(StatementClass *) * (STMT_INCREMENT + self->num_stmts)); + if (!self->stmts) + return FALSE; + + memset(&self->stmts[self->num_stmts], 0, sizeof(StatementClass *) * STMT_INCREMENT); + + stmt->hdbc = self; + self->stmts[self->num_stmts] = stmt; + + self->num_stmts += STMT_INCREMENT; + + return TRUE; +} + + +char +CC_remove_statement(ConnectionClass *self, StatementClass *stmt) +{ + int i; + + for (i = 0; i < self->num_stmts; i++) + { + if (self->stmts[i] == stmt && stmt->status != STMT_EXECUTING) + { + self->stmts[i] = NULL; + return TRUE; + } + } + + return FALSE; +} + + +/* + * Create a more informative error message by concatenating the connection + * error message with its socket error message. + */ +char * +CC_create_errormsg(ConnectionClass *self) +{ + SocketClass *sock = self->sock; + int pos; + static char msg[4096]; + + mylog("enter CC_create_errormsg\n"); + + msg[0] = '\0'; + + if (self->errormsg) + strcpy(msg, self->errormsg); + + mylog("msg = '%s'\n", msg); + + if (sock && sock->errormsg && sock->errormsg[0] != '\0') + { + pos = strlen(msg); + sprintf(&msg[pos], ";\n%s", sock->errormsg); + } + + mylog("exit CC_create_errormsg\n"); + return msg; +} + + +char +CC_get_error(ConnectionClass *self, int *number, char **message) +{ + int rv; + + mylog("enter CC_get_error\n"); + + /* Create a very informative errormsg if it hasn't been done yet. */ + if (!self->errormsg_created) + { + self->errormsg = CC_create_errormsg(self); + self->errormsg_created = TRUE; + } + + if (self->errornumber) + { + *number = self->errornumber; + *message = self->errormsg; + } + rv = (self->errornumber != 0); + + self->errornumber = 0; /* clear the error */ + + mylog("exit CC_get_error\n"); + + return rv; +} + + +/* + * The "result_in" is only used by QR_next_tuple() to fetch another group of rows into + * the same existing QResultClass (this occurs when the tuple cache is depleted and + * needs to be re-filled). + * + * The "cursor" is used by SQLExecute to associate a statement handle as the cursor name + * (i.e., C3326857) for SQL select statements. This cursor is then used in future + * 'declare cursor C3326857 for ...' and 'fetch 100 in C3326857' statements. + */ +QResultClass * +CC_send_query(ConnectionClass *self, char *query, QueryInfo *qi) +{ + QResultClass *result_in = NULL, + *res = NULL, + *retres = NULL; + char swallow, + *wq; + int id; + SocketClass *sock = self->sock; + int maxlen, + empty_reqs; + BOOL msg_truncated, + ReadyToReturn, + tuples_return = FALSE, + query_completed = FALSE, + before_64 = PG_VERSION_LT(self, 6.4); + + /* ERROR_MSG_LENGTH is suffcient */ + static char msgbuffer[ERROR_MSG_LENGTH + 1]; + + /* QR_set_command() dups this string so doesn't need static */ + char cmdbuffer[ERROR_MSG_LENGTH + 1]; + + mylog("send_query(): conn=%u, query='%s'\n", self, query); + qlog("conn=%u, query='%s'\n", self, query); + + /* Indicate that we are sending a query to the backend */ + maxlen = CC_get_max_query_len(self); + if (maxlen > 0 && maxlen < (int) strlen(query) + 1) + { + self->errornumber = CONNECTION_MSG_TOO_LONG; + self->errormsg = "Query string is too long"; + return NULL; + } + + if ((NULL == query) || (query[0] == '\0')) + return NULL; + + if (SOCK_get_errcode(sock) != 0) + { + self->errornumber = CONNECTION_COULD_NOT_SEND; + self->errormsg = "Could not send Query to backend"; + CC_set_no_trans(self); + return NULL; + } + + SOCK_put_char(sock, 'Q'); + if (SOCK_get_errcode(sock) != 0) + { + self->errornumber = CONNECTION_COULD_NOT_SEND; + self->errormsg = "Could not send Query to backend"; + CC_set_no_trans(self); + return NULL; + } + + SOCK_put_string(sock, query); + SOCK_flush_output(sock); + + if (SOCK_get_errcode(sock) != 0) + { + self->errornumber = CONNECTION_COULD_NOT_SEND; + self->errormsg = "Could not send Query to backend"; + CC_set_no_trans(self); + return NULL; + } + + mylog("send_query: done sending query\n"); + + ReadyToReturn = FALSE; + empty_reqs = 0; + for (wq = query; isspace((unsigned char) *wq); wq++) + ; + if (*wq == '\0') + empty_reqs = 1; + while (!ReadyToReturn) + { + /* what type of message is coming now ? */ + id = SOCK_get_char(sock); + + if ((SOCK_get_errcode(sock) != 0) || (id == EOF)) + { + self->errornumber = CONNECTION_NO_RESPONSE; + self->errormsg = "No response from the backend"; + + mylog("send_query: 'id' - %s\n", self->errormsg); + CC_set_no_trans(self); + ReadyToReturn = TRUE; + retres = NULL; + break; + } + + mylog("send_query: got id = '%c'\n", id); + + switch (id) + { + case 'A': /* Asynchronous Messages are ignored */ + (void) SOCK_get_int(sock, 4); /* id of notification */ + SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH); + /* name of the relation the message comes from */ + break; + case 'C': /* portal query command, no tuples + * returned */ + /* read in the return message from the backend */ + SOCK_get_string(sock, cmdbuffer, ERROR_MSG_LENGTH); + if (SOCK_get_errcode(sock) != 0) + { + self->errornumber = CONNECTION_NO_RESPONSE; + self->errormsg = "No response from backend while receiving a portal query command"; + mylog("send_query: 'C' - %s\n", self->errormsg); + CC_set_no_trans(self); + ReadyToReturn = TRUE; + retres = NULL; + } + else + { + mylog("send_query: ok - 'C' - %s\n", cmdbuffer); + + if (res == NULL) /* allow for "show" style notices */ + res = QR_Constructor(); + + mylog("send_query: setting cmdbuffer = '%s'\n", cmdbuffer); + + /* Only save the first command */ + if (QR_command_successful(res)) + QR_set_status(res, PGRES_COMMAND_OK); + QR_set_command(res, cmdbuffer); + query_completed = TRUE; + mylog("send_query: returning res = %u\n", res); + if (!before_64) + break; + + /* + * (Quotation from the original comments) since + * backend may produce more than one result for some + * commands we need to poll until clear so we send an + * empty query, and keep reading out of the pipe until + * an 'I' is received + */ + + if (empty_reqs == 0) + { + SOCK_put_string(sock, "Q "); + SOCK_flush_output(sock); + empty_reqs++; + } + } + break; + case 'Z': /* Backend is ready for new query (6.4) */ + if (empty_reqs == 0) + { + ReadyToReturn = TRUE; + if (res && QR_get_aborted(res)) + retres = res; + else if (tuples_return) + retres = result_in; + else if (query_completed) + retres = res; + else + ReadyToReturn = FALSE; + } + break; + case 'N': /* NOTICE: */ + msg_truncated = SOCK_get_string(sock, cmdbuffer, ERROR_MSG_LENGTH); + if (!res) + res = QR_Constructor(); + if (QR_command_successful(res)) + QR_set_status(res, PGRES_NONFATAL_ERROR); + QR_set_notice(res, cmdbuffer); /* will dup this string */ + + mylog("~~~ NOTICE: '%s'\n", cmdbuffer); + qlog("NOTICE from backend during send_query: '%s'\n", cmdbuffer); + while (msg_truncated) + msg_truncated = SOCK_get_string(sock, cmdbuffer, ERROR_MSG_LENGTH); + + continue; /* dont return a result -- continue + * reading */ + + case 'I': /* The server sends an empty query */ + /* There is a closing '\0' following the 'I', so we eat it */ + swallow = SOCK_get_char(sock); + if (!res) + res = QR_Constructor(); + if ((swallow != '\0') || SOCK_get_errcode(sock) != 0) + { + self->errornumber = CONNECTION_BACKEND_CRAZY; + self->errormsg = "Unexpected protocol character from backend (send_query - I)"; + QR_set_status(res, PGRES_FATAL_ERROR); + ReadyToReturn = TRUE; + retres = res; + break; + } + else + { + /* We return the empty query */ + QR_set_status(res, PGRES_EMPTY_QUERY); + } + if (empty_reqs > 0) + { + if (--empty_reqs == 0) + query_completed = TRUE; + } + break; + case 'E': + msg_truncated = SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH); + + /* Remove a newline */ + if (msgbuffer[0] != '\0' && msgbuffer[strlen(msgbuffer) - 1] == '\n') + msgbuffer[strlen(msgbuffer) - 1] = '\0'; + + self->errormsg = msgbuffer; + + mylog("send_query: 'E' - %s\n", self->errormsg); + qlog("ERROR from backend during send_query: '%s'\n", self->errormsg); + + /* We should report that an error occured. Zoltan */ + if (!res) + res = QR_Constructor(); + + if (!strncmp(self->errormsg, "FATAL", 5)) + { + self->errornumber = CONNECTION_SERVER_REPORTED_ERROR; + CC_set_no_trans(self); + } + else + self->errornumber = CONNECTION_SERVER_REPORTED_WARNING; + QR_set_status(res, PGRES_FATAL_ERROR); + QR_set_aborted(res, TRUE); + while (msg_truncated) + msg_truncated = SOCK_get_string(sock, cmdbuffer, ERROR_MSG_LENGTH); + + query_completed = TRUE; + break; + + case 'P': /* get the Portal name */ + SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH); + break; + case 'T': /* Tuple results start here */ + result_in = qi ? qi->result_in : NULL; + + if (result_in == NULL) + { + result_in = QR_Constructor(); + mylog("send_query: 'T' no result_in: res = %u\n", result_in); + if (!result_in) + { + self->errornumber = CONNECTION_COULD_NOT_RECEIVE; + self->errormsg = "Could not create result info in send_query."; + ReadyToReturn = TRUE; + retres = NULL; + break; + } + + if (qi) + QR_set_cache_size(result_in, qi->row_size); + + if (!QR_fetch_tuples(result_in, self, qi ? qi->cursor : NULL)) + { + self->errornumber = CONNECTION_COULD_NOT_RECEIVE; + self->errormsg = QR_get_message(result_in); + ReadyToReturn = TRUE; + retres = NULL; + break; + } + } + else + { /* next fetch, so reuse an existing result */ + + /* + * called from QR_next_tuple and must return + * immediately. + */ + ReadyToReturn = TRUE; + if (!QR_fetch_tuples(result_in, NULL, NULL)) + { + self->errornumber = CONNECTION_COULD_NOT_RECEIVE; + self->errormsg = QR_get_message(result_in); + retres = NULL; + break; + } + retres = result_in; + } + + tuples_return = TRUE; + break; + case 'D': /* Copy in command began successfully */ + if (!res) + res = QR_Constructor(); + if (QR_command_successful(res)) + QR_set_status(res, PGRES_COPY_IN); + ReadyToReturn = TRUE; + retres = res; + break; + case 'B': /* Copy out command began successfully */ + if (!res) + res = QR_Constructor(); + if (QR_command_successful(res)) + QR_set_status(res, PGRES_COPY_OUT); + ReadyToReturn = TRUE; + retres = res; + break; + default: + self->errornumber = CONNECTION_BACKEND_CRAZY; + self->errormsg = "Unexpected protocol character from backend (send_query)"; + CC_set_no_trans(self); + + mylog("send_query: error - %s\n", self->errormsg); + ReadyToReturn = TRUE; + retres = NULL; + break; + } + + /* + * There were no ReadyForQuery response before 6.4. + */ + if (before_64) + { + if (empty_reqs == 0 && (query_completed || tuples_return)) + break; + } + } + + /* + * Break before being ready to return. + */ + if (!ReadyToReturn) + { + if (res && QR_get_aborted(res)) + retres = res; + else if (tuples_return) + retres = result_in; + else + retres = res; + } + + /* + * set notice message to result_in. + */ + if (result_in && res && retres == result_in) + { + if (QR_command_successful(result_in)) + QR_set_status(result_in, QR_get_status(res)); + QR_set_notice(result_in, QR_get_notice(res)); + } + + /* + * Cleanup garbage results before returning. + */ + if (res && retres != res) + QR_Destructor(res); + if (result_in && retres != result_in) + { + if (qi && qi->result_in) + ; + else + QR_Destructor(result_in); + } + return retres; +} + + +int +CC_send_function(ConnectionClass *self, int fnid, void *result_buf, int *actual_result_len, int result_is_int, LO_ARG *args, int nargs) +{ + char id, + c, + done; + SocketClass *sock = self->sock; + + /* ERROR_MSG_LENGTH is sufficient */ + static char msgbuffer[ERROR_MSG_LENGTH + 1]; + int i; + + mylog("send_function(): conn=%u, fnid=%d, result_is_int=%d, nargs=%d\n", self, fnid, result_is_int, nargs); + + if (SOCK_get_errcode(sock) != 0) + { + self->errornumber = CONNECTION_COULD_NOT_SEND; + self->errormsg = "Could not send function to backend"; + CC_set_no_trans(self); + return FALSE; + } + + SOCK_put_string(sock, "F "); + if (SOCK_get_errcode(sock) != 0) + { + self->errornumber = CONNECTION_COULD_NOT_SEND; + self->errormsg = "Could not send function to backend"; + CC_set_no_trans(self); + return FALSE; + } + + SOCK_put_int(sock, fnid, 4); + SOCK_put_int(sock, nargs, 4); + + + mylog("send_function: done sending function\n"); + + for (i = 0; i < nargs; ++i) + { + mylog(" arg[%d]: len = %d, isint = %d, integer = %d, ptr = %u\n", i, args[i].len, args[i].isint, args[i].u.integer, args[i].u.ptr); + + SOCK_put_int(sock, args[i].len, 4); + if (args[i].isint) + SOCK_put_int(sock, args[i].u.integer, 4); + else + SOCK_put_n_char(sock, (char *) args[i].u.ptr, args[i].len); + + + } + + mylog(" done sending args\n"); + + SOCK_flush_output(sock); + mylog(" after flush output\n"); + + done = FALSE; + while (!done) + { + id = SOCK_get_char(sock); + mylog(" got id = %c\n", id); + + switch (id) + { + case 'V': + done = TRUE; + break; /* ok */ + + case 'N': + SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH); + mylog("send_function(V): 'N' - %s\n", msgbuffer); + /* continue reading */ + break; + + case 'E': + SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH); + self->errormsg = msgbuffer; + + mylog("send_function(V): 'E' - %s\n", self->errormsg); + qlog("ERROR from backend during send_function: '%s'\n", self->errormsg); + + return FALSE; + + case 'Z': + break; + + default: + self->errornumber = CONNECTION_BACKEND_CRAZY; + self->errormsg = "Unexpected protocol character from backend (send_function, args)"; + CC_set_no_trans(self); + + mylog("send_function: error - %s\n", self->errormsg); + return FALSE; + } + } + + id = SOCK_get_char(sock); + for (;;) + { + switch (id) + { + case 'G': /* function returned properly */ + mylog(" got G!\n"); + + *actual_result_len = SOCK_get_int(sock, 4); + mylog(" actual_result_len = %d\n", *actual_result_len); + + if (result_is_int) + *((int *) result_buf) = SOCK_get_int(sock, 4); + else + SOCK_get_n_char(sock, (char *) result_buf, *actual_result_len); + + mylog(" after get result\n"); + + c = SOCK_get_char(sock); /* get the last '0' */ + + mylog(" after get 0\n"); + + return TRUE; + + case 'E': + SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH); + self->errormsg = msgbuffer; + + mylog("send_function(G): 'E' - %s\n", self->errormsg); + qlog("ERROR from backend during send_function: '%s'\n", self->errormsg); + + return FALSE; + + case 'N': + SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH); + + mylog("send_function(G): 'N' - %s\n", msgbuffer); + qlog("NOTICE from backend during send_function: '%s'\n", msgbuffer); + + continue; /* dont return a result -- continue + * reading */ + + case '0': /* empty result */ + return TRUE; + + default: + self->errornumber = CONNECTION_BACKEND_CRAZY; + self->errormsg = "Unexpected protocol character from backend (send_function, result)"; + CC_set_no_trans(self); + + mylog("send_function: error - %s\n", self->errormsg); + return FALSE; + } + } +} + + +char +CC_send_settings(ConnectionClass *self) +{ + /* char ini_query[MAX_MESSAGE_LEN]; */ + ConnInfo *ci = &(self->connInfo); + +/* QResultClass *res; */ + HSTMT hstmt; + StatementClass *stmt; + RETCODE result; + char status = TRUE; + char *cs, + *ptr; + static char *func = "CC_send_settings"; + + + mylog("%s: entering...\n", func); + +/* + * This function must use the local odbc API functions since the odbc state + * has not transitioned to "connected" yet. + */ + + result = PGAPI_AllocStmt(self, &hstmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + return FALSE; + stmt = (StatementClass *) hstmt; + + stmt->internal = TRUE; /* ensure no BEGIN/COMMIT/ABORT stuff */ + + /* Set the Datestyle to the format the driver expects it to be in */ + result = PGAPI_ExecDirect(hstmt, "set DateStyle to 'ISO'", SQL_NTS); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + status = FALSE; + + mylog("%s: result %d, status %d from set DateStyle\n", func, result, status); + + /* Disable genetic optimizer based on global flag */ + if (ci->drivers.disable_optimizer) + { + result = PGAPI_ExecDirect(hstmt, "set geqo to 'OFF'", SQL_NTS); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + status = FALSE; + + mylog("%s: result %d, status %d from set geqo\n", func, result, status); + + } + + /* KSQO */ + if (ci->drivers.ksqo) + { + result = PGAPI_ExecDirect(hstmt, "set ksqo to 'ON'", SQL_NTS); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + status = FALSE; + + mylog("%s: result %d, status %d from set ksqo\n", func, result, status); + + } + + /* Global settings */ + if (ci->drivers.conn_settings[0] != '\0') + { + cs = strdup(ci->drivers.conn_settings); + ptr = strtok(cs, ";"); + while (ptr) + { + result = PGAPI_ExecDirect(hstmt, ptr, SQL_NTS); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + status = FALSE; + + mylog("%s: result %d, status %d from '%s'\n", func, result, status, ptr); + + ptr = strtok(NULL, ";"); + } + + free(cs); + } + + /* Per Datasource settings */ + if (ci->conn_settings[0] != '\0') + { + cs = strdup(ci->conn_settings); + ptr = strtok(cs, ";"); + while (ptr) + { + result = PGAPI_ExecDirect(hstmt, ptr, SQL_NTS); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + status = FALSE; + + mylog("%s: result %d, status %d from '%s'\n", func, result, status, ptr); + + ptr = strtok(NULL, ";"); + } + + free(cs); + } + + + PGAPI_FreeStmt(hstmt, SQL_DROP); + + return status; +} + + +/* + * This function is just a hack to get the oid of our Large Object oid type. + * If a real Large Object oid type is made part of Postgres, this function + * will go away and the define 'PG_TYPE_LO' will be updated. + */ +void +CC_lookup_lo(ConnectionClass *self) +{ + HSTMT hstmt; + StatementClass *stmt; + RETCODE result; + static char *func = "CC_lookup_lo"; + + mylog("%s: entering...\n", func); + +/* + * This function must use the local odbc API functions since the odbc state + * has not transitioned to "connected" yet. + */ + result = PGAPI_AllocStmt(self, &hstmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + return; + stmt = (StatementClass *) hstmt; + + result = PGAPI_ExecDirect(hstmt, "select oid from pg_type where typname='" PG_TYPE_LO_NAME "'", SQL_NTS); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + PGAPI_FreeStmt(hstmt, SQL_DROP); + return; + } + + result = PGAPI_Fetch(hstmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + PGAPI_FreeStmt(hstmt, SQL_DROP); + return; + } + + result = PGAPI_GetData(hstmt, 1, SQL_C_SLONG, &self->lobj_type, sizeof(self->lobj_type), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + PGAPI_FreeStmt(hstmt, SQL_DROP); + return; + } + + mylog("Got the large object oid: %d\n", self->lobj_type); + qlog(" [ Large Object oid = %d ]\n", self->lobj_type); + + result = PGAPI_FreeStmt(hstmt, SQL_DROP); +} + + +/* + * This function initializes the version of PostgreSQL from + * connInfo.protocol that we're connected to. + * h-inoue 01-2-2001 + */ +void +CC_initialize_pg_version(ConnectionClass *self) +{ + strcpy(self->pg_version, self->connInfo.protocol); + if (PROTOCOL_62(&self->connInfo)) + { + self->pg_version_number = (float) 6.2; + self->pg_version_major = 6; + self->pg_version_minor = 2; + } + else if (PROTOCOL_63(&self->connInfo)) + { + self->pg_version_number = (float) 6.3; + self->pg_version_major = 6; + self->pg_version_minor = 3; + } + else + { + self->pg_version_number = (float) 6.4; + self->pg_version_major = 6; + self->pg_version_minor = 4; + } +} + + +/* + * This function gets the version of PostgreSQL that we're connected to. + * This is used to return the correct info in SQLGetInfo + * DJP - 25-1-2001 + */ +void +CC_lookup_pg_version(ConnectionClass *self) +{ + HSTMT hstmt; + StatementClass *stmt; + RETCODE result; + char szVersion[32]; + int major, + minor; + static char *func = "CC_lookup_pg_version"; + + mylog("%s: entering...\n", func); + +/* + * This function must use the local odbc API functions since the odbc state + * has not transitioned to "connected" yet. + */ + result = PGAPI_AllocStmt(self, &hstmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + return; + stmt = (StatementClass *) hstmt; + + /* get the server's version if possible */ + result = PGAPI_ExecDirect(hstmt, "select version()", SQL_NTS); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + PGAPI_FreeStmt(hstmt, SQL_DROP); + return; + } + + result = PGAPI_Fetch(hstmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + PGAPI_FreeStmt(hstmt, SQL_DROP); + return; + } + + result = PGAPI_GetData(hstmt, 1, SQL_C_CHAR, self->pg_version, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + PGAPI_FreeStmt(hstmt, SQL_DROP); + return; + } + + /* + * Extract the Major and Minor numbers from the string. This assumes + * the string starts 'Postgresql X.X' + */ + strcpy(szVersion, "0.0"); + if (sscanf(self->pg_version, "%*s %d.%d", &major, &minor) >= 2) + { + sprintf(szVersion, "%d.%d", major, minor); + self->pg_version_major = major; + self->pg_version_minor = minor; + } + self->pg_version_number = (float) atof(szVersion); + + mylog("Got the PostgreSQL version string: '%s'\n", self->pg_version); + mylog("Extracted PostgreSQL version number: '%1.1f'\n", self->pg_version_number); + qlog(" [ PostgreSQL version string = '%s' ]\n", self->pg_version); + qlog(" [ PostgreSQL version number = '%1.1f' ]\n", self->pg_version_number); + + result = PGAPI_FreeStmt(hstmt, SQL_DROP); +} + + +void +CC_log_error(char *func, char *desc, ConnectionClass *self) +{ +#ifdef PRN_NULLCHECK +#define nullcheck(a) (a ? a : "(NULL)") +#endif + + if (self) + { + qlog("CONN ERROR: func=%s, desc='%s', errnum=%d, errmsg='%s'\n", func, desc, self->errornumber, nullcheck(self->errormsg)); + mylog("CONN ERROR: func=%s, desc='%s', errnum=%d, errmsg='%s'\n", func, desc, self->errornumber, nullcheck(self->errormsg)); + qlog(" ------------------------------------------------------------\n"); + qlog(" henv=%u, conn=%u, status=%u, num_stmts=%d\n", self->henv, self, self->status, self->num_stmts); + qlog(" sock=%u, stmts=%u, lobj_type=%d\n", self->sock, self->stmts, self->lobj_type); + + qlog(" ---------------- Socket Info -------------------------------\n"); + if (self->sock) + { + SocketClass *sock = self->sock; + + qlog(" socket=%d, reverse=%d, errornumber=%d, errormsg='%s'\n", sock->socket, sock->reverse, sock->errornumber, nullcheck(sock->errormsg)); + qlog(" buffer_in=%u, buffer_out=%u\n", sock->buffer_in, sock->buffer_out); + qlog(" buffer_filled_in=%d, buffer_filled_out=%d, buffer_read_in=%d\n", sock->buffer_filled_in, sock->buffer_filled_out, sock->buffer_read_in); + } + } + else + qlog("INVALID CONNECTION HANDLE ERROR: func=%s, desc='%s'\n", func, desc); +#undef PRN_NULLCHECK +} + +int +CC_get_max_query_len(const ConnectionClass *conn) +{ + int value; + + /* Long Queries in 7.0+ */ + if (PG_VERSION_GE(conn, 7.0)) + value = 0 /* MAX_STATEMENT_LEN */ ; + /* Prior to 7.0 we used 2*BLCKSZ */ + else if (PG_VERSION_GE(conn, 6.5)) + value = (2 * BLCKSZ); + else + /* Prior to 6.5 we used BLCKSZ */ + value = BLCKSZ; + return value; +} diff --git a/src/interfaces/odbc/windev/connection.h b/src/interfaces/odbc/windev/connection.h new file mode 100644 index 0000000000..a80c31b921 --- /dev/null +++ b/src/interfaces/odbc/windev/connection.h @@ -0,0 +1,312 @@ +/* File: connection.h + * + * Description: See "connection.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __CONNECTION_H__ +#define __CONNECTION_H__ + +#include "psqlodbc.h" + +#include +#include + + +typedef enum +{ + CONN_NOT_CONNECTED, /* Connection has not been established */ + CONN_CONNECTED, /* Connection is up and has been + * established */ + CONN_DOWN, /* Connection is broken */ + CONN_EXECUTING /* the connection is currently executing a + * statement */ +} CONN_Status; + +/* These errors have general sql error state */ +#define CONNECTION_SERVER_NOT_REACHED 101 +#define CONNECTION_MSG_TOO_LONG 103 +#define CONNECTION_COULD_NOT_SEND 104 +#define CONNECTION_NO_SUCH_DATABASE 105 +#define CONNECTION_BACKEND_CRAZY 106 +#define CONNECTION_NO_RESPONSE 107 +#define CONNECTION_SERVER_REPORTED_ERROR 108 +#define CONNECTION_COULD_NOT_RECEIVE 109 +#define CONNECTION_SERVER_REPORTED_WARNING 110 +#define CONNECTION_NEED_PASSWORD 112 + +/* These errors correspond to specific SQL states */ +#define CONN_INIREAD_ERROR 201 +#define CONN_OPENDB_ERROR 202 +#define CONN_STMT_ALLOC_ERROR 203 +#define CONN_IN_USE 204 +#define CONN_UNSUPPORTED_OPTION 205 +/* Used by SetConnectoption to indicate unsupported options */ +#define CONN_INVALID_ARGUMENT_NO 206 +/* SetConnectOption: corresponds to ODBC--"S1009" */ +#define CONN_TRANSACT_IN_PROGRES 207 +#define CONN_NO_MEMORY_ERROR 208 +#define CONN_NOT_IMPLEMENTED_ERROR 209 +#define CONN_INVALID_AUTHENTICATION 210 +#define CONN_AUTH_TYPE_UNSUPPORTED 211 +#define CONN_UNABLE_TO_LOAD_DLL 212 + +#define CONN_OPTION_VALUE_CHANGED 213 +#define CONN_VALUE_OUT_OF_RANGE 214 + +#define CONN_TRUNCATED 215 + +/* Conn_status defines */ +#define CONN_IN_AUTOCOMMIT 0x01 +#define CONN_IN_TRANSACTION 0x02 + +/* AutoCommit functions */ +#define CC_set_autocommit_off(x) (x->transact_status &= ~CONN_IN_AUTOCOMMIT) +#define CC_set_autocommit_on(x) (x->transact_status |= CONN_IN_AUTOCOMMIT) +#define CC_is_in_autocommit(x) (x->transact_status & CONN_IN_AUTOCOMMIT) + +/* Transaction in/not functions */ +#define CC_set_in_trans(x) (x->transact_status |= CONN_IN_TRANSACTION) +#define CC_set_no_trans(x) (x->transact_status &= ~CONN_IN_TRANSACTION) +#define CC_is_in_trans(x) (x->transact_status & CONN_IN_TRANSACTION) + + +/* Authentication types */ +#define AUTH_REQ_OK 0 +#define AUTH_REQ_KRB4 1 +#define AUTH_REQ_KRB5 2 +#define AUTH_REQ_PASSWORD 3 +#define AUTH_REQ_CRYPT 4 +#define AUTH_REQ_MD5 5 +#define AUTH_REQ_SCM_CREDS 6 + +/* Startup Packet sizes */ +#define SM_DATABASE 64 +#define SM_USER 32 +#define SM_OPTIONS 64 +#define SM_UNUSED 64 +#define SM_TTY 64 + +/* Old 6.2 protocol defines */ +#define NO_AUTHENTICATION 7 +#define PATH_SIZE 64 +#define ARGV_SIZE 64 +#define NAMEDATALEN 16 + +typedef unsigned int ProtocolVersion; + +#define PG_PROTOCOL(major, minor) (((major) << 16) | (minor)) +#define PG_PROTOCOL_LATEST PG_PROTOCOL(2, 0) +#define PG_PROTOCOL_63 PG_PROTOCOL(1, 0) +#define PG_PROTOCOL_62 PG_PROTOCOL(0, 0) + +/* This startup packet is to support latest Postgres protocol (6.4, 6.3) */ +typedef struct _StartupPacket +{ + ProtocolVersion protoVersion; + char database[SM_DATABASE]; + char user[SM_USER]; + char options[SM_OPTIONS]; + char unused[SM_UNUSED]; + char tty[SM_TTY]; +} StartupPacket; + + +/* This startup packet is to support pre-Postgres 6.3 protocol */ +typedef struct _StartupPacket6_2 +{ + unsigned int authtype; + char database[PATH_SIZE]; + char user[NAMEDATALEN]; + char options[ARGV_SIZE]; + char execfile[ARGV_SIZE]; + char tty[PATH_SIZE]; +} StartupPacket6_2; + + +/* Structure to hold all the connection attributes for a specific + connection (used for both registry and file, DSN and DRIVER) +*/ +typedef struct +{ + char dsn[MEDIUM_REGISTRY_LEN]; + char desc[MEDIUM_REGISTRY_LEN]; + char driver[MEDIUM_REGISTRY_LEN]; + char server[MEDIUM_REGISTRY_LEN]; + char database[MEDIUM_REGISTRY_LEN]; + char username[MEDIUM_REGISTRY_LEN]; + char password[MEDIUM_REGISTRY_LEN]; + char conn_settings[LARGE_REGISTRY_LEN]; + char protocol[SMALL_REGISTRY_LEN]; + char port[SMALL_REGISTRY_LEN]; + char onlyread[SMALL_REGISTRY_LEN]; + char fake_oid_index[SMALL_REGISTRY_LEN]; + char show_oid_column[SMALL_REGISTRY_LEN]; + char row_versioning[SMALL_REGISTRY_LEN]; + char show_system_tables[SMALL_REGISTRY_LEN]; + char translation_dll[MEDIUM_REGISTRY_LEN]; + char translation_option[SMALL_REGISTRY_LEN]; + char focus_password; + char disallow_premature; + char updatable_cursors; + GLOBAL_VALUES drivers; /* moved from driver's option */ +} ConnInfo; + +/* Macro to determine is the connection using 6.2 protocol? */ +#define PROTOCOL_62(conninfo_) (strncmp((conninfo_)->protocol, PG62, strlen(PG62)) == 0) + +/* Macro to determine is the connection using 6.3 protocol? */ +#define PROTOCOL_63(conninfo_) (strncmp((conninfo_)->protocol, PG63, strlen(PG63)) == 0) + +/* + * Macros to compare the server's version with a specified version + * 1st parameter: pointer to a ConnectionClass object + * 2nd parameter: major version number + * 3rd parameter: minor version number + */ +#define SERVER_VERSION_GT(conn, major, minor) \ + ((conn)->pg_version_major > major || \ + ((conn)->pg_version_major == major && (conn)->pg_version_minor > minor)) +#define SERVER_VERSION_GE(conn, major, minor) \ + ((conn)->pg_version_major > major || \ + ((conn)->pg_version_major == major && (conn)->pg_version_minor >= minor)) +#define SERVER_VERSION_EQ(conn, major, minor) \ + ((conn)->pg_version_major == major && (conn)->pg_version_minor == minor) +#define SERVER_VERSION_LE(conn, major, minor) (! SERVER_VERSION_GT(conn, major, minor)) +#define SERVER_VERSION_LT(conn, major, minor) (! SERVER_VERSION_GE(conn, major, minor)) +/*#if ! defined(HAVE_CONFIG_H) || defined(HAVE_STRINGIZE)*/ +#define STRING_AFTER_DOT(string) (strchr(#string, '.') + 1) +/*#else +#define STRING_AFTER_DOT(str) (strchr("str", '.') + 1) +#endif*/ +/* + * Simplified macros to compare the server's version with a + * specified version + * Note: Never pass a variable as the second parameter. + * It must be a decimal constant of the form %d.%d . + */ +#define PG_VERSION_GT(conn, ver) \ + (SERVER_VERSION_GT(conn, (int) ver, atoi(STRING_AFTER_DOT(ver)))) +#define PG_VERSION_GE(conn, ver) \ + (SERVER_VERSION_GE(conn, (int) ver, atoi(STRING_AFTER_DOT(ver)))) +#define PG_VERSION_EQ(conn, ver) \ + (SERVER_VERSION_EQ(conn, (int) ver, atoi(STRING_AFTER_DOT(ver)))) +#define PG_VERSION_LE(conn, ver) (! PG_VERSION_GT(conn, ver)) +#define PG_VERSION_LT(conn, ver) (! PG_VERSION_GE(conn, ver)) + +/* This is used to store cached table information in the connection */ +struct col_info +{ + QResultClass *result; + char name[MAX_TABLE_LEN + 1]; +}; + + /* Translation DLL entry points */ +#ifdef WIN32 +#define DLLHANDLE HINSTANCE +#else +#define WINAPI CALLBACK +#define DLLHANDLE void * +#define HINSTANCE void * +#endif + +typedef BOOL (FAR WINAPI * DataSourceToDriverProc) (UDWORD, + SWORD, + PTR, + SDWORD, + PTR, + SDWORD, + SDWORD FAR *, + UCHAR FAR *, + SWORD, + SWORD FAR *); + +typedef BOOL (FAR WINAPI * DriverToDataSourceProc) (UDWORD, + SWORD, + PTR, + SDWORD, + PTR, + SDWORD, + SDWORD FAR *, + UCHAR FAR *, + SWORD, + SWORD FAR *); + +/******* The Connection handle ************/ +struct ConnectionClass_ +{ + HENV henv; /* environment this connection was created + * on */ + StatementOptions stmtOptions; + char *errormsg; + int errornumber; + CONN_Status status; + ConnInfo connInfo; + StatementClass **stmts; + int num_stmts; + SocketClass *sock; + int lobj_type; + int ntables; + COL_INFO **col_info; + long translation_option; + HINSTANCE translation_handle; + DataSourceToDriverProc DataSourceToDriver; + DriverToDataSourceProc DriverToDataSource; + Int2 driver_version; /* prepared for ODBC3.0 */ + char transact_status;/* Is a transaction is currently in + * progress */ + char errormsg_created; /* has an informative error msg + * been created? */ + char pg_version[MAX_INFO_STRING]; /* Version of PostgreSQL + * we're connected to - + * DJP 25-1-2001 */ + float pg_version_number; + Int2 pg_version_major; + Int2 pg_version_minor; + char ms_jet; +#ifdef MULTIBYTE + char *client_encoding; + char *server_encoding; +#endif /* MULTIBYTE */ +}; + + +/* Accessor functions */ +#define CC_get_socket(x) (x->sock) +#define CC_get_database(x) (x->connInfo.database) +#define CC_get_server(x) (x->connInfo.server) +#define CC_get_DSN(x) (x->connInfo.dsn) +#define CC_get_username(x) (x->connInfo.username) +#define CC_is_onlyread(x) (x->connInfo.onlyread[0] == '1') + + +/* for CC_DSN_info */ +#define CONN_DONT_OVERWRITE 0 +#define CONN_OVERWRITE 1 + + +/* prototypes */ +ConnectionClass *CC_Constructor(void); +char CC_Destructor(ConnectionClass *self); +int CC_cursor_count(ConnectionClass *self); +char CC_cleanup(ConnectionClass *self); +char CC_abort(ConnectionClass *self); +int CC_set_translation(ConnectionClass *self); +char CC_connect(ConnectionClass *self, char do_password); +char CC_add_statement(ConnectionClass *self, StatementClass *stmt); +char CC_remove_statement(ConnectionClass *self, StatementClass *stmt); +char CC_get_error(ConnectionClass *self, int *number, char **message); +QResultClass *CC_send_query(ConnectionClass *self, char *query, QueryInfo *qi); +void CC_clear_error(ConnectionClass *self); +char *CC_create_errormsg(ConnectionClass *self); +int CC_send_function(ConnectionClass *conn, int fnid, void *result_buf, int *actual_result_len, int result_is_int, LO_ARG *argv, int nargs); +char CC_send_settings(ConnectionClass *self); +void CC_lookup_lo(ConnectionClass *conn); +void CC_lookup_pg_version(ConnectionClass *conn); +void CC_initialize_pg_version(ConnectionClass *conn); +void CC_log_error(char *func, char *desc, ConnectionClass *self); +int CC_get_max_query_len(const ConnectionClass *self); + +#endif diff --git a/src/interfaces/odbc/windev/convert.c b/src/interfaces/odbc/windev/convert.c new file mode 100644 index 0000000000..0b609a07db --- /dev/null +++ b/src/interfaces/odbc/windev/convert.c @@ -0,0 +1,2655 @@ +/*------- + * Module: convert.c + * + * Description: This module contains routines related to + * converting parameters and columns into requested data types. + * Parameters are converted from their SQL_C data types into + * the appropriate postgres type. Columns are converted from + * their postgres type (SQL type) into the appropriate SQL_C + * data type. + * + * Classes: n/a + * + * API functions: none + * + * Comments: See "notice.txt" for copyright and license information. + *------- + */ +/* Multibyte support Eiji Tokuya 2001-03-15 */ + +#include "convert.h" + +#include +#include +#include + +#ifdef MULTIBYTE +#include "multibyte.h" +#endif + +#include +#include +#include +#include "statement.h" +#include "qresult.h" +#include "bind.h" +#include "pgtypes.h" +#include "lobj.h" +#include "connection.h" +#include "pgapifunc.h" + +#ifdef __CYGWIN__ +#define TIMEZONE_GLOBAL _timezone +#elif defined(WIN32) || defined(HAVE_INT_TIMEZONE) +#define TIMEZONE_GLOBAL timezone +#endif + +/* + * How to map ODBC scalar functions {fn func(args)} to Postgres. + * This is just a simple substitution. List augmented from: + * http://www.merant.com/datadirect/download/docs/odbc16/Odbcref/rappc.htm + * - thomas 2000-04-03 + */ +char *mapFuncs[][2] = { +/* { "ASCII", "ascii" }, */ + {"CHAR", "chr"}, + {"CONCAT", "textcat"}, +/* { "DIFFERENCE", "difference" }, */ +/* { "INSERT", "insert" }, */ + {"LCASE", "lower"}, + {"LEFT", "ltrunc"}, + {"LOCATE", "strpos"}, + {"LENGTH", "char_length"}, +/* { "LTRIM", "ltrim" }, */ + {"RIGHT", "rtrunc"}, +/* { "REPEAT", "repeat" }, */ +/* { "REPLACE", "replace" }, */ +/* { "RTRIM", "rtrim" }, */ +/* { "SOUNDEX", "soundex" }, */ + {"SUBSTRING", "substr"}, + {"UCASE", "upper"}, + +/* { "ABS", "abs" }, */ +/* { "ACOS", "acos" }, */ +/* { "ASIN", "asin" }, */ +/* { "ATAN", "atan" }, */ +/* { "ATAN2", "atan2" }, */ + {"CEILING", "ceil"}, +/* { "COS", "cos" }, */ +/* { "COT", "cot" }, */ +/* { "DEGREES", "degrees" }, */ +/* { "EXP", "exp" }, */ +/* { "FLOOR", "floor" }, */ + {"LOG", "ln"}, + {"LOG10", "log"}, +/* { "MOD", "mod" }, */ +/* { "PI", "pi" }, */ + {"POWER", "pow"}, +/* { "RADIANS", "radians" }, */ + {"RAND", "random"}, +/* { "ROUND", "round" }, */ +/* { "SIGN", "sign" }, */ +/* { "SIN", "sin" }, */ +/* { "SQRT", "sqrt" }, */ +/* { "TAN", "tan" }, */ + {"TRUNCATE", "trunc"}, + + {"CURRENT_DATE", "curdate"}, + {"CURRENT_TIME", "curtime"}, + {"CURRENT_TIMESTAMP", "odbc_timestamp"}, + {"CURRENT_USER", "odbc_current_user"}, + {"SESSION_USER", "odbc_session_user"}, +/* { "CURDATE", "curdate" }, */ +/* { "CURTIME", "curtime" }, */ +/* { "DAYNAME", "dayname" }, */ +/* { "DAYOFMONTH", "dayofmonth" }, */ +/* { "DAYOFWEEK", "dayofweek" }, */ +/* { "DAYOFYEAR", "dayofyear" }, */ +/* { "HOUR", "hour" }, */ +/* { "MINUTE", "minute" }, */ +/* { "MONTH", "month" }, */ +/* { "MONTHNAME", "monthname" }, */ +/* { "NOW", "now" }, */ +/* { "QUARTER", "quarter" }, */ +/* { "SECOND", "second" }, */ +/* { "WEEK", "week" }, */ +/* { "YEAR", "year" }, */ + +/* { "DATABASE", "database" }, */ + {"IFNULL", "coalesce"}, + {"USER", "odbc_user"}, + {0, 0} +}; + +static char *mapFunction(const char *func); +static unsigned int conv_from_octal(const unsigned char *s); +static unsigned int conv_from_hex(const unsigned char *s); +static char *conv_to_octal(unsigned char val); + +/*--------- + * A Guide for date/time/timestamp conversions + * + * field_type fCType Output + * ---------- ------ ---------- + * PG_TYPE_DATE SQL_C_DEFAULT SQL_C_DATE + * PG_TYPE_DATE SQL_C_DATE SQL_C_DATE + * PG_TYPE_DATE SQL_C_TIMESTAMP SQL_C_TIMESTAMP (time = 0 (midnight)) + * PG_TYPE_TIME SQL_C_DEFAULT SQL_C_TIME + * PG_TYPE_TIME SQL_C_TIME SQL_C_TIME + * PG_TYPE_TIME SQL_C_TIMESTAMP SQL_C_TIMESTAMP (date = current date) + * PG_TYPE_ABSTIME SQL_C_DEFAULT SQL_C_TIMESTAMP + * PG_TYPE_ABSTIME SQL_C_DATE SQL_C_DATE (time is truncated) + * PG_TYPE_ABSTIME SQL_C_TIME SQL_C_TIME (date is truncated) + * PG_TYPE_ABSTIME SQL_C_TIMESTAMP SQL_C_TIMESTAMP + *--------- + */ + + + +/* + * TIMESTAMP <-----> SIMPLE_TIME + * precision support since 7.2. + * time zone support is unavailable(the stuff is unreliable) + */ +static BOOL +timestamp2stime(const char *str, SIMPLE_TIME *st, BOOL *bZone, int *zone) +{ + char rest[64], + *ptr; + int scnt, + i; + long timediff; + BOOL withZone = *bZone; + + *bZone = FALSE; + *zone = 0; + st->fr = 0; + if ((scnt = sscanf(str, "%4d-%2d-%2d %2d:%2d:%2d%s", &st->y, &st->m, &st->d, &st->hh, &st->mm, &st->ss, rest)) < 6) + return FALSE; + else if (scnt == 6) + return TRUE; + switch (rest[0]) + { + case '+': + *bZone = TRUE; + *zone = atoi(&rest[1]); + break; + case '-': + *bZone = TRUE; + *zone = -atoi(&rest[1]); + break; + case '.': + if ((ptr = strchr(rest, '+')) != NULL) + { + *bZone = TRUE; + *zone = atoi(&ptr[1]); + *ptr = '\0'; + } + else if ((ptr = strchr(rest, '-')) != NULL) + { + *bZone = TRUE; + *zone = -atoi(&ptr[1]); + *ptr = '\0'; + } + for (i = 1; i < 10; i++) + { + if (!isdigit((unsigned char) rest[i])) + break; + } + for (; i < 10; i++) + rest[i] = '0'; + rest[i] = '\0'; + st->fr = atoi(&rest[1]); + break; + default: + return TRUE; + } + if (!withZone || !*bZone || st->y < 1970) + return TRUE; +#if defined(WIN32) || defined(HAVE_INT_TIMEZONE) + if (!tzname[0] || !tzname[0][0]) + { + *bZone = FALSE; + return TRUE; + } + timediff = TIMEZONE_GLOBAL + (*zone) * 3600; + if (!daylight && timediff == 0) /* the same timezone */ + return TRUE; + else + { + struct tm tm, + *tm2; + time_t time0; + + *bZone = FALSE; + tm.tm_year = st->y - 1900; + tm.tm_mon = st->m - 1; + tm.tm_mday = st->d; + tm.tm_hour = st->hh; + tm.tm_min = st->mm; + tm.tm_sec = st->ss; + tm.tm_isdst = -1; + time0 = mktime(&tm); + if (time0 < 0) + return TRUE; + if (tm.tm_isdst > 0) + timediff -= 3600; + if (timediff == 0) /* the same time zone */ + return TRUE; + time0 -= timediff; + if (time0 >= 0 && (tm2 = localtime(&time0)) != NULL) + { + st->y = tm2->tm_year + 1900; + st->m = tm2->tm_mon + 1; + st->d = tm2->tm_mday; + st->hh = tm2->tm_hour; + st->mm = tm2->tm_min; + st->ss = tm2->tm_sec; + *bZone = TRUE; + } + } +#endif /* WIN32 */ + return TRUE; +} + +static BOOL +stime2timestamp(const SIMPLE_TIME *st, char *str, BOOL bZone, BOOL precision) +{ + char precstr[16], + zonestr[16]; + int i; + + precstr[0] = '\0'; + if (precision && st->fr) + { + sprintf(precstr, ".%09d", st->fr); + for (i = 9; i > 0; i--) + { + if (precstr[i] != '0') + break; + precstr[i] = '\0'; + } + } + zonestr[0] = '\0'; +#if defined(WIN32) || defined(HAVE_INT_TIMEZONE) + if (bZone && tzname[0] && tzname[0][0] && st->y >= 1970) + { + long zoneint; + struct tm tm; + time_t time0; + + zoneint = TIMEZONE_GLOBAL; + if (daylight && st->y >= 1900) + { + tm.tm_year = st->y - 1900; + tm.tm_mon = st->m - 1; + tm.tm_mday = st->d; + tm.tm_hour = st->hh; + tm.tm_min = st->mm; + tm.tm_sec = st->ss; + tm.tm_isdst = -1; + time0 = mktime(&tm); + if (time0 >= 0 && tm.tm_isdst > 0) + zoneint -= 3600; + } + if (zoneint > 0) + sprintf(zonestr, "-%02d", (int) zoneint / 3600); + else + sprintf(zonestr, "+%02d", -(int) zoneint / 3600); + } +#endif /* WIN32 */ + sprintf(str, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d%s%s", st->y, st->m, st->d, st->hh, st->mm, st->ss, precstr, zonestr); + return TRUE; +} + +/* This is called by SQLFetch() */ +int +copy_and_convert_field_bindinfo(StatementClass *stmt, Int4 field_type, void *value, int col) +{ + BindInfoClass *bic = &(stmt->bindings[col]); + + return copy_and_convert_field(stmt, field_type, value, (Int2) bic->returntype, (PTR) bic->buffer, + (SDWORD) bic->buflen, (SDWORD *) bic->used); +} + + +/* This is called by SQLGetData() */ +int +copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2 fCType, + PTR rgbValue, SDWORD cbValueMax, SDWORD *pcbValue) +{ + Int4 len = 0, + copy_len = 0; + SIMPLE_TIME st; + time_t t = time(NULL); + struct tm *tim; + int pcbValueOffset, + rgbValueOffset; + char *rgbValueBindRow; + const char *ptr; + int bind_row = stmt->bind_row; + int bind_size = stmt->options.bind_size; + int result = COPY_OK; + BOOL changed; + const char *neut_str = value; + char midtemp[2][32]; + int mtemp_cnt = 0; + static BindInfoClass sbic; + BindInfoClass *pbic; + + if (stmt->current_col >= 0) + { + pbic = &stmt->bindings[stmt->current_col]; + if (pbic->data_left == -2) + pbic->data_left = (cbValueMax > 0) ? 0 : -1; /* This seems to be * + * needed for ADO ? */ + if (pbic->data_left == 0) + { + if (pbic->ttlbuf != NULL) + { + free(pbic->ttlbuf); + pbic->ttlbuf = NULL; + pbic->ttlbuflen = 0; + } + pbic->data_left = -2; /* needed by ADO ? */ + return COPY_NO_DATA_FOUND; + } + } + /*--------- + * rgbValueOffset is *ONLY* for character and binary data. + * pcbValueOffset is for computing any pcbValue location + *--------- + */ + + if (bind_size > 0) + pcbValueOffset = rgbValueOffset = (bind_size * bind_row); + else + { + pcbValueOffset = bind_row * sizeof(SDWORD); + rgbValueOffset = bind_row * cbValueMax; + + } + + memset(&st, 0, sizeof(SIMPLE_TIME)); + + /* Initialize current date */ + tim = localtime(&t); + st.m = tim->tm_mon + 1; + st.d = tim->tm_mday; + st.y = tim->tm_year + 1900; + + mylog("copy_and_convert: field_type = %d, fctype = %d, value = '%s', cbValueMax=%d\n", field_type, fCType, (value == NULL) ? "" : value, cbValueMax); + + if (!value) + { + /* + * handle a null just by returning SQL_NULL_DATA in pcbValue, and + * doing nothing to the buffer. + */ + if (pcbValue) + *(SDWORD *) ((char *) pcbValue + pcbValueOffset) = SQL_NULL_DATA; + return COPY_OK; + } + + if (stmt->hdbc->DataSourceToDriver != NULL) + { + int length = strlen(value); + + stmt->hdbc->DataSourceToDriver(stmt->hdbc->translation_option, + SQL_CHAR, + value, length, + value, length, NULL, + NULL, 0, NULL); + } + + /* + * First convert any specific postgres types into more useable data. + * + * NOTE: Conversions from PG char/varchar of a date/time/timestamp value + * to SQL_C_DATE,SQL_C_TIME, SQL_C_TIMESTAMP not supported + */ + switch (field_type) + { + /* + * $$$ need to add parsing for date/time/timestamp strings in + * PG_TYPE_CHAR,VARCHAR $$$ + */ + case PG_TYPE_DATE: + sscanf(value, "%4d-%2d-%2d", &st.y, &st.m, &st.d); + break; + + case PG_TYPE_TIME: + sscanf(value, "%2d:%2d:%2d", &st.hh, &st.mm, &st.ss); + break; + + case PG_TYPE_ABSTIME: + case PG_TYPE_DATETIME: + case PG_TYPE_TIMESTAMP: + st.fr = 0; + if (strnicmp(value, "invalid", 7) != 0) + { + BOOL bZone = (field_type != PG_TYPE_TIMESTAMP_NO_TMZONE && PG_VERSION_GE(SC_get_conn(stmt), 7.2)); + int zone; + + /* + * sscanf(value, "%4d-%2d-%2d %2d:%2d:%2d", &st.y, &st.m, + * &st.d, &st.hh, &st.mm, &st.ss); + */ + bZone = FALSE; /* time zone stuff is unreliable */ + timestamp2stime(value, &st, &bZone, &zone); + } + else + { + /* + * The timestamp is invalid so set something conspicuous, + * like the epoch + */ + t = 0; + tim = localtime(&t); + st.m = tim->tm_mon + 1; + st.d = tim->tm_mday; + st.y = tim->tm_year + 1900; + st.hh = tim->tm_hour; + st.mm = tim->tm_min; + st.ss = tim->tm_sec; + } + break; + + case PG_TYPE_BOOL: + { /* change T/F to 1/0 */ + char *s; + + s = midtemp[mtemp_cnt]; + strcpy(s, (char *) value); + if (s[0] == 'f' || s[0] == 'F' || s[0] == 'n' || s[0] == 'N' || s[0] == '0') + s[0] = '0'; + else + s[0] = '1'; + s[1] = '\0'; + neut_str = midtemp[mtemp_cnt]; + mtemp_cnt++; + + } + break; + + /* This is for internal use by SQLStatistics() */ + case PG_TYPE_INT2VECTOR: + { + int nval, + i; + const char *vp; + + /* this is an array of eight integers */ + short *short_array = (short *) ((char *) rgbValue + rgbValueOffset); + + len = 32; + vp = value; + nval = 0; + mylog("index=("); + for (i = 0; i < 16; i++) + { + if (sscanf(vp, "%hd", &short_array[i]) != 1) + break; + + mylog(" %d", short_array[i]); + nval++; + + /* skip the current token */ + while ((*vp != '\0') && (!isspace((unsigned char) *vp))) + vp++; + /* and skip the space to the next token */ + while ((*vp != '\0') && (isspace((unsigned char) *vp))) + vp++; + if (*vp == '\0') + break; + } + mylog(") nval = %d\n", nval); + + for (i = nval; i < 16; i++) + short_array[i] = 0; + +#if 0 + sscanf(value, "%hd %hd %hd %hd %hd %hd %hd %hd", + &short_array[0], + &short_array[1], + &short_array[2], + &short_array[3], + &short_array[4], + &short_array[5], + &short_array[6], + &short_array[7]); +#endif + + /* There is no corresponding fCType for this. */ + if (pcbValue) + *(SDWORD *) ((char *) pcbValue + pcbValueOffset) = len; + + return COPY_OK; /* dont go any further or the data will be + * trashed */ + } + + /* + * This is a large object OID, which is used to store + * LONGVARBINARY objects. + */ + case PG_TYPE_LO: + + return convert_lo(stmt, value, fCType, ((char *) rgbValue + rgbValueOffset), cbValueMax, (SDWORD *) ((char *) pcbValue + pcbValueOffset)); + + default: + + if (field_type == stmt->hdbc->lobj_type) /* hack until permanent + * type available */ + return convert_lo(stmt, value, fCType, ((char *) rgbValue + rgbValueOffset), cbValueMax, (SDWORD *) ((char *) pcbValue + pcbValueOffset)); + } + + /* Change default into something useable */ + if (fCType == SQL_C_DEFAULT) + { + fCType = pgtype_to_ctype(stmt, field_type); + + mylog("copy_and_convert, SQL_C_DEFAULT: fCType = %d\n", fCType); + } + + rgbValueBindRow = (char *) rgbValue + rgbValueOffset; + + if (fCType == SQL_C_CHAR) + { + /* Special character formatting as required */ + + /* + * These really should return error if cbValueMax is not big + * enough. + */ + switch (field_type) + { + case PG_TYPE_DATE: + len = 10; + if (cbValueMax > len) + sprintf(rgbValueBindRow, "%.4d-%.2d-%.2d", st.y, st.m, st.d); + break; + + case PG_TYPE_TIME: + len = 8; + if (cbValueMax > len) + sprintf(rgbValueBindRow, "%.2d:%.2d:%.2d", st.hh, st.mm, st.ss); + break; + + case PG_TYPE_ABSTIME: + case PG_TYPE_DATETIME: + case PG_TYPE_TIMESTAMP: + len = 19; + if (cbValueMax > len) + sprintf(rgbValueBindRow, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d", + st.y, st.m, st.d, st.hh, st.mm, st.ss); + break; + + case PG_TYPE_BOOL: + len = 1; + if (cbValueMax > len) + { + strcpy(rgbValueBindRow, neut_str); + mylog("PG_TYPE_BOOL: rgbValueBindRow = '%s'\n", rgbValueBindRow); + } + break; + + /* + * Currently, data is SILENTLY TRUNCATED for BYTEA and + * character data types if there is not enough room in + * cbValueMax because the driver can't handle multiple + * calls to SQLGetData for these, yet. Most likely, the + * buffer passed in will be big enough to handle the + * maximum limit of postgres, anyway. + * + * LongVarBinary types are handled correctly above, observing + * truncation and all that stuff since there is + * essentially no limit on the large object used to store + * those. + */ + case PG_TYPE_BYTEA:/* convert binary data to hex strings + * (i.e, 255 = "FF") */ + len = convert_pgbinary_to_char(neut_str, rgbValueBindRow, cbValueMax); + + /***** THIS IS NOT PROPERLY IMPLEMENTED *****/ + break; + + default: + if (stmt->current_col < 0) + { + pbic = &sbic; + pbic->data_left = -1; + } + else + pbic = &stmt->bindings[stmt->current_col]; + if (pbic->data_left < 0) + { + /* convert linefeeds to carriage-return/linefeed */ + len = convert_linefeeds(neut_str, NULL, 0, &changed); + if (cbValueMax == 0) /* just returns length + * info */ + { + result = COPY_RESULT_TRUNCATED; + break; + } + if (!pbic->ttlbuf) + pbic->ttlbuflen = 0; + if (changed || len >= cbValueMax) + { + if (len >= (int) pbic->ttlbuflen) + { + pbic->ttlbuf = realloc(pbic->ttlbuf, len + 1); + pbic->ttlbuflen = len + 1; + } + convert_linefeeds(neut_str, pbic->ttlbuf, pbic->ttlbuflen, &changed); + ptr = pbic->ttlbuf; + } + else + { + if (pbic->ttlbuf) + { + free(pbic->ttlbuf); + pbic->ttlbuf = NULL; + } + ptr = neut_str; + } + } + else + ptr = pbic->ttlbuf; + + mylog("DEFAULT: len = %d, ptr = '%s'\n", len, ptr); + + if (stmt->current_col >= 0) + { + if (pbic->data_left > 0) + { + ptr += strlen(ptr) - pbic->data_left; + len = pbic->data_left; + } + else + pbic->data_left = len; + } + + if (cbValueMax > 0) + { + copy_len = (len >= cbValueMax) ? cbValueMax - 1 : len; + + /* Copy the data */ + memcpy(rgbValueBindRow, ptr, copy_len); + rgbValueBindRow[copy_len] = '\0'; + + /* Adjust data_left for next time */ + if (stmt->current_col >= 0) + pbic->data_left -= copy_len; + } + + /* + * Finally, check for truncation so that proper status can + * be returned + */ + if (cbValueMax > 0 && len >= cbValueMax) + result = COPY_RESULT_TRUNCATED; + else + { + if (pbic->ttlbuf != NULL) + { + free(pbic->ttlbuf); + pbic->ttlbuf = NULL; + } + } + + + mylog(" SQL_C_CHAR, default: len = %d, cbValueMax = %d, rgbValueBindRow = '%s'\n", len, cbValueMax, rgbValueBindRow); + break; + } + + + } + else + { + /* + * for SQL_C_CHAR, it's probably ok to leave currency symbols in. + * But to convert to numeric types, it is necessary to get rid of + * those. + */ + if (field_type == PG_TYPE_MONEY) + { + if (convert_money(neut_str, midtemp[mtemp_cnt], sizeof(midtemp[0]))) + { + neut_str = midtemp[mtemp_cnt]; + mtemp_cnt++; + } + else + return COPY_UNSUPPORTED_TYPE; + } + + switch (fCType) + { + case SQL_C_DATE: +#if (ODBCVER >= 0x0300) + case SQL_C_TYPE_DATE: /* 91 */ +#endif + len = 6; + { + DATE_STRUCT *ds; + + if (bind_size > 0) + ds = (DATE_STRUCT *) ((char *) rgbValue + (bind_row * bind_size)); + else + ds = (DATE_STRUCT *) rgbValue + bind_row; + ds->year = st.y; + ds->month = st.m; + ds->day = st.d; + } + break; + + case SQL_C_TIME: +#if (ODBCVER >= 0x0300) + case SQL_C_TYPE_TIME: /* 92 */ +#endif + len = 6; + { + TIME_STRUCT *ts; + + if (bind_size > 0) + ts = (TIME_STRUCT *) ((char *) rgbValue + (bind_row * bind_size)); + else + ts = (TIME_STRUCT *) rgbValue + bind_row; + ts->hour = st.hh; + ts->minute = st.mm; + ts->second = st.ss; + } + break; + + case SQL_C_TIMESTAMP: +#if (ODBCVER >= 0x0300) + case SQL_C_TYPE_TIMESTAMP: /* 93 */ +#endif + len = 16; + { + TIMESTAMP_STRUCT *ts; + + if (bind_size > 0) + ts = (TIMESTAMP_STRUCT *) ((char *) rgbValue + (bind_row * bind_size)); + else + ts = (TIMESTAMP_STRUCT *) rgbValue + bind_row; + ts->year = st.y; + ts->month = st.m; + ts->day = st.d; + ts->hour = st.hh; + ts->minute = st.mm; + ts->second = st.ss; + ts->fraction = st.fr; + } + break; + + case SQL_C_BIT: + len = 1; + if (bind_size > 0) + *(UCHAR *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(neut_str); + else + *((UCHAR *) rgbValue + bind_row) = atoi(neut_str); + + /* + * mylog("SQL_C_BIT: bind_row = %d val = %d, cb = %d, + * rgb=%d\n", bind_row, atoi(neut_str), cbValueMax, + * *((UCHAR *)rgbValue)); + */ + break; + + case SQL_C_STINYINT: + case SQL_C_TINYINT: + len = 1; + if (bind_size > 0) + *(SCHAR *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(neut_str); + else + *((SCHAR *) rgbValue + bind_row) = atoi(neut_str); + break; + + case SQL_C_UTINYINT: + len = 1; + if (bind_size > 0) + *(UCHAR *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(neut_str); + else + *((UCHAR *) rgbValue + bind_row) = atoi(neut_str); + break; + + case SQL_C_FLOAT: + len = 4; + if (bind_size > 0) + *(SFLOAT *) ((char *) rgbValue + (bind_row * bind_size)) = (float) atof(neut_str); + else + *((SFLOAT *) rgbValue + bind_row) = (float) atof(neut_str); + break; + + case SQL_C_DOUBLE: + len = 8; + if (bind_size > 0) + *(SDOUBLE *) ((char *) rgbValue + (bind_row * bind_size)) = atof(neut_str); + else + *((SDOUBLE *) rgbValue + bind_row) = atof(neut_str); + break; + + case SQL_C_SSHORT: + case SQL_C_SHORT: + len = 2; + if (bind_size > 0) + *(SWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(neut_str); + else + *((SWORD *) rgbValue + bind_row) = atoi(neut_str); + break; + + case SQL_C_USHORT: + len = 2; + if (bind_size > 0) + *(UWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(neut_str); + else + *((UWORD *) rgbValue + bind_row) = atoi(neut_str); + break; + + case SQL_C_SLONG: + case SQL_C_LONG: + len = 4; + if (bind_size > 0) + *(SDWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atol(neut_str); + else + *((SDWORD *) rgbValue + bind_row) = atol(neut_str); + break; + + case SQL_C_ULONG: + len = 4; + if (bind_size > 0) + *(UDWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atol(neut_str); + else + *((UDWORD *) rgbValue + bind_row) = atol(neut_str); + break; + + case SQL_C_BINARY: + + /* truncate if necessary */ + /* convert octal escapes to bytes */ + + if (stmt->current_col < 0) + { + pbic = &sbic; + pbic->data_left = -1; + } + else + pbic = &stmt->bindings[stmt->current_col]; + if (!pbic->ttlbuf) + pbic->ttlbuflen = 0; + if (len = strlen(neut_str), len >= (int) pbic->ttlbuflen) + { + pbic->ttlbuf = realloc(pbic->ttlbuf, len + 1); + pbic->ttlbuflen = len + 1; + } + len = convert_from_pgbinary(neut_str, pbic->ttlbuf, pbic->ttlbuflen); + ptr = pbic->ttlbuf; + + if (stmt->current_col >= 0) + { + /* + * Second (or more) call to SQLGetData so move the + * pointer + */ + if (pbic->data_left > 0) + { + ptr += len - pbic->data_left; + len = pbic->data_left; + } + + /* First call to SQLGetData so initialize data_left */ + else + pbic->data_left = len; + + } + + if (cbValueMax > 0) + { + copy_len = (len > cbValueMax) ? cbValueMax : len; + + /* Copy the data */ + memcpy(rgbValueBindRow, ptr, copy_len); + + /* Adjust data_left for next time */ + if (stmt->current_col >= 0) + pbic->data_left -= copy_len; + } + + /* + * Finally, check for truncation so that proper status can + * be returned + */ + if (len > cbValueMax) + result = COPY_RESULT_TRUNCATED; + + if (pbic->ttlbuf) + { + free(pbic->ttlbuf); + pbic->ttlbuf = NULL; + } + mylog("SQL_C_BINARY: len = %d, copy_len = %d\n", len, copy_len); + break; + + default: + return COPY_UNSUPPORTED_TYPE; + } + } + + /* store the length of what was copied, if there's a place for it */ + if (pcbValue) + *(SDWORD *) ((char *) pcbValue + pcbValueOffset) = len; + + if (result == COPY_OK && stmt->current_col >= 0) + stmt->bindings[stmt->current_col].data_left = 0; + return result; + +} + + +/*-------------------------------------------------------------------- + * Functions/Macros to get rid of query size limit. + * + * I always used the follwoing macros to convert from + * old_statement to new_statement. Please improve it + * if you have a better way. Hiroshi 2001/05/22 + *-------------------------------------------------------------------- + */ +#define INIT_MIN_ALLOC 4096 +static int +enlarge_statement(StatementClass *stmt, unsigned int newsize) +{ + unsigned int newalsize = INIT_MIN_ALLOC; + static char *func = "enlarge_statement"; + + if (stmt->stmt_size_limit > 0 && stmt->stmt_size_limit < (int) newsize) + { + stmt->errormsg = "Query buffer overflow in copy_statement_with_parameters"; + stmt->errornumber = STMT_EXEC_ERROR; + SC_log_error(func, "", stmt); + return -1; + } + while (newalsize <= newsize) + newalsize *= 2; + if (!(stmt->stmt_with_params = realloc(stmt->stmt_with_params, newalsize))) + { + stmt->errormsg = "Query buffer allocate error in copy_statement_with_parameters"; + stmt->errornumber = STMT_EXEC_ERROR; + SC_log_error(func, "", stmt); + return 0; + } + return newalsize; +} + +/*---------- + * Enlarge stmt_with_params if necessary. + *---------- + */ +#define ENLARGE_NEWSTATEMENT(newpos) \ + if (newpos >= new_stsize) \ + { \ + if ((new_stsize = enlarge_statement(stmt, newpos)) <= 0) \ + return SQL_ERROR; \ + new_statement = stmt->stmt_with_params; \ + } +/*---------- + * Initialize stmt_with_params, new_statement etc. + *---------- + */ +#define CVT_INIT(size) \ +do { \ + if (stmt->stmt_with_params) \ + free(stmt->stmt_with_params); \ + if (stmt->stmt_size_limit > 0) \ + new_stsize = stmt->stmt_size_limit; \ + else \ + { \ + new_stsize = INIT_MIN_ALLOC; \ + while (new_stsize <= size) \ + new_stsize *= 2; \ + } \ + new_statement = malloc(new_stsize); \ + stmt->stmt_with_params = new_statement; \ + npos = 0; \ + new_statement[0] = '\0'; \ +} while (0) + +/*---------- + * Terminate the stmt_with_params string with NULL. + *---------- + */ +#define CVT_TERMINATE \ +do { \ + new_statement[npos] = '\0'; \ +} while (0) + +/*---------- + * Append a data. + *---------- + */ +#define CVT_APPEND_DATA(s, len) \ +do { \ + unsigned int newpos = npos + len; \ + ENLARGE_NEWSTATEMENT(newpos) \ + memcpy(&new_statement[npos], s, len); \ + npos = newpos; \ + new_statement[npos] = '\0'; \ +} while (0) + +/*---------- + * Append a string. + *---------- + */ +#define CVT_APPEND_STR(s) \ +do { \ + unsigned int len = strlen(s); \ + CVT_APPEND_DATA(s, len); \ +} while (0) + +/*---------- + * Append a char. + *---------- + */ +#define CVT_APPEND_CHAR(c) \ +do { \ + ENLARGE_NEWSTATEMENT(npos + 1); \ + new_statement[npos++] = c; \ +} while (0) + +/*---------- + * Append a binary data. + * Newly reqeuired size may be overestimated currently. + *---------- + */ +#define CVT_APPEND_BINARY(buf, used) \ +do { \ + unsigned int newlimit = npos + 5 * used; \ + ENLARGE_NEWSTATEMENT(newlimit); \ + npos += convert_to_pgbinary(buf, &new_statement[npos], used); \ +} while (0) + +/*---------- + * + *---------- + */ +#define CVT_SPECIAL_CHARS(buf, used) \ +do { \ + int cnvlen = convert_special_chars(buf, NULL, used); \ + unsigned int newlimit = npos + cnvlen; \ +\ + ENLARGE_NEWSTATEMENT(newlimit); \ + convert_special_chars(buf, &new_statement[npos], used); \ + npos += cnvlen; \ +} while (0) + +/*---------- + * Check if the statement is + * SELECT ... INTO table FROM ..... + * This isn't really a strict check but ... + *---------- + */ +static BOOL +into_table_from(const char *stmt) +{ + if (strnicmp(stmt, "into", 4)) + return FALSE; + stmt += 4; + if (!isspace((unsigned char) *stmt)) + return FALSE; + while (isspace((unsigned char) *(++stmt))); + switch (*stmt) + { + case '\0': + case ',': + case '\'': + return FALSE; + case '\"': /* double quoted table name ? */ + do + { + do + while (*(++stmt) != '\"' && *stmt); + while (*stmt && *(++stmt) == '\"'); + while (*stmt && !isspace((unsigned char) *stmt) && *stmt != '\"') + stmt++; + } + while (*stmt == '\"'); + break; + default: + while (!isspace((unsigned char) *(++stmt))); + break; + } + if (!*stmt) + return FALSE; + while (isspace((unsigned char) *(++stmt))); + if (strnicmp(stmt, "from", 4)) + return FALSE; + return isspace((unsigned char) stmt[4]); +} + +/*---------- + * Check if the statement is + * SELECT ... FOR UPDATE ..... + * This isn't really a strict check but ... + *---------- + */ +static BOOL +table_for_update(const char *stmt, int *endpos) +{ + const char *wstmt = stmt; + + while (isspace((unsigned char) *(++wstmt))); + if (!*wstmt) + return FALSE; + if (strnicmp(wstmt, "update", 6)) + return FALSE; + wstmt += 6; + *endpos = wstmt - stmt; + return !wstmt[0] || isspace((unsigned char) wstmt[0]); +} + +/* + * This function inserts parameters into an SQL statements. + * It will also modify a SELECT statement for use with declare/fetch cursors. + * This function does a dynamic memory allocation to get rid of query size limit! + */ +int +copy_statement_with_parameters(StatementClass *stmt) +{ + static char *func = "copy_statement_with_parameters"; + unsigned int opos, + npos, + oldstmtlen; + char param_string[128], + tmp[256], + cbuf[PG_NUMERIC_MAX_PRECISION * 2]; /* seems big enough to + * handle the data in + * this function */ + int param_number; + Int2 param_ctype, + param_sqltype; + char *old_statement = stmt->statement, + oldchar; + char *new_statement = stmt->stmt_with_params; + unsigned int new_stsize = 0; + SIMPLE_TIME st; + time_t t = time(NULL); + struct tm *tim; + SDWORD used; + char *buffer, + *buf; + BOOL in_quote = FALSE, + in_dquote = FALSE, + in_escape = FALSE; + Oid lobj_oid; + int lobj_fd, + retval; + BOOL check_cursor_ok = FALSE; /* check cursor + * restriction */ + BOOL proc_no_param = TRUE; + unsigned int declare_pos = 0; + ConnectionClass *conn = SC_get_conn(stmt); + ConnInfo *ci = &(conn->connInfo); + BOOL prepare_dummy_cursor = FALSE, + begin_first = FALSE; + char token_save[64]; + int token_len; + BOOL prev_token_end; + +#ifdef DRIVER_CURSOR_IMPLEMENT + BOOL search_from_pos = FALSE; +#endif /* DRIVER_CURSOR_IMPLEMENT */ + if (ci->disallow_premature) + prepare_dummy_cursor = stmt->pre_executing; + + if (!old_statement) + { + SC_log_error(func, "No statement string", stmt); + return SQL_ERROR; + } + + memset(&st, 0, sizeof(SIMPLE_TIME)); + + /* Initialize current date */ + tim = localtime(&t); + st.m = tim->tm_mon + 1; + st.d = tim->tm_mday; + st.y = tim->tm_year + 1900; + +#ifdef DRIVER_CURSOR_IMPLEMENT + if (stmt->statement_type != STMT_TYPE_SELECT) + { + stmt->options.cursor_type = SQL_CURSOR_FORWARD_ONLY; + stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; + } + else if (stmt->options.cursor_type == SQL_CURSOR_FORWARD_ONLY) + stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; + else if (stmt->options.scroll_concurrency != SQL_CONCUR_READ_ONLY) + { + if (stmt->parse_status == STMT_PARSE_NONE) + parse_statement(stmt); + if (stmt->parse_status != STMT_PARSE_COMPLETE) + stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; + else if (!stmt->ti || stmt->ntab != 1) + stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; + else + search_from_pos = TRUE; + } +#endif /* DRIVER_CURSOR_IMPLEMENT */ + + /* If the application hasn't set a cursor name, then generate one */ + if (stmt->cursor_name[0] == '\0') + sprintf(stmt->cursor_name, "SQL_CUR%p", stmt); + oldstmtlen = strlen(old_statement); + CVT_INIT(oldstmtlen); + + stmt->miscinfo = 0; + token_len = 0; + prev_token_end = TRUE; + /* For selects, prepend a declare cursor to the statement */ + if (stmt->statement_type == STMT_TYPE_SELECT) + { + SC_set_pre_executable(stmt); + if (prepare_dummy_cursor || ci->drivers.use_declarefetch) + { + if (prepare_dummy_cursor) + { + if (!CC_is_in_trans(conn) && PG_VERSION_GE(conn, 7.1)) + { + strcpy(new_statement, "BEGIN;"); + begin_first = TRUE; + } + } + else if (ci->drivers.use_declarefetch) + SC_set_fetchcursor(stmt); + sprintf(new_statement, "%sdeclare %s cursor for ", + new_statement, stmt->cursor_name); + npos = strlen(new_statement); + check_cursor_ok = TRUE; + declare_pos = npos; + } + } + param_number = -1; +#ifdef MULTIBYTE + multibyte_init(); +#endif + + for (opos = 0; opos < oldstmtlen; opos++) + { + oldchar = old_statement[opos]; +#ifdef MULTIBYTE + if (multibyte_char_check(oldchar) != 0) + { + CVT_APPEND_CHAR(oldchar); + continue; + } + + /* + * From here we are guaranteed to handle a 1-byte character. + */ +#endif + + if (in_escape) /* escape check */ + { + in_escape = FALSE; + CVT_APPEND_CHAR(oldchar); + continue; + } + else if (in_quote || in_dquote) /* quote/double quote check */ + { + if (oldchar == '\\') + in_escape = TRUE; + else if (oldchar == '\'' && in_quote) + in_quote = FALSE; + else if (oldchar == '\"' && in_dquote) + in_dquote = FALSE; + CVT_APPEND_CHAR(oldchar); + continue; + } + + /* + * From here we are guranteed to be in neither an escape, a quote + * nor a double quote. + */ + /* Squeeze carriage-return/linefeed pairs to linefeed only */ + else if (oldchar == '\r' && opos + 1 < oldstmtlen && + old_statement[opos + 1] == '\n') + continue; + + /* + * Handle literals (date, time, timestamp) and ODBC scalar + * functions + */ + else if (oldchar == '{') + { + char *esc; + char *begin = &old_statement[opos + 1]; + +#ifdef MULTIBYTE + char *end = multibyte_strchr(begin, '}'); + +#else + char *end = strchr(begin, '}'); +#endif + + if (!end) + continue; + /* procedure calls */ + if (stmt->statement_type == STMT_TYPE_PROCCALL) + { + int lit_call_len = 4; + + while (isspace((unsigned char) old_statement[++opos])); + /* '=?' to accept return values exists ? */ + if (old_statement[opos] == '?') + { + param_number++; + while (isspace((unsigned char) old_statement[++opos])); + if (old_statement[opos] != '=') + { + opos--; + continue; + } + while (isspace((unsigned char) old_statement[++opos])); + } + if (strnicmp(&old_statement[opos], "call", lit_call_len) || + !isspace((unsigned char) old_statement[opos + lit_call_len])) + { + opos--; + continue; + } + opos += lit_call_len; + CVT_APPEND_STR("SELECT "); +#ifdef MULTIBYTE + if (multibyte_strchr(&old_statement[opos], '(')) +#else + if (strchr(&old_statement[opos], '(')) +#endif /* MULTIBYTE */ + proc_no_param = FALSE; + continue; + } + *end = '\0'; + + esc = convert_escape(begin); + if (esc) + CVT_APPEND_STR(esc); + else + { /* it's not a valid literal so just copy */ + *end = '}'; + CVT_APPEND_CHAR(oldchar); + continue; + } + + opos += end - begin + 1; + *end = '}'; + continue; + } + /* End of a procedure call */ + else if (oldchar == '}' && stmt->statement_type == STMT_TYPE_PROCCALL) + { + if (proc_no_param) + CVT_APPEND_STR("()"); + continue; + } + + /* + * Can you have parameter markers inside of quotes? I dont think + * so. All the queries I've seen expect the driver to put quotes + * if needed. + */ + else if (oldchar == '?') + ; /* ok */ + else + { + if (oldchar == '\'') + in_quote = TRUE; + else if (oldchar == '\\') + in_escape = TRUE; + else if (oldchar == '\"') + in_dquote = TRUE; + else + { + if (isspace((unsigned char) oldchar)) + { + if (!prev_token_end) + { + prev_token_end = TRUE; + token_save[token_len] = '\0'; + if (token_len == 4) + { + if (check_cursor_ok && + into_table_from(&old_statement[opos - token_len])) + { + stmt->statement_type = STMT_TYPE_CREATE; + SC_no_pre_executable(stmt); + SC_no_fetchcursor(stmt); + stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; + memmove(new_statement, new_statement + declare_pos, npos - declare_pos); + npos -= declare_pos; + } +#ifdef DRIVER_CURSOR_IMPLEMENT + else if (search_from_pos && /* where's from clause */ + strnicmp(token_save, "from", 4) == 0) + { + search_from_pos = FALSE; + npos -= 5; + CVT_APPEND_STR(", CTID, OID from"); + } +#endif /* DRIVER_CURSOR_IMPLEMENT */ + } + if (token_len == 3) + { + int endpos; + + if (check_cursor_ok && + strnicmp(token_save, "for", 3) == 0 && + table_for_update(&old_statement[opos], &endpos)) + { + SC_no_fetchcursor(stmt); + stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; + if (prepare_dummy_cursor) + { + npos -= 4; + opos += endpos; + } + else + { + memmove(new_statement, new_statement + declare_pos, npos - declare_pos); + npos -= declare_pos; + } + } + } + } + } + else if (prev_token_end) + { + prev_token_end = FALSE; + token_save[0] = oldchar; + token_len = 1; + } + else if (token_len + 1 < sizeof(token_save)) + token_save[token_len++] = oldchar; + } + CVT_APPEND_CHAR(oldchar); + continue; + } + + /* + * Its a '?' parameter alright + */ + param_number++; + + if (param_number >= stmt->parameters_allocated) + { + if (stmt->pre_executing) + { + CVT_APPEND_STR("NULL"); + stmt->inaccurate_result = TRUE; + continue; + } + else + { + CVT_APPEND_CHAR('?'); + continue; + } + } + + /* Assign correct buffers based on data at exec param or not */ + if (stmt->parameters[param_number].data_at_exec) + { + used = stmt->parameters[param_number].EXEC_used ? *stmt->parameters[param_number].EXEC_used : SQL_NTS; + buffer = stmt->parameters[param_number].EXEC_buffer; + } + else + { + + + used = stmt->parameters[param_number].used ? *stmt->parameters[param_number].used : SQL_NTS; + + buffer = stmt->parameters[param_number].buffer; + } + + /* Handle NULL parameter data */ + if (used == SQL_NULL_DATA) + { + CVT_APPEND_STR("NULL"); + continue; + } + + /* + * If no buffer, and it's not null, then what the hell is it? Just + * leave it alone then. + */ + if (!buffer) + { + if (stmt->pre_executing) + { + CVT_APPEND_STR("NULL"); + stmt->inaccurate_result = TRUE; + continue; + } + else + { + CVT_APPEND_CHAR('?'); + continue; + } + } + + param_ctype = stmt->parameters[param_number].CType; + param_sqltype = stmt->parameters[param_number].SQLType; + + mylog("copy_statement_with_params: from(fcType)=%d, to(fSqlType)=%d\n", param_ctype, param_sqltype); + + /* replace DEFAULT with something we can use */ + if (param_ctype == SQL_C_DEFAULT) + param_ctype = sqltype_to_default_ctype(param_sqltype); + + buf = NULL; + param_string[0] = '\0'; + cbuf[0] = '\0'; + + /* Convert input C type to a neutral format */ + switch (param_ctype) + { + case SQL_C_BINARY: + case SQL_C_CHAR: + buf = buffer; + break; + + case SQL_C_DOUBLE: + sprintf(param_string, "%.15g", + *((SDOUBLE *) buffer)); + break; + + case SQL_C_FLOAT: + sprintf(param_string, "%.6g", + *((SFLOAT *) buffer)); + break; + + case SQL_C_SLONG: + case SQL_C_LONG: + sprintf(param_string, "%ld", + *((SDWORD *) buffer)); + break; + + case SQL_C_SSHORT: + case SQL_C_SHORT: + sprintf(param_string, "%d", + *((SWORD *) buffer)); + break; + + case SQL_C_STINYINT: + case SQL_C_TINYINT: + sprintf(param_string, "%d", + *((SCHAR *) buffer)); + break; + + case SQL_C_ULONG: + sprintf(param_string, "%lu", + *((UDWORD *) buffer)); + break; + + case SQL_C_USHORT: + sprintf(param_string, "%u", + *((UWORD *) buffer)); + break; + + case SQL_C_UTINYINT: + sprintf(param_string, "%u", + *((UCHAR *) buffer)); + break; + + case SQL_C_BIT: + { + int i = *((UCHAR *) buffer); + + sprintf(param_string, "%d", i ? 1 : 0); + break; + } + + case SQL_C_DATE: +#if (ODBCVER >= 0x0300) + case SQL_C_TYPE_DATE: /* 91 */ +#endif + { + DATE_STRUCT *ds = (DATE_STRUCT *) buffer; + + st.m = ds->month; + st.d = ds->day; + st.y = ds->year; + + break; + } + + case SQL_C_TIME: +#if (ODBCVER >= 0x0300) + case SQL_C_TYPE_TIME: /* 92 */ +#endif + { + TIME_STRUCT *ts = (TIME_STRUCT *) buffer; + + st.hh = ts->hour; + st.mm = ts->minute; + st.ss = ts->second; + + break; + } + + case SQL_C_TIMESTAMP: +#if (ODBCVER >= 0x0300) + case SQL_C_TYPE_TIMESTAMP: /* 93 */ +#endif + { + TIMESTAMP_STRUCT *tss = (TIMESTAMP_STRUCT *) buffer; + + st.m = tss->month; + st.d = tss->day; + st.y = tss->year; + st.hh = tss->hour; + st.mm = tss->minute; + st.ss = tss->second; + st.fr = tss->fraction; + + mylog("m=%d,d=%d,y=%d,hh=%d,mm=%d,ss=%d\n", st.m, st.d, st.y, st.hh, st.mm, st.ss); + + break; + + } + default: + /* error */ + stmt->errormsg = "Unrecognized C_parameter type in copy_statement_with_parameters"; + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + CVT_TERMINATE; /* just in case */ + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* + * Now that the input data is in a neutral format, convert it to + * the desired output format (sqltype) + */ + + switch (param_sqltype) + { + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_LONGVARCHAR: + + CVT_APPEND_CHAR('\''); /* Open Quote */ + + /* it was a SQL_C_CHAR */ + if (buf) + CVT_SPECIAL_CHARS(buf, used); + + /* it was a numeric type */ + else if (param_string[0] != '\0') + CVT_APPEND_STR(param_string); + + /* it was date,time,timestamp -- use m,d,y,hh,mm,ss */ + else + { + sprintf(tmp, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d", + st.y, st.m, st.d, st.hh, st.mm, st.ss); + + CVT_APPEND_STR(tmp); + } + + CVT_APPEND_CHAR('\''); /* Close Quote */ + + break; + + case SQL_DATE: +#if (ODBCVER >= 0x0300) + case SQL_TYPE_DATE: /* 91 */ +#endif + if (buf) + { /* copy char data to time */ + my_strcpy(cbuf, sizeof(cbuf), buf, used); + parse_datetime(cbuf, &st); + } + + sprintf(tmp, "'%.4d-%.2d-%.2d'", st.y, st.m, st.d); + + CVT_APPEND_STR(tmp); + break; + + case SQL_TIME: +#if (ODBCVER >= 0x0300) + case SQL_TYPE_TIME: /* 92 */ +#endif + if (buf) + { /* copy char data to time */ + my_strcpy(cbuf, sizeof(cbuf), buf, used); + parse_datetime(cbuf, &st); + } + + sprintf(tmp, "'%.2d:%.2d:%.2d'", st.hh, st.mm, st.ss); + + CVT_APPEND_STR(tmp); + break; + + case SQL_TIMESTAMP: +#if (ODBCVER >= 0x0300) + case SQL_TYPE_TIMESTAMP: /* 93 */ +#endif + + if (buf) + { + my_strcpy(cbuf, sizeof(cbuf), buf, used); + parse_datetime(cbuf, &st); + } + + /* + * sprintf(tmp, "'%.4d-%.2d-%.2d %.2d:%.2d:%.2d'", st.y, + * st.m, st.d, st.hh, st.mm, st.ss); + */ + tmp[0] = '\''; + /* Time zone stuff is unreliable */ + stime2timestamp(&st, tmp + 1, FALSE, PG_VERSION_GE(conn, 7.2)); + strcat(tmp, "'"); + + CVT_APPEND_STR(tmp); + + break; + + case SQL_BINARY: + case SQL_VARBINARY:/* non-ascii characters should be + * converted to octal */ + CVT_APPEND_CHAR('\''); /* Open Quote */ + + mylog("SQL_VARBINARY: about to call convert_to_pgbinary, used = %d\n", used); + + CVT_APPEND_BINARY(buf, used); + + CVT_APPEND_CHAR('\''); /* Close Quote */ + + break; + + case SQL_LONGVARBINARY: + + if (stmt->parameters[param_number].data_at_exec) + lobj_oid = stmt->parameters[param_number].lobj_oid; + else + { + /* begin transaction if needed */ + if (!CC_is_in_trans(conn)) + { + QResultClass *res; + char ok; + + res = CC_send_query(conn, "BEGIN", NULL); + if (!res) + { + stmt->errormsg = "Could not begin (in-line) a transaction"; + stmt->errornumber = STMT_EXEC_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + ok = QR_command_successful(res); + QR_Destructor(res); + if (!ok) + { + stmt->errormsg = "Could not begin (in-line) a transaction"; + stmt->errornumber = STMT_EXEC_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + CC_set_in_trans(conn); + } + + /* store the oid */ + lobj_oid = lo_creat(conn, INV_READ | INV_WRITE); + if (lobj_oid == 0) + { + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "Couldnt create (in-line) large object."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* store the fd */ + lobj_fd = lo_open(conn, lobj_oid, INV_WRITE); + if (lobj_fd < 0) + { + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "Couldnt open (in-line) large object for writing."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + retval = lo_write(conn, lobj_fd, buffer, used); + + lo_close(conn, lobj_fd); + + /* commit transaction if needed */ + if (!ci->drivers.use_declarefetch && CC_is_in_autocommit(conn)) + { + QResultClass *res; + char ok; + + res = CC_send_query(conn, "COMMIT", NULL); + if (!res) + { + stmt->errormsg = "Could not commit (in-line) a transaction"; + stmt->errornumber = STMT_EXEC_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + ok = QR_command_successful(res); + QR_Destructor(res); + if (!ok) + { + stmt->errormsg = "Could not commit (in-line) a transaction"; + stmt->errornumber = STMT_EXEC_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + CC_set_no_trans(conn); + } + } + + /* + * the oid of the large object -- just put that in for the + * parameter marker -- the data has already been sent to + * the large object + */ + sprintf(param_string, "'%d'", lobj_oid); + CVT_APPEND_STR(param_string); + + break; + + /* + * because of no conversion operator for bool and int4, + * SQL_BIT + */ + /* must be quoted (0 or 1 is ok to use inside the quotes) */ + + case SQL_REAL: + if (buf) + my_strcpy(param_string, sizeof(param_string), buf, used); + sprintf(tmp, "'%s'::float4", param_string); + CVT_APPEND_STR(tmp); + break; + case SQL_FLOAT: + case SQL_DOUBLE: + if (buf) + my_strcpy(param_string, sizeof(param_string), buf, used); + sprintf(tmp, "'%s'::float8", param_string); + CVT_APPEND_STR(tmp); + break; + case SQL_NUMERIC: + if (buf) + { + cbuf[0] = '\''; + my_strcpy(cbuf + 1, sizeof(cbuf) - 12, buf, used); /* 12 = 1('\'') + + * strlen("'::numeric") + * + 1('\0') */ + strcat(cbuf, "'::numeric"); + } + else + sprintf(cbuf, "'%s'::numeric", param_string); + CVT_APPEND_STR(cbuf); + break; + default: /* a numeric type or SQL_BIT */ + if (param_sqltype == SQL_BIT) + CVT_APPEND_CHAR('\''); /* Open Quote */ + + if (buf) + { + switch (used) + { + case SQL_NULL_DATA: + break; + case SQL_NTS: + CVT_APPEND_STR(buf); + break; + default: + CVT_APPEND_DATA(buf, used); + } + } + else + CVT_APPEND_STR(param_string); + + if (param_sqltype == SQL_BIT) + CVT_APPEND_CHAR('\''); /* Close Quote */ + + break; + } + } /* end, for */ + + /* make sure new_statement is always null-terminated */ + CVT_TERMINATE; + + if (conn->DriverToDataSource != NULL) + { + int length = strlen(new_statement); + + conn->DriverToDataSource(conn->translation_option, + SQL_CHAR, + new_statement, length, + new_statement, length, NULL, + NULL, 0, NULL); + } + +#ifdef DRIVER_CURSOR_IMPLEMENT + if (search_from_pos) + stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; +#endif /* DRIVER_CURSOR_IMPLEMENT */ + if (prepare_dummy_cursor && SC_is_pre_executable(stmt)) + { + char fetchstr[128]; + + sprintf(fetchstr, ";fetch backward in %s;close %s;", + stmt->cursor_name, stmt->cursor_name); + if (begin_first && CC_is_in_autocommit(conn)) + strcat(fetchstr, "COMMIT;"); + CVT_APPEND_STR(fetchstr); + stmt->inaccurate_result = TRUE; + } + + return SQL_SUCCESS; +} + + +static char * +mapFunction(const char *func) +{ + int i; + + for (i = 0; mapFuncs[i][0]; i++) + if (!stricmp(mapFuncs[i][0], func)) + return mapFuncs[i][1]; + + return NULL; +} + + +/* + * convert_escape() + * + * This function returns a pointer to static memory! + */ +char * +convert_escape(char *value) +{ + static char escape[1024]; + char key[33]; + + /* Separate off the key, skipping leading and trailing whitespace */ + while ((*value != '\0') && isspace((unsigned char) *value)) + value++; + sscanf(value, "%32s", key); + while ((*value != '\0') && (!isspace((unsigned char) *value))) + value++; + while ((*value != '\0') && isspace((unsigned char) *value)) + value++; + + mylog("convert_escape: key='%s', val='%s'\n", key, value); + + if ((strcmp(key, "d") == 0) || + (strcmp(key, "t") == 0) || + (strcmp(key, "oj") == 0) || /* {oj syntax support for 7.1 + * servers */ + (strcmp(key, "ts") == 0)) + { + /* Literal; return the escape part as-is */ + strncpy(escape, value, sizeof(escape) - 1); + } + else if (strcmp(key, "fn") == 0) + { + /* + * Function invocation Separate off the func name, skipping + * trailing whitespace. + */ + char *funcEnd = value; + char svchar; + char *mapFunc; + + while ((*funcEnd != '\0') && (*funcEnd != '(') && + (!isspace((unsigned char) *funcEnd))) + funcEnd++; + svchar = *funcEnd; + *funcEnd = '\0'; + sscanf(value, "%32s", key); + *funcEnd = svchar; + while ((*funcEnd != '\0') && isspace((unsigned char) *funcEnd)) + funcEnd++; + + /* + * We expect left parenthesis here, else return fn body as-is + * since it is one of those "function constants". + */ + if (*funcEnd != '(') + { + strncpy(escape, value, sizeof(escape) - 1); + return escape; + } + mapFunc = mapFunction(key); + + /* + * We could have mapFunction() return key if not in table... - + * thomas 2000-04-03 + */ + if (mapFunc == NULL) + { + /* If unrecognized function name, return fn body as-is */ + strncpy(escape, value, sizeof(escape) - 1); + return escape; + } + /* copy mapped name and remaining input string */ + strcpy(escape, mapFunc); + strncat(escape, funcEnd, sizeof(escape) - 1 - strlen(mapFunc)); + } + else + { + /* Bogus key, leave untranslated */ + return NULL; + } + + return escape; +} + + +BOOL +convert_money(const char *s, char *sout, size_t soutmax) +{ + size_t i = 0, + out = 0; + + for (i = 0; s[i]; i++) + { + if (s[i] == '$' || s[i] == ',' || s[i] == ')') + ; /* skip these characters */ + else + { + if (out + 1 >= soutmax) + return FALSE; /* sout is too short */ + if (s[i] == '(') + sout[out++] = '-'; + else + sout[out++] = s[i]; + } + } + sout[out] = '\0'; + return TRUE; +} + + +/* + * This function parses a character string for date/time info and fills in SIMPLE_TIME + * It does not zero out SIMPLE_TIME in case it is desired to initialize it with a value + */ +char +parse_datetime(char *buf, SIMPLE_TIME *st) +{ + int y, + m, + d, + hh, + mm, + ss; + int nf; + + y = m = d = hh = mm = ss = 0; + + /* escape sequence ? */ + if (buf[0] == '{') + { + while (*(++buf) && *buf != '\''); + if (!(*buf)) + return FALSE; + buf++; + } + if (buf[4] == '-') /* year first */ + nf = sscanf(buf, "%4d-%2d-%2d %2d:%2d:%2d", &y, &m, &d, &hh, &mm, &ss); + else + nf = sscanf(buf, "%2d-%2d-%4d %2d:%2d:%2d", &m, &d, &y, &hh, &mm, &ss); + + if (nf == 5 || nf == 6) + { + st->y = y; + st->m = m; + st->d = d; + st->hh = hh; + st->mm = mm; + st->ss = ss; + + return TRUE; + } + + if (buf[4] == '-') /* year first */ + nf = sscanf(buf, "%4d-%2d-%2d", &y, &m, &d); + else + nf = sscanf(buf, "%2d-%2d-%4d", &m, &d, &y); + + if (nf == 3) + { + st->y = y; + st->m = m; + st->d = d; + + return TRUE; + } + + nf = sscanf(buf, "%2d:%2d:%2d", &hh, &mm, &ss); + if (nf == 2 || nf == 3) + { + st->hh = hh; + st->mm = mm; + st->ss = ss; + + return TRUE; + } + + return FALSE; +} + + +/* Change linefeed to carriage-return/linefeed */ +int +convert_linefeeds(const char *si, char *dst, size_t max, BOOL *changed) +{ + size_t i = 0, + out = 0; + + if (max == 0) + max = 0xffffffff; + *changed = FALSE; + for (i = 0; si[i] && out < max - 1; i++) + { + if (si[i] == '\n') + { + /* Only add the carriage-return if needed */ + if (i > 0 && si[i - 1] == '\r') + { + if (dst) + dst[out++] = si[i]; + else + out++; + continue; + } + *changed = TRUE; + + if (dst) + { + dst[out++] = '\r'; + dst[out++] = '\n'; + } + else + out += 2; + } + else + { + if (dst) + dst[out++] = si[i]; + else + out++; + } + } + if (dst) + dst[out] = '\0'; + return out; +} + + +/* + * Change carriage-return/linefeed to just linefeed + * Plus, escape any special characters. + */ +int +convert_special_chars(const char *si, char *dst, int used) +{ + size_t i = 0, + out = 0, + max; + char *p = NULL; + + + if (used == SQL_NTS) + max = strlen(si); + else + max = used; + if (dst) + { + p = dst; + p[0] = '\0'; + } +#ifdef MULTIBYTE + multibyte_init(); +#endif + + for (i = 0; i < max; i++) + { +#ifdef MULTIBYTE + if (multibyte_char_check(si[i]) != 0) + { + if (p) + p[out] = si[i]; + out++; + continue; + } +#endif + if (si[i] == '\r' && si[i + 1] == '\n') + continue; + else if (si[i] == '\'' || si[i] == '\\') + { + if (p) + p[out++] = '\\'; + else + out++; + } + if (p) + p[out++] = si[i]; + else + out++; + } + if (p) + p[out] = '\0'; + return out; +} + + +/* !!! Need to implement this function !!! */ +int +convert_pgbinary_to_char(const char *value, char *rgbValue, int cbValueMax) +{ + mylog("convert_pgbinary_to_char: value = '%s'\n", value); + + strncpy_null(rgbValue, value, cbValueMax); + return 0; +} + + +static unsigned int +conv_from_octal(const unsigned char *s) +{ + int i, + y = 0; + + for (i = 1; i <= 3; i++) + y += (s[i] - 48) * (int) pow(8, 3 - i); + + return y; + +} + + +static unsigned int +conv_from_hex(const unsigned char *s) +{ + int i, + y = 0, + val; + + for (i = 1; i <= 2; i++) + { + if (s[i] >= 'a' && s[i] <= 'f') + val = s[i] - 'a' + 10; + else if (s[i] >= 'A' && s[i] <= 'F') + val = s[i] - 'A' + 10; + else + val = s[i] - '0'; + + y += val * (int) pow(16, 2 - i); + } + + return y; +} + + +/* convert octal escapes to bytes */ +int +convert_from_pgbinary(const unsigned char *value, unsigned char *rgbValue, int cbValueMax) +{ + size_t i, + ilen = strlen(value); + int o = 0; + + + for (i = 0; i < ilen;) + { + if (value[i] == '\\') + { + if (value[i + 1] == '\\') + { + rgbValue[o] = value[i]; + i += 2; + } + else + { + rgbValue[o] = conv_from_octal(&value[i]); + i += 4; + } + } + else + rgbValue[o] = value[i++]; + mylog("convert_from_pgbinary: i=%d, rgbValue[%d] = %d, %c\n", i, o, rgbValue[o], rgbValue[o]); + o++; + } + + rgbValue[o] = '\0'; /* extra protection */ + + return o; +} + + +static char * +conv_to_octal(unsigned char val) +{ + int i; + static char x[6]; + + x[0] = '\\'; + x[1] = '\\'; + x[5] = '\0'; + + for (i = 4; i > 1; i--) + { + x[i] = (val & 7) + 48; + val >>= 3; + } + + return x; +} + + +/* convert non-ascii bytes to octal escape sequences */ +int +convert_to_pgbinary(const unsigned char *in, char *out, int len) +{ + int i, + o = 0; + + for (i = 0; i < len; i++) + { + mylog("convert_to_pgbinary: in[%d] = %d, %c\n", i, in[i], in[i]); + if (isalnum(in[i]) || in[i] == ' ') + out[o++] = in[i]; + else + { + strcpy(&out[o], conv_to_octal(in[i])); + o += 5; + } + } + + mylog("convert_to_pgbinary: returning %d, out='%.*s'\n", o, o, out); + + return o; +} + + +void +encode(const char *in, char *out) +{ + unsigned int i, + ilen = strlen(in), + o = 0; + + for (i = 0; i < ilen; i++) + { + if (in[i] == '+') + { + sprintf(&out[o], "%%2B"); + o += 3; + } + else if (isspace((unsigned char) in[i])) + out[o++] = '+'; + else if (!isalnum((unsigned char) in[i])) + { + sprintf(&out[o], "%%%02x", (unsigned char) in[i]); + o += 3; + } + else + out[o++] = in[i]; + } + out[o++] = '\0'; +} + + +void +decode(const char *in, char *out) +{ + unsigned int i, + ilen = strlen(in), + o = 0; + + for (i = 0; i < ilen; i++) + { + if (in[i] == '+') + out[o++] = ' '; + else if (in[i] == '%') + { + sprintf(&out[o++], "%c", conv_from_hex(&in[i])); + i += 2; + } + else + out[o++] = in[i]; + } + out[o++] = '\0'; +} + +static const char *hextbl = "0123456789ABCDEF"; +static int +pg_bin2hex(UCHAR *src, UCHAR *dst, int length) +{ + UCHAR chr, + *src_wk, + *dst_wk; + BOOL backwards; + int i; + + backwards = FALSE; + if (dst < src) + { + if (dst + length > src + 1) + return -1; + } + else if (dst < src + length) + backwards = TRUE; + if (backwards) + { + for (i = 0, src_wk = src + length - 1, dst_wk = dst + 2 * length - 1; i < length; i++, src_wk--) + { + chr = *src_wk; + *dst_wk-- = hextbl[chr % 16]; + *dst_wk-- = hextbl[chr >> 4]; + } + } + else + { + for (i = 0, src_wk = src, dst_wk = dst; i < length; i++, src_wk++) + { + chr = *src_wk; + *dst_wk++ = hextbl[chr >> 4]; + *dst_wk++ = hextbl[chr % 16]; + } + } + dst[2 * length] = '\0'; + return length; +} + +/*------- + * 1. get oid (from 'value') + * 2. open the large object + * 3. read from the large object (handle multiple GetData) + * 4. close when read less than requested? -OR- + * lseek/read each time + * handle case where application receives truncated and + * decides not to continue reading. + * + * CURRENTLY, ONLY LONGVARBINARY is handled, since that is the only + * data type currently mapped to a PG_TYPE_LO. But, if any other types + * are desired to map to a large object (PG_TYPE_LO), then that would + * need to be handled here. For example, LONGVARCHAR could possibly be + * mapped to PG_TYPE_LO someday, instead of PG_TYPE_TEXT as it is now. + *------- + */ +int +convert_lo(StatementClass *stmt, const void *value, Int2 fCType, PTR rgbValue, + SDWORD cbValueMax, SDWORD *pcbValue) +{ + Oid oid; + int retval, + result, + left = -1; + BindInfoClass *bindInfo = NULL; + ConnectionClass *conn = SC_get_conn(stmt); + ConnInfo *ci = &(conn->connInfo); + int factor = (fCType == SQL_C_CHAR ? 2 : 1); + + /* If using SQLGetData, then current_col will be set */ + if (stmt->current_col >= 0) + { + bindInfo = &stmt->bindings[stmt->current_col]; + left = bindInfo->data_left; + } + + /* + * if this is the first call for this column, open the large object + * for reading + */ + + if (!bindInfo || bindInfo->data_left == -1) + { + /* begin transaction if needed */ + if (!CC_is_in_trans(conn)) + { + QResultClass *res; + char ok; + + res = CC_send_query(conn, "BEGIN", NULL); + if (!res) + { + stmt->errormsg = "Could not begin (in-line) a transaction"; + stmt->errornumber = STMT_EXEC_ERROR; + return COPY_GENERAL_ERROR; + } + ok = QR_command_successful(res); + QR_Destructor(res); + if (!ok) + { + stmt->errormsg = "Could not begin (in-line) a transaction"; + stmt->errornumber = STMT_EXEC_ERROR; + return COPY_GENERAL_ERROR; + } + + CC_set_in_trans(conn); + } + + oid = atoi(value); + stmt->lobj_fd = lo_open(conn, oid, INV_READ); + if (stmt->lobj_fd < 0) + { + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "Couldnt open large object for reading."; + return COPY_GENERAL_ERROR; + } + + /* Get the size */ + retval = lo_lseek(conn, stmt->lobj_fd, 0L, SEEK_END); + if (retval >= 0) + { + left = lo_tell(conn, stmt->lobj_fd); + if (bindInfo) + bindInfo->data_left = left; + + /* return to beginning */ + lo_lseek(conn, stmt->lobj_fd, 0L, SEEK_SET); + } + } + mylog("lo data left = %d\n", left); + + if (left == 0) + return COPY_NO_DATA_FOUND; + + if (stmt->lobj_fd < 0) + { + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "Large object FD undefined for multiple read."; + return COPY_GENERAL_ERROR; + } + + retval = lo_read(conn, stmt->lobj_fd, (char *) rgbValue, factor > 1 ? (cbValueMax - 1) / factor : cbValueMax); + if (retval < 0) + { + lo_close(conn, stmt->lobj_fd); + + /* commit transaction if needed */ + if (!ci->drivers.use_declarefetch && CC_is_in_autocommit(conn)) + { + QResultClass *res; + char ok; + + res = CC_send_query(conn, "COMMIT", NULL); + if (!res) + { + stmt->errormsg = "Could not commit (in-line) a transaction"; + stmt->errornumber = STMT_EXEC_ERROR; + return COPY_GENERAL_ERROR; + } + ok = QR_command_successful(res); + QR_Destructor(res); + if (!ok) + { + stmt->errormsg = "Could not commit (in-line) a transaction"; + stmt->errornumber = STMT_EXEC_ERROR; + return COPY_GENERAL_ERROR; + } + + CC_set_no_trans(conn); + } + + stmt->lobj_fd = -1; + + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "Error reading from large object."; + return COPY_GENERAL_ERROR; + } + + if (factor > 1) + pg_bin2hex((char *) rgbValue, (char *) rgbValue, retval); + if (retval < left) + result = COPY_RESULT_TRUNCATED; + else + result = COPY_OK; + + if (pcbValue) + *pcbValue = left < 0 ? SQL_NO_TOTAL : left * factor; + + if (bindInfo && bindInfo->data_left > 0) + bindInfo->data_left -= retval; + + if (!bindInfo || bindInfo->data_left == 0) + { + lo_close(conn, stmt->lobj_fd); + + /* commit transaction if needed */ + if (!ci->drivers.use_declarefetch && CC_is_in_autocommit(conn)) + { + QResultClass *res; + char ok; + + res = CC_send_query(conn, "COMMIT", NULL); + if (!res) + { + stmt->errormsg = "Could not commit (in-line) a transaction"; + stmt->errornumber = STMT_EXEC_ERROR; + return COPY_GENERAL_ERROR; + } + ok = QR_command_successful(res); + QR_Destructor(res); + if (!ok) + { + stmt->errormsg = "Could not commit (in-line) a transaction"; + stmt->errornumber = STMT_EXEC_ERROR; + return COPY_GENERAL_ERROR; + } + + CC_set_no_trans(conn); + } + + stmt->lobj_fd = -1; /* prevent further reading */ + } + + return result; +} diff --git a/src/interfaces/odbc/windev/convert.h b/src/interfaces/odbc/windev/convert.h new file mode 100644 index 0000000000..565ce6bf19 --- /dev/null +++ b/src/interfaces/odbc/windev/convert.h @@ -0,0 +1,52 @@ +/* File: convert.h + * + * Description: See "convert.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __CONVERT_H__ +#define __CONVERT_H__ + +#include "psqlodbc.h" + +/* copy_and_convert results */ +#define COPY_OK 0 +#define COPY_UNSUPPORTED_TYPE 1 +#define COPY_UNSUPPORTED_CONVERSION 2 +#define COPY_RESULT_TRUNCATED 3 +#define COPY_GENERAL_ERROR 4 +#define COPY_NO_DATA_FOUND 5 + +typedef struct +{ + int m; + int d; + int y; + int hh; + int mm; + int ss; + int fr; +} SIMPLE_TIME; + +int copy_and_convert_field_bindinfo(StatementClass *stmt, Int4 field_type, void *value, int col); +int copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2 fCType, + PTR rgbValue, SDWORD cbValueMax, SDWORD *pcbValue); + +int copy_statement_with_parameters(StatementClass *stmt); +char *convert_escape(char *value); +BOOL convert_money(const char *s, char *sout, size_t soutmax); +char parse_datetime(char *buf, SIMPLE_TIME *st); +int convert_linefeeds(const char *s, char *dst, size_t max, BOOL *changed); +int convert_special_chars(const char *si, char *dst, int used); + +int convert_pgbinary_to_char(const char *value, char *rgbValue, int cbValueMax); +int convert_from_pgbinary(const unsigned char *value, unsigned char *rgbValue, int cbValueMax); +int convert_to_pgbinary(const unsigned char *in, char *out, int len); +void encode(const char *in, char *out); +void decode(const char *in, char *out); +int convert_lo(StatementClass *stmt, const void *value, Int2 fCType, PTR rgbValue, + SDWORD cbValueMax, SDWORD *pcbValue); + +#endif diff --git a/src/interfaces/odbc/windev/dlg_specific.c b/src/interfaces/odbc/windev/dlg_specific.c new file mode 100644 index 0000000000..f23fb9c527 --- /dev/null +++ b/src/interfaces/odbc/windev/dlg_specific.c @@ -0,0 +1,1138 @@ +/*------- + * Module: dlg_specific.c + * + * Description: This module contains any specific code for handling + * dialog boxes such as driver/datasource options. Both the + * ConfigDSN() and the SQLDriverConnect() functions use + * functions in this module. If you were to add a new option + * to any dialog box, you would most likely only have to change + * things in here rather than in 2 separate places as before. + * + * Classes: none + * + * API functions: none + * + * Comments: See "notice.txt" for copyright and license information. + *------- + */ +/* Multibyte support Eiji Tokuya 2001-03-15 */ + +#include "dlg_specific.h" + +#include "convert.h" + +#ifdef MULTIBYTE +#include "multibyte.h" +#endif +#include "pgapifunc.h" + +#ifndef BOOL +#define BOOL int +#endif +#ifndef FALSE +#define FALSE (BOOL)0 +#endif +#ifndef TRUE +#define TRUE (BOOL)1 +#endif + +extern GLOBAL_VALUES globals; + +#ifdef WIN32 +static int driver_optionsDraw(HWND, const ConnInfo *, int src, BOOL enable); +static int driver_options_update(HWND hdlg, ConnInfo *ci, BOOL); +static void updateCommons(const ConnInfo *ci); +#endif + +#ifdef WIN32 +void +SetDlgStuff(HWND hdlg, const ConnInfo *ci) +{ + /* + * If driver attribute NOT present, then set the datasource name and + * description + */ + if (ci->driver[0] == '\0') + { + SetDlgItemText(hdlg, IDC_DSNAME, ci->dsn); + SetDlgItemText(hdlg, IDC_DESC, ci->desc); + } + + SetDlgItemText(hdlg, IDC_DATABASE, ci->database); + SetDlgItemText(hdlg, IDC_SERVER, ci->server); + SetDlgItemText(hdlg, IDC_USER, ci->username); + SetDlgItemText(hdlg, IDC_PASSWORD, ci->password); + SetDlgItemText(hdlg, IDC_PORT, ci->port); +} + + +void +GetDlgStuff(HWND hdlg, ConnInfo *ci) +{ + GetDlgItemText(hdlg, IDC_DESC, ci->desc, sizeof(ci->desc)); + + GetDlgItemText(hdlg, IDC_DATABASE, ci->database, sizeof(ci->database)); + GetDlgItemText(hdlg, IDC_SERVER, ci->server, sizeof(ci->server)); + GetDlgItemText(hdlg, IDC_USER, ci->username, sizeof(ci->username)); + GetDlgItemText(hdlg, IDC_PASSWORD, ci->password, sizeof(ci->password)); + GetDlgItemText(hdlg, IDC_PORT, ci->port, sizeof(ci->port)); +} + + +static int +driver_optionsDraw(HWND hdlg, const ConnInfo *ci, int src, BOOL enable) +{ + const GLOBAL_VALUES *comval; + static BOOL defset = FALSE; + static GLOBAL_VALUES defval; + + switch (src) + { + case 0: /* driver common */ + comval = &globals; + break; + case 1: /* dsn specific */ + comval = &(ci->drivers); + break; + case 2: /* default */ + if (!defset) + { + defval.commlog = DEFAULT_COMMLOG; + defval.disable_optimizer = DEFAULT_OPTIMIZER; + defval.ksqo = DEFAULT_KSQO; + defval.unique_index = DEFAULT_UNIQUEINDEX; + defval.onlyread = DEFAULT_READONLY; + defval.use_declarefetch = DEFAULT_USEDECLAREFETCH; + + defval.parse = DEFAULT_PARSE; + defval.cancel_as_freestmt = DEFAULT_CANCELASFREESTMT; + defval.debug = DEFAULT_DEBUG; + + /* Unknown Sizes */ + defval.unknown_sizes = DEFAULT_UNKNOWNSIZES; + defval.text_as_longvarchar = DEFAULT_TEXTASLONGVARCHAR; + defval.unknowns_as_longvarchar = DEFAULT_UNKNOWNSASLONGVARCHAR; + defval.bools_as_char = DEFAULT_BOOLSASCHAR; + } + defset = TRUE; + comval = &defval; + break; + } + + CheckDlgButton(hdlg, DRV_COMMLOG, comval->commlog); + CheckDlgButton(hdlg, DRV_OPTIMIZER, comval->disable_optimizer); + CheckDlgButton(hdlg, DRV_KSQO, comval->ksqo); + CheckDlgButton(hdlg, DRV_UNIQUEINDEX, comval->unique_index); + EnableWindow(GetDlgItem(hdlg, DRV_UNIQUEINDEX), enable); + CheckDlgButton(hdlg, DRV_READONLY, comval->onlyread); + EnableWindow(GetDlgItem(hdlg, DRV_READONLY), enable); + CheckDlgButton(hdlg, DRV_USEDECLAREFETCH, comval->use_declarefetch); + + /* Unknown Sizes clear */ + CheckDlgButton(hdlg, DRV_UNKNOWN_DONTKNOW, 0); + CheckDlgButton(hdlg, DRV_UNKNOWN_LONGEST, 0); + CheckDlgButton(hdlg, DRV_UNKNOWN_MAX, 0); + /* Unknown (Default) Data Type sizes */ + switch (comval->unknown_sizes) + { + case UNKNOWNS_AS_DONTKNOW: + CheckDlgButton(hdlg, DRV_UNKNOWN_DONTKNOW, 1); + break; + case UNKNOWNS_AS_LONGEST: + CheckDlgButton(hdlg, DRV_UNKNOWN_LONGEST, 1); + break; + case UNKNOWNS_AS_MAX: + default: + CheckDlgButton(hdlg, DRV_UNKNOWN_MAX, 1); + break; + } + + CheckDlgButton(hdlg, DRV_TEXT_LONGVARCHAR, comval->text_as_longvarchar); + CheckDlgButton(hdlg, DRV_UNKNOWNS_LONGVARCHAR, comval->unknowns_as_longvarchar); + CheckDlgButton(hdlg, DRV_BOOLS_CHAR, comval->bools_as_char); + CheckDlgButton(hdlg, DRV_PARSE, comval->parse); + CheckDlgButton(hdlg, DRV_CANCELASFREESTMT, comval->cancel_as_freestmt); + CheckDlgButton(hdlg, DRV_DEBUG, comval->debug); + SetDlgItemInt(hdlg, DRV_CACHE_SIZE, comval->fetch_max, FALSE); + SetDlgItemInt(hdlg, DRV_VARCHAR_SIZE, comval->max_varchar_size, FALSE); + SetDlgItemInt(hdlg, DRV_LONGVARCHAR_SIZE, comval->max_longvarchar_size, TRUE); + SetDlgItemText(hdlg, DRV_EXTRASYSTABLEPREFIXES, comval->extra_systable_prefixes); + + /* Driver Connection Settings */ + SetDlgItemText(hdlg, DRV_CONNSETTINGS, comval->conn_settings); + EnableWindow(GetDlgItem(hdlg, DRV_CONNSETTINGS), enable); + return 0; +} +static int +driver_options_update(HWND hdlg, ConnInfo *ci, BOOL updateProfile) +{ + GLOBAL_VALUES *comval; + + if (ci) + comval = &(ci->drivers); + else + comval = &globals; + comval->commlog = IsDlgButtonChecked(hdlg, DRV_COMMLOG); + comval->disable_optimizer = IsDlgButtonChecked(hdlg, DRV_OPTIMIZER); + comval->ksqo = IsDlgButtonChecked(hdlg, DRV_KSQO); + if (!ci) + { + comval->unique_index = IsDlgButtonChecked(hdlg, DRV_UNIQUEINDEX); + comval->onlyread = IsDlgButtonChecked(hdlg, DRV_READONLY); + } + comval->use_declarefetch = IsDlgButtonChecked(hdlg, DRV_USEDECLAREFETCH); + + /* Unknown (Default) Data Type sizes */ + if (IsDlgButtonChecked(hdlg, DRV_UNKNOWN_MAX)) + comval->unknown_sizes = UNKNOWNS_AS_MAX; + else if (IsDlgButtonChecked(hdlg, DRV_UNKNOWN_DONTKNOW)) + comval->unknown_sizes = UNKNOWNS_AS_DONTKNOW; + else if (IsDlgButtonChecked(hdlg, DRV_UNKNOWN_LONGEST)) + comval->unknown_sizes = UNKNOWNS_AS_LONGEST; + else + comval->unknown_sizes = UNKNOWNS_AS_MAX; + + comval->text_as_longvarchar = IsDlgButtonChecked(hdlg, DRV_TEXT_LONGVARCHAR); + comval->unknowns_as_longvarchar = IsDlgButtonChecked(hdlg, DRV_UNKNOWNS_LONGVARCHAR); + comval->bools_as_char = IsDlgButtonChecked(hdlg, DRV_BOOLS_CHAR); + + comval->parse = IsDlgButtonChecked(hdlg, DRV_PARSE); + + comval->cancel_as_freestmt = IsDlgButtonChecked(hdlg, DRV_CANCELASFREESTMT); + comval->debug = IsDlgButtonChecked(hdlg, DRV_DEBUG); + + comval->fetch_max = GetDlgItemInt(hdlg, DRV_CACHE_SIZE, NULL, FALSE); + comval->max_varchar_size = GetDlgItemInt(hdlg, DRV_VARCHAR_SIZE, NULL, FALSE); + comval->max_longvarchar_size = GetDlgItemInt(hdlg, DRV_LONGVARCHAR_SIZE, NULL, TRUE); /* allows for + * SQL_NO_TOTAL */ + + GetDlgItemText(hdlg, DRV_EXTRASYSTABLEPREFIXES, comval->extra_systable_prefixes, sizeof(comval->extra_systable_prefixes)); + + /* Driver Connection Settings */ + if (!ci) + GetDlgItemText(hdlg, DRV_CONNSETTINGS, comval->conn_settings, sizeof(comval->conn_settings)); + + if (updateProfile) + updateCommons(ci); + + /* fall through */ + return 0; +} + +int CALLBACK +driver_optionsProc(HWND hdlg, + WORD wMsg, + WPARAM wParam, + LPARAM lParam) +{ + ConnInfo *ci; + + switch (wMsg) + { + case WM_INITDIALOG: + SetWindowLong(hdlg, DWL_USER, lParam); /* save for OK etc */ + ci = (ConnInfo *) lParam; + CheckDlgButton(hdlg, DRV_OR_DSN, 0); + if (ci && ci->dsn && ci->dsn[0]) + SetWindowText(hdlg, "Advanced Options (per DSN)"); + else + { + SetWindowText(hdlg, "Advanced Options (Connection)"); + ShowWindow(GetDlgItem(hdlg, DRV_OR_DSN), SW_HIDE); + } + driver_optionsDraw(hdlg, ci, 1, FALSE); + break; + + case WM_COMMAND: + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + case IDOK: + ci = (ConnInfo *) GetWindowLong(hdlg, DWL_USER); + driver_options_update(hdlg, IsDlgButtonChecked(hdlg, DRV_OR_DSN) ? NULL : ci, + ci && ci->dsn && ci->dsn[0]); + + case IDCANCEL: + EndDialog(hdlg, GET_WM_COMMAND_ID(wParam, lParam) == IDOK); + return TRUE; + + case IDDEFAULTS: + if (IsDlgButtonChecked(hdlg, DRV_OR_DSN)) + driver_optionsDraw(hdlg, NULL, 2, TRUE); + else + { + ConnInfo *ci = (ConnInfo *) GetWindowLong(hdlg, DWL_USER); + + driver_optionsDraw(hdlg, ci, 0, FALSE); + } + break; + + case DRV_OR_DSN: + if (GET_WM_COMMAND_CMD(wParam, lParam) == BN_CLICKED) + { + mylog("DRV_OR_DSN clicked\n"); + if (IsDlgButtonChecked(hdlg, DRV_OR_DSN)) + { + SetWindowText(hdlg, "Advanced Options (Common)"); + driver_optionsDraw(hdlg, NULL, 0, TRUE); + } + else + { + ConnInfo *ci = (ConnInfo *) GetWindowLong(hdlg, DWL_USER); + + SetWindowText(hdlg, "Advanced Options (per DSN)"); + driver_optionsDraw(hdlg, ci, ci ? 1 : 0, ci == NULL); + } + } + break; + } + } + + return FALSE; +} + + +int CALLBACK +ds_optionsProc(HWND hdlg, + WORD wMsg, + WPARAM wParam, + LPARAM lParam) +{ + ConnInfo *ci; + char buf[128]; + + switch (wMsg) + { + case WM_INITDIALOG: + ci = (ConnInfo *) lParam; + SetWindowLong(hdlg, DWL_USER, lParam); /* save for OK */ + + /* Change window caption */ + if (ci->driver[0]) + SetWindowText(hdlg, "Advanced Options (Connection)"); + else + { + sprintf(buf, "Advanced Options (%s)", ci->dsn); + SetWindowText(hdlg, buf); + } + + /* Readonly */ + CheckDlgButton(hdlg, DS_READONLY, atoi(ci->onlyread)); + + /* Protocol */ + if (strncmp(ci->protocol, PG62, strlen(PG62)) == 0) + CheckDlgButton(hdlg, DS_PG62, 1); + else if (strncmp(ci->protocol, PG63, strlen(PG63)) == 0) + CheckDlgButton(hdlg, DS_PG63, 1); + else + /* latest */ + CheckDlgButton(hdlg, DS_PG64, 1); + + CheckDlgButton(hdlg, DS_SHOWOIDCOLUMN, atoi(ci->show_oid_column)); + CheckDlgButton(hdlg, DS_FAKEOIDINDEX, atoi(ci->fake_oid_index)); + CheckDlgButton(hdlg, DS_ROWVERSIONING, atoi(ci->row_versioning)); + CheckDlgButton(hdlg, DS_SHOWSYSTEMTABLES, atoi(ci->show_system_tables)); + CheckDlgButton(hdlg, DS_DISALLOWPREMATURE, ci->disallow_premature); + + EnableWindow(GetDlgItem(hdlg, DS_FAKEOIDINDEX), atoi(ci->show_oid_column)); + + /* Datasource Connection Settings */ + SetDlgItemText(hdlg, DS_CONNSETTINGS, ci->conn_settings); + break; + + case WM_COMMAND: + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + case DS_SHOWOIDCOLUMN: + mylog("WM_COMMAND: DS_SHOWOIDCOLUMN\n"); + EnableWindow(GetDlgItem(hdlg, DS_FAKEOIDINDEX), IsDlgButtonChecked(hdlg, DS_SHOWOIDCOLUMN)); + return TRUE; + + case IDOK: + ci = (ConnInfo *) GetWindowLong(hdlg, DWL_USER); + mylog("IDOK: got ci = %u\n", ci); + + /* Readonly */ + sprintf(ci->onlyread, "%d", IsDlgButtonChecked(hdlg, DS_READONLY)); + + /* Protocol */ + if (IsDlgButtonChecked(hdlg, DS_PG62)) + strcpy(ci->protocol, PG62); + else if (IsDlgButtonChecked(hdlg, DS_PG63)) + strcpy(ci->protocol, PG63); + else + /* latest */ + strcpy(ci->protocol, PG64); + + sprintf(ci->show_system_tables, "%d", IsDlgButtonChecked(hdlg, DS_SHOWSYSTEMTABLES)); + + sprintf(ci->row_versioning, "%d", IsDlgButtonChecked(hdlg, DS_ROWVERSIONING)); + ci->disallow_premature = IsDlgButtonChecked(hdlg, DS_DISALLOWPREMATURE); + + /* OID Options */ + sprintf(ci->fake_oid_index, "%d", IsDlgButtonChecked(hdlg, DS_FAKEOIDINDEX)); + sprintf(ci->show_oid_column, "%d", IsDlgButtonChecked(hdlg, DS_SHOWOIDCOLUMN)); + + /* Datasource Connection Settings */ + GetDlgItemText(hdlg, DS_CONNSETTINGS, ci->conn_settings, sizeof(ci->conn_settings)); + + /* fall through */ + + case IDCANCEL: + EndDialog(hdlg, GET_WM_COMMAND_ID(wParam, lParam) == IDOK); + return TRUE; + } + } + + return FALSE; +} + +/* + * This function writes any global parameters (that can be manipulated) + * to the ODBCINST.INI portion of the registry + */ +static void +updateCommons(const ConnInfo *ci) +{ + const char *sectionName; + const char *fileName; + const GLOBAL_VALUES *comval; + char tmp[128]; + + if (ci) + if (ci->dsn && ci->dsn[0]) + { + mylog("DSN=%s updating\n", ci->dsn); + comval = &(ci->drivers); + sectionName = ci->dsn; + fileName = ODBC_INI; + } + else + { + mylog("ci but dsn==NULL\n"); + return; + } + else + { + mylog("drivers updating\n"); + comval = &globals; + sectionName = DBMS_NAME; + fileName = ODBCINST_INI; + } + sprintf(tmp, "%d", comval->fetch_max); + SQLWritePrivateProfileString(sectionName, + INI_FETCH, tmp, fileName); + + sprintf(tmp, "%d", comval->commlog); + SQLWritePrivateProfileString(sectionName, + INI_COMMLOG, tmp, fileName); + + sprintf(tmp, "%d", comval->debug); + SQLWritePrivateProfileString(sectionName, + INI_DEBUG, tmp, fileName); + + sprintf(tmp, "%d", comval->disable_optimizer); + SQLWritePrivateProfileString(sectionName, + INI_OPTIMIZER, tmp, fileName); + + sprintf(tmp, "%d", comval->ksqo); + SQLWritePrivateProfileString(sectionName, + INI_KSQO, tmp, fileName); + + /* + * Never update the onlyread, unique_index from this module. + */ + if (!ci) + { + sprintf(tmp, "%d", comval->unique_index); + SQLWritePrivateProfileString(sectionName, INI_UNIQUEINDEX, tmp, + fileName); + + sprintf(tmp, "%d", comval->onlyread); + SQLWritePrivateProfileString(sectionName, INI_READONLY, tmp, + fileName); + } + + sprintf(tmp, "%d", comval->use_declarefetch); + SQLWritePrivateProfileString(sectionName, + INI_USEDECLAREFETCH, tmp, fileName); + + sprintf(tmp, "%d", comval->unknown_sizes); + SQLWritePrivateProfileString(sectionName, + INI_UNKNOWNSIZES, tmp, fileName); + + sprintf(tmp, "%d", comval->text_as_longvarchar); + SQLWritePrivateProfileString(sectionName, + INI_TEXTASLONGVARCHAR, tmp, fileName); + + sprintf(tmp, "%d", comval->unknowns_as_longvarchar); + SQLWritePrivateProfileString(sectionName, + INI_UNKNOWNSASLONGVARCHAR, tmp, fileName); + + sprintf(tmp, "%d", comval->bools_as_char); + SQLWritePrivateProfileString(sectionName, + INI_BOOLSASCHAR, tmp, fileName); + + sprintf(tmp, "%d", comval->parse); + SQLWritePrivateProfileString(sectionName, + INI_PARSE, tmp, fileName); + + sprintf(tmp, "%d", comval->cancel_as_freestmt); + SQLWritePrivateProfileString(sectionName, + INI_CANCELASFREESTMT, tmp, fileName); + + sprintf(tmp, "%d", comval->max_varchar_size); + SQLWritePrivateProfileString(sectionName, + INI_MAXVARCHARSIZE, tmp, fileName); + + sprintf(tmp, "%d", comval->max_longvarchar_size); + SQLWritePrivateProfileString(sectionName, + INI_MAXLONGVARCHARSIZE, tmp, fileName); + + SQLWritePrivateProfileString(sectionName, + INI_EXTRASYSTABLEPREFIXES, comval->extra_systable_prefixes, fileName); + + /* + * Never update the conn_setting from this module + * SQLWritePrivateProfileString(sectionName, INI_CONNSETTINGS, + * comval->conn_settings, fileName); + */ +} +#endif /* WIN32 */ + + +void +makeConnectString(char *connect_string, const ConnInfo *ci, UWORD len) +{ + char got_dsn = (ci->dsn[0] != '\0'); + char encoded_conn_settings[LARGE_REGISTRY_LEN]; + UWORD hlen; + /*BOOL abbrev = (len <= 400);*/ + BOOL abbrev = (len < 1024); + + /* fundamental info */ + sprintf(connect_string, "%s=%s;DATABASE=%s;SERVER=%s;PORT=%s;UID=%s;PWD=%s", + got_dsn ? "DSN" : "DRIVER", + got_dsn ? ci->dsn : ci->driver, + ci->database, + ci->server, + ci->port, + ci->username, + ci->password); + + encode(ci->conn_settings, encoded_conn_settings); + + /* extra info */ + hlen = strlen(connect_string); + if (!abbrev) + sprintf(&connect_string[hlen], + ";READONLY=%s;PROTOCOL=%s;FAKEOIDINDEX=%s;SHOWOIDCOLUMN=%s;ROWVERSIONING=%s;SHOWSYSTEMTABLES=%s;CONNSETTINGS=%s;FETCH=%d;SOCKET=%d;UNKNOWNSIZES=%d;MAXVARCHARSIZE=%d;MAXLONGVARCHARSIZE=%d;DEBUG=%d;COMMLOG=%d;OPTIMIZER=%d;KSQO=%d;USEDECLAREFETCH=%d;TEXTASLONGVARCHAR=%d;UNKNOWNSASLONGVARCHAR=%d;BOOLSASCHAR=%d;PARSE=%d;CANCELASFREESTMT=%d;EXTRASYSTABLEPREFIXES=%s", + ci->onlyread, + ci->protocol, + ci->fake_oid_index, + ci->show_oid_column, + ci->row_versioning, + ci->show_system_tables, + encoded_conn_settings, + ci->drivers.fetch_max, + ci->drivers.socket_buffersize, + ci->drivers.unknown_sizes, + ci->drivers.max_varchar_size, + ci->drivers.max_longvarchar_size, + ci->drivers.debug, + ci->drivers.commlog, + ci->drivers.disable_optimizer, + ci->drivers.ksqo, + ci->drivers.use_declarefetch, + ci->drivers.text_as_longvarchar, + ci->drivers.unknowns_as_longvarchar, + ci->drivers.bools_as_char, + ci->drivers.parse, + ci->drivers.cancel_as_freestmt, + ci->drivers.extra_systable_prefixes); + /* Abbrebiation is needed ? */ + if (abbrev || strlen(connect_string) >= len) + sprintf(&connect_string[hlen], + ";A0=%s;A1=%s;A2=%s;A3=%s;A4=%s;A5=%s;A6=%s;A7=%d;A8=%d;A9=%d;B0=%d;B1=%d;B2=%d;B3=%d;B4=%d;B5=%d;B6=%d;B7=%d;B8=%d;B9=%d;C0=%d;C1=%d;C2=%s", + ci->onlyread, + ci->protocol, + ci->fake_oid_index, + ci->show_oid_column, + ci->row_versioning, + ci->show_system_tables, + encoded_conn_settings, + ci->drivers.fetch_max, + ci->drivers.socket_buffersize, + ci->drivers.unknown_sizes, + ci->drivers.max_varchar_size, + ci->drivers.max_longvarchar_size, + ci->drivers.debug, + ci->drivers.commlog, + ci->drivers.disable_optimizer, + ci->drivers.ksqo, + ci->drivers.use_declarefetch, + ci->drivers.text_as_longvarchar, + ci->drivers.unknowns_as_longvarchar, + ci->drivers.bools_as_char, + ci->drivers.parse, + ci->drivers.cancel_as_freestmt, + ci->drivers.extra_systable_prefixes); +} + + +void +copyAttributes(ConnInfo *ci, const char *attribute, const char *value) +{ + if (stricmp(attribute, "DSN") == 0) + strcpy(ci->dsn, value); + + else if (stricmp(attribute, "driver") == 0) + strcpy(ci->driver, value); + + else if (stricmp(attribute, INI_DATABASE) == 0) + strcpy(ci->database, value); + + else if (stricmp(attribute, INI_SERVER) == 0 || stricmp(attribute, "server") == 0) + strcpy(ci->server, value); + + else if (stricmp(attribute, INI_USER) == 0 || stricmp(attribute, "uid") == 0) + strcpy(ci->username, value); + + else if (stricmp(attribute, INI_PASSWORD) == 0 || stricmp(attribute, "pwd") == 0) + strcpy(ci->password, value); + + else if (stricmp(attribute, INI_PORT) == 0) + strcpy(ci->port, value); + + else if (stricmp(attribute, INI_READONLY) == 0 || stricmp(attribute, "A0") == 0) + strcpy(ci->onlyread, value); + + else if (stricmp(attribute, INI_PROTOCOL) == 0 || stricmp(attribute, "A1") == 0) + strcpy(ci->protocol, value); + + else if (stricmp(attribute, INI_SHOWOIDCOLUMN) == 0 || stricmp(attribute, "A3") == 0) + strcpy(ci->show_oid_column, value); + + else if (stricmp(attribute, INI_FAKEOIDINDEX) == 0 || stricmp(attribute, "A2") == 0) + strcpy(ci->fake_oid_index, value); + + else if (stricmp(attribute, INI_ROWVERSIONING) == 0 || stricmp(attribute, "A4") == 0) + strcpy(ci->row_versioning, value); + + else if (stricmp(attribute, INI_SHOWSYSTEMTABLES) == 0 || stricmp(attribute, "A5") == 0) + strcpy(ci->show_system_tables, value); + + else if (stricmp(attribute, INI_CONNSETTINGS) == 0 || stricmp(attribute, "A6") == 0) + { + decode(value, ci->conn_settings); + /* strcpy(ci->conn_settings, value); */ + } + else if (stricmp(attribute, INI_DISALLOWPREMATURE) == 0 || stricmp(attribute, "C3") == 0) + ci->disallow_premature = atoi(value); + else if (stricmp(attribute, INI_UPDATABLECURSORS) == 0 || stricmp(attribute, "C4") == 0) + ci->updatable_cursors = atoi(value); + + mylog("copyAttributes: DSN='%s',server='%s',dbase='%s',user='%s',passwd='%s',port='%s',onlyread='%s',protocol='%s',conn_settings='%s',disallow_premature=%d)\n", ci->dsn, ci->server, ci->database, ci->username, ci->password, ci->port, ci->onlyread, ci->protocol, ci->conn_settings, ci->disallow_premature); +} + +void +copyCommonAttributes(ConnInfo *ci, const char *attribute, const char *value) +{ + if (stricmp(attribute, INI_FETCH) == 0 || stricmp(attribute, "A7") == 0) + ci->drivers.fetch_max = atoi(value); + else if (stricmp(attribute, INI_SOCKET) == 0 || stricmp(attribute, "A8") == 0) + ci->drivers.socket_buffersize = atoi(value); + else if (stricmp(attribute, INI_DEBUG) == 0 || stricmp(attribute, "B2") == 0) + ci->drivers.debug = atoi(value); + else if (stricmp(attribute, INI_COMMLOG) == 0 || stricmp(attribute, "B3") == 0) + ci->drivers.commlog = atoi(value); + else if (stricmp(attribute, INI_OPTIMIZER) == 0 || stricmp(attribute, "B4") == 0) + ci->drivers.disable_optimizer = atoi(value); + else if (stricmp(attribute, INI_KSQO) == 0 || stricmp(attribute, "B5") == 0) + ci->drivers.ksqo = atoi(value); + + /* + * else if (stricmp(attribute, INI_UNIQUEINDEX) == 0 || + * stricmp(attribute, "UIX") == 0) ci->drivers.unique_index = + * atoi(value); + */ + else if (stricmp(attribute, INI_UNKNOWNSIZES) == 0 || stricmp(attribute, "A9") == 0) + ci->drivers.unknown_sizes = atoi(value); + else if (stricmp(attribute, INI_LIE) == 0) + ci->drivers.lie = atoi(value); + else if (stricmp(attribute, INI_PARSE) == 0 || stricmp(attribute, "C0") == 0) + ci->drivers.parse = atoi(value); + else if (stricmp(attribute, INI_CANCELASFREESTMT) == 0 || stricmp(attribute, "C1") == 0) + ci->drivers.cancel_as_freestmt = atoi(value); + else if (stricmp(attribute, INI_USEDECLAREFETCH) == 0 || stricmp(attribute, "B6") == 0) + ci->drivers.use_declarefetch = atoi(value); + else if (stricmp(attribute, INI_MAXVARCHARSIZE) == 0 || stricmp(attribute, "B0") == 0) + ci->drivers.max_varchar_size = atoi(value); + else if (stricmp(attribute, INI_MAXLONGVARCHARSIZE) == 0 || stricmp(attribute, "B1") == 0) + ci->drivers.max_longvarchar_size = atoi(value); + else if (stricmp(attribute, INI_TEXTASLONGVARCHAR) == 0 || stricmp(attribute, "B7") == 0) + ci->drivers.text_as_longvarchar = atoi(value); + else if (stricmp(attribute, INI_UNKNOWNSASLONGVARCHAR) == 0 || stricmp(attribute, "B8") == 0) + ci->drivers.unknowns_as_longvarchar = atoi(value); + else if (stricmp(attribute, INI_BOOLSASCHAR) == 0 || stricmp(attribute, "B9") == 0) + ci->drivers.bools_as_char = atoi(value); + else if (stricmp(attribute, INI_EXTRASYSTABLEPREFIXES) == 0 || stricmp(attribute, "C2") == 0) + strcpy(ci->drivers.extra_systable_prefixes, value); + mylog("CopyCommonAttributes: A7=%d;A8=%d;A9=%d;B0=%d;B1=%d;B2=%d;B3=%d;B4=%d;B5=%d;B6=%d;B7=%d;B8=%d;B9=%d;C0=%d;C1=%d;C2=%s", + ci->drivers.fetch_max, + ci->drivers.socket_buffersize, + ci->drivers.unknown_sizes, + ci->drivers.max_varchar_size, + ci->drivers.max_longvarchar_size, + ci->drivers.debug, + ci->drivers.commlog, + ci->drivers.disable_optimizer, + ci->drivers.ksqo, + ci->drivers.use_declarefetch, + ci->drivers.text_as_longvarchar, + ci->drivers.unknowns_as_longvarchar, + ci->drivers.bools_as_char, + ci->drivers.parse, + ci->drivers.cancel_as_freestmt, + ci->drivers.extra_systable_prefixes); +} + + +void +getDSNdefaults(ConnInfo *ci) +{ + if (ci->port[0] == '\0') + strcpy(ci->port, DEFAULT_PORT); + + if (ci->onlyread[0] == '\0') + sprintf(ci->onlyread, "%d", globals.onlyread); + + if (ci->protocol[0] == '\0') + strcpy(ci->protocol, globals.protocol); + + if (ci->fake_oid_index[0] == '\0') + sprintf(ci->fake_oid_index, "%d", DEFAULT_FAKEOIDINDEX); + + if (ci->show_oid_column[0] == '\0') + sprintf(ci->show_oid_column, "%d", DEFAULT_SHOWOIDCOLUMN); + + if (ci->show_system_tables[0] == '\0') + sprintf(ci->show_system_tables, "%d", DEFAULT_SHOWSYSTEMTABLES); + + if (ci->row_versioning[0] == '\0') + sprintf(ci->row_versioning, "%d", DEFAULT_ROWVERSIONING); +} + + +void +getDSNinfo(ConnInfo *ci, char overwrite) +{ + char *DSN = ci->dsn; + char encoded_conn_settings[LARGE_REGISTRY_LEN], + temp[SMALL_REGISTRY_LEN]; + +/* + * If a driver keyword was present, then dont use a DSN and return. + * If DSN is null and no driver, then use the default datasource. + */ + memcpy(&ci->drivers, &globals, sizeof(globals)); + if (DSN[0] == '\0') + { + if (ci->driver[0] != '\0') + return; + else + strcpy(DSN, INI_DSN); + } + + /* brute-force chop off trailing blanks... */ + while (*(DSN + strlen(DSN) - 1) == ' ') + *(DSN + strlen(DSN) - 1) = '\0'; + + /* Proceed with getting info for the given DSN. */ + + if (ci->desc[0] == '\0' || overwrite) + SQLGetPrivateProfileString(DSN, INI_KDESC, "", ci->desc, sizeof(ci->desc), ODBC_INI); + + if (ci->server[0] == '\0' || overwrite) + SQLGetPrivateProfileString(DSN, INI_SERVER, "", ci->server, sizeof(ci->server), ODBC_INI); + + if (ci->database[0] == '\0' || overwrite) + SQLGetPrivateProfileString(DSN, INI_DATABASE, "", ci->database, sizeof(ci->database), ODBC_INI); + + if (ci->username[0] == '\0' || overwrite) + SQLGetPrivateProfileString(DSN, INI_USER, "", ci->username, sizeof(ci->username), ODBC_INI); + + if (ci->password[0] == '\0' || overwrite) + SQLGetPrivateProfileString(DSN, INI_PASSWORD, "", ci->password, sizeof(ci->password), ODBC_INI); + + if (ci->port[0] == '\0' || overwrite) + SQLGetPrivateProfileString(DSN, INI_PORT, "", ci->port, sizeof(ci->port), ODBC_INI); + + if (ci->onlyread[0] == '\0' || overwrite) + SQLGetPrivateProfileString(DSN, INI_READONLY, "", ci->onlyread, sizeof(ci->onlyread), ODBC_INI); + + if (ci->show_oid_column[0] == '\0' || overwrite) + SQLGetPrivateProfileString(DSN, INI_SHOWOIDCOLUMN, "", ci->show_oid_column, sizeof(ci->show_oid_column), ODBC_INI); + + if (ci->fake_oid_index[0] == '\0' || overwrite) + SQLGetPrivateProfileString(DSN, INI_FAKEOIDINDEX, "", ci->fake_oid_index, sizeof(ci->fake_oid_index), ODBC_INI); + + if (ci->row_versioning[0] == '\0' || overwrite) + SQLGetPrivateProfileString(DSN, INI_ROWVERSIONING, "", ci->row_versioning, sizeof(ci->row_versioning), ODBC_INI); + + if (ci->show_system_tables[0] == '\0' || overwrite) + SQLGetPrivateProfileString(DSN, INI_SHOWSYSTEMTABLES, "", ci->show_system_tables, sizeof(ci->show_system_tables), ODBC_INI); + + if (ci->protocol[0] == '\0' || overwrite) + SQLGetPrivateProfileString(DSN, INI_PROTOCOL, "", ci->protocol, sizeof(ci->protocol), ODBC_INI); + + if (ci->conn_settings[0] == '\0' || overwrite) + { + SQLGetPrivateProfileString(DSN, INI_CONNSETTINGS, "", encoded_conn_settings, sizeof(encoded_conn_settings), ODBC_INI); + decode(encoded_conn_settings, ci->conn_settings); + } + + if (ci->translation_dll[0] == '\0' || overwrite) + SQLGetPrivateProfileString(DSN, INI_TRANSLATIONDLL, "", ci->translation_dll, sizeof(ci->translation_dll), ODBC_INI); + + if (ci->translation_option[0] == '\0' || overwrite) + SQLGetPrivateProfileString(DSN, INI_TRANSLATIONOPTION, "", ci->translation_option, sizeof(ci->translation_option), ODBC_INI); + + if (ci->disallow_premature == 0 || overwrite) + { + SQLGetPrivateProfileString(DSN, INI_DISALLOWPREMATURE, "", temp, sizeof(temp), ODBC_INI); + ci->disallow_premature = atoi(temp); + } + + if (ci->updatable_cursors == 0 || overwrite) + { + SQLGetPrivateProfileString(DSN, INI_UPDATABLECURSORS, "", temp, sizeof(temp), ODBC_INI); + ci->updatable_cursors = atoi(temp); + } + + /* Allow override of odbcinst.ini parameters here */ + getCommonDefaults(DSN, ODBC_INI, ci); + + qlog("DSN info: DSN='%s',server='%s',port='%s',dbase='%s',user='%s',passwd='%s'\n", + DSN, + ci->server, + ci->port, + ci->database, + ci->username, + ci->password); + qlog(" onlyread='%s',protocol='%s',showoid='%s',fakeoidindex='%s',showsystable='%s'\n", + ci->onlyread, + ci->protocol, + ci->show_oid_column, + ci->fake_oid_index, + ci->show_system_tables); + +#ifdef MULTIBYTE + check_client_encoding(ci->conn_settings); + qlog(" conn_settings='%s',conn_encoding='%s'\n", + ci->conn_settings, + check_client_encoding(ci->conn_settings)); +#else + qlog(" conn_settings='%s'\n", + ci->conn_settings); +#endif + + qlog(" translation_dll='%s',translation_option='%s'\n", + ci->translation_dll, + ci->translation_option); +} + + +/* This is for datasource based options only */ +void +writeDSNinfo(const ConnInfo *ci) +{ + const char *DSN = ci->dsn; + char encoded_conn_settings[LARGE_REGISTRY_LEN], + temp[SMALL_REGISTRY_LEN]; + + encode(ci->conn_settings, encoded_conn_settings); + + SQLWritePrivateProfileString(DSN, + INI_KDESC, + ci->desc, + ODBC_INI); + + SQLWritePrivateProfileString(DSN, + INI_DATABASE, + ci->database, + ODBC_INI); + + SQLWritePrivateProfileString(DSN, + INI_SERVER, + ci->server, + ODBC_INI); + + SQLWritePrivateProfileString(DSN, + INI_PORT, + ci->port, + ODBC_INI); + + SQLWritePrivateProfileString(DSN, + INI_USER, + ci->username, + ODBC_INI); + + SQLWritePrivateProfileString(DSN, + INI_PASSWORD, + ci->password, + ODBC_INI); + + SQLWritePrivateProfileString(DSN, + INI_READONLY, + ci->onlyread, + ODBC_INI); + + SQLWritePrivateProfileString(DSN, + INI_SHOWOIDCOLUMN, + ci->show_oid_column, + ODBC_INI); + + SQLWritePrivateProfileString(DSN, + INI_FAKEOIDINDEX, + ci->fake_oid_index, + ODBC_INI); + + SQLWritePrivateProfileString(DSN, + INI_ROWVERSIONING, + ci->row_versioning, + ODBC_INI); + + SQLWritePrivateProfileString(DSN, + INI_SHOWSYSTEMTABLES, + ci->show_system_tables, + ODBC_INI); + + SQLWritePrivateProfileString(DSN, + INI_PROTOCOL, + ci->protocol, + ODBC_INI); + + SQLWritePrivateProfileString(DSN, + INI_CONNSETTINGS, + encoded_conn_settings, + ODBC_INI); + + sprintf(temp, "%d", ci->disallow_premature); + SQLWritePrivateProfileString(DSN, + INI_DISALLOWPREMATURE, + temp, + ODBC_INI); + sprintf(temp, "%d", ci->updatable_cursors); + SQLWritePrivateProfileString(DSN, + INI_UPDATABLECURSORS, + temp, + ODBC_INI); +} + + +/* + * This function reads the ODBCINST.INI portion of + * the registry and gets any driver defaults. + */ +void +getCommonDefaults(const char *section, const char *filename, ConnInfo *ci) +{ + char temp[256]; + GLOBAL_VALUES *comval; + + if (ci) + comval = &(ci->drivers); + else + comval = &globals; + /* Fetch Count is stored in driver section */ + SQLGetPrivateProfileString(section, INI_FETCH, "", + temp, sizeof(temp), filename); + if (temp[0]) + { + comval->fetch_max = atoi(temp); + /* sanity check if using cursors */ + if (comval->fetch_max <= 0) + comval->fetch_max = FETCH_MAX; + } + else if (!ci) + comval->fetch_max = FETCH_MAX; + + /* Socket Buffersize is stored in driver section */ + SQLGetPrivateProfileString(section, INI_SOCKET, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->socket_buffersize = atoi(temp); + else if (!ci) + comval->socket_buffersize = SOCK_BUFFER_SIZE; + + /* Debug is stored in the driver section */ + SQLGetPrivateProfileString(section, INI_DEBUG, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->debug = atoi(temp); + else if (!ci) + comval->debug = DEFAULT_DEBUG; + + /* CommLog is stored in the driver section */ + SQLGetPrivateProfileString(section, INI_COMMLOG, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->commlog = atoi(temp); + else if (!ci) + comval->commlog = DEFAULT_COMMLOG; + + if (!ci) + logs_on_off(0, 0, 0); + /* Optimizer is stored in the driver section only */ + SQLGetPrivateProfileString(section, INI_OPTIMIZER, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->disable_optimizer = atoi(temp); + else if (!ci) + comval->disable_optimizer = DEFAULT_OPTIMIZER; + + /* KSQO is stored in the driver section only */ + SQLGetPrivateProfileString(section, INI_KSQO, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->ksqo = atoi(temp); + else if (!ci) + comval->ksqo = DEFAULT_KSQO; + + /* Recognize Unique Index is stored in the driver section only */ + SQLGetPrivateProfileString(section, INI_UNIQUEINDEX, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->unique_index = atoi(temp); + else if (!ci) + comval->unique_index = DEFAULT_UNIQUEINDEX; + + + /* Unknown Sizes is stored in the driver section only */ + SQLGetPrivateProfileString(section, INI_UNKNOWNSIZES, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->unknown_sizes = atoi(temp); + else if (!ci) + comval->unknown_sizes = DEFAULT_UNKNOWNSIZES; + + + /* Lie about supported functions? */ + SQLGetPrivateProfileString(section, INI_LIE, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->lie = atoi(temp); + else if (!ci) + comval->lie = DEFAULT_LIE; + + /* Parse statements */ + SQLGetPrivateProfileString(section, INI_PARSE, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->parse = atoi(temp); + else if (!ci) + comval->parse = DEFAULT_PARSE; + + /* SQLCancel calls SQLFreeStmt in Driver Manager */ + SQLGetPrivateProfileString(section, INI_CANCELASFREESTMT, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->cancel_as_freestmt = atoi(temp); + else if (!ci) + comval->cancel_as_freestmt = DEFAULT_CANCELASFREESTMT; + + /* UseDeclareFetch is stored in the driver section only */ + SQLGetPrivateProfileString(section, INI_USEDECLAREFETCH, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->use_declarefetch = atoi(temp); + else if (!ci) + comval->use_declarefetch = DEFAULT_USEDECLAREFETCH; + + /* Max Varchar Size */ + SQLGetPrivateProfileString(section, INI_MAXVARCHARSIZE, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->max_varchar_size = atoi(temp); + else if (!ci) + comval->max_varchar_size = MAX_VARCHAR_SIZE; + + /* Max TextField Size */ + SQLGetPrivateProfileString(section, INI_MAXLONGVARCHARSIZE, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->max_longvarchar_size = atoi(temp); + else if (!ci) + comval->max_longvarchar_size = TEXT_FIELD_SIZE; + + /* Text As LongVarchar */ + SQLGetPrivateProfileString(section, INI_TEXTASLONGVARCHAR, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->text_as_longvarchar = atoi(temp); + else if (!ci) + comval->text_as_longvarchar = DEFAULT_TEXTASLONGVARCHAR; + + /* Unknowns As LongVarchar */ + SQLGetPrivateProfileString(section, INI_UNKNOWNSASLONGVARCHAR, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->unknowns_as_longvarchar = atoi(temp); + else if (!ci) + comval->unknowns_as_longvarchar = DEFAULT_UNKNOWNSASLONGVARCHAR; + + /* Bools As Char */ + SQLGetPrivateProfileString(section, INI_BOOLSASCHAR, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->bools_as_char = atoi(temp); + else if (!ci) + comval->bools_as_char = DEFAULT_BOOLSASCHAR; + + /* Extra Systable prefixes */ + + /* + * Use @@@ to distinguish between blank extra prefixes and no key + * entry + */ + SQLGetPrivateProfileString(section, INI_EXTRASYSTABLEPREFIXES, "@@@", + temp, sizeof(temp), filename); + if (strcmp(temp, "@@@")) + strcpy(comval->extra_systable_prefixes, temp); + else if (!ci) + strcpy(comval->extra_systable_prefixes, DEFAULT_EXTRASYSTABLEPREFIXES); + + mylog("globals.extra_systable_prefixes = '%s'\n", comval->extra_systable_prefixes); + + + /* Dont allow override of an override! */ + if (!ci) + { + /* + * ConnSettings is stored in the driver section and per datasource + * for override + */ + SQLGetPrivateProfileString(section, INI_CONNSETTINGS, "", + comval->conn_settings, sizeof(comval->conn_settings), filename); + + /* Default state for future DSN's Readonly attribute */ + SQLGetPrivateProfileString(section, INI_READONLY, "", + temp, sizeof(temp), filename); + if (temp[0]) + comval->onlyread = atoi(temp); + else + comval->onlyread = DEFAULT_READONLY; + + /* + * Default state for future DSN's protocol attribute This isn't a + * real driver option YET. This is more intended for + * customization from the install. + */ + SQLGetPrivateProfileString(section, INI_PROTOCOL, "@@@", + temp, sizeof(temp), filename); + if (strcmp(temp, "@@@")) + strcpy(comval->protocol, temp); + else + strcpy(comval->protocol, DEFAULT_PROTOCOL); + } +} diff --git a/src/interfaces/odbc/windev/dlg_specific.h b/src/interfaces/odbc/windev/dlg_specific.h new file mode 100644 index 0000000000..b3192123d0 --- /dev/null +++ b/src/interfaces/odbc/windev/dlg_specific.h @@ -0,0 +1,147 @@ +/* File: dlg_specific.h + * + * Description: See "dlg_specific.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __DLG_SPECIFIC_H__ +#define __DLG_SPECIFIC_H__ + +#include "psqlodbc.h" +#include "connection.h" + +#ifdef WIN32 +#include +#include "resource.h" +#endif + +/* Unknown data type sizes */ +#define UNKNOWNS_AS_MAX 0 +#define UNKNOWNS_AS_DONTKNOW 1 +#define UNKNOWNS_AS_LONGEST 2 + +/* ODBC initialization files */ +#ifndef WIN32 +#define ODBC_INI ".odbc.ini" +#define ODBCINST_INI "odbcinst.ini" +#else +#define ODBC_INI "ODBC.INI" +#define ODBCINST_INI "ODBCINST.INI" +#endif + + +#define INI_DSN DBMS_NAME /* Name of default + * Datasource in ini + * file (not used?) */ +#define INI_KDESC "Description" /* Data source + * description */ +#define INI_SERVER "Servername" /* Name of Server + * running the Postgres + * service */ +#define INI_PORT "Port" /* Port on which the + * Postmaster is listening */ +#define INI_DATABASE "Database" /* Database Name */ +#define INI_USER "Username" /* Default User Name */ +#define INI_PASSWORD "Password" /* Default Password */ +#define INI_DEBUG "Debug" /* Debug flag */ +#define INI_FETCH "Fetch" /* Fetch Max Count */ +#define INI_SOCKET "Socket" /* Socket buffer size */ +#define INI_READONLY "ReadOnly" /* Database is read only */ +#define INI_COMMLOG "CommLog" /* Communication to + * backend logging */ +#define INI_PROTOCOL "Protocol" /* What protocol (6.2) */ +#define INI_OPTIMIZER "Optimizer" /* Use backend genetic + * optimizer */ +#define INI_KSQO "Ksqo" /* Keyset query + * optimization */ +#define INI_CONNSETTINGS "ConnSettings" /* Anything to send to + * backend on successful + * connection */ +#define INI_UNIQUEINDEX "UniqueIndex" /* Recognize unique + * indexes */ +#define INI_UNKNOWNSIZES "UnknownSizes" /* How to handle unknown + * result set sizes */ + +#define INI_CANCELASFREESTMT "CancelAsFreeStmt" + +#define INI_USEDECLAREFETCH "UseDeclareFetch" /* Use Declare/Fetch + * cursors */ + +/* More ini stuff */ +#define INI_TEXTASLONGVARCHAR "TextAsLongVarchar" +#define INI_UNKNOWNSASLONGVARCHAR "UnknownsAsLongVarchar" +#define INI_BOOLSASCHAR "BoolsAsChar" +#define INI_MAXVARCHARSIZE "MaxVarcharSize" +#define INI_MAXLONGVARCHARSIZE "MaxLongVarcharSize" + +#define INI_FAKEOIDINDEX "FakeOidIndex" +#define INI_SHOWOIDCOLUMN "ShowOidColumn" +#define INI_ROWVERSIONING "RowVersioning" +#define INI_SHOWSYSTEMTABLES "ShowSystemTables" +#define INI_LIE "Lie" +#define INI_PARSE "Parse" +#define INI_EXTRASYSTABLEPREFIXES "ExtraSysTablePrefixes" + +#define INI_TRANSLATIONNAME "TranslationName" +#define INI_TRANSLATIONDLL "TranslationDLL" +#define INI_TRANSLATIONOPTION "TranslationOption" +#define INI_DISALLOWPREMATURE "DisallowPremature" +#define INI_UPDATABLECURSORS "UpdatableCursors" + + +/* Connection Defaults */ +#define DEFAULT_PORT "5432" +#define DEFAULT_READONLY 0 +#define DEFAULT_PROTOCOL "6.4" /* the latest protocol is + * the default */ +#define DEFAULT_USEDECLAREFETCH 0 +#define DEFAULT_TEXTASLONGVARCHAR 1 +#define DEFAULT_UNKNOWNSASLONGVARCHAR 0 +#define DEFAULT_BOOLSASCHAR 1 +#define DEFAULT_OPTIMIZER 1 /* disable */ +#define DEFAULT_KSQO 1 /* on */ +#define DEFAULT_UNIQUEINDEX 1 /* dont recognize */ +#define DEFAULT_COMMLOG 0 /* dont log */ +#define DEFAULT_DEBUG 0 +#define DEFAULT_UNKNOWNSIZES UNKNOWNS_AS_MAX + + +#define DEFAULT_FAKEOIDINDEX 0 +#define DEFAULT_SHOWOIDCOLUMN 0 +#define DEFAULT_ROWVERSIONING 0 +#define DEFAULT_SHOWSYSTEMTABLES 0 /* dont show system tables */ +#define DEFAULT_LIE 0 +#define DEFAULT_PARSE 0 + +#define DEFAULT_CANCELASFREESTMT 0 + +#define DEFAULT_EXTRASYSTABLEPREFIXES "dd_;" + +/* prototypes */ +void getCommonDefaults(const char *section, const char *filename, ConnInfo *ci); + +#ifdef WIN32 +void SetDlgStuff(HWND hdlg, const ConnInfo *ci); +void GetDlgStuff(HWND hdlg, ConnInfo *ci); + +int CALLBACK driver_optionsProc(HWND hdlg, + WORD wMsg, + WPARAM wParam, + LPARAM lParam); +int CALLBACK ds_optionsProc(HWND hdlg, + WORD wMsg, + WPARAM wParam, + LPARAM lParam); +#endif /* WIN32 */ + +void updateGlobals(void); +void writeDSNinfo(const ConnInfo *ci); +void getDSNdefaults(ConnInfo *ci); +void getDSNinfo(ConnInfo *ci, char overwrite); +void makeConnectString(char *connect_string, const ConnInfo *ci, UWORD); +void copyAttributes(ConnInfo *ci, const char *attribute, const char *value); +void copyCommonAttributes(ConnInfo *ci, const char *attribute, const char *value); + +#endif diff --git a/src/interfaces/odbc/windev/drvconn.c b/src/interfaces/odbc/windev/drvconn.c new file mode 100644 index 0000000000..e369525ca0 --- /dev/null +++ b/src/interfaces/odbc/windev/drvconn.c @@ -0,0 +1,435 @@ +/*------- + Module: drvconn.c + * + * Description: This module contains only routines related to + * implementing SQLDriverConnect. + * + * Classes: n/a + * + * API functions: SQLDriverConnect + * + * Comments: See "notice.txt" for copyright and license information. + *------- + */ + +#include "psqlodbc.h" + +#include +#include + +#include "connection.h" + +#ifndef WIN32 +#include +#include +#define NEAR +#else +#include +#endif + +#include + +#ifdef WIN32 +#include +#include "resource.h" +#endif +#include "pgapifunc.h" + +#ifndef TRUE +#define TRUE (BOOL)1 +#endif +#ifndef FALSE +#define FALSE (BOOL)0 +#endif + +#include "dlg_specific.h" + +/* prototypes */ +void dconn_get_connect_attributes(const UCHAR FAR * connect_string, ConnInfo *ci); +static void dconn_get_common_attributes(const UCHAR FAR * connect_string, ConnInfo *ci); + +#ifdef WIN32 +BOOL FAR PASCAL dconn_FDriverConnectProc(HWND hdlg, UINT wMsg, WPARAM wParam, LPARAM lParam); +RETCODE dconn_DoDialog(HWND hwnd, ConnInfo *ci); + +extern HINSTANCE NEAR s_hModule; /* Saved module handle. */ +#endif + + +RETCODE SQL_API +PGAPI_DriverConnect( + HDBC hdbc, + HWND hwnd, + UCHAR FAR * szConnStrIn, + SWORD cbConnStrIn, + UCHAR FAR * szConnStrOut, + SWORD cbConnStrOutMax, + SWORD FAR * pcbConnStrOut, + UWORD fDriverCompletion) +{ + static char *func = "PGAPI_DriverConnect"; + ConnectionClass *conn = (ConnectionClass *) hdbc; + ConnInfo *ci; + +#ifdef WIN32 + RETCODE dialog_result; +#endif + RETCODE result; + char connStrIn[MAX_CONNECT_STRING]; + char connStrOut[MAX_CONNECT_STRING]; + int retval; + char password_required = FALSE; + int len = 0; + SWORD lenStrout; + + + mylog("%s: entering...\n", func); + + if (!conn) + { + CC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + make_string(szConnStrIn, cbConnStrIn, connStrIn); + + mylog("**** PGAPI_DriverConnect: fDriverCompletion=%d, connStrIn='%s'\n", fDriverCompletion, connStrIn); + qlog("conn=%u, PGAPI_DriverConnect( in)='%s', fDriverCompletion=%d\n", conn, connStrIn, fDriverCompletion); + + ci = &(conn->connInfo); + + /* Parse the connect string and fill in conninfo for this hdbc. */ + dconn_get_connect_attributes(connStrIn, ci); + + /* + * If the ConnInfo in the hdbc is missing anything, this function will + * fill them in from the registry (assuming of course there is a DSN + * given -- if not, it does nothing!) + */ + getDSNinfo(ci, CONN_DONT_OVERWRITE); + dconn_get_common_attributes(connStrIn, ci); + logs_on_off(1, ci->drivers.debug, ci->drivers.commlog); + + /* Fill in any default parameters if they are not there. */ + getDSNdefaults(ci); + /* initialize pg_version */ + CC_initialize_pg_version(conn); + +#ifdef WIN32 +dialog: +#endif + ci->focus_password = password_required; + + switch (fDriverCompletion) + { +#ifdef WIN32 + case SQL_DRIVER_PROMPT: + dialog_result = dconn_DoDialog(hwnd, ci); + if (dialog_result != SQL_SUCCESS) + return dialog_result; + break; + + case SQL_DRIVER_COMPLETE_REQUIRED: + + /* Fall through */ + + case SQL_DRIVER_COMPLETE: + + /* Password is not a required parameter. */ + if (ci->username[0] == '\0' || + ci->server[0] == '\0' || + ci->database[0] == '\0' || + ci->port[0] == '\0' || + password_required) + { + dialog_result = dconn_DoDialog(hwnd, ci); + if (dialog_result != SQL_SUCCESS) + return dialog_result; + } + break; +#else + case SQL_DRIVER_PROMPT: + case SQL_DRIVER_COMPLETE: + case SQL_DRIVER_COMPLETE_REQUIRED: +#endif + case SQL_DRIVER_NOPROMPT: + break; + } + + /* + * Password is not a required parameter unless authentication asks for + * it. For now, I think it's better to just let the application ask + * over and over until a password is entered (the user can always hit + * Cancel to get out) + */ + if (ci->username[0] == '\0' || + ci->server[0] == '\0' || + ci->database[0] == '\0' || + ci->port[0] == '\0') + { + /* (password_required && ci->password[0] == '\0')) */ + + return SQL_NO_DATA_FOUND; + } + + /* do the actual connect */ + retval = CC_connect(conn, password_required); + if (retval < 0) + { /* need a password */ + if (fDriverCompletion == SQL_DRIVER_NOPROMPT) + { + CC_log_error(func, "Need password but Driver_NoPrompt", conn); + return SQL_ERROR; /* need a password but not allowed to + * prompt so error */ + } + else + { +#ifdef WIN32 + password_required = TRUE; + goto dialog; +#else + return SQL_ERROR; /* until a better solution is found. */ +#endif + } + } + else if (retval == 0) + { + /* error msg filled in above */ + CC_log_error(func, "Error from CC_Connect", conn); + return SQL_ERROR; + } + + /* + * Create the Output Connection String + */ + result = SQL_SUCCESS; + + lenStrout = cbConnStrOutMax; + if (conn->ms_jet && lenStrout > 255) + lenStrout = 255; + makeConnectString(connStrOut, ci, lenStrout); + len = strlen(connStrOut); + + if (szConnStrOut) + { + /* + * Return the completed string to the caller. The correct method + * is to only construct the connect string if a dialog was put up, + * otherwise, it should just copy the connection input string to + * the output. However, it seems ok to just always construct an + * output string. There are possible bad side effects on working + * applications (Access) by implementing the correct behavior, + * anyway. + */ + strncpy_null(szConnStrOut, connStrOut, cbConnStrOutMax); + + if (len >= cbConnStrOutMax) + { + int clen; + + for (clen = strlen(szConnStrOut) - 1; clen >= 0 && szConnStrOut[clen] != ';'; clen--) + szConnStrOut[clen] = '\0'; + result = SQL_SUCCESS_WITH_INFO; + conn->errornumber = CONN_TRUNCATED; + conn->errormsg = "The buffer was too small for the ConnStrOut."; + } + } + + if (pcbConnStrOut) + *pcbConnStrOut = len; + + mylog("szConnStrOut = '%s' len=%d,%d\n", szConnStrOut, len, cbConnStrOutMax); + qlog("conn=%u, PGAPI_DriverConnect(out)='%s'\n", conn, szConnStrOut); + + + mylog("PGAPI_DRiverConnect: returning %d\n", result); + return result; +} + + +#ifdef WIN32 +RETCODE +dconn_DoDialog(HWND hwnd, ConnInfo *ci) +{ + int dialog_result; + + mylog("dconn_DoDialog: ci = %u\n", ci); + + if (hwnd) + { + dialog_result = DialogBoxParam(s_hModule, MAKEINTRESOURCE(DLG_CONFIG), + hwnd, dconn_FDriverConnectProc, (LPARAM) ci); + if (!dialog_result || (dialog_result == -1)) + return SQL_NO_DATA_FOUND; + else + return SQL_SUCCESS; + } + + return SQL_ERROR; +} + + +BOOL FAR PASCAL +dconn_FDriverConnectProc( + HWND hdlg, + UINT wMsg, + WPARAM wParam, + LPARAM lParam) +{ + ConnInfo *ci; + + switch (wMsg) + { + case WM_INITDIALOG: + ci = (ConnInfo *) lParam; + + /* Change the caption for the setup dialog */ + SetWindowText(hdlg, "PostgreSQL Connection"); + + SetWindowText(GetDlgItem(hdlg, IDC_DATASOURCE), "Connection"); + + /* Hide the DSN and description fields */ + ShowWindow(GetDlgItem(hdlg, IDC_DSNAMETEXT), SW_HIDE); + ShowWindow(GetDlgItem(hdlg, IDC_DSNAME), SW_HIDE); + ShowWindow(GetDlgItem(hdlg, IDC_DESCTEXT), SW_HIDE); + ShowWindow(GetDlgItem(hdlg, IDC_DESC), SW_HIDE); + + SetWindowLong(hdlg, DWL_USER, lParam); /* Save the ConnInfo for + * the "OK" */ + SetDlgStuff(hdlg, ci); + + if (ci->database[0] == '\0') + ; /* default focus */ + else if (ci->server[0] == '\0') + SetFocus(GetDlgItem(hdlg, IDC_SERVER)); + else if (ci->port[0] == '\0') + SetFocus(GetDlgItem(hdlg, IDC_PORT)); + else if (ci->username[0] == '\0') + SetFocus(GetDlgItem(hdlg, IDC_USER)); + else if (ci->focus_password) + SetFocus(GetDlgItem(hdlg, IDC_PASSWORD)); + break; + + case WM_COMMAND: + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + case IDOK: + ci = (ConnInfo *) GetWindowLong(hdlg, DWL_USER); + + GetDlgStuff(hdlg, ci); + + case IDCANCEL: + EndDialog(hdlg, GET_WM_COMMAND_ID(wParam, lParam) == IDOK); + return TRUE; + + case IDC_DRIVER: + ci = (ConnInfo *) GetWindowLong(hdlg, DWL_USER); + DialogBoxParam(s_hModule, MAKEINTRESOURCE(DLG_OPTIONS_DRV), + hdlg, driver_optionsProc, (LPARAM) ci); + break; + + case IDC_DATASOURCE: + ci = (ConnInfo *) GetWindowLong(hdlg, DWL_USER); + DialogBoxParam(s_hModule, MAKEINTRESOURCE(DLG_OPTIONS_DS), + hdlg, ds_optionsProc, (LPARAM) ci); + break; + } + } + + return FALSE; +} +#endif /* WIN32 */ + + +void +dconn_get_connect_attributes(const UCHAR FAR * connect_string, ConnInfo *ci) +{ + char *our_connect_string; + char *pair, + *attribute, + *value, + *equals; + char *strtok_arg; + + memset(ci, 0, sizeof(ConnInfo)); +#ifdef DRIVER_CURSOR_IMPLEMENT + ci->updatable_cursors = 1; +#endif /* DRIVER_CURSOR_IMPLEMENT */ + + our_connect_string = strdup(connect_string); + strtok_arg = our_connect_string; + + mylog("our_connect_string = '%s'\n", our_connect_string); + + while (1) + { + pair = strtok(strtok_arg, ";"); + if (strtok_arg) + strtok_arg = 0; + if (!pair) + break; + + equals = strchr(pair, '='); + if (!equals) + continue; + + *equals = '\0'; + attribute = pair; /* ex. DSN */ + value = equals + 1; /* ex. 'CEO co1' */ + + mylog("attribute = '%s', value = '%s'\n", attribute, value); + + if (!attribute || !value) + continue; + + /* Copy the appropriate value to the conninfo */ + copyAttributes(ci, attribute, value); + + } + + free(our_connect_string); +} + +static void +dconn_get_common_attributes(const UCHAR FAR * connect_string, ConnInfo *ci) +{ + char *our_connect_string; + char *pair, + *attribute, + *value, + *equals; + char *strtok_arg; + + our_connect_string = strdup(connect_string); + strtok_arg = our_connect_string; + + mylog("our_connect_string = '%s'\n", our_connect_string); + + while (1) + { + pair = strtok(strtok_arg, ";"); + if (strtok_arg) + strtok_arg = 0; + if (!pair) + break; + + equals = strchr(pair, '='); + if (!equals) + continue; + + *equals = '\0'; + attribute = pair; /* ex. DSN */ + value = equals + 1; /* ex. 'CEO co1' */ + + mylog("attribute = '%s', value = '%s'\n", attribute, value); + + if (!attribute || !value) + continue; + + /* Copy the appropriate value to the conninfo */ + copyCommonAttributes(ci, attribute, value); + + } + + free(our_connect_string); +} diff --git a/src/interfaces/odbc/windev/environ.c b/src/interfaces/odbc/windev/environ.c new file mode 100644 index 0000000000..304d31d33e --- /dev/null +++ b/src/interfaces/odbc/windev/environ.c @@ -0,0 +1,588 @@ +/*------- + * Module: environ.c + * + * Description: This module contains routines related to + * the environment, such as storing connection handles, + * and returning errors. + * + * Classes: EnvironmentClass (Functions prefix: "EN_") + * + * API functions: SQLAllocEnv, SQLFreeEnv, SQLError + * + * Comments: See "notice.txt" for copyright and license information. + *------- + */ + +#include "environ.h" + +#include "connection.h" +#include "dlg_specific.h" +#include "statement.h" +#include +#include +#include "pgapifunc.h" + +extern GLOBAL_VALUES globals; + +/* The one instance of the handles */ +ConnectionClass *conns[MAX_CONNECTIONS]; + + +RETCODE SQL_API +PGAPI_AllocEnv(HENV FAR * phenv) +{ + static char *func = "PGAPI_AllocEnv"; + + mylog("**** in PGAPI_AllocEnv ** \n"); + + /* + * Hack for systems on which none of the constructor-making techniques + * in psqlodbc.c work: if globals appears not to have been + * initialized, then cause it to be initialized. Since this should be + * the first function called in this shared library, doing it here + * should work. + */ + if (globals.socket_buffersize <= 0) + getCommonDefaults(DBMS_NAME, ODBCINST_INI, NULL); + + *phenv = (HENV) EN_Constructor(); + if (!*phenv) + { + *phenv = SQL_NULL_HENV; + EN_log_error(func, "Error allocating environment", NULL); + return SQL_ERROR; + } + + mylog("** exit PGAPI_AllocEnv: phenv = %u **\n", *phenv); + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_FreeEnv(HENV henv) +{ + static char *func = "PGAPI_FreeEnv"; + EnvironmentClass *env = (EnvironmentClass *) henv; + + mylog("**** in PGAPI_FreeEnv: env = %u ** \n", env); + + if (env && EN_Destructor(env)) + { + mylog(" ok\n"); + return SQL_SUCCESS; + } + + mylog(" error\n"); + EN_log_error(func, "Error freeing environment", env); + return SQL_ERROR; +} + + +/* Returns the next SQL error information. */ +RETCODE SQL_API +PGAPI_Error( + HENV henv, + HDBC hdbc, + HSTMT hstmt, + UCHAR FAR * szSqlState, + SDWORD FAR * pfNativeError, + UCHAR FAR * szErrorMsg, + SWORD cbErrorMsgMax, + SWORD FAR * pcbErrorMsg) +{ + char *msg; + int status; + BOOL once_again = FALSE; + SWORD msglen; + + mylog("**** PGAPI_Error: henv=%u, hdbc=%u, hstmt=%u <%d>\n", henv, hdbc, hstmt, cbErrorMsgMax); + + if (cbErrorMsgMax < 0) + return SQL_ERROR; + if (SQL_NULL_HSTMT != hstmt) + { + /* CC: return an error of a hstmt */ + StatementClass *stmt = (StatementClass *) hstmt; + + if (SC_get_error(stmt, &status, &msg)) + { + mylog("SC_get_error: status = %d, msg = #%s#\n", status, msg); + if (NULL == msg) + { + if (NULL != szSqlState) + strcpy(szSqlState, "00000"); + if (NULL != pcbErrorMsg) + *pcbErrorMsg = 0; + if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0)) + szErrorMsg[0] = '\0'; + + return SQL_NO_DATA_FOUND; + } + msglen = (SWORD) strlen(msg); + if (NULL != pcbErrorMsg) + { + *pcbErrorMsg = msglen; + if (cbErrorMsgMax == 0) + once_again = TRUE; + else if (msglen >= cbErrorMsgMax) + { + once_again = TRUE; + *pcbErrorMsg = cbErrorMsgMax - 1; + } + } + + if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0)) + strncpy_null(szErrorMsg, msg, cbErrorMsgMax); + + if (NULL != pfNativeError) + *pfNativeError = status; + + if (NULL != szSqlState) + + switch (status) + { + /* now determine the SQLSTATE to be returned */ + case STMT_ROW_VERSION_CHANGED: + strcpy(szSqlState, "01001"); + /* data truncated */ + break; + case STMT_TRUNCATED: + strcpy(szSqlState, "01004"); + /* data truncated */ + break; + case STMT_INFO_ONLY: + strcpy(szSqlState, "00000"); + /* just information that is returned, no error */ + break; + case STMT_BAD_ERROR: + strcpy(szSqlState, "08S01"); + /* communication link failure */ + break; + case STMT_CREATE_TABLE_ERROR: + strcpy(szSqlState, "S0001"); + /* table already exists */ + break; + case STMT_STATUS_ERROR: + case STMT_SEQUENCE_ERROR: + strcpy(szSqlState, "S1010"); + /* Function sequence error */ + break; + case STMT_NO_MEMORY_ERROR: + strcpy(szSqlState, "S1001"); + /* memory allocation failure */ + break; + case STMT_COLNUM_ERROR: + strcpy(szSqlState, "S1002"); + /* invalid column number */ + break; + case STMT_NO_STMTSTRING: + strcpy(szSqlState, "S1001"); + /* having no stmtstring is also a malloc problem */ + break; + case STMT_ERROR_TAKEN_FROM_BACKEND: + strcpy(szSqlState, "S1000"); + /* general error */ + break; + case STMT_INTERNAL_ERROR: + strcpy(szSqlState, "S1000"); + /* general error */ + break; + case STMT_ROW_OUT_OF_RANGE: + strcpy(szSqlState, "S1107"); + break; + + case STMT_OPERATION_CANCELLED: + strcpy(szSqlState, "S1008"); + break; + + case STMT_NOT_IMPLEMENTED_ERROR: + strcpy(szSqlState, "S1C00"); /* == 'driver not + * capable' */ + break; + case STMT_OPTION_OUT_OF_RANGE_ERROR: + strcpy(szSqlState, "S1092"); + break; + case STMT_BAD_PARAMETER_NUMBER_ERROR: + strcpy(szSqlState, "S1093"); + break; + case STMT_INVALID_COLUMN_NUMBER_ERROR: + strcpy(szSqlState, "S1002"); + break; + case STMT_RESTRICTED_DATA_TYPE_ERROR: + strcpy(szSqlState, "07006"); + break; + case STMT_INVALID_CURSOR_STATE_ERROR: + strcpy(szSqlState, "24000"); + break; + case STMT_OPTION_VALUE_CHANGED: + strcpy(szSqlState, "01S02"); + break; + case STMT_POS_BEFORE_RECORDSET: + strcpy(szSqlState, "01S06"); + break; + case STMT_INVALID_CURSOR_NAME: + strcpy(szSqlState, "34000"); + break; + case STMT_NO_CURSOR_NAME: + strcpy(szSqlState, "S1015"); + break; + case STMT_INVALID_ARGUMENT_NO: + strcpy(szSqlState, "S1009"); + /* invalid argument value */ + break; + case STMT_INVALID_CURSOR_POSITION: + strcpy(szSqlState, "S1109"); + break; + case STMT_VALUE_OUT_OF_RANGE: + strcpy(szSqlState, "22003"); + break; + case STMT_OPERATION_INVALID: + strcpy(szSqlState, "S1011"); + break; + case STMT_INVALID_OPTION_IDENTIFIER: + strcpy(szSqlState, "HY092"); + break; + case STMT_EXEC_ERROR: + default: + strcpy(szSqlState, "S1000"); + /* also a general error */ + break; + } + mylog(" szSqlState = '%s', szError='%s'\n", szSqlState, szErrorMsg); + } + else + { + if (NULL != szSqlState) + strcpy(szSqlState, "00000"); + if (NULL != pcbErrorMsg) + *pcbErrorMsg = 0; + if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0)) + szErrorMsg[0] = '\0'; + + mylog(" returning NO_DATA_FOUND\n"); + + return SQL_NO_DATA_FOUND; + } + + if (once_again) + { + int outlen; + + stmt->errornumber = status; + if (cbErrorMsgMax > 0) + outlen = *pcbErrorMsg; + else + outlen = 0; + if (!stmt->errormsg_malloced || !stmt->errormsg) + { + stmt->errormsg = malloc(msglen - outlen + 1); + stmt->errormsg_malloced = TRUE; + } + memmove(stmt->errormsg, msg + outlen, msglen - outlen + 1); + } + else if (stmt->errormsg_malloced) + SC_clear_error(stmt); + if (cbErrorMsgMax == 0) + return SQL_SUCCESS_WITH_INFO; + else + return SQL_SUCCESS; + } + else if (SQL_NULL_HDBC != hdbc) + { + ConnectionClass *conn = (ConnectionClass *) hdbc; + + mylog("calling CC_get_error\n"); + if (CC_get_error(conn, &status, &msg)) + { + mylog("CC_get_error: status = %d, msg = #%s#\n", status, msg); + if (NULL == msg) + { + if (NULL != szSqlState) + strcpy(szSqlState, "00000"); + if (NULL != pcbErrorMsg) + *pcbErrorMsg = 0; + if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0)) + szErrorMsg[0] = '\0'; + + return SQL_NO_DATA_FOUND; + } + + msglen = strlen(msg); + if (NULL != pcbErrorMsg) + { + *pcbErrorMsg = msglen; + if (cbErrorMsgMax == 0) + once_again = TRUE; + else if (msglen >= cbErrorMsgMax) + *pcbErrorMsg = cbErrorMsgMax - 1; + } + if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0)) + strncpy_null(szErrorMsg, msg, cbErrorMsgMax); + if (NULL != pfNativeError) + *pfNativeError = status; + + if (NULL != szSqlState) + switch (status) + { + case STMT_OPTION_VALUE_CHANGED: + case CONN_OPTION_VALUE_CHANGED: + strcpy(szSqlState, "01S02"); + break; + case STMT_TRUNCATED: + case CONN_TRUNCATED: + strcpy(szSqlState, "01004"); + /* data truncated */ + break; + case CONN_INIREAD_ERROR: + strcpy(szSqlState, "IM002"); + /* data source not found */ + break; + case CONN_OPENDB_ERROR: + strcpy(szSqlState, "08001"); + /* unable to connect to data source */ + break; + case CONN_INVALID_AUTHENTICATION: + case CONN_AUTH_TYPE_UNSUPPORTED: + strcpy(szSqlState, "28000"); + break; + case CONN_STMT_ALLOC_ERROR: + strcpy(szSqlState, "S1001"); + /* memory allocation failure */ + break; + case CONN_IN_USE: + strcpy(szSqlState, "S1000"); + /* general error */ + break; + case CONN_UNSUPPORTED_OPTION: + strcpy(szSqlState, "IM001"); + /* driver does not support this function */ + case CONN_INVALID_ARGUMENT_NO: + strcpy(szSqlState, "S1009"); + /* invalid argument value */ + break; + case CONN_TRANSACT_IN_PROGRES: + strcpy(szSqlState, "S1010"); + + /* + * when the user tries to switch commit mode in a + * transaction + */ + /* -> function sequence error */ + break; + case CONN_NO_MEMORY_ERROR: + strcpy(szSqlState, "S1001"); + break; + case CONN_NOT_IMPLEMENTED_ERROR: + case STMT_NOT_IMPLEMENTED_ERROR: + strcpy(szSqlState, "S1C00"); + break; + case CONN_VALUE_OUT_OF_RANGE: + case STMT_VALUE_OUT_OF_RANGE: + strcpy(szSqlState, "22003"); + break; + default: + strcpy(szSqlState, "S1000"); + /* general error */ + break; + } + } + else + { + mylog("CC_Get_error returned nothing.\n"); + if (NULL != szSqlState) + strcpy(szSqlState, "00000"); + if (NULL != pcbErrorMsg) + *pcbErrorMsg = 0; + if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0)) + szErrorMsg[0] = '\0'; + + return SQL_NO_DATA_FOUND; + } + + if (once_again) + { + conn->errornumber = status; + return SQL_SUCCESS_WITH_INFO; + } + else + return SQL_SUCCESS; + } + else if (SQL_NULL_HENV != henv) + { + EnvironmentClass *env = (EnvironmentClass *) henv; + + if (EN_get_error(env, &status, &msg)) + { + mylog("EN_get_error: status = %d, msg = #%s#\n", status, msg); + if (NULL == msg) + { + if (NULL != szSqlState) + strcpy(szSqlState, "00000"); + if (NULL != pcbErrorMsg) + *pcbErrorMsg = 0; + if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0)) + szErrorMsg[0] = '\0'; + + return SQL_NO_DATA_FOUND; + } + + if (NULL != pcbErrorMsg) + *pcbErrorMsg = (SWORD) strlen(msg); + if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0)) + strncpy_null(szErrorMsg, msg, cbErrorMsgMax); + if (NULL != pfNativeError) + *pfNativeError = status; + + if (szSqlState) + { + switch (status) + { + case ENV_ALLOC_ERROR: + /* memory allocation failure */ + strcpy(szSqlState, "S1001"); + break; + default: + strcpy(szSqlState, "S1000"); + /* general error */ + break; + } + } + } + else + { + if (NULL != szSqlState) + strcpy(szSqlState, "00000"); + if (NULL != pcbErrorMsg) + *pcbErrorMsg = 0; + if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0)) + szErrorMsg[0] = '\0'; + + return SQL_NO_DATA_FOUND; + } + + return SQL_SUCCESS; + } + + if (NULL != szSqlState) + strcpy(szSqlState, "00000"); + if (NULL != pcbErrorMsg) + *pcbErrorMsg = 0; + if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0)) + szErrorMsg[0] = '\0'; + + return SQL_NO_DATA_FOUND; +} + + +/* + * EnvironmentClass implementation + */ +EnvironmentClass * +EN_Constructor(void) +{ + EnvironmentClass *rv; + + rv = (EnvironmentClass *) malloc(sizeof(EnvironmentClass)); + if (rv) + { + rv->errormsg = 0; + rv->errornumber = 0; + } + + return rv; +} + + +char +EN_Destructor(EnvironmentClass *self) +{ + int lf; + char rv = 1; + + mylog("in EN_Destructor, self=%u\n", self); + + /* + * the error messages are static strings distributed throughout the + * source--they should not be freed + */ + + /* Free any connections belonging to this environment */ + for (lf = 0; lf < MAX_CONNECTIONS; lf++) + { + if (conns[lf] && conns[lf]->henv == self) + rv = rv && CC_Destructor(conns[lf]); + } + free(self); + + mylog("exit EN_Destructor: rv = %d\n", rv); +#ifdef _MEMORY_DEBUG_ + debug_memory_inouecheck(); +#endif /* _MEMORY_DEBUG_ */ + return rv; +} + + +char +EN_get_error(EnvironmentClass *self, int *number, char **message) +{ + if (self && self->errormsg && self->errornumber) + { + *message = self->errormsg; + *number = self->errornumber; + self->errormsg = 0; + self->errornumber = 0; + return 1; + } + else + return 0; +} + + +char +EN_add_connection(EnvironmentClass *self, ConnectionClass *conn) +{ + int i; + + mylog("EN_add_connection: self = %u, conn = %u\n", self, conn); + + for (i = 0; i < MAX_CONNECTIONS; i++) + { + if (!conns[i]) + { + conn->henv = self; + conns[i] = conn; + + mylog(" added at i =%d, conn->henv = %u, conns[i]->henv = %u\n", i, conn->henv, conns[i]->henv); + + return TRUE; + } + } + + return FALSE; +} + + +char +EN_remove_connection(EnvironmentClass *self, ConnectionClass *conn) +{ + int i; + + for (i = 0; i < MAX_CONNECTIONS; i++) + if (conns[i] == conn && conns[i]->status != CONN_EXECUTING) + { + conns[i] = NULL; + return TRUE; + } + + return FALSE; +} + + +void +EN_log_error(char *func, char *desc, EnvironmentClass *self) +{ + if (self) + qlog("ENVIRON ERROR: func=%s, desc='%s', errnum=%d, errmsg='%s'\n", func, desc, self->errornumber, self->errormsg); + else + qlog("INVALID ENVIRON HANDLE ERROR: func=%s, desc='%s'\n", func, desc); +} diff --git a/src/interfaces/odbc/windev/environ.h b/src/interfaces/odbc/windev/environ.h new file mode 100644 index 0000000000..7b463b3e74 --- /dev/null +++ b/src/interfaces/odbc/windev/environ.h @@ -0,0 +1,31 @@ +/* File: environ.h + * + * Description: See "environ.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __ENVIRON_H__ +#define __ENVIRON_H__ + +#include "psqlodbc.h" + +#define ENV_ALLOC_ERROR 1 + +/********** Environment Handle *************/ +struct EnvironmentClass_ +{ + char *errormsg; + int errornumber; +}; + +/* Environment prototypes */ +EnvironmentClass *EN_Constructor(void); +char EN_Destructor(EnvironmentClass *self); +char EN_get_error(EnvironmentClass *self, int *number, char **message); +char EN_add_connection(EnvironmentClass *self, ConnectionClass *conn); +char EN_remove_connection(EnvironmentClass *self, ConnectionClass *conn); +void EN_log_error(char *func, char *desc, EnvironmentClass *self); + +#endif diff --git a/src/interfaces/odbc/windev/execute.c b/src/interfaces/odbc/windev/execute.c new file mode 100644 index 0000000000..5a693b7163 --- /dev/null +++ b/src/interfaces/odbc/windev/execute.c @@ -0,0 +1,955 @@ +/*------- + * Module: execute.c + * + * Description: This module contains routines related to + * preparing and executing an SQL statement. + * + * Classes: n/a + * + * API functions: SQLPrepare, SQLExecute, SQLExecDirect, SQLTransact, + * SQLCancel, SQLNativeSql, SQLParamData, SQLPutData + * + * Comments: See "notice.txt" for copyright and license information. + *------- + */ + +#include "psqlodbc.h" + +#include +#include + +#include "connection.h" +#include "statement.h" +#include "qresult.h" +#include "convert.h" +#include "bind.h" +#include "pgtypes.h" +#include "lobj.h" +#include "pgapifunc.h" + +/*extern GLOBAL_VALUES globals;*/ + + +/* Perform a Prepare on the SQL statement */ +RETCODE SQL_API +PGAPI_Prepare(HSTMT hstmt, + UCHAR FAR * szSqlStr, + SDWORD cbSqlStr) +{ + static char *func = "PGAPI_Prepare"; + StatementClass *self = (StatementClass *) hstmt; + + mylog("%s: entering...\n", func); + + if (!self) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + /* + * According to the ODBC specs it is valid to call SQLPrepare mulitple + * times. In that case, the bound SQL statement is replaced by the new + * one + */ + + switch (self->status) + { + case STMT_PREMATURE: + mylog("**** PGAPI_Prepare: STMT_PREMATURE, recycle\n"); + SC_recycle_statement(self); /* recycle the statement, but do + * not remove parameter bindings */ + break; + + case STMT_FINISHED: + mylog("**** PGAPI_Prepare: STMT_FINISHED, recycle\n"); + SC_recycle_statement(self); /* recycle the statement, but do + * not remove parameter bindings */ + break; + + case STMT_ALLOCATED: + mylog("**** PGAPI_Prepare: STMT_ALLOCATED, copy\n"); + self->status = STMT_READY; + break; + + case STMT_READY: + mylog("**** PGAPI_Prepare: STMT_READY, change SQL\n"); + break; + + case STMT_EXECUTING: + mylog("**** PGAPI_Prepare: STMT_EXECUTING, error!\n"); + + self->errornumber = STMT_SEQUENCE_ERROR; + self->errormsg = "PGAPI_Prepare(): The handle does not point to a statement that is ready to be executed"; + SC_log_error(func, "", self); + + return SQL_ERROR; + + default: + self->errornumber = STMT_INTERNAL_ERROR; + self->errormsg = "An Internal Error has occured -- Unknown statement status."; + SC_log_error(func, "", self); + return SQL_ERROR; + } + + if (self->statement) + free(self->statement); + + self->statement = make_string(szSqlStr, cbSqlStr, NULL); + if (!self->statement) + { + self->errornumber = STMT_NO_MEMORY_ERROR; + self->errormsg = "No memory available to store statement"; + SC_log_error(func, "", self); + return SQL_ERROR; + } + + self->prepare = TRUE; + self->statement_type = statement_type(self->statement); + + /* Check if connection is onlyread (only selects are allowed) */ + if (CC_is_onlyread(self->hdbc) && STMT_UPDATE(self)) + { + self->errornumber = STMT_EXEC_ERROR; + self->errormsg = "Connection is readonly, only select statements are allowed."; + SC_log_error(func, "", self); + return SQL_ERROR; + } + + return SQL_SUCCESS; +} + + +/* Performs the equivalent of SQLPrepare, followed by SQLExecute. */ +RETCODE SQL_API +PGAPI_ExecDirect( + HSTMT hstmt, + UCHAR FAR * szSqlStr, + SDWORD cbSqlStr) +{ + StatementClass *stmt = (StatementClass *) hstmt; + RETCODE result; + static char *func = "PGAPI_ExecDirect"; + + mylog("%s: entering...\n", func); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + if (stmt->statement) + free(stmt->statement); + + /* + * keep a copy of the un-parametrized statement, in case they try to + * execute this statement again + */ + stmt->statement = make_string(szSqlStr, cbSqlStr, NULL); + if (!stmt->statement) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "No memory available to store statement"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + mylog("**** %s: hstmt=%u, statement='%s'\n", func, hstmt, stmt->statement); + + stmt->prepare = FALSE; + + /* + * If an SQLPrepare was performed prior to this, but was left in the + * premature state because an error occurred prior to SQLExecute then + * set the statement to finished so it can be recycled. + */ + if (stmt->status == STMT_PREMATURE) + stmt->status = STMT_FINISHED; + + stmt->statement_type = statement_type(stmt->statement); + + /* Check if connection is onlyread (only selects are allowed) */ + if (CC_is_onlyread(stmt->hdbc) && STMT_UPDATE(stmt)) + { + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "Connection is readonly, only select statements are allowed."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + mylog("%s: calling PGAPI_Execute...\n", func); + + result = PGAPI_Execute(hstmt); + + mylog("%s: returned %hd from PGAPI_Execute\n", func, result); + return result; +} + + +/* Execute a prepared SQL statement */ +RETCODE SQL_API +PGAPI_Execute( + HSTMT hstmt) +{ + static char *func = "PGAPI_Execute"; + StatementClass *stmt = (StatementClass *) hstmt; + ConnectionClass *conn; + int i, + retval; + + mylog("%s: entering...\n", func); + + if (!stmt) + { + SC_log_error(func, "", NULL); + mylog("%s: NULL statement so return SQL_INVALID_HANDLE\n", func); + return SQL_INVALID_HANDLE; + } + + /* + * If the statement is premature, it means we already executed it from + * an SQLPrepare/SQLDescribeCol type of scenario. So just return + * success. + */ + if (stmt->prepare && stmt->status == STMT_PREMATURE) + { + if (stmt->inaccurate_result) + SC_recycle_statement(stmt); + else + { + stmt->status = STMT_FINISHED; + if (stmt->errormsg == NULL) + { + mylog("%s: premature statement but return SQL_SUCCESS\n", func); + return SQL_SUCCESS; + } + else + { + SC_log_error(func, "", stmt); + mylog("%s: premature statement so return SQL_ERROR\n", func); + return SQL_ERROR; + } + } + } + + mylog("%s: clear errors...\n", func); + + SC_clear_error(stmt); + + conn = SC_get_conn(stmt); + if (conn->status == CONN_EXECUTING) + { + stmt->errormsg = "Connection is already in use."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + SC_log_error(func, "", stmt); + mylog("%s: problem with connection\n", func); + return SQL_ERROR; + } + + if (!stmt->statement) + { + stmt->errornumber = STMT_NO_STMTSTRING; + stmt->errormsg = "This handle does not have a SQL statement stored in it"; + SC_log_error(func, "", stmt); + mylog("%s: problem with handle\n", func); + return SQL_ERROR; + } + + /* + * If SQLExecute is being called again, recycle the statement. Note + * this should have been done by the application in a call to + * SQLFreeStmt(SQL_CLOSE) or SQLCancel. + */ + if (stmt->status == STMT_FINISHED) + { + mylog("%s: recycling statement (should have been done by app)...\n", func); + SC_recycle_statement(stmt); + } + + /* Check if the statement is in the correct state */ + if ((stmt->prepare && stmt->status != STMT_READY) || + (stmt->status != STMT_ALLOCATED && stmt->status != STMT_READY)) + { + stmt->errornumber = STMT_STATUS_ERROR; + stmt->errormsg = "The handle does not point to a statement that is ready to be executed"; + SC_log_error(func, "", stmt); + mylog("%s: problem with statement\n", func); + return SQL_ERROR; + } + + /* + * Check if statement has any data-at-execute parameters when it is + * not in SC_pre_execute. + */ + if (!stmt->pre_executing) + { + /* + * The bound parameters could have possibly changed since the last + * execute of this statement? Therefore check for params and + * re-copy. + */ + stmt->data_at_exec = -1; + for (i = 0; i < stmt->parameters_allocated; i++) + { + Int4 *pcVal = stmt->parameters[i].used; + + if (pcVal && (*pcVal == SQL_DATA_AT_EXEC || *pcVal <= SQL_LEN_DATA_AT_EXEC_OFFSET)) + stmt->parameters[i].data_at_exec = TRUE; + else + stmt->parameters[i].data_at_exec = FALSE; + /* Check for data at execution parameters */ + if (stmt->parameters[i].data_at_exec == TRUE) + { + if (stmt->data_at_exec < 0) + stmt->data_at_exec = 1; + else + stmt->data_at_exec++; + } + } + + /* + * If there are some data at execution parameters, return need + * data + */ + + /* + * SQLParamData and SQLPutData will be used to send params and + * execute the statement. + */ + if (stmt->data_at_exec > 0) + return SQL_NEED_DATA; + + } + + + mylog("%s: copying statement params: trans_status=%d, len=%d, stmt='%s'\n", func, conn->transact_status, strlen(stmt->statement), stmt->statement); + + /* Create the statement with parameters substituted. */ + retval = copy_statement_with_parameters(stmt); + if (retval != SQL_SUCCESS) + /* error msg passed from above */ + return retval; + + mylog(" stmt_with_params = '%s'\n", stmt->stmt_with_params); + + /* + * Get the field info for the prepared query using dummy backward + * fetch. + */ + if (stmt->inaccurate_result && conn->connInfo.disallow_premature) + { + if (SC_is_pre_executable(stmt)) + { + BOOL in_trans = CC_is_in_trans(conn); + BOOL issued_begin = FALSE, + begin_included = FALSE; + QResultClass *res; + + if (strnicmp(stmt->stmt_with_params, "BEGIN;", 6) == 0) + begin_included = TRUE; + else if (!in_trans) + { + res = CC_send_query(conn, "BEGIN", NULL); + if (res && !QR_aborted(res)) + issued_begin = TRUE; + if (res) + QR_Destructor(res); + if (!issued_begin) + { + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "Handle prepare error"; + return SQL_ERROR; + } + } + /* we are now in a transaction */ + CC_set_in_trans(conn); + stmt->result = res = CC_send_query(conn, stmt->stmt_with_params, NULL); + if (!res || QR_aborted(res)) + { + CC_abort(conn); + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "Handle prepare error"; + return SQL_ERROR; + } + else + { + if (CC_is_in_autocommit(conn)) + { + if (issued_begin) + { + res = CC_send_query(conn, "COMMIT", NULL); + CC_set_no_trans(conn); + if (res) + QR_Destructor(res); + } + else if (!in_trans && begin_included) + CC_set_no_trans(conn); + } + stmt->status = STMT_FINISHED; + return SQL_SUCCESS; + } + } + else + return SQL_SUCCESS; + } + + return SC_execute(stmt); +} + + +RETCODE SQL_API +PGAPI_Transact( + HENV henv, + HDBC hdbc, + UWORD fType) +{ + static char *func = "PGAPI_Transact"; + extern ConnectionClass *conns[]; + ConnectionClass *conn; + QResultClass *res; + char ok, + *stmt_string; + int lf; + + mylog("entering %s: hdbc=%u, henv=%u\n", func, hdbc, henv); + + if (hdbc == SQL_NULL_HDBC && henv == SQL_NULL_HENV) + { + CC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + /* + * If hdbc is null and henv is valid, it means transact all + * connections on that henv. + */ + if (hdbc == SQL_NULL_HDBC && henv != SQL_NULL_HENV) + { + for (lf = 0; lf < MAX_CONNECTIONS; lf++) + { + conn = conns[lf]; + + if (conn && conn->henv == henv) + if (PGAPI_Transact(henv, (HDBC) conn, fType) != SQL_SUCCESS) + return SQL_ERROR; + } + return SQL_SUCCESS; + } + + conn = (ConnectionClass *) hdbc; + + if (fType == SQL_COMMIT) + stmt_string = "COMMIT"; + else if (fType == SQL_ROLLBACK) + stmt_string = "ROLLBACK"; + else + { + conn->errornumber = CONN_INVALID_ARGUMENT_NO; + conn->errormsg = "PGAPI_Transact can only be called with SQL_COMMIT or SQL_ROLLBACK as parameter"; + CC_log_error(func, "", conn); + return SQL_ERROR; + } + + /* If manual commit and in transaction, then proceed. */ + if (!CC_is_in_autocommit(conn) && CC_is_in_trans(conn)) + { + mylog("PGAPI_Transact: sending on conn %d '%s'\n", conn, stmt_string); + + res = CC_send_query(conn, stmt_string, NULL); + CC_set_no_trans(conn); + + if (!res) + { + /* error msg will be in the connection */ + CC_log_error(func, "", conn); + return SQL_ERROR; + } + + ok = QR_command_successful(res); + QR_Destructor(res); + + if (!ok) + { + CC_log_error(func, "", conn); + return SQL_ERROR; + } + } + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_Cancel( + HSTMT hstmt) /* Statement to cancel. */ +{ + static char *func = "PGAPI_Cancel"; + StatementClass *stmt = (StatementClass *) hstmt; + RETCODE result; + ConnInfo *ci; + +#ifdef WIN32 + HMODULE hmodule; + FARPROC addr; +#endif + + mylog("%s: entering...\n", func); + + /* Check if this can handle canceling in the middle of a SQLPutData? */ + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + ci = &(SC_get_conn(stmt)->connInfo); + + /* + * Not in the middle of SQLParamData/SQLPutData so cancel like a + * close. + */ + if (stmt->data_at_exec < 0) + { + /* + * MAJOR HACK for Windows to reset the driver manager's cursor + * state: Because of what seems like a bug in the Odbc driver + * manager, SQLCancel does not act like a SQLFreeStmt(CLOSE), as + * many applications depend on this behavior. So, this brute + * force method calls the driver manager's function on behalf of + * the application. + */ + +#ifdef WIN32 + if (ci->drivers.cancel_as_freestmt) + { + hmodule = GetModuleHandle("ODBC32"); + addr = GetProcAddress(hmodule, "SQLFreeStmt"); + result = addr((char *) (stmt->phstmt) - 96, SQL_CLOSE); + } + else + result = PGAPI_FreeStmt(hstmt, SQL_CLOSE); +#else + result = PGAPI_FreeStmt(hstmt, SQL_CLOSE); +#endif + + mylog("PGAPI_Cancel: PGAPI_FreeStmt returned %d\n", result); + + SC_clear_error(hstmt); + return SQL_SUCCESS; + } + + /* In the middle of SQLParamData/SQLPutData, so cancel that. */ + + /* + * Note, any previous data-at-exec buffers will be freed in the + * recycle + */ + /* if they call SQLExecDirect or SQLExecute again. */ + + stmt->data_at_exec = -1; + stmt->current_exec_param = -1; + stmt->put_data = FALSE; + + return SQL_SUCCESS; +} + + +/* + * Returns the SQL string as modified by the driver. + * Currently, just copy the input string without modification + * observing buffer limits and truncation. + */ +RETCODE SQL_API +PGAPI_NativeSql( + HDBC hdbc, + UCHAR FAR * szSqlStrIn, + SDWORD cbSqlStrIn, + UCHAR FAR * szSqlStr, + SDWORD cbSqlStrMax, + SDWORD FAR * pcbSqlStr) +{ + static char *func = "PGAPI_NativeSql"; + int len = 0; + char *ptr; + ConnectionClass *conn = (ConnectionClass *) hdbc; + RETCODE result; + + mylog("%s: entering...cbSqlStrIn=%d\n", func, cbSqlStrIn); + + ptr = (cbSqlStrIn == 0) ? "" : make_string(szSqlStrIn, cbSqlStrIn, NULL); + if (!ptr) + { + conn->errornumber = CONN_NO_MEMORY_ERROR; + conn->errormsg = "No memory available to store native sql string"; + CC_log_error(func, "", conn); + return SQL_ERROR; + } + + result = SQL_SUCCESS; + len = strlen(ptr); + + if (szSqlStr) + { + strncpy_null(szSqlStr, ptr, cbSqlStrMax); + + if (len >= cbSqlStrMax) + { + result = SQL_SUCCESS_WITH_INFO; + conn->errornumber = STMT_TRUNCATED; + conn->errormsg = "The buffer was too small for the NativeSQL."; + } + } + + if (pcbSqlStr) + *pcbSqlStr = len; + + if (cbSqlStrIn) + free(ptr); + + return result; +} + + +/* + * Supplies parameter data at execution time. + * Used in conjuction with SQLPutData. + */ +RETCODE SQL_API +PGAPI_ParamData( + HSTMT hstmt, + PTR FAR * prgbValue) +{ + static char *func = "PGAPI_ParamData"; + StatementClass *stmt = (StatementClass *) hstmt; + int i, + retval; + ConnInfo *ci; + + mylog("%s: entering...\n", func); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + ci = &(SC_get_conn(stmt)->connInfo); + + mylog("%s: data_at_exec=%d, params_alloc=%d\n", func, stmt->data_at_exec, stmt->parameters_allocated); + + if (stmt->data_at_exec < 0) + { + stmt->errornumber = STMT_SEQUENCE_ERROR; + stmt->errormsg = "No execution-time parameters for this statement"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + if (stmt->data_at_exec > stmt->parameters_allocated) + { + stmt->errornumber = STMT_SEQUENCE_ERROR; + stmt->errormsg = "Too many execution-time parameters were present"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* close the large object */ + if (stmt->lobj_fd >= 0) + { + lo_close(stmt->hdbc, stmt->lobj_fd); + + /* commit transaction if needed */ + if (!ci->drivers.use_declarefetch && CC_is_in_autocommit(stmt->hdbc)) + { + QResultClass *res; + char ok; + + res = CC_send_query(stmt->hdbc, "COMMIT", NULL); + if (!res) + { + stmt->errormsg = "Could not commit (in-line) a transaction"; + stmt->errornumber = STMT_EXEC_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + ok = QR_command_successful(res); + CC_set_no_trans(stmt->hdbc); + QR_Destructor(res); + if (!ok) + { + stmt->errormsg = "Could not commit (in-line) a transaction"; + stmt->errornumber = STMT_EXEC_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + } + stmt->lobj_fd = -1; + } + + /* Done, now copy the params and then execute the statement */ + if (stmt->data_at_exec == 0) + { + retval = copy_statement_with_parameters(stmt); + if (retval != SQL_SUCCESS) + return retval; + + stmt->current_exec_param = -1; + + return SC_execute(stmt); + } + + /* + * Set beginning param; if first time SQLParamData is called , start + * at 0. Otherwise, start at the last parameter + 1. + */ + i = stmt->current_exec_param >= 0 ? stmt->current_exec_param + 1 : 0; + + /* At least 1 data at execution parameter, so Fill in the token value */ + for (; i < stmt->parameters_allocated; i++) + { + if (stmt->parameters[i].data_at_exec == TRUE) + { + stmt->data_at_exec--; + stmt->current_exec_param = i; + stmt->put_data = FALSE; + *prgbValue = stmt->parameters[i].buffer; /* token */ + break; + } + } + + return SQL_NEED_DATA; +} + + +/* + * Supplies parameter data at execution time. + * Used in conjunction with SQLParamData. + */ +RETCODE SQL_API +PGAPI_PutData( + HSTMT hstmt, + PTR rgbValue, + SDWORD cbValue) +{ + static char *func = "PGAPI_PutData"; + StatementClass *stmt = (StatementClass *) hstmt; + int old_pos, + retval; + ParameterInfoClass *current_param; + char *buffer; + + mylog("%s: entering...\n", func); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + if (stmt->current_exec_param < 0) + { + stmt->errornumber = STMT_SEQUENCE_ERROR; + stmt->errormsg = "Previous call was not SQLPutData or SQLParamData"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + current_param = &(stmt->parameters[stmt->current_exec_param]); + + if (!stmt->put_data) + { /* first call */ + mylog("PGAPI_PutData: (1) cbValue = %d\n", cbValue); + + stmt->put_data = TRUE; + + current_param->EXEC_used = (SDWORD *) malloc(sizeof(SDWORD)); + if (!current_param->EXEC_used) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Out of memory in PGAPI_PutData (1)"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + *current_param->EXEC_used = cbValue; + + if (cbValue == SQL_NULL_DATA) + return SQL_SUCCESS; + + /* Handle Long Var Binary with Large Objects */ + if (current_param->SQLType == SQL_LONGVARBINARY) + { + /* begin transaction if needed */ + if (!CC_is_in_trans(stmt->hdbc)) + { + QResultClass *res; + char ok; + + res = CC_send_query(stmt->hdbc, "BEGIN", NULL); + if (!res) + { + stmt->errormsg = "Could not begin (in-line) a transaction"; + stmt->errornumber = STMT_EXEC_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + ok = QR_command_successful(res); + QR_Destructor(res); + if (!ok) + { + stmt->errormsg = "Could not begin (in-line) a transaction"; + stmt->errornumber = STMT_EXEC_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + CC_set_in_trans(stmt->hdbc); + } + + /* store the oid */ + current_param->lobj_oid = lo_creat(stmt->hdbc, INV_READ | INV_WRITE); + if (current_param->lobj_oid == 0) + { + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "Couldnt create large object."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* + * major hack -- to allow convert to see somethings there have + * to modify convert to handle this better + */ + current_param->EXEC_buffer = (char *) ¤t_param->lobj_oid; + + /* store the fd */ + stmt->lobj_fd = lo_open(stmt->hdbc, current_param->lobj_oid, INV_WRITE); + if (stmt->lobj_fd < 0) + { + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "Couldnt open large object for writing."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + retval = lo_write(stmt->hdbc, stmt->lobj_fd, rgbValue, cbValue); + mylog("lo_write: cbValue=%d, wrote %d bytes\n", cbValue, retval); + } + else + { + /* for handling fields */ + if (cbValue == SQL_NTS) + { + current_param->EXEC_buffer = strdup(rgbValue); + if (!current_param->EXEC_buffer) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Out of memory in PGAPI_PutData (2)"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + } + else + { + Int2 ctype = current_param->CType; + + if (ctype == SQL_C_DEFAULT) + ctype = sqltype_to_default_ctype(current_param->SQLType); + if (ctype == SQL_C_CHAR || ctype == SQL_C_BINARY) + { + current_param->EXEC_buffer = malloc(cbValue + 1); + if (!current_param->EXEC_buffer) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Out of memory in PGAPI_PutData (2)"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + memcpy(current_param->EXEC_buffer, rgbValue, cbValue); + current_param->EXEC_buffer[cbValue] = '\0'; + } + else + { + Int4 used = ctype_length(ctype); + + current_param->EXEC_buffer = malloc(used); + if (!current_param->EXEC_buffer) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Out of memory in PGAPI_PutData (2)"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + memcpy(current_param->EXEC_buffer, rgbValue, used); + } + } + } + } + else + { + /* calling SQLPutData more than once */ + mylog("PGAPI_PutData: (>1) cbValue = %d\n", cbValue); + + if (current_param->SQLType == SQL_LONGVARBINARY) + { + /* the large object fd is in EXEC_buffer */ + retval = lo_write(stmt->hdbc, stmt->lobj_fd, rgbValue, cbValue); + mylog("lo_write(2): cbValue = %d, wrote %d bytes\n", cbValue, retval); + + *current_param->EXEC_used += cbValue; + } + else + { + buffer = current_param->EXEC_buffer; + + if (cbValue == SQL_NTS) + { + buffer = realloc(buffer, strlen(buffer) + strlen(rgbValue) + 1); + if (!buffer) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Out of memory in PGAPI_PutData (3)"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + strcat(buffer, rgbValue); + + mylog(" cbValue = SQL_NTS: strlen(buffer) = %d\n", strlen(buffer)); + + *current_param->EXEC_used = cbValue; + + /* reassign buffer incase realloc moved it */ + current_param->EXEC_buffer = buffer; + } + else if (cbValue > 0) + { + old_pos = *current_param->EXEC_used; + + *current_param->EXEC_used += cbValue; + + mylog(" cbValue = %d, old_pos = %d, *used = %d\n", cbValue, old_pos, *current_param->EXEC_used); + + /* dont lose the old pointer in case out of memory */ + buffer = realloc(current_param->EXEC_buffer, *current_param->EXEC_used + 1); + if (!buffer) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Out of memory in PGAPI_PutData (3)"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + memcpy(&buffer[old_pos], rgbValue, cbValue); + buffer[*current_param->EXEC_used] = '\0'; + + /* reassign buffer incase realloc moved it */ + current_param->EXEC_buffer = buffer; + } + else + { + SC_log_error(func, "bad cbValue", stmt); + return SQL_ERROR; + } + } + } + + return SQL_SUCCESS; +} diff --git a/src/interfaces/odbc/windev/info.c b/src/interfaces/odbc/windev/info.c new file mode 100644 index 0000000000..c77b3c5e89 --- /dev/null +++ b/src/interfaces/odbc/windev/info.c @@ -0,0 +1,3714 @@ +/*-------- + * Module: info.c + * + * Description: This module contains routines related to + * ODBC informational functions. + * + * Classes: n/a + * + * API functions: SQLGetInfo, SQLGetTypeInfo, SQLGetFunctions, + * SQLTables, SQLColumns, SQLStatistics, SQLSpecialColumns, + * SQLPrimaryKeys, SQLForeignKeys, + * SQLProcedureColumns(NI), SQLProcedures(NI), + * SQLTablePrivileges(NI), SQLColumnPrivileges(NI) + * + * Comments: See "notice.txt" for copyright and license information. + *-------- + */ + +#include "psqlodbc.h" + +#include +#include + +#ifndef WIN32 +#include +#endif + +#include "tuple.h" +#include "pgtypes.h" + +#include "environ.h" +#include "connection.h" +#include "statement.h" +#include "qresult.h" +#include "bind.h" +#include "misc.h" +#include "pgtypes.h" +#include "pgapifunc.h" + + +/* Trigger related stuff for SQLForeign Keys */ +#define TRIGGER_SHIFT 3 +#define TRIGGER_MASK 0x03 +#define TRIGGER_DELETE 0x01 +#define TRIGGER_UPDATE 0x02 + + +/* extern GLOBAL_VALUES globals; */ + + + +RETCODE SQL_API +PGAPI_GetInfo( + HDBC hdbc, + UWORD fInfoType, + PTR rgbInfoValue, + SWORD cbInfoValueMax, + SWORD FAR * pcbInfoValue) +{ + static char *func = "PGAPI_GetInfo"; + ConnectionClass *conn = (ConnectionClass *) hdbc; + ConnInfo *ci; + char *p = NULL, + tmp[MAX_INFO_STRING]; + int len = 0, + value = 0; + RETCODE result; + + mylog("%s: entering...fInfoType=%d\n", func, fInfoType); + + if (!conn) + { + CC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + ci = &(conn->connInfo); + + switch (fInfoType) + { + case SQL_ACCESSIBLE_PROCEDURES: /* ODBC 1.0 */ + p = "N"; + break; + + case SQL_ACCESSIBLE_TABLES: /* ODBC 1.0 */ + p = "N"; + break; + + case SQL_ACTIVE_CONNECTIONS: /* ODBC 1.0 */ + len = 2; + value = MAX_CONNECTIONS; + break; + + case SQL_ACTIVE_STATEMENTS: /* ODBC 1.0 */ + len = 2; + value = 0; + break; + + case SQL_ALTER_TABLE: /* ODBC 2.0 */ + len = 4; + value = SQL_AT_ADD_COLUMN; + break; + + case SQL_BOOKMARK_PERSISTENCE: /* ODBC 2.0 */ + /* very simple bookmark support */ + len = 4; + value = ci->drivers.use_declarefetch ? 0 : (SQL_BP_SCROLL); + break; + + case SQL_COLUMN_ALIAS: /* ODBC 2.0 */ + p = "N"; + break; + + case SQL_CONCAT_NULL_BEHAVIOR: /* ODBC 1.0 */ + len = 2; + value = SQL_CB_NON_NULL; + break; + + case SQL_CONVERT_BIGINT: + case SQL_CONVERT_BINARY: + case SQL_CONVERT_BIT: + case SQL_CONVERT_CHAR: + case SQL_CONVERT_DATE: + case SQL_CONVERT_DECIMAL: + case SQL_CONVERT_DOUBLE: + case SQL_CONVERT_FLOAT: + case SQL_CONVERT_INTEGER: + case SQL_CONVERT_LONGVARBINARY: + case SQL_CONVERT_LONGVARCHAR: + case SQL_CONVERT_NUMERIC: + case SQL_CONVERT_REAL: + case SQL_CONVERT_SMALLINT: + case SQL_CONVERT_TIME: + case SQL_CONVERT_TIMESTAMP: + case SQL_CONVERT_TINYINT: + case SQL_CONVERT_VARBINARY: + case SQL_CONVERT_VARCHAR: /* ODBC 1.0 */ + len = 4; + value = fInfoType; + break; + + case SQL_CONVERT_FUNCTIONS: /* ODBC 1.0 */ + len = 4; + value = 0; + break; + + case SQL_CORRELATION_NAME: /* ODBC 1.0 */ + + /* + * Saying no correlation name makes Query not work right. + * value = SQL_CN_NONE; + */ + len = 2; + value = SQL_CN_ANY; + break; + + case SQL_CURSOR_COMMIT_BEHAVIOR: /* ODBC 1.0 */ + len = 2; + value = SQL_CB_CLOSE; + if (ci->updatable_cursors) + if (!ci->drivers.use_declarefetch) + value = SQL_CB_PRESERVE; + break; + + case SQL_CURSOR_ROLLBACK_BEHAVIOR: /* ODBC 1.0 */ + len = 2; + value = SQL_CB_CLOSE; + if (ci->updatable_cursors) + if (!ci->drivers.use_declarefetch) + value = SQL_CB_PRESERVE; + break; + + case SQL_DATA_SOURCE_NAME: /* ODBC 1.0 */ + p = CC_get_DSN(conn); + break; + + case SQL_DATA_SOURCE_READ_ONLY: /* ODBC 1.0 */ + p = CC_is_onlyread(conn) ? "Y" : "N"; + break; + + case SQL_DATABASE_NAME: /* Support for old ODBC 1.0 Apps */ + + /* + * Returning the database name causes problems in MS Query. It + * generates query like: "SELECT DISTINCT a FROM byronnbad3 + * bad3" + * + * p = CC_get_database(conn); + */ + p = ""; + break; + + case SQL_DBMS_NAME: /* ODBC 1.0 */ + p = DBMS_NAME; + break; + + case SQL_DBMS_VER: /* ODBC 1.0 */ + + /* + * The ODBC spec wants ##.##.#### ...whatever... so prepend + * the driver + */ + /* version number to the dbms version string */ + sprintf(tmp, "%s %s", POSTGRESDRIVERVERSION, conn->pg_version); + p = tmp; + break; + + case SQL_DEFAULT_TXN_ISOLATION: /* ODBC 1.0 */ + len = 4; + value = SQL_TXN_READ_COMMITTED; /* SQL_TXN_SERIALIZABLE; */ + break; + + case SQL_DRIVER_NAME: /* ODBC 1.0 */ + p = DRIVER_FILE_NAME; + break; + + case SQL_DRIVER_ODBC_VER: + p = DRIVER_ODBC_VER; + break; + + case SQL_DRIVER_VER: /* ODBC 1.0 */ + p = POSTGRESDRIVERVERSION; + break; + + case SQL_EXPRESSIONS_IN_ORDERBY: /* ODBC 1.0 */ + p = "N"; + break; + + case SQL_FETCH_DIRECTION: /* ODBC 1.0 */ + len = 4; + value = ci->drivers.use_declarefetch ? (SQL_FD_FETCH_NEXT) : (SQL_FD_FETCH_NEXT | + SQL_FD_FETCH_FIRST | + SQL_FD_FETCH_LAST | + SQL_FD_FETCH_PRIOR | + SQL_FD_FETCH_ABSOLUTE | + SQL_FD_FETCH_RELATIVE | + SQL_FD_FETCH_BOOKMARK); + break; + + case SQL_FILE_USAGE: /* ODBC 2.0 */ + len = 2; + value = SQL_FILE_NOT_SUPPORTED; + break; + + case SQL_GETDATA_EXTENSIONS: /* ODBC 2.0 */ + len = 4; + value = (SQL_GD_ANY_COLUMN | SQL_GD_ANY_ORDER | SQL_GD_BOUND | SQL_GD_BLOCK); + break; + + case SQL_GROUP_BY: /* ODBC 2.0 */ + len = 2; + value = SQL_GB_GROUP_BY_EQUALS_SELECT; + break; + + case SQL_IDENTIFIER_CASE: /* ODBC 1.0 */ + + /* + * are identifiers case-sensitive (yes, but only when quoted. + * If not quoted, they default to lowercase) + */ + len = 2; + value = SQL_IC_LOWER; + break; + + case SQL_IDENTIFIER_QUOTE_CHAR: /* ODBC 1.0 */ + /* the character used to quote "identifiers" */ + p = PG_VERSION_LE(conn, 6.2) ? " " : "\""; + break; + + case SQL_KEYWORDS: /* ODBC 2.0 */ + p = ""; + break; + + case SQL_LIKE_ESCAPE_CLAUSE: /* ODBC 2.0 */ + + /* + * is there a character that escapes '%' and '_' in a LIKE + * clause? not as far as I can tell + */ + p = "N"; + break; + + case SQL_LOCK_TYPES: /* ODBC 2.0 */ + len = 4; + value = ci->drivers.lie ? (SQL_LCK_NO_CHANGE | SQL_LCK_EXCLUSIVE | SQL_LCK_UNLOCK) : SQL_LCK_NO_CHANGE; + break; + + case SQL_MAX_BINARY_LITERAL_LEN: /* ODBC 2.0 */ + len = 4; + value = 0; + break; + + case SQL_MAX_CHAR_LITERAL_LEN: /* ODBC 2.0 */ + len = 4; + value = 0; + break; + + case SQL_MAX_COLUMN_NAME_LEN: /* ODBC 1.0 */ + len = 2; + value = MAX_COLUMN_LEN; + break; + + case SQL_MAX_COLUMNS_IN_GROUP_BY: /* ODBC 2.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_COLUMNS_IN_INDEX: /* ODBC 2.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_COLUMNS_IN_ORDER_BY: /* ODBC 2.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_COLUMNS_IN_SELECT: /* ODBC 2.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_COLUMNS_IN_TABLE: /* ODBC 2.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_CURSOR_NAME_LEN: /* ODBC 1.0 */ + len = 2; + value = MAX_CURSOR_LEN; + break; + + case SQL_MAX_INDEX_SIZE: /* ODBC 2.0 */ + len = 4; + value = 0; + break; + + case SQL_MAX_OWNER_NAME_LEN: /* ODBC 1.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_PROCEDURE_NAME_LEN: /* ODBC 1.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_QUALIFIER_NAME_LEN: /* ODBC 1.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_ROW_SIZE: /* ODBC 2.0 */ + len = 4; + if (PG_VERSION_GE(conn, 7.1)) + { + /* Large Rowa in 7.1+ */ + value = MAX_ROW_SIZE; + } + else + { + /* Without the Toaster we're limited to the blocksize */ + value = BLCKSZ; + } + break; + + case SQL_MAX_ROW_SIZE_INCLUDES_LONG: /* ODBC 2.0 */ + + /* + * does the preceding value include LONGVARCHAR and + * LONGVARBINARY fields? Well, it does include longvarchar, + * but not longvarbinary. + */ + p = "Y"; + break; + + case SQL_MAX_STATEMENT_LEN: /* ODBC 2.0 */ + /* maybe this should be 0? */ + len = 4; + value = CC_get_max_query_len(conn); + break; + + case SQL_MAX_TABLE_NAME_LEN: /* ODBC 1.0 */ + len = 2; + value = MAX_TABLE_LEN; + break; + + case SQL_MAX_TABLES_IN_SELECT: /* ODBC 2.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_USER_NAME_LEN: + len = 2; + value = 0; + break; + + case SQL_MULT_RESULT_SETS: /* ODBC 1.0 */ + /* Don't support multiple result sets but say yes anyway? */ + p = "Y"; + break; + + case SQL_MULTIPLE_ACTIVE_TXN: /* ODBC 1.0 */ + p = "Y"; + break; + + case SQL_NEED_LONG_DATA_LEN: /* ODBC 2.0 */ + + /* + * Don't need the length, SQLPutData can handle any size and + * multiple calls + */ + p = "N"; + break; + + case SQL_NON_NULLABLE_COLUMNS: /* ODBC 1.0 */ + len = 2; + value = SQL_NNC_NON_NULL; + break; + + case SQL_NULL_COLLATION: /* ODBC 2.0 */ + /* where are nulls sorted? */ + len = 2; + value = SQL_NC_END; + break; + + case SQL_NUMERIC_FUNCTIONS: /* ODBC 1.0 */ + len = 4; + value = 0; + break; + + case SQL_ODBC_API_CONFORMANCE: /* ODBC 1.0 */ + len = 2; + value = SQL_OAC_LEVEL1; + break; + + case SQL_ODBC_SAG_CLI_CONFORMANCE: /* ODBC 1.0 */ + len = 2; + value = SQL_OSCC_NOT_COMPLIANT; + break; + + case SQL_ODBC_SQL_CONFORMANCE: /* ODBC 1.0 */ + len = 2; + value = SQL_OSC_CORE; + break; + + case SQL_ODBC_SQL_OPT_IEF: /* ODBC 1.0 */ + p = "N"; + break; + + case SQL_OJ_CAPABILITIES: /* ODBC 2.01 */ + len = 4; + if (PG_VERSION_GE(conn, 7.1)) + { + /* OJs in 7.1+ */ + value = (SQL_OJ_LEFT | + SQL_OJ_RIGHT | + SQL_OJ_FULL | + SQL_OJ_NESTED | + SQL_OJ_NOT_ORDERED | + SQL_OJ_INNER | + SQL_OJ_ALL_COMPARISON_OPS); + } + else + /* OJs not in <7.1 */ + value = 0; + break; + + case SQL_ORDER_BY_COLUMNS_IN_SELECT: /* ODBC 2.0 */ + p = (PG_VERSION_LE(conn, 6.3)) ? "Y" : "N"; + break; + + case SQL_OUTER_JOINS: /* ODBC 1.0 */ + if (PG_VERSION_GE(conn, 7.1)) + /* OJs in 7.1+ */ + p = "Y"; + else + /* OJs not in <7.1 */ + p = "N"; + break; + + case SQL_OWNER_TERM: /* ODBC 1.0 */ + p = "owner"; + break; + + case SQL_OWNER_USAGE: /* ODBC 2.0 */ + len = 4; + value = 0; + break; + + case SQL_POS_OPERATIONS: /* ODBC 2.0 */ + len = 4; + value = ci->drivers.lie ? (SQL_POS_POSITION | SQL_POS_REFRESH | SQL_POS_UPDATE | SQL_POS_DELETE | SQL_POS_ADD) : (SQL_POS_POSITION | SQL_POS_REFRESH); + if (ci->updatable_cursors) + value |= (SQL_POS_UPDATE | SQL_POS_DELETE | SQL_POS_ADD); + break; + + case SQL_POSITIONED_STATEMENTS: /* ODBC 2.0 */ + len = 4; + value = ci->drivers.lie ? (SQL_PS_POSITIONED_DELETE | + SQL_PS_POSITIONED_UPDATE | + SQL_PS_SELECT_FOR_UPDATE) : 0; + break; + + case SQL_PROCEDURE_TERM: /* ODBC 1.0 */ + p = "procedure"; + break; + + case SQL_PROCEDURES: /* ODBC 1.0 */ + p = "Y"; + break; + + case SQL_QUALIFIER_LOCATION: /* ODBC 2.0 */ + len = 2; + value = SQL_QL_START; + break; + + case SQL_QUALIFIER_NAME_SEPARATOR: /* ODBC 1.0 */ + p = ""; + break; + + case SQL_QUALIFIER_TERM: /* ODBC 1.0 */ + p = ""; + break; + + case SQL_QUALIFIER_USAGE: /* ODBC 2.0 */ + len = 4; + value = 0; + break; + + case SQL_QUOTED_IDENTIFIER_CASE: /* ODBC 2.0 */ + /* are "quoted" identifiers case-sensitive? YES! */ + len = 2; + value = SQL_IC_SENSITIVE; + break; + + case SQL_ROW_UPDATES: /* ODBC 1.0 */ + + /* + * Driver doesn't support keyset-driven or mixed cursors, so + * not much point in saying row updates are supported + */ + p = (ci->drivers.lie || ci->updatable_cursors) ? "Y" : "N"; + break; + + case SQL_SCROLL_CONCURRENCY: /* ODBC 1.0 */ + len = 4; + value = ci->drivers.lie ? (SQL_SCCO_READ_ONLY | + SQL_SCCO_LOCK | + SQL_SCCO_OPT_ROWVER | + SQL_SCCO_OPT_VALUES) : + (SQL_SCCO_READ_ONLY); + if (ci->updatable_cursors) + value |= SQL_SCCO_OPT_ROWVER; + break; + + case SQL_SCROLL_OPTIONS: /* ODBC 1.0 */ + len = 4; + value = ci->drivers.lie ? (SQL_SO_FORWARD_ONLY | + SQL_SO_STATIC | + SQL_SO_KEYSET_DRIVEN | + SQL_SO_DYNAMIC | + SQL_SO_MIXED) + : (ci->drivers.use_declarefetch ? SQL_SO_FORWARD_ONLY : (SQL_SO_FORWARD_ONLY | SQL_SO_STATIC)); + if (ci->updatable_cursors) + value |= 0; /* SQL_SO_KEYSET_DRIVEN in the furure */ + break; + + case SQL_SEARCH_PATTERN_ESCAPE: /* ODBC 1.0 */ + p = ""; + break; + + case SQL_SERVER_NAME: /* ODBC 1.0 */ + p = CC_get_server(conn); + break; + + case SQL_SPECIAL_CHARACTERS: /* ODBC 2.0 */ + p = "_"; + break; + + case SQL_STATIC_SENSITIVITY: /* ODBC 2.0 */ + len = 4; + value = ci->drivers.lie ? (SQL_SS_ADDITIONS | SQL_SS_DELETIONS | SQL_SS_UPDATES) : 0; + if (ci->updatable_cursors) + value |= (SQL_SS_ADDITIONS | SQL_SS_DELETIONS | SQL_SS_UPDATES); + break; + + case SQL_STRING_FUNCTIONS: /* ODBC 1.0 */ + len = 4; + value = (SQL_FN_STR_CONCAT | + SQL_FN_STR_LCASE | + SQL_FN_STR_LENGTH | + SQL_FN_STR_LOCATE | + SQL_FN_STR_LTRIM | + SQL_FN_STR_RTRIM | + SQL_FN_STR_SUBSTRING | + SQL_FN_STR_UCASE); + break; + + case SQL_SUBQUERIES: /* ODBC 2.0 */ + /* postgres 6.3 supports subqueries */ + len = 4; + value = (SQL_SQ_QUANTIFIED | + SQL_SQ_IN | + SQL_SQ_EXISTS | + SQL_SQ_COMPARISON); + break; + + case SQL_SYSTEM_FUNCTIONS: /* ODBC 1.0 */ + len = 4; + value = 0; + break; + + case SQL_TABLE_TERM: /* ODBC 1.0 */ + p = "table"; + break; + + case SQL_TIMEDATE_ADD_INTERVALS: /* ODBC 2.0 */ + len = 4; + value = 0; + break; + + case SQL_TIMEDATE_DIFF_INTERVALS: /* ODBC 2.0 */ + len = 4; + value = 0; + break; + + case SQL_TIMEDATE_FUNCTIONS: /* ODBC 1.0 */ + len = 4; + value = (SQL_FN_TD_NOW); + break; + + case SQL_TXN_CAPABLE: /* ODBC 1.0 */ + + /* + * Postgres can deal with create or drop table statements in a + * transaction + */ + len = 2; + value = SQL_TC_ALL; + break; + + case SQL_TXN_ISOLATION_OPTION: /* ODBC 1.0 */ + len = 4; + value = SQL_TXN_READ_COMMITTED; /* SQL_TXN_SERIALIZABLE; */ + break; + + case SQL_UNION: /* ODBC 2.0 */ + /* unions with all supported in postgres 6.3 */ + len = 4; + value = (SQL_U_UNION | SQL_U_UNION_ALL); + break; + + case SQL_USER_NAME: /* ODBC 1.0 */ + p = CC_get_username(conn); + break; + + default: + /* unrecognized key */ + conn->errormsg = "Unrecognized key passed to PGAPI_GetInfo."; + conn->errornumber = CONN_NOT_IMPLEMENTED_ERROR; + CC_log_error(func, "", conn); + return SQL_ERROR; + } + + result = SQL_SUCCESS; + + mylog("%s: p='%s', len=%d, value=%d, cbMax=%d\n", func, p ? p : "", len, value, cbInfoValueMax); + + /* + * NOTE, that if rgbInfoValue is NULL, then no warnings or errors + * should result and just pcbInfoValue is returned, which indicates + * what length would be required if a real buffer had been passed in. + */ + if (p) + { + /* char/binary data */ + len = strlen(p); + + if (rgbInfoValue) + { + strncpy_null((char *) rgbInfoValue, p, (size_t) cbInfoValueMax); + + if (len >= cbInfoValueMax) + { + result = SQL_SUCCESS_WITH_INFO; + conn->errornumber = STMT_TRUNCATED; + conn->errormsg = "The buffer was too small for tthe InfoValue."; + } + } + } + else + { + /* numeric data */ + if (rgbInfoValue) + { + if (len == 2) + *((WORD *) rgbInfoValue) = (WORD) value; + else if (len == 4) + *((DWORD *) rgbInfoValue) = (DWORD) value; + } + } + + if (pcbInfoValue) + *pcbInfoValue = len; + + return result; +} + + +RETCODE SQL_API +PGAPI_GetTypeInfo( + HSTMT hstmt, + SWORD fSqlType) +{ + static char *func = "PGAPI_GetTypeInfo"; + StatementClass *stmt = (StatementClass *) hstmt; + TupleNode *row; + int i; + + /* Int4 type; */ + Int4 pgType; + Int2 sqlType; + + mylog("%s: entering...fSqlType = %d\n", func, fSqlType); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + stmt->manual_result = TRUE; + stmt->result = QR_Constructor(); + if (!stmt->result) + { + SC_log_error(func, "Error creating result.", stmt); + return SQL_ERROR; + } + + extend_bindings(stmt, 15); + + QR_set_num_fields(stmt->result, 15); + QR_set_field_info(stmt->result, 0, "TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 1, "DATA_TYPE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 2, "PRECISION", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 3, "LITERAL_PREFIX", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 4, "LITERAL_SUFFIX", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 5, "CREATE_PARAMS", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 6, "NULLABLE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 7, "CASE_SENSITIVE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 8, "SEARCHABLE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 9, "UNSIGNED_ATTRIBUTE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 10, "MONEY", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 11, "AUTO_INCREMENT", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 12, "LOCAL_TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 13, "MINIMUM_SCALE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 14, "MAXIMUM_SCALE", PG_TYPE_INT2, 2); + + for (i = 0, sqlType = sqlTypes[0]; sqlType; sqlType = sqlTypes[++i]) + { + pgType = sqltype_to_pgtype(stmt, sqlType); + + if (fSqlType == SQL_ALL_TYPES || fSqlType == sqlType) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + (15 - 1) *sizeof(TupleField)); + + /* These values can't be NULL */ + set_tuplefield_string(&row->tuple[0], pgtype_to_name(stmt, pgType)); + set_tuplefield_int2(&row->tuple[1], (Int2) sqlType); + set_tuplefield_int2(&row->tuple[6], pgtype_nullable(stmt, pgType)); + set_tuplefield_int2(&row->tuple[7], pgtype_case_sensitive(stmt, pgType)); + set_tuplefield_int2(&row->tuple[8], pgtype_searchable(stmt, pgType)); + set_tuplefield_int2(&row->tuple[10], pgtype_money(stmt, pgType)); + + /* + * Localized data-source dependent data type name (always + * NULL) + */ + set_tuplefield_null(&row->tuple[12]); + + /* These values can be NULL */ + set_nullfield_int4(&row->tuple[2], pgtype_precision(stmt, pgType, PG_STATIC, PG_STATIC)); + set_nullfield_string(&row->tuple[3], pgtype_literal_prefix(stmt, pgType)); + set_nullfield_string(&row->tuple[4], pgtype_literal_suffix(stmt, pgType)); + set_nullfield_string(&row->tuple[5], pgtype_create_params(stmt, pgType)); + set_nullfield_int2(&row->tuple[9], pgtype_unsigned(stmt, pgType)); + set_nullfield_int2(&row->tuple[11], pgtype_auto_increment(stmt, pgType)); + set_nullfield_int2(&row->tuple[13], pgtype_scale(stmt, pgType, PG_STATIC)); + set_nullfield_int2(&row->tuple[14], pgtype_scale(stmt, pgType, PG_STATIC)); + + QR_add_tuple(stmt->result, row); + } + } + + stmt->status = STMT_FINISHED; + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_GetFunctions( + HDBC hdbc, + UWORD fFunction, + UWORD FAR * pfExists) +{ + static char *func = "PGAPI_GetFunctions"; + ConnectionClass *conn = (ConnectionClass *) hdbc; + ConnInfo *ci = &(conn->connInfo); + + mylog("%s: entering...%u\n", func, fFunction); + + if (fFunction == SQL_API_ALL_FUNCTIONS) + { +#if (ODBCVER < 0x0300) + if (ci->drivers.lie) + { + int i; + + memset(pfExists, 0, sizeof(UWORD) * 100); + + pfExists[SQL_API_SQLALLOCENV] = TRUE; + pfExists[SQL_API_SQLFREEENV] = TRUE; + for (i = SQL_API_SQLALLOCCONNECT; i <= SQL_NUM_FUNCTIONS; i++) + pfExists[i] = TRUE; + for (i = SQL_EXT_API_START; i <= SQL_EXT_API_LAST; i++) + pfExists[i] = TRUE; + } + else +#endif + { + memset(pfExists, 0, sizeof(UWORD) * 100); + + /* ODBC core functions */ + pfExists[SQL_API_SQLALLOCCONNECT] = TRUE; + pfExists[SQL_API_SQLALLOCENV] = TRUE; + pfExists[SQL_API_SQLALLOCSTMT] = TRUE; + pfExists[SQL_API_SQLBINDCOL] = TRUE; + pfExists[SQL_API_SQLCANCEL] = TRUE; + pfExists[SQL_API_SQLCOLATTRIBUTES] = TRUE; + pfExists[SQL_API_SQLCONNECT] = TRUE; + pfExists[SQL_API_SQLDESCRIBECOL] = TRUE; /* partial */ + pfExists[SQL_API_SQLDISCONNECT] = TRUE; + pfExists[SQL_API_SQLERROR] = TRUE; + pfExists[SQL_API_SQLEXECDIRECT] = TRUE; + pfExists[SQL_API_SQLEXECUTE] = TRUE; + pfExists[SQL_API_SQLFETCH] = TRUE; + pfExists[SQL_API_SQLFREECONNECT] = TRUE; + pfExists[SQL_API_SQLFREEENV] = TRUE; + pfExists[SQL_API_SQLFREESTMT] = TRUE; + pfExists[SQL_API_SQLGETCURSORNAME] = TRUE; + pfExists[SQL_API_SQLNUMRESULTCOLS] = TRUE; + pfExists[SQL_API_SQLPREPARE] = TRUE; /* complete? */ + pfExists[SQL_API_SQLROWCOUNT] = TRUE; + pfExists[SQL_API_SQLSETCURSORNAME] = TRUE; + pfExists[SQL_API_SQLSETPARAM] = FALSE; /* odbc 1.0 */ + pfExists[SQL_API_SQLTRANSACT] = TRUE; + + /* ODBC level 1 functions */ + pfExists[SQL_API_SQLBINDPARAMETER] = TRUE; + pfExists[SQL_API_SQLCOLUMNS] = TRUE; + pfExists[SQL_API_SQLDRIVERCONNECT] = TRUE; + pfExists[SQL_API_SQLGETCONNECTOPTION] = TRUE; /* partial */ + pfExists[SQL_API_SQLGETDATA] = TRUE; + pfExists[SQL_API_SQLGETFUNCTIONS] = TRUE; + pfExists[SQL_API_SQLGETINFO] = TRUE; + pfExists[SQL_API_SQLGETSTMTOPTION] = TRUE; /* partial */ + pfExists[SQL_API_SQLGETTYPEINFO] = TRUE; + pfExists[SQL_API_SQLPARAMDATA] = TRUE; + pfExists[SQL_API_SQLPUTDATA] = TRUE; + pfExists[SQL_API_SQLSETCONNECTOPTION] = TRUE; /* partial */ + pfExists[SQL_API_SQLSETSTMTOPTION] = TRUE; + pfExists[SQL_API_SQLSPECIALCOLUMNS] = TRUE; + pfExists[SQL_API_SQLSTATISTICS] = TRUE; + pfExists[SQL_API_SQLTABLES] = TRUE; + + /* ODBC level 2 functions */ + pfExists[SQL_API_SQLBROWSECONNECT] = FALSE; + pfExists[SQL_API_SQLCOLUMNPRIVILEGES] = FALSE; + pfExists[SQL_API_SQLDATASOURCES] = FALSE; /* only implemented by + * DM */ + pfExists[SQL_API_SQLDESCRIBEPARAM] = FALSE; /* not properly + * implemented */ + pfExists[SQL_API_SQLDRIVERS] = FALSE; /* only implemented by + * DM */ + pfExists[SQL_API_SQLEXTENDEDFETCH] = TRUE; + pfExists[SQL_API_SQLFOREIGNKEYS] = TRUE; + pfExists[SQL_API_SQLMORERESULTS] = TRUE; + pfExists[SQL_API_SQLNATIVESQL] = TRUE; + pfExists[SQL_API_SQLNUMPARAMS] = TRUE; + pfExists[SQL_API_SQLPARAMOPTIONS] = FALSE; + pfExists[SQL_API_SQLPRIMARYKEYS] = TRUE; + pfExists[SQL_API_SQLPROCEDURECOLUMNS] = FALSE; + if (PG_VERSION_LT(conn, 6.5)) + pfExists[SQL_API_SQLPROCEDURES] = FALSE; + else + pfExists[SQL_API_SQLPROCEDURES] = TRUE; + pfExists[SQL_API_SQLSETPOS] = TRUE; + pfExists[SQL_API_SQLSETSCROLLOPTIONS] = TRUE; /* odbc 1.0 */ + pfExists[SQL_API_SQLTABLEPRIVILEGES] = FALSE; + } + } + else + { + if (ci->drivers.lie) + *pfExists = TRUE; + else + { + switch (fFunction) + { + case SQL_API_SQLALLOCCONNECT: + *pfExists = TRUE; + break; + case SQL_API_SQLALLOCENV: + *pfExists = TRUE; + break; + case SQL_API_SQLALLOCSTMT: + *pfExists = TRUE; + break; + case SQL_API_SQLBINDCOL: + *pfExists = TRUE; + break; + case SQL_API_SQLCANCEL: + *pfExists = TRUE; + break; + case SQL_API_SQLCOLATTRIBUTES: + *pfExists = TRUE; + break; + case SQL_API_SQLCONNECT: + *pfExists = TRUE; + break; + case SQL_API_SQLDESCRIBECOL: + *pfExists = TRUE; + break; /* partial */ + case SQL_API_SQLDISCONNECT: + *pfExists = TRUE; + break; + case SQL_API_SQLERROR: + *pfExists = TRUE; + break; + case SQL_API_SQLEXECDIRECT: + *pfExists = TRUE; + break; + case SQL_API_SQLEXECUTE: + *pfExists = TRUE; + break; + case SQL_API_SQLFETCH: + *pfExists = TRUE; + break; + case SQL_API_SQLFREECONNECT: + *pfExists = TRUE; + break; + case SQL_API_SQLFREEENV: + *pfExists = TRUE; + break; + case SQL_API_SQLFREESTMT: + *pfExists = TRUE; + break; + case SQL_API_SQLGETCURSORNAME: + *pfExists = TRUE; + break; + case SQL_API_SQLNUMRESULTCOLS: + *pfExists = TRUE; + break; + case SQL_API_SQLPREPARE: + *pfExists = TRUE; + break; + case SQL_API_SQLROWCOUNT: + *pfExists = TRUE; + break; + case SQL_API_SQLSETCURSORNAME: + *pfExists = TRUE; + break; + case SQL_API_SQLSETPARAM: + *pfExists = FALSE; + break; /* odbc 1.0 */ + case SQL_API_SQLTRANSACT: + *pfExists = TRUE; + break; + + /* ODBC level 1 functions */ + case SQL_API_SQLBINDPARAMETER: + *pfExists = TRUE; + break; + case SQL_API_SQLCOLUMNS: + *pfExists = TRUE; + break; + case SQL_API_SQLDRIVERCONNECT: + *pfExists = TRUE; + break; + case SQL_API_SQLGETCONNECTOPTION: + *pfExists = TRUE; + break; /* partial */ + case SQL_API_SQLGETDATA: + *pfExists = TRUE; + break; + case SQL_API_SQLGETFUNCTIONS: + *pfExists = TRUE; + break; + case SQL_API_SQLGETINFO: + *pfExists = TRUE; + break; + case SQL_API_SQLGETSTMTOPTION: + *pfExists = TRUE; + break; /* partial */ + case SQL_API_SQLGETTYPEINFO: + *pfExists = TRUE; + break; + case SQL_API_SQLPARAMDATA: + *pfExists = TRUE; + break; + case SQL_API_SQLPUTDATA: + *pfExists = TRUE; + break; + case SQL_API_SQLSETCONNECTOPTION: + *pfExists = TRUE; + break; /* partial */ + case SQL_API_SQLSETSTMTOPTION: + *pfExists = TRUE; + break; + case SQL_API_SQLSPECIALCOLUMNS: + *pfExists = TRUE; + break; + case SQL_API_SQLSTATISTICS: + *pfExists = TRUE; + break; + case SQL_API_SQLTABLES: + *pfExists = TRUE; + break; + + /* ODBC level 2 functions */ + case SQL_API_SQLBROWSECONNECT: + *pfExists = FALSE; + break; + case SQL_API_SQLCOLUMNPRIVILEGES: + *pfExists = FALSE; + break; + case SQL_API_SQLDATASOURCES: + *pfExists = FALSE; + break; /* only implemented by DM */ + case SQL_API_SQLDESCRIBEPARAM: + *pfExists = FALSE; + break; /* not properly implemented */ + case SQL_API_SQLDRIVERS: + *pfExists = FALSE; + break; /* only implemented by DM */ + case SQL_API_SQLEXTENDEDFETCH: + *pfExists = TRUE; + break; + case SQL_API_SQLFOREIGNKEYS: + *pfExists = TRUE; + break; + case SQL_API_SQLMORERESULTS: + *pfExists = TRUE; + break; + case SQL_API_SQLNATIVESQL: + *pfExists = TRUE; + break; + case SQL_API_SQLNUMPARAMS: + *pfExists = TRUE; + break; + case SQL_API_SQLPARAMOPTIONS: + *pfExists = FALSE; + break; + case SQL_API_SQLPRIMARYKEYS: + *pfExists = TRUE; + break; + case SQL_API_SQLPROCEDURECOLUMNS: + *pfExists = FALSE; + break; + case SQL_API_SQLPROCEDURES: + if (PG_VERSION_LT(conn, 6.5)) + *pfExists = FALSE; + else + *pfExists = TRUE; + break; + case SQL_API_SQLSETPOS: + *pfExists = TRUE; + break; + case SQL_API_SQLSETSCROLLOPTIONS: + *pfExists = TRUE; + break; /* odbc 1.0 */ + case SQL_API_SQLTABLEPRIVILEGES: + *pfExists = FALSE; + break; + } + } + } + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_Tables( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName, + UCHAR FAR * szTableType, + SWORD cbTableType) +{ + static char *func = "PGAPI_Tables"; + StatementClass *stmt = (StatementClass *) hstmt; + StatementClass *tbl_stmt; + TupleNode *row; + HSTMT htbl_stmt; + RETCODE result; + char *tableType; + char tables_query[INFO_INQUIRY_LEN]; + char table_name[MAX_INFO_STRING], + table_owner[MAX_INFO_STRING], + relkind_or_hasrules[MAX_INFO_STRING]; + ConnectionClass *conn; + ConnInfo *ci; + char *prefix[32], + prefixes[MEDIUM_REGISTRY_LEN]; + char *table_type[32], + table_types[MAX_INFO_STRING]; + char show_system_tables, + show_regular_tables, + show_views; + char regular_table, + view, + systable; + int i; + + mylog("%s: entering...stmt=%u\n", func, stmt); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + stmt->manual_result = TRUE; + stmt->errormsg_created = TRUE; + + conn = SC_get_conn(stmt); + ci = &(conn->connInfo); + + result = PGAPI_AllocStmt(stmt->hdbc, &htbl_stmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't allocate statement for PGAPI_Tables result."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + tbl_stmt = (StatementClass *) htbl_stmt; + + /* + * Create the query to find out the tables + */ + if (PG_VERSION_GE(conn, 7.1)) + { + /* view is represented by its relkind since 7.1 */ + strcpy(tables_query, "select relname, usename, relkind from pg_class, pg_user"); + strcat(tables_query, " where relkind in ('r', 'v')"); + } + else + { + strcpy(tables_query, "select relname, usename, relhasrules from pg_class, pg_user"); + strcat(tables_query, " where relkind = 'r'"); + } + + my_strcat(tables_query, " and usename like '%.*s'", szTableOwner, cbTableOwner); + my_strcat(tables_query, " and relname like '%.*s'", szTableName, cbTableName); + + /* Parse the extra systable prefix */ + strcpy(prefixes, ci->drivers.extra_systable_prefixes); + i = 0; + prefix[i] = strtok(prefixes, ";"); + while (prefix[i] && i < 32) + prefix[++i] = strtok(NULL, ";"); + + /* Parse the desired table types to return */ + show_system_tables = FALSE; + show_regular_tables = FALSE; + show_views = FALSE; + + /* make_string mallocs memory */ + tableType = make_string(szTableType, cbTableType, NULL); + if (tableType) + { + strcpy(table_types, tableType); + free(tableType); + i = 0; + table_type[i] = strtok(table_types, ","); + while (table_type[i] && i < 32) + table_type[++i] = strtok(NULL, ","); + + /* Check for desired table types to return */ + i = 0; + while (table_type[i]) + { + if (strstr(table_type[i], "SYSTEM TABLE")) + show_system_tables = TRUE; + else if (strstr(table_type[i], "TABLE")) + show_regular_tables = TRUE; + else if (strstr(table_type[i], "VIEW")) + show_views = TRUE; + i++; + } + } + else + { + show_regular_tables = TRUE; + show_views = TRUE; + } + + /* + * If not interested in SYSTEM TABLES then filter them out to save + * some time on the query. If treating system tables as regular + * tables, then dont filter either. + */ + if (!atoi(ci->show_system_tables) && !show_system_tables) + { + strcat(tables_query, " and relname !~ '^" POSTGRES_SYS_PREFIX); + + /* Also filter out user-defined system table types */ + i = 0; + while (prefix[i]) + { + strcat(tables_query, "|^"); + strcat(tables_query, prefix[i]); + i++; + } + strcat(tables_query, "'"); + } + + /* match users */ + if (PG_VERSION_LT(conn, 7.1)) + /* filter out large objects in older versions */ + strcat(tables_query, " and relname !~ '^xinv[0-9]+'"); + + strcat(tables_query, " and usesysid = relowner"); + strcat(tables_query, " order by relname"); + + result = PGAPI_ExecDirect(htbl_stmt, tables_query, strlen(tables_query)); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 1, SQL_C_CHAR, + table_name, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 2, SQL_C_CHAR, + table_owner, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + result = PGAPI_BindCol(htbl_stmt, 3, SQL_C_CHAR, + relkind_or_hasrules, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + stmt->result = QR_Constructor(); + if (!stmt->result) + { + stmt->errormsg = "Couldn't allocate memory for PGAPI_Tables result."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + /* the binding structure for a statement is not set up until */ + + /* + * a statement is actually executed, so we'll have to do this + * ourselves. + */ + extend_bindings(stmt, 5); + + /* set the field names */ + QR_set_num_fields(stmt->result, 5); + QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 3, "TABLE_TYPE", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 4, "REMARKS", PG_TYPE_TEXT, 254); + + /* add the tuples */ + result = PGAPI_Fetch(htbl_stmt); + while ((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) + { + /* + * Determine if this table name is a system table. If treating + * system tables as regular tables, then no need to do this test. + */ + systable = FALSE; + if (!atoi(ci->show_system_tables)) + { + if (strncmp(table_name, POSTGRES_SYS_PREFIX, strlen(POSTGRES_SYS_PREFIX)) == 0) + systable = TRUE; + + else + { + /* Check extra system table prefixes */ + i = 0; + while (prefix[i]) + { + mylog("table_name='%s', prefix[%d]='%s'\n", table_name, i, prefix[i]); + if (strncmp(table_name, prefix[i], strlen(prefix[i])) == 0) + { + systable = TRUE; + break; + } + i++; + } + } + } + + /* Determine if the table name is a view */ + if (PG_VERSION_GE(conn, 7.1)) + /* view is represented by its relkind since 7.1 */ + view = (relkind_or_hasrules[0] == 'v'); + else + view = (relkind_or_hasrules[0] == '1'); + + /* It must be a regular table */ + regular_table = (!systable && !view); + + + /* Include the row in the result set if meets all criteria */ + + /* + * NOTE: Unsupported table types (i.e., LOCAL TEMPORARY, ALIAS, + * etc) will return nothing + */ + if ((systable && show_system_tables) || + (view && show_views) || + (regular_table && show_regular_tables)) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + (5 - 1) *sizeof(TupleField)); + + set_tuplefield_string(&row->tuple[0], ""); + + /* + * I have to hide the table owner from Access, otherwise it + * insists on referring to the table as 'owner.table'. (this + * is valid according to the ODBC SQL grammar, but Postgres + * won't support it.) + * + * set_tuplefield_string(&row->tuple[1], table_owner); + */ + + mylog("%s: table_name = '%s'\n", func, table_name); + + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], table_name); + set_tuplefield_string(&row->tuple[3], systable ? "SYSTEM TABLE" : (view ? "VIEW" : "TABLE")); + set_tuplefield_string(&row->tuple[4], ""); + + QR_add_tuple(stmt->result, row); + } + result = PGAPI_Fetch(htbl_stmt); + } + if (result != SQL_NO_DATA_FOUND) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + /* + * also, things need to think that this statement is finished so the + * results can be retrieved. + */ + stmt->status = STMT_FINISHED; + + /* set up the current tuple pointer for SQLFetch */ + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + mylog("%s: EXIT, stmt=%u\n", func, stmt); + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_Columns( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName, + UCHAR FAR * szColumnName, + SWORD cbColumnName) +{ + static char *func = "PGAPI_Columns"; + StatementClass *stmt = (StatementClass *) hstmt; + TupleNode *row; + HSTMT hcol_stmt; + StatementClass *col_stmt; + char columns_query[INFO_INQUIRY_LEN]; + RETCODE result; + char table_owner[MAX_INFO_STRING], + table_name[MAX_INFO_STRING], + field_name[MAX_INFO_STRING], + field_type_name[MAX_INFO_STRING]; + Int2 field_number, + result_cols, + scale; + Int4 field_type, + the_type, + field_length, + mod_length, + precision; + char useStaticPrecision; + char not_null[MAX_INFO_STRING], + relhasrules[MAX_INFO_STRING]; + ConnInfo *ci; + ConnectionClass *conn; + + + mylog("%s: entering...stmt=%u\n", func, stmt); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + stmt->manual_result = TRUE; + stmt->errormsg_created = TRUE; + + conn = SC_get_conn(stmt); + ci = &(conn->connInfo); + + /* + * Create the query to find out the columns (Note: pre 6.3 did not + * have the atttypmod field) + */ + sprintf(columns_query, "select u.usename, c.relname, a.attname, a.atttypid" + ", t.typname, a.attnum, a.attlen, %s, a.attnotnull, c.relhasrules" + " from pg_user u, pg_class c, pg_attribute a, pg_type t" + " where u.usesysid = c.relowner" + " and c.oid= a.attrelid and a.atttypid = t.oid and (a.attnum > 0)", + PG_VERSION_LE(conn, 6.2) ? "a.attlen" : "a.atttypmod"); + + my_strcat(columns_query, " and c.relname like '%.*s'", szTableName, cbTableName); + my_strcat(columns_query, " and u.usename like '%.*s'", szTableOwner, cbTableOwner); + my_strcat(columns_query, " and a.attname like '%.*s'", szColumnName, cbColumnName); + + /* + * give the output in the order the columns were defined when the + * table was created + */ + strcat(columns_query, " order by attnum"); + + result = PGAPI_AllocStmt(stmt->hdbc, &hcol_stmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't allocate statement for PGAPI_Columns result."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + col_stmt = (StatementClass *) hcol_stmt; + + mylog("%s: hcol_stmt = %u, col_stmt = %u\n", func, hcol_stmt, col_stmt); + + result = PGAPI_ExecDirect(hcol_stmt, columns_query, + strlen(columns_query)); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = SC_create_errormsg(hcol_stmt); + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 1, SQL_C_CHAR, + table_owner, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 2, SQL_C_CHAR, + table_name, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 3, SQL_C_CHAR, + field_name, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 4, SQL_C_LONG, + &field_type, 4, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 5, SQL_C_CHAR, + field_type_name, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 6, SQL_C_SHORT, + &field_number, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 7, SQL_C_LONG, + &field_length, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 8, SQL_C_LONG, + &mod_length, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 9, SQL_C_CHAR, + not_null, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 10, SQL_C_CHAR, + relhasrules, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + stmt->result = QR_Constructor(); + if (!stmt->result) + { + stmt->errormsg = "Couldn't allocate memory for PGAPI_Columns result."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + /* the binding structure for a statement is not set up until */ + + /* + * a statement is actually executed, so we'll have to do this + * ourselves. + */ + result_cols = 14; + extend_bindings(stmt, result_cols); + + /* set the field names */ + QR_set_num_fields(stmt->result, result_cols); + QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 3, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 4, "DATA_TYPE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 5, "TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 6, "PRECISION", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 7, "LENGTH", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 8, "SCALE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 9, "RADIX", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 10, "NULLABLE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 11, "REMARKS", PG_TYPE_TEXT, 254); + + /* User defined fields */ + QR_set_field_info(stmt->result, 12, "DISPLAY_SIZE", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 13, "FIELD_TYPE", PG_TYPE_INT4, 4); + + result = PGAPI_Fetch(hcol_stmt); + + /* + * Only show oid if option AND there are other columns AND it's not + * being called by SQLStatistics . Always show OID if it's a system + * table + */ + + if (result != SQL_ERROR && !stmt->internal) + { + if (relhasrules[0] != '1' && + (atoi(ci->show_oid_column) || + strncmp(table_name, POSTGRES_SYS_PREFIX, strlen(POSTGRES_SYS_PREFIX)) == 0)) + { + /* For OID fields */ + the_type = PG_TYPE_OID; + row = (TupleNode *) malloc(sizeof(TupleNode) + + (result_cols - 1) *sizeof(TupleField)); + + set_tuplefield_string(&row->tuple[0], ""); + /* see note in SQLTables() */ + /* set_tuplefield_string(&row->tuple[1], table_owner); */ + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], table_name); + set_tuplefield_string(&row->tuple[3], "oid"); + set_tuplefield_int2(&row->tuple[4], pgtype_to_sqltype(stmt, the_type)); + set_tuplefield_string(&row->tuple[5], "OID"); + + set_tuplefield_int4(&row->tuple[7], pgtype_length(stmt, the_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[6], pgtype_precision(stmt, the_type, PG_STATIC, PG_STATIC)); + + set_nullfield_int2(&row->tuple[8], pgtype_scale(stmt, the_type, PG_STATIC)); + set_nullfield_int2(&row->tuple[9], pgtype_radix(stmt, the_type)); + set_tuplefield_int2(&row->tuple[10], SQL_NO_NULLS); + set_tuplefield_string(&row->tuple[11], ""); + + set_tuplefield_int4(&row->tuple[12], pgtype_display_size(stmt, the_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[13], the_type); + + QR_add_tuple(stmt->result, row); + } + } + + while ((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + + (result_cols - 1) *sizeof(TupleField)); + + + set_tuplefield_string(&row->tuple[0], ""); + /* see note in SQLTables() */ + /* set_tuplefield_string(&row->tuple[1], table_owner); */ + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], table_name); + set_tuplefield_string(&row->tuple[3], field_name); + set_tuplefield_int2(&row->tuple[4], pgtype_to_sqltype(stmt, field_type)); + set_tuplefield_string(&row->tuple[5], field_type_name); + + + /*---------- + * Some Notes about Postgres Data Types: + * + * VARCHAR - the length is stored in the pg_attribute.atttypmod field + * BPCHAR - the length is also stored as varchar is + * + * NUMERIC - the scale is stored in atttypmod as follows: + * + * precision =((atttypmod - VARHDRSZ) >> 16) & 0xffff + * scale = (atttypmod - VARHDRSZ) & 0xffff + * + *---------- + */ + qlog("PGAPI_Columns: table='%s',field_name='%s',type=%d,sqltype=%d,name='%s'\n", + table_name, field_name, field_type, pgtype_to_sqltype, field_type_name); + + useStaticPrecision = TRUE; + + if (field_type == PG_TYPE_NUMERIC) + { + if (mod_length >= 4) + mod_length -= 4; /* the length is in atttypmod - 4 */ + + if (mod_length >= 0) + { + useStaticPrecision = FALSE; + + precision = (mod_length >> 16) & 0xffff; + scale = mod_length & 0xffff; + + mylog("%s: field type is NUMERIC: field_type = %d, mod_length=%d, precision=%d, scale=%d\n", func, field_type, mod_length, precision, scale); + + set_tuplefield_int4(&row->tuple[7], precision + 2); /* sign+dec.point */ + set_tuplefield_int4(&row->tuple[6], precision); + set_tuplefield_int4(&row->tuple[12], precision + 2); /* sign+dec.point */ + set_nullfield_int2(&row->tuple[8], scale); + } + } + + if ((field_type == PG_TYPE_VARCHAR) || + (field_type == PG_TYPE_BPCHAR)) + { + useStaticPrecision = FALSE; + + if (mod_length >= 4) + mod_length -= 4; /* the length is in atttypmod - 4 */ + + if (mod_length > ci->drivers.max_varchar_size || mod_length <= 0) + mod_length = ci->drivers.max_varchar_size; + + mylog("%s: field type is VARCHAR,BPCHAR: field_type = %d, mod_length = %d\n", func, field_type, mod_length); + + set_tuplefield_int4(&row->tuple[7], mod_length); + set_tuplefield_int4(&row->tuple[6], mod_length); + set_tuplefield_int4(&row->tuple[12], mod_length); + set_nullfield_int2(&row->tuple[8], pgtype_scale(stmt, field_type, PG_STATIC)); + } + + if (useStaticPrecision) + { + mylog("%s: field type is OTHER: field_type = %d, pgtype_length = %d\n", func, field_type, pgtype_length(stmt, field_type, PG_STATIC, PG_STATIC)); + + set_tuplefield_int4(&row->tuple[7], pgtype_length(stmt, field_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[6], pgtype_precision(stmt, field_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[12], pgtype_display_size(stmt, field_type, PG_STATIC, PG_STATIC)); + set_nullfield_int2(&row->tuple[8], pgtype_scale(stmt, field_type, PG_STATIC)); + } + + set_nullfield_int2(&row->tuple[9], pgtype_radix(stmt, field_type)); + set_tuplefield_int2(&row->tuple[10], (Int2) (not_null[0] == '1' ? SQL_NO_NULLS : pgtype_nullable(stmt, field_type))); + set_tuplefield_string(&row->tuple[11], ""); + set_tuplefield_int4(&row->tuple[13], field_type); + + QR_add_tuple(stmt->result, row); + + + result = PGAPI_Fetch(hcol_stmt); + + } + if (result != SQL_NO_DATA_FOUND) + { + stmt->errormsg = SC_create_errormsg(hcol_stmt); + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + /* + * Put the row version column at the end so it might not be mistaken + * for a key field. + */ + if (relhasrules[0] != '1' && !stmt->internal && atoi(ci->row_versioning)) + { + /* For Row Versioning fields */ + the_type = PG_TYPE_INT4; + + row = (TupleNode *) malloc(sizeof(TupleNode) + + (result_cols - 1) *sizeof(TupleField)); + + set_tuplefield_string(&row->tuple[0], ""); + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], table_name); + set_tuplefield_string(&row->tuple[3], "xmin"); + set_tuplefield_int2(&row->tuple[4], pgtype_to_sqltype(stmt, the_type)); + set_tuplefield_string(&row->tuple[5], pgtype_to_name(stmt, the_type)); + set_tuplefield_int4(&row->tuple[6], pgtype_precision(stmt, the_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[7], pgtype_length(stmt, the_type, PG_STATIC, PG_STATIC)); + set_nullfield_int2(&row->tuple[8], pgtype_scale(stmt, the_type, PG_STATIC)); + set_nullfield_int2(&row->tuple[9], pgtype_radix(stmt, the_type)); + set_tuplefield_int2(&row->tuple[10], SQL_NO_NULLS); + set_tuplefield_string(&row->tuple[11], ""); + set_tuplefield_int4(&row->tuple[12], pgtype_display_size(stmt, the_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[13], the_type); + + QR_add_tuple(stmt->result, row); + } + + /* + * also, things need to think that this statement is finished so the + * results can be retrieved. + */ + stmt->status = STMT_FINISHED; + + /* set up the current tuple pointer for SQLFetch */ + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + mylog("%s: EXIT, stmt=%u\n", func, stmt); + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_SpecialColumns( + HSTMT hstmt, + UWORD fColType, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName, + UWORD fScope, + UWORD fNullable) +{ + static char *func = "PGAPI_SpecialColumns"; + TupleNode *row; + StatementClass *stmt = (StatementClass *) hstmt; + ConnInfo *ci; + HSTMT hcol_stmt; + StatementClass *col_stmt; + char columns_query[INFO_INQUIRY_LEN]; + RETCODE result; + char relhasrules[MAX_INFO_STRING]; + + mylog("%s: entering...stmt=%u\n", func, stmt); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + ci = &(SC_get_conn(stmt)->connInfo); + + stmt->manual_result = TRUE; + + /* + * Create the query to find out if this is a view or not... + */ + sprintf(columns_query, "select c.relhasrules " + "from pg_user u, pg_class c where " + "u.usesysid = c.relowner"); + + my_strcat(columns_query, " and c.relname like '%.*s'", szTableName, cbTableName); + my_strcat(columns_query, " and u.usename like '%.*s'", szTableOwner, cbTableOwner); + + + result = PGAPI_AllocStmt(stmt->hdbc, &hcol_stmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't allocate statement for SQLSpecialColumns result."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + col_stmt = (StatementClass *) hcol_stmt; + + mylog("%s: hcol_stmt = %u, col_stmt = %u\n", func, hcol_stmt, col_stmt); + + result = PGAPI_ExecDirect(hcol_stmt, columns_query, + strlen(columns_query)); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = SC_create_errormsg(hcol_stmt); + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 1, SQL_C_CHAR, + relhasrules, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_Fetch(hcol_stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + + stmt->result = QR_Constructor(); + extend_bindings(stmt, 8); + + QR_set_num_fields(stmt->result, 8); + QR_set_field_info(stmt->result, 0, "SCOPE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 1, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 2, "DATA_TYPE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 3, "TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 4, "PRECISION", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 5, "LENGTH", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 6, "SCALE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 7, "PSEUDO_COLUMN", PG_TYPE_INT2, 2); + + if (relhasrules[0] != '1') + { + /* use the oid value for the rowid */ + if (fColType == SQL_BEST_ROWID) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + (8 - 1) *sizeof(TupleField)); + + set_tuplefield_int2(&row->tuple[0], SQL_SCOPE_SESSION); + set_tuplefield_string(&row->tuple[1], "oid"); + set_tuplefield_int2(&row->tuple[2], pgtype_to_sqltype(stmt, PG_TYPE_OID)); + set_tuplefield_string(&row->tuple[3], "OID"); + set_tuplefield_int4(&row->tuple[4], pgtype_precision(stmt, PG_TYPE_OID, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[5], pgtype_length(stmt, PG_TYPE_OID, PG_STATIC, PG_STATIC)); + set_tuplefield_int2(&row->tuple[6], pgtype_scale(stmt, PG_TYPE_OID, PG_STATIC)); + set_tuplefield_int2(&row->tuple[7], SQL_PC_PSEUDO); + + QR_add_tuple(stmt->result, row); + + } + else if (fColType == SQL_ROWVER) + { + Int2 the_type = PG_TYPE_INT4; + + if (atoi(ci->row_versioning)) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + (8 - 1) *sizeof(TupleField)); + + set_tuplefield_null(&row->tuple[0]); + set_tuplefield_string(&row->tuple[1], "xmin"); + set_tuplefield_int2(&row->tuple[2], pgtype_to_sqltype(stmt, the_type)); + set_tuplefield_string(&row->tuple[3], pgtype_to_name(stmt, the_type)); + set_tuplefield_int4(&row->tuple[4], pgtype_precision(stmt, the_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[5], pgtype_length(stmt, the_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int2(&row->tuple[6], pgtype_scale(stmt, the_type, PG_STATIC)); + set_tuplefield_int2(&row->tuple[7], SQL_PC_PSEUDO); + + QR_add_tuple(stmt->result, row); + } + } + } + + stmt->status = STMT_FINISHED; + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + mylog("%s: EXIT, stmt=%u\n", func, stmt); + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_Statistics( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName, + UWORD fUnique, + UWORD fAccuracy) +{ + static char *func = "PGAPI_Statistics"; + StatementClass *stmt = (StatementClass *) hstmt; + char index_query[INFO_INQUIRY_LEN]; + HSTMT hindx_stmt; + RETCODE result; + char *table_name; + char index_name[MAX_INFO_STRING]; + short fields_vector[16]; + char isunique[10], + isclustered[10], + ishash[MAX_INFO_STRING]; + SDWORD index_name_len, + fields_vector_len; + TupleNode *row; + int i; + HSTMT hcol_stmt; + StatementClass *col_stmt, + *indx_stmt; + char column_name[MAX_INFO_STRING], + relhasrules[MAX_INFO_STRING]; + char **column_names = 0; + SQLINTEGER column_name_len; + int total_columns = 0; + char error = TRUE; + ConnInfo *ci; + char buf[256]; + + mylog("%s: entering...stmt=%u\n", func, stmt); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + stmt->manual_result = TRUE; + stmt->errormsg_created = TRUE; + + ci = &(SC_get_conn(stmt)->connInfo); + + stmt->result = QR_Constructor(); + if (!stmt->result) + { + stmt->errormsg = "Couldn't allocate memory for PGAPI_Statistics result."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* the binding structure for a statement is not set up until */ + + /* + * a statement is actually executed, so we'll have to do this + * ourselves. + */ + extend_bindings(stmt, 13); + + /* set the field names */ + QR_set_num_fields(stmt->result, 13); + QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 3, "NON_UNIQUE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 4, "INDEX_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 5, "INDEX_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 6, "TYPE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 7, "SEQ_IN_INDEX", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 8, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 9, "COLLATION", PG_TYPE_CHAR, 1); + QR_set_field_info(stmt->result, 10, "CARDINALITY", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 11, "PAGES", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 12, "FILTER_CONDITION", PG_TYPE_TEXT, MAX_INFO_STRING); + + /* + * only use the table name... the owner should be redundant, and we + * never use qualifiers. + */ + table_name = make_string(szTableName, cbTableName, NULL); + if (!table_name) + { + stmt->errormsg = "No table name passed to PGAPI_Statistics."; + stmt->errornumber = STMT_INTERNAL_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* + * we need to get a list of the field names first, so we can return + * them later. + */ + result = PGAPI_AllocStmt(stmt->hdbc, &hcol_stmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = "PGAPI_AllocStmt failed in PGAPI_Statistics for columns."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + goto SEEYA; + } + + col_stmt = (StatementClass *) hcol_stmt; + + /* + * "internal" prevents SQLColumns from returning the oid if it is + * being shown. This would throw everything off. + */ + col_stmt->internal = TRUE; + result = PGAPI_Columns(hcol_stmt, "", 0, "", 0, + table_name, (SWORD) strlen(table_name), "", 0); + col_stmt->internal = FALSE; + + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; /* "SQLColumns failed in + * SQLStatistics."; */ + stmt->errornumber = col_stmt->errornumber; /* STMT_EXEC_ERROR; */ + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + goto SEEYA; + } + result = PGAPI_BindCol(hcol_stmt, 4, SQL_C_CHAR, + column_name, MAX_INFO_STRING, &column_name_len); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + goto SEEYA; + + } + + result = PGAPI_Fetch(hcol_stmt); + while ((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) + { + total_columns++; + + column_names = + (char **) realloc(column_names, + total_columns * sizeof(char *)); + column_names[total_columns - 1] = + (char *) malloc(strlen(column_name) + 1); + strcpy(column_names[total_columns - 1], column_name); + + mylog("%s: column_name = '%s'\n", func, column_name); + + result = PGAPI_Fetch(hcol_stmt); + } + + if (result != SQL_NO_DATA_FOUND || total_columns == 0) + { + stmt->errormsg = SC_create_errormsg(hcol_stmt); /* "Couldn't get column + * names in + * SQLStatistics."; */ + stmt->errornumber = col_stmt->errornumber; + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + goto SEEYA; + + } + + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + + /* get a list of indexes on this table */ + result = PGAPI_AllocStmt(stmt->hdbc, &hindx_stmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = "PGAPI_AllocStmt failed in SQLStatistics for indices."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + goto SEEYA; + + } + indx_stmt = (StatementClass *) hindx_stmt; + + sprintf(index_query, "select c.relname, i.indkey, i.indisunique" + ", i.indisclustered, a.amname, c.relhasrules" + " from pg_index i, pg_class c, pg_class d, pg_am a" + " where d.relname = '%s'" + " and d.oid = i.indrelid" + " and i.indexrelid = c.oid" + " and c.relam = a.oid" + ,table_name); + if (PG_VERSION_GT(SC_get_conn(stmt), 6.4)) + strcat(index_query, " order by i.indisprimary desc"); + + result = PGAPI_ExecDirect(hindx_stmt, index_query, strlen(index_query)); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + /* + * "Couldn't execute index query (w/SQLExecDirect) in + * SQLStatistics."; + */ + stmt->errormsg = SC_create_errormsg(hindx_stmt); + + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + } + + /* bind the index name column */ + result = PGAPI_BindCol(hindx_stmt, 1, SQL_C_CHAR, + index_name, MAX_INFO_STRING, &index_name_len); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = indx_stmt->errormsg; /* "Couldn't bind column + * in SQLStatistics."; */ + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + + } + /* bind the vector column */ + result = PGAPI_BindCol(hindx_stmt, 2, SQL_C_DEFAULT, + fields_vector, 32, &fields_vector_len); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = indx_stmt->errormsg; /* "Couldn't bind column + * in SQLStatistics."; */ + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + + } + /* bind the "is unique" column */ + result = PGAPI_BindCol(hindx_stmt, 3, SQL_C_CHAR, + isunique, sizeof(isunique), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = indx_stmt->errormsg; /* "Couldn't bind column + * in SQLStatistics."; */ + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + } + + /* bind the "is clustered" column */ + result = PGAPI_BindCol(hindx_stmt, 4, SQL_C_CHAR, + isclustered, sizeof(isclustered), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = indx_stmt->errormsg; /* "Couldn't bind column + * in SQLStatistics."; */ + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + + } + + /* bind the "is hash" column */ + result = PGAPI_BindCol(hindx_stmt, 5, SQL_C_CHAR, + ishash, sizeof(ishash), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = indx_stmt->errormsg; /* "Couldn't bind column + * in SQLStatistics."; */ + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + + } + + result = PGAPI_BindCol(hindx_stmt, 6, SQL_C_CHAR, + relhasrules, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = indx_stmt->errormsg; + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + } + + /* fake index of OID */ + if (relhasrules[0] != '1' && atoi(ci->show_oid_column) && atoi(ci->fake_oid_index)) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + + (13 - 1) *sizeof(TupleField)); + + /* no table qualifier */ + set_tuplefield_string(&row->tuple[0], ""); + /* don't set the table owner, else Access tries to use it */ + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], table_name); + + /* non-unique index? */ + set_tuplefield_int2(&row->tuple[3], (Int2) (ci->drivers.unique_index ? FALSE : TRUE)); + + /* no index qualifier */ + set_tuplefield_string(&row->tuple[4], ""); + + sprintf(buf, "%s_idx_fake_oid", table_name); + set_tuplefield_string(&row->tuple[5], buf); + + /* + * Clustered/HASH index? + */ + set_tuplefield_int2(&row->tuple[6], (Int2) SQL_INDEX_OTHER); + set_tuplefield_int2(&row->tuple[7], (Int2) 1); + + set_tuplefield_string(&row->tuple[8], "oid"); + set_tuplefield_string(&row->tuple[9], "A"); + set_tuplefield_null(&row->tuple[10]); + set_tuplefield_null(&row->tuple[11]); + set_tuplefield_null(&row->tuple[12]); + + QR_add_tuple(stmt->result, row); + } + + result = PGAPI_Fetch(hindx_stmt); + while ((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) + { + /* If only requesting unique indexs, then just return those. */ + if (fUnique == SQL_INDEX_ALL || + (fUnique == SQL_INDEX_UNIQUE && atoi(isunique))) + { + i = 0; + /* add a row in this table for each field in the index */ + while (i < 16 && fields_vector[i] != 0) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + + (13 - 1) *sizeof(TupleField)); + + /* no table qualifier */ + set_tuplefield_string(&row->tuple[0], ""); + /* don't set the table owner, else Access tries to use it */ + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], table_name); + + /* non-unique index? */ + if (ci->drivers.unique_index) + set_tuplefield_int2(&row->tuple[3], (Int2) (atoi(isunique) ? FALSE : TRUE)); + else + set_tuplefield_int2(&row->tuple[3], TRUE); + + /* no index qualifier */ + set_tuplefield_string(&row->tuple[4], ""); + set_tuplefield_string(&row->tuple[5], index_name); + + /* + * Clustered/HASH index? + */ + set_tuplefield_int2(&row->tuple[6], (Int2) + (atoi(isclustered) ? SQL_INDEX_CLUSTERED : + (!strncmp(ishash, "hash", 4)) ? SQL_INDEX_HASHED : SQL_INDEX_OTHER)); + set_tuplefield_int2(&row->tuple[7], (Int2) (i + 1)); + + if (fields_vector[i] == OID_ATTNUM) + { + set_tuplefield_string(&row->tuple[8], "oid"); + mylog("%s: column name = oid\n", func); + } + else if (fields_vector[i] < 0 || fields_vector[i] > total_columns) + { + set_tuplefield_string(&row->tuple[8], "UNKNOWN"); + mylog("%s: column name = UNKNOWN\n", func); + } + else + { + set_tuplefield_string(&row->tuple[8], column_names[fields_vector[i] - 1]); + mylog("%s: column name = '%s'\n", func, column_names[fields_vector[i] - 1]); + } + + set_tuplefield_string(&row->tuple[9], "A"); + set_tuplefield_null(&row->tuple[10]); + set_tuplefield_null(&row->tuple[11]); + set_tuplefield_null(&row->tuple[12]); + + QR_add_tuple(stmt->result, row); + i++; + } + } + + result = PGAPI_Fetch(hindx_stmt); + } + if (result != SQL_NO_DATA_FOUND) + { + /* "SQLFetch failed in SQLStatistics."; */ + stmt->errormsg = SC_create_errormsg(hindx_stmt); + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + } + + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + + /* + * also, things need to think that this statement is finished so the + * results can be retrieved. + */ + stmt->status = STMT_FINISHED; + + /* set up the current tuple pointer for SQLFetch */ + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + error = FALSE; + +SEEYA: + /* These things should be freed on any error ALSO! */ + free(table_name); + for (i = 0; i < total_columns; i++) + free(column_names[i]); + free(column_names); + + mylog("%s: EXIT, %s, stmt=%u\n", func, error ? "error" : "success", stmt); + + if (error) + { + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + else + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_ColumnPrivileges( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName, + UCHAR FAR * szColumnName, + SWORD cbColumnName) +{ + static char *func = "PGAPI_ColumnPrivileges"; + + mylog("%s: entering...\n", func); + + /* Neither Access or Borland care about this. */ + + SC_log_error(func, "Function not implemented", (StatementClass *) hstmt); + return SQL_ERROR; +} + + +/* + * SQLPrimaryKeys() + * + * Retrieve the primary key columns for the specified table. + */ +RETCODE SQL_API +PGAPI_PrimaryKeys( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName) +{ + static char *func = "PGAPI_PrimaryKeys"; + StatementClass *stmt = (StatementClass *) hstmt; + ConnectionClass *conn; + TupleNode *row; + RETCODE result; + int seq = 0; + HSTMT htbl_stmt; + StatementClass *tbl_stmt; + char tables_query[INFO_INQUIRY_LEN]; + char attname[MAX_INFO_STRING]; + SDWORD attname_len; + char pktab[MAX_TABLE_LEN + 1]; + Int2 result_cols; + int qno, + qstart, + qend; + + mylog("%s: entering...stmt=%u\n", func, stmt); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + stmt->manual_result = TRUE; + stmt->errormsg_created = TRUE; + + stmt->result = QR_Constructor(); + if (!stmt->result) + { + stmt->errormsg = "Couldn't allocate memory for PGAPI_PrimaryKeys result."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* the binding structure for a statement is not set up until */ + + /* + * a statement is actually executed, so we'll have to do this + * ourselves. + */ + result_cols = 6; + extend_bindings(stmt, result_cols); + + /* set the field names */ + QR_set_num_fields(stmt->result, result_cols); + QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 3, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 4, "KEY_SEQ", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 5, "PK_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + + + result = PGAPI_AllocStmt(stmt->hdbc, &htbl_stmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't allocate statement for Primary Key result."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + tbl_stmt = (StatementClass *) htbl_stmt; + + pktab[0] = '\0'; + make_string(szTableName, cbTableName, pktab); + if (pktab[0] == '\0') + { + stmt->errormsg = "No Table specified to PGAPI_PrimaryKeys."; + stmt->errornumber = STMT_INTERNAL_ERROR; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 1, SQL_C_CHAR, + attname, MAX_INFO_STRING, &attname_len); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + conn = SC_get_conn(stmt); + if (PG_VERSION_LE(conn, 6.4)) + qstart = 2; + else + qstart = 1; + qend = 2; + for (qno = qstart; qno <= qend; qno++) + { + switch (qno) + { + case 1: + + /* + * Simplified query to remove assumptions about number of + * possible index columns. Courtesy of Tom Lane - thomas + * 2000-03-21 + */ + sprintf(tables_query, "select ta.attname, ia.attnum" + " from pg_attribute ta, pg_attribute ia, pg_class c, pg_index i" + " where c.relname = '%s'" + " AND c.oid = i.indrelid" + " AND i.indisprimary = 't'" + " AND ia.attrelid = i.indexrelid" + " AND ta.attrelid = i.indrelid" + " AND ta.attnum = i.indkey[ia.attnum-1]" + " order by ia.attnum", pktab); + break; + case 2: + + /* + * Simplified query to search old fashoned primary key + */ + sprintf(tables_query, "select ta.attname, ia.attnum" + " from pg_attribute ta, pg_attribute ia, pg_class c, pg_index i" + " where c.relname = '%s_pkey'" + " AND c.oid = i.indexrelid" + " AND ia.attrelid = i.indexrelid" + " AND ta.attrelid = i.indrelid" + " AND ta.attnum = i.indkey[ia.attnum-1]" + " order by ia.attnum", pktab); + break; + } + mylog("%s: tables_query='%s'\n", func, tables_query); + + result = PGAPI_ExecDirect(htbl_stmt, tables_query, strlen(tables_query)); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_Fetch(htbl_stmt); + if (result != SQL_NO_DATA_FOUND) + break; + } + + while ((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + (result_cols - 1) *sizeof(TupleField)); + + set_tuplefield_null(&row->tuple[0]); + + /* + * I have to hide the table owner from Access, otherwise it + * insists on referring to the table as 'owner.table'. (this is + * valid according to the ODBC SQL grammar, but Postgres won't + * support it.) + */ + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], pktab); + set_tuplefield_string(&row->tuple[3], attname); + set_tuplefield_int2(&row->tuple[4], (Int2) (++seq)); + set_tuplefield_null(&row->tuple[5]); + + QR_add_tuple(stmt->result, row); + + mylog(">> primaryKeys: pktab = '%s', attname = '%s', seq = %d\n", pktab, attname, seq); + + result = PGAPI_Fetch(htbl_stmt); + } + + if (result != SQL_NO_DATA_FOUND) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + + + /* + * also, things need to think that this statement is finished so the + * results can be retrieved. + */ + stmt->status = STMT_FINISHED; + + /* set up the current tuple pointer for SQLFetch */ + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + mylog("%s: EXIT, stmt=%u\n", func, stmt); + return SQL_SUCCESS; +} + + +#ifdef MULTIBYTE +/* + * Multibyte support stuff for SQLForeignKeys(). + * There may be much more effective way in the + * future version. The way is very forcible currently. + */ +static BOOL +isMultibyte(const unsigned char *str) +{ + for (; *str; str++) + { + if (*str >= 0x80) + return TRUE; + } + return FALSE; +} +static char * +getClientTableName(ConnectionClass *conn, char *serverTableName, BOOL *nameAlloced) +{ + char query[1024], + saveoid[24], + *ret = serverTableName; + BOOL continueExec = TRUE, + bError = FALSE; + QResultClass *res; + + *nameAlloced = FALSE; + if (!conn->client_encoding || !isMultibyte(serverTableName)) + return ret; + if (!conn->server_encoding) + { + if (res = CC_send_query(conn, "select getdatabaseencoding()", NULL), res) + { + if (QR_get_num_tuples(res) > 0) + conn->server_encoding = strdup(QR_get_value_backend_row(res, 0, 0)); + QR_Destructor(res); + } + } + if (!conn->server_encoding) + return ret; + sprintf(query, "SET CLIENT_ENCODING TO '%s'", conn->server_encoding); + if (res = CC_send_query(conn, query, NULL), res) + { + bError = QR_get_aborted(res); + QR_Destructor(res); + } + else + bError = TRUE; + if (!bError && continueExec) + { + sprintf(query, "select OID from pg_class where relname = '%s'", serverTableName); + if (res = CC_send_query(conn, query, NULL), res) + { + if (QR_get_num_tuples(res) > 0) + strcpy(saveoid, QR_get_value_backend_row(res, 0, 0)); + else + { + continueExec = FALSE; + bError = QR_get_aborted(res); + } + QR_Destructor(res); + } + else + bError = TRUE; + } + continueExec = (continueExec && !bError); + if (bError && CC_is_in_trans(conn)) + { + if (res = CC_send_query(conn, "abort", NULL), res) + QR_Destructor(res); + CC_set_no_trans(conn); + bError = FALSE; + } + /* restore the client encoding */ + sprintf(query, "SET CLIENT_ENCODING TO '%s'", conn->client_encoding); + if (res = CC_send_query(conn, query, NULL), res) + { + bError = QR_get_aborted(res); + QR_Destructor(res); + } + else + bError = TRUE; + if (bError || !continueExec) + return ret; + sprintf(query, "select relname from pg_class where OID = %s", saveoid); + if (res = CC_send_query(conn, query, NULL), res) + { + if (QR_get_num_tuples(res) > 0) + { + ret = strdup(QR_get_value_backend_row(res, 0, 0)); + *nameAlloced = TRUE; + } + QR_Destructor(res); + } + return ret; +} +static char * +getClientColumnName(ConnectionClass *conn, const char *serverTableName, char *serverColumnName, BOOL *nameAlloced) +{ + char query[1024], + saveattrelid[24], + saveattnum[16], + *ret = serverColumnName; + BOOL continueExec = TRUE, + bError = FALSE; + QResultClass *res; + + *nameAlloced = FALSE; + if (!conn->client_encoding || !isMultibyte(serverColumnName)) + return ret; + if (!conn->server_encoding) + { + if (res = CC_send_query(conn, "select getdatabaseencoding()", NULL), res) + { + if (QR_get_num_tuples(res) > 0) + conn->server_encoding = strdup(QR_get_value_backend_row(res, 0, 0)); + QR_Destructor(res); + } + } + if (!conn->server_encoding) + return ret; + sprintf(query, "SET CLIENT_ENCODING TO '%s'", conn->server_encoding); + if (res = CC_send_query(conn, query, NULL), res) + { + bError = QR_get_aborted(res); + QR_Destructor(res); + } + else + bError = TRUE; + if (!bError && continueExec) + { + sprintf(query, "select attrelid, attnum from pg_class, pg_attribute " + "where relname = '%s' and attrelid = pg_class.oid " + "and attname = '%s'", serverTableName, serverColumnName); + if (res = CC_send_query(conn, query, NULL), res) + { + if (QR_get_num_tuples(res) > 0) + { + strcpy(saveattrelid, QR_get_value_backend_row(res, 0, 0)); + strcpy(saveattnum, QR_get_value_backend_row(res, 0, 1)); + } + else + { + continueExec = FALSE; + bError = QR_get_aborted(res); + } + QR_Destructor(res); + } + else + bError = TRUE; + } + continueExec = (continueExec && !bError); + if (bError && CC_is_in_trans(conn)) + { + if (res = CC_send_query(conn, "abort", NULL), res) + QR_Destructor(res); + CC_set_no_trans(conn); + bError = FALSE; + } + /* restore the cleint encoding */ + sprintf(query, "SET CLIENT_ENCODING TO '%s'", conn->client_encoding); + if (res = CC_send_query(conn, query, NULL), res) + { + bError = QR_get_aborted(res); + QR_Destructor(res); + } + else + bError = TRUE; + if (bError || !continueExec) + return ret; + sprintf(query, "select attname from pg_attribute where attrelid = %s and attnum = %s", saveattrelid, saveattnum); + if (res = CC_send_query(conn, query, NULL), res) + { + if (QR_get_num_tuples(res) > 0) + { + ret = strdup(QR_get_value_backend_row(res, 0, 0)); + *nameAlloced = TRUE; + } + QR_Destructor(res); + } + return ret; +} +#endif /* MULTIBYTE */ + +RETCODE SQL_API +PGAPI_ForeignKeys( + HSTMT hstmt, + UCHAR FAR * szPkTableQualifier, + SWORD cbPkTableQualifier, + UCHAR FAR * szPkTableOwner, + SWORD cbPkTableOwner, + UCHAR FAR * szPkTableName, + SWORD cbPkTableName, + UCHAR FAR * szFkTableQualifier, + SWORD cbFkTableQualifier, + UCHAR FAR * szFkTableOwner, + SWORD cbFkTableOwner, + UCHAR FAR * szFkTableName, + SWORD cbFkTableName) +{ + static char *func = "PGAPI_ForeignKeys"; + StatementClass *stmt = (StatementClass *) hstmt; + TupleNode *row; + HSTMT htbl_stmt, + hpkey_stmt; + StatementClass *tbl_stmt; + RETCODE result, + keyresult; + char tables_query[INFO_INQUIRY_LEN]; + char trig_deferrable[2]; + char trig_initdeferred[2]; + char trig_args[1024]; + char upd_rule[MAX_TABLE_LEN], + del_rule[MAX_TABLE_LEN]; + char pk_table_needed[MAX_TABLE_LEN + 1]; + char fk_table_needed[MAX_TABLE_LEN + 1]; + char *pkey_ptr, + *pkey_text, + *fkey_ptr, + *fkey_text, + *pk_table, + *pkt_text, + *fk_table, + *fkt_text; + +#ifdef MULTIBYTE + BOOL pkey_alloced, + fkey_alloced, + pkt_alloced, + fkt_alloced; + ConnectionClass *conn; +#endif /* MULTIBYTE */ + int i, + j, + k, + num_keys; + SWORD trig_nargs, + upd_rule_type = 0, + del_rule_type = 0; + +#if (ODBCVER >= 0x0300) + SWORD defer_type; +#endif + char pkey[MAX_INFO_STRING]; + Int2 result_cols; + + mylog("%s: entering...stmt=%u\n", func, stmt); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + stmt->manual_result = TRUE; + stmt->errormsg_created = TRUE; + + stmt->result = QR_Constructor(); + if (!stmt->result) + { + stmt->errormsg = "Couldn't allocate memory for PGAPI_ForeignKeys result."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* the binding structure for a statement is not set up until */ + + /* + * a statement is actually executed, so we'll have to do this + * ourselves. + */ + result_cols = 14; + extend_bindings(stmt, result_cols); + + /* set the field names */ + QR_set_num_fields(stmt->result, result_cols); + QR_set_field_info(stmt->result, 0, "PKTABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 1, "PKTABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 2, "PKTABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 3, "PKCOLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 4, "FKTABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 5, "FKTABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 6, "FKTABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 7, "FKCOLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 8, "KEY_SEQ", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 9, "UPDATE_RULE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 10, "DELETE_RULE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 11, "FK_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 12, "PK_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 13, "TRIGGER_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); +#if (ODBCVER >= 0x0300) + QR_set_field_info(stmt->result, 14, "DEFERRABILITY", PG_TYPE_INT2, 2); +#endif /* ODBCVER >= 0x0300 */ + + /* + * also, things need to think that this statement is finished so the + * results can be retrieved. + */ + stmt->status = STMT_FINISHED; + + /* set up the current tuple pointer for SQLFetch */ + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + + result = PGAPI_AllocStmt(stmt->hdbc, &htbl_stmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't allocate statement for PGAPI_ForeignKeys result."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + tbl_stmt = (StatementClass *) htbl_stmt; + + pk_table_needed[0] = '\0'; + fk_table_needed[0] = '\0'; + + make_string(szPkTableName, cbPkTableName, pk_table_needed); + make_string(szFkTableName, cbFkTableName, fk_table_needed); + +#ifdef MULTIBYTE + pkey_text = fkey_text = pkt_text = fkt_text = NULL; + pkey_alloced = fkey_alloced = pkt_alloced = fkt_alloced = FALSE; + conn = SC_get_conn(stmt); +#endif /* MULTIBYTE */ + + /* + * Case #2 -- Get the foreign keys in the specified table (fktab) that + * refer to the primary keys of other table(s). + */ + if (fk_table_needed[0] != '\0') + { + mylog("%s: entering Foreign Key Case #2", func); + sprintf(tables_query, "SELECT pt.tgargs, " + " pt.tgnargs, " + " pt.tgdeferrable, " + " pt.tginitdeferred, " + " pg_proc.proname, " + " pg_proc_1.proname " + "FROM pg_class pc, " + " pg_proc pg_proc, " + " pg_proc pg_proc_1, " + " pg_trigger pg_trigger, " + " pg_trigger pg_trigger_1, " + " pg_proc pp, " + " pg_trigger pt " + "WHERE pt.tgrelid = pc.oid " + "AND pp.oid = pt.tgfoid " + "AND pg_trigger.tgconstrrelid = pc.oid " + "AND pg_proc.oid = pg_trigger.tgfoid " + "AND pg_trigger_1.tgfoid = pg_proc_1.oid " + "AND pg_trigger_1.tgconstrrelid = pc.oid " + "AND ((pc.relname='%s') " + "AND (pp.proname LIKE '%%ins') " + "AND (pg_proc.proname LIKE '%%upd') " + "AND (pg_proc_1.proname LIKE '%%del') " + "AND (pg_trigger.tgrelid=pt.tgconstrrelid) " + "AND (pg_trigger.tgconstrname=pt.tgconstrname) " + "AND (pg_trigger_1.tgrelid=pt.tgconstrrelid) " + "AND (pg_trigger_1.tgconstrname=pt.tgconstrname))", + fk_table_needed); + + result = PGAPI_ExecDirect(htbl_stmt, tables_query, strlen(tables_query)); + + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 1, SQL_C_BINARY, + trig_args, sizeof(trig_args), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 2, SQL_C_SHORT, + &trig_nargs, 0, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 3, SQL_C_CHAR, + trig_deferrable, sizeof(trig_deferrable), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 4, SQL_C_CHAR, + trig_initdeferred, sizeof(trig_initdeferred), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 5, SQL_C_CHAR, + upd_rule, sizeof(upd_rule), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 6, SQL_C_CHAR, + del_rule, sizeof(del_rule), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_Fetch(htbl_stmt); + if (result == SQL_NO_DATA_FOUND) + return SQL_SUCCESS; + + if (result != SQL_SUCCESS) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + keyresult = PGAPI_AllocStmt(stmt->hdbc, &hpkey_stmt); + if ((keyresult != SQL_SUCCESS) && (keyresult != SQL_SUCCESS_WITH_INFO)) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't allocate statement for PGAPI_ForeignKeys (pkeys) result."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + keyresult = PGAPI_BindCol(hpkey_stmt, 4, SQL_C_CHAR, + pkey, sizeof(pkey), NULL); + if (keyresult != SQL_SUCCESS) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't bindcol for primary keys for PGAPI_ForeignKeys result."; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hpkey_stmt, SQL_DROP); + return SQL_ERROR; + } + + while (result == SQL_SUCCESS) + { + /* Compute the number of keyparts. */ + num_keys = (trig_nargs - 4) / 2; + + mylog("Foreign Key Case#2: trig_nargs = %d, num_keys = %d\n", trig_nargs, num_keys); + + pk_table = trig_args; + + /* Get to the PK Table Name */ + for (k = 0; k < 2; k++) + pk_table += strlen(pk_table) + 1; + +#ifdef MULTIBYTE + fk_table = trig_args + strlen(trig_args) + 1; + pkt_text = getClientTableName(conn, pk_table, &pkt_alloced); +#else + pkt_text = pk_table; +#endif /* MULTIBYTE */ + /* If there is a pk table specified, then check it. */ + if (pk_table_needed[0] != '\0') + { + /* If it doesn't match, then continue */ + if (strcmp(pkt_text, pk_table_needed)) + { + result = PGAPI_Fetch(htbl_stmt); + continue; + } + } + + keyresult = PGAPI_PrimaryKeys(hpkey_stmt, NULL, 0, NULL, 0, pkt_text, SQL_NTS); + if (keyresult != SQL_SUCCESS) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't get primary keys for PGAPI_ForeignKeys result."; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hpkey_stmt, SQL_DROP); + return SQL_ERROR; + } + + + /* Get to first primary key */ + pkey_ptr = trig_args; + for (i = 0; i < 5; i++) + pkey_ptr += strlen(pkey_ptr) + 1; + + for (k = 0; k < num_keys; k++) + { + /* Check that the key listed is the primary key */ + keyresult = PGAPI_Fetch(hpkey_stmt); + if (keyresult != SQL_SUCCESS) + { + num_keys = 0; + break; + } +#ifdef MULTIBYTE + pkey_text = getClientColumnName(conn, pk_table, pkey_ptr, &pkey_alloced); +#else + pkey_text = pkey_ptr; +#endif /* MULTIBYTE */ + mylog("%s: pkey_ptr='%s', pkey='%s'\n", func, pkey_text, pkey); + if (strcmp(pkey_text, pkey)) + { + num_keys = 0; + break; + } +#ifdef MULTIBYTE + if (pkey_alloced) + free(pkey_text); +#endif /* MULTIBYTE */ + /* Get to next primary key */ + for (k = 0; k < 2; k++) + pkey_ptr += strlen(pkey_ptr) + 1; + + } + + /* Set to first fk column */ + fkey_ptr = trig_args; + for (k = 0; k < 4; k++) + fkey_ptr += strlen(fkey_ptr) + 1; + + /* Set update and delete actions for foreign keys */ + if (!strcmp(upd_rule, "RI_FKey_cascade_upd")) + upd_rule_type = SQL_CASCADE; + else if (!strcmp(upd_rule, "RI_FKey_noaction_upd")) + upd_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_restrict_upd")) + upd_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_setdefault_upd")) + upd_rule_type = SQL_SET_DEFAULT; + else if (!strcmp(upd_rule, "RI_FKey_setnull_upd")) + upd_rule_type = SQL_SET_NULL; + + if (!strcmp(upd_rule, "RI_FKey_cascade_del")) + del_rule_type = SQL_CASCADE; + else if (!strcmp(upd_rule, "RI_FKey_noaction_del")) + del_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_restrict_del")) + del_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_setdefault_del")) + del_rule_type = SQL_SET_DEFAULT; + else if (!strcmp(upd_rule, "RI_FKey_setnull_del")) + del_rule_type = SQL_SET_NULL; + +#if (ODBCVER >= 0x0300) + /* Set deferrability type */ + if (!strcmp(trig_initdeferred, "y")) + defer_type = SQL_INITIALLY_DEFERRED; + else if (!strcmp(trig_deferrable, "y")) + defer_type = SQL_INITIALLY_IMMEDIATE; + else + defer_type = SQL_NOT_DEFERRABLE; +#endif /* ODBCVER >= 0x0300 */ + + /* Get to first primary key */ + pkey_ptr = trig_args; + for (i = 0; i < 5; i++) + pkey_ptr += strlen(pkey_ptr) + 1; + + for (k = 0; k < num_keys; k++) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + (result_cols - 1) *sizeof(TupleField)); + +#ifdef MULTIBYTE + pkey_text = getClientColumnName(conn, pk_table, pkey_ptr, &pkey_alloced); + fkey_text = getClientColumnName(conn, fk_table, fkey_ptr, &fkey_alloced); +#else + pkey_text = pkey_ptr; + fkey_text = fkey_ptr; +#endif /* MULTIBYTE */ + mylog("%s: pk_table = '%s', pkey_ptr = '%s'\n", func, pkt_text, pkey_text); + set_tuplefield_null(&row->tuple[0]); + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], pkt_text); + set_tuplefield_string(&row->tuple[3], pkey_text); + + mylog("%s: fk_table_needed = '%s', fkey_ptr = '%s'\n", func, fk_table_needed, fkey_text); + set_tuplefield_null(&row->tuple[4]); + set_tuplefield_string(&row->tuple[5], ""); + set_tuplefield_string(&row->tuple[6], fk_table_needed); + set_tuplefield_string(&row->tuple[7], fkey_text); + + mylog("%s: upd_rule_type = '%i', del_rule_type = '%i'\n, trig_name = '%s'", func, upd_rule_type, del_rule_type, trig_args); + set_tuplefield_int2(&row->tuple[8], (Int2) (k + 1)); + set_tuplefield_int2(&row->tuple[9], (Int2) upd_rule_type); + set_tuplefield_int2(&row->tuple[10], (Int2) del_rule_type); + set_tuplefield_null(&row->tuple[11]); + set_tuplefield_null(&row->tuple[12]); + set_tuplefield_string(&row->tuple[13], trig_args); +#if (ODBCVER >= 0x0300) + set_tuplefield_int2(&row->tuple[14], defer_type); +#endif /* ODBCVER >= 0x0300 */ + + QR_add_tuple(stmt->result, row); +#ifdef MULTIBYTE + if (fkey_alloced) + free(fkey_text); + fkey_alloced = FALSE; + if (pkey_alloced) + free(pkey_text); + pkey_alloced = FALSE; +#endif /* MULTIBYTE */ + /* next primary/foreign key */ + for (i = 0; i < 2; i++) + { + fkey_ptr += strlen(fkey_ptr) + 1; + pkey_ptr += strlen(pkey_ptr) + 1; + } + } +#ifdef MULTIBYTE + if (pkt_alloced) + free(pkt_text); + pkt_alloced = FALSE; +#endif /* MULTIBYTE */ + + result = PGAPI_Fetch(htbl_stmt); + } + PGAPI_FreeStmt(hpkey_stmt, SQL_DROP); + } + + /* + * Case #1 -- Get the foreign keys in other tables that refer to the + * primary key in the specified table (pktab). i.e., Who points to + * me? + */ + else if (pk_table_needed[0] != '\0') + { + sprintf(tables_query, "SELECT pg_trigger.tgargs, " + " pg_trigger.tgnargs, " + " pg_trigger.tgdeferrable, " + " pg_trigger.tginitdeferred, " + " pg_proc.proname, " + " pg_proc_1.proname " + "FROM pg_class pg_class, " + " pg_class pg_class_1, " + " pg_class pg_class_2, " + " pg_proc pg_proc, " + " pg_proc pg_proc_1, " + " pg_trigger pg_trigger, " + " pg_trigger pg_trigger_1, " + " pg_trigger pg_trigger_2 " + "WHERE pg_trigger.tgconstrrelid = pg_class.oid " + " AND pg_trigger.tgrelid = pg_class_1.oid " + " AND pg_trigger_1.tgfoid = pg_proc_1.oid " + " AND pg_trigger_1.tgconstrrelid = pg_class_1.oid " + " AND pg_trigger_2.tgconstrrelid = pg_class_2.oid " + " AND pg_trigger_2.tgfoid = pg_proc.oid " + " AND pg_class_2.oid = pg_trigger.tgrelid " + " AND (" + " (pg_class.relname='%s') " + " AND (pg_proc.proname Like '%%upd') " + " AND (pg_proc_1.proname Like '%%del')" + " AND (pg_trigger_1.tgrelid = pg_trigger.tgconstrrelid) " + " AND (pg_trigger_2.tgrelid = pg_trigger.tgconstrrelid) " + " )", + pk_table_needed); + + result = PGAPI_ExecDirect(htbl_stmt, tables_query, strlen(tables_query)); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 1, SQL_C_BINARY, + trig_args, sizeof(trig_args), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 2, SQL_C_SHORT, + &trig_nargs, 0, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 3, SQL_C_CHAR, + trig_deferrable, sizeof(trig_deferrable), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 4, SQL_C_CHAR, + trig_initdeferred, sizeof(trig_initdeferred), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 5, SQL_C_CHAR, + upd_rule, sizeof(upd_rule), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 6, SQL_C_CHAR, + del_rule, sizeof(del_rule), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_Fetch(htbl_stmt); + if (result == SQL_NO_DATA_FOUND) + return SQL_SUCCESS; + + if (result != SQL_SUCCESS) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + while (result == SQL_SUCCESS) + { + /* Calculate the number of key parts */ + num_keys = (trig_nargs - 4) / 2;; + + /* Handle action (i.e., 'cascade', 'restrict', 'setnull') */ + if (!strcmp(upd_rule, "RI_FKey_cascade_upd")) + upd_rule_type = SQL_CASCADE; + else if (!strcmp(upd_rule, "RI_FKey_noaction_upd")) + upd_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_restrict_upd")) + upd_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_setdefault_upd")) + upd_rule_type = SQL_SET_DEFAULT; + else if (!strcmp(upd_rule, "RI_FKey_setnull_upd")) + upd_rule_type = SQL_SET_NULL; + + if (!strcmp(upd_rule, "RI_FKey_cascade_del")) + del_rule_type = SQL_CASCADE; + else if (!strcmp(upd_rule, "RI_FKey_noaction_del")) + del_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_restrict_del")) + del_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_setdefault_del")) + del_rule_type = SQL_SET_DEFAULT; + else if (!strcmp(upd_rule, "RI_FKey_setnull_del")) + del_rule_type = SQL_SET_NULL; + +#if (ODBCVER >= 0x0300) + /* Set deferrability type */ + if (!strcmp(trig_initdeferred, "y")) + defer_type = SQL_INITIALLY_DEFERRED; + else if (!strcmp(trig_deferrable, "y")) + defer_type = SQL_INITIALLY_IMMEDIATE; + else + defer_type = SQL_NOT_DEFERRABLE; +#endif /* ODBCVER >= 0x0300 */ + + mylog("Foreign Key Case#1: trig_nargs = %d, num_keys = %d\n", trig_nargs, num_keys); + + /* Get to first primary key */ + pkey_ptr = trig_args; + for (i = 0; i < 5; i++) + pkey_ptr += strlen(pkey_ptr) + 1; + + /* Get to first foreign table */ + fk_table = trig_args; + fk_table += strlen(fk_table) + 1; +#ifdef MULTIBYTE + pk_table = fk_table + strlen(fk_table) + 1; + fkt_text = getClientTableName(conn, fk_table, &fkt_alloced); +#else + fkt_text = fk_table; +#endif /* MULTIBYTE */ + + /* Get to first foreign key */ + fkey_ptr = trig_args; + for (k = 0; k < 4; k++) + fkey_ptr += strlen(fkey_ptr) + 1; + + for (k = 0; k < num_keys; k++) + { +#ifdef MULTIBYTE + pkey_text = getClientColumnName(conn, pk_table, pkey_ptr, &pkey_alloced); + fkey_text = getClientColumnName(conn, fk_table, fkey_ptr, &fkey_alloced); +#else + pkey_text = pkey_ptr; + fkey_text = fkey_ptr; +#endif /* MULTIBYTE */ + mylog("pkey_ptr = '%s', fk_table = '%s', fkey_ptr = '%s'\n", pkey_text, fkt_text, fkey_text); + + row = (TupleNode *) malloc(sizeof(TupleNode) + (result_cols - 1) *sizeof(TupleField)); + + mylog("pk_table_needed = '%s', pkey_ptr = '%s'\n", pk_table_needed, pkey_text); + set_tuplefield_null(&row->tuple[0]); + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], pk_table_needed); + set_tuplefield_string(&row->tuple[3], pkey_text); + + mylog("fk_table = '%s', fkey_ptr = '%s'\n", fkt_text, fkey_text); + set_tuplefield_null(&row->tuple[4]); + set_tuplefield_string(&row->tuple[5], ""); + set_tuplefield_string(&row->tuple[6], fkt_text); + set_tuplefield_string(&row->tuple[7], fkey_text); + + set_tuplefield_int2(&row->tuple[8], (Int2) (k + 1)); + + mylog("upd_rule = %d, del_rule= %d", upd_rule_type, del_rule_type); + set_nullfield_int2(&row->tuple[9], (Int2) upd_rule_type); + set_nullfield_int2(&row->tuple[10], (Int2) del_rule_type); + + set_tuplefield_null(&row->tuple[11]); + set_tuplefield_null(&row->tuple[12]); + + set_tuplefield_string(&row->tuple[13], trig_args); + +#if (ODBCVER >= 0x0300) + mylog("defer_type = '%s'", defer_type); + set_tuplefield_int2(&row->tuple[14], defer_type); +#endif /* ODBCVER >= 0x0300 */ + + QR_add_tuple(stmt->result, row); +#ifdef MULTIBYTE + if (pkey_alloced) + free(pkey_text); + pkey_alloced = FALSE; + if (fkey_alloced) + free(fkey_text); + fkey_alloced = FALSE; +#endif /* MULTIBYTE */ + + /* next primary/foreign key */ + for (j = 0; j < 2; j++) + { + pkey_ptr += strlen(pkey_ptr) + 1; + fkey_ptr += strlen(fkey_ptr) + 1; + } + } +#ifdef MULTIBYTE + if (fkt_alloced) + free(fkt_text); + fkt_alloced = FALSE; +#endif /* MULTIBYTE */ + result = PGAPI_Fetch(htbl_stmt); + } + } + else + { + stmt->errormsg = "No tables specified to PGAPI_ForeignKeys."; + stmt->errornumber = STMT_INTERNAL_ERROR; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } +#ifdef MULTIBYTE + if (pkt_alloced) + free(pkt_text); + if (pkey_alloced) + free(pkey_text); + if (fkt_alloced) + free(fkt_text); + if (fkey_alloced) + free(fkey_text); +#endif /* MULTIBYTE */ + + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + + mylog("PGAPI_ForeignKeys(): EXIT, stmt=%u\n", stmt); + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_ProcedureColumns( + HSTMT hstmt, + UCHAR FAR * szProcQualifier, + SWORD cbProcQualifier, + UCHAR FAR * szProcOwner, + SWORD cbProcOwner, + UCHAR FAR * szProcName, + SWORD cbProcName, + UCHAR FAR * szColumnName, + SWORD cbColumnName) +{ + static char *func = "PGAPI_ProcedureColumns"; + + mylog("%s: entering...\n", func); + + SC_log_error(func, "Function not implemented", (StatementClass *) hstmt); + return SQL_ERROR; +} + + +RETCODE SQL_API +PGAPI_Procedures( + HSTMT hstmt, + UCHAR FAR * szProcQualifier, + SWORD cbProcQualifier, + UCHAR FAR * szProcOwner, + SWORD cbProcOwner, + UCHAR FAR * szProcName, + SWORD cbProcName) +{ + static char *func = "PGAPI_Procedures"; + StatementClass *stmt = (StatementClass *) hstmt; + ConnectionClass *conn = SC_get_conn(stmt); + char proc_query[INFO_INQUIRY_LEN]; + QResultClass *res; + + mylog("%s: entering...\n", func); + + if (PG_VERSION_LT(conn, 6.5)) + { + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + stmt->errormsg = "Version is too old"; + SC_log_error(func, "Function not implemented", (StatementClass *) hstmt); + return SQL_ERROR; + } + if (!SC_recycle_statement(stmt)) + return SQL_ERROR; + + /* + * The following seems the simplest implementation + */ + strcpy(proc_query, "select '' as " "PROCEDURE_CAT" ", '' as " "PROCEDURE_SCHEM" "," + " proname as " "PROCEDURE_NAME" ", '' as " "NUM_INPUT_PARAMS" "," + " '' as " "NUM_OUTPUT_PARAMS" ", '' as " "NUM_RESULT_SETS" "," + " '' as " "REMARKS" "," + " case when prorettype =0 then 1::int2 else 2::int2 end as " "PROCEDURE_TYPE" " from pg_proc"); + my_strcat(proc_query, " where proname like '%.*s'", szProcName, cbProcName); + + res = CC_send_query(conn, proc_query, NULL); + if (!res || QR_aborted(res)) + { + if (res) + QR_Destructor(res); + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "PGAPI_Procedures query error"; + return SQL_ERROR; + } + stmt->result = res; + + /* + * also, things need to think that this statement is finished so the + * results can be retrieved. + */ + stmt->status = STMT_FINISHED; + extend_bindings(stmt, 8); + /* set up the current tuple pointer for SQLFetch */ + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_TablePrivileges( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName) +{ + StatementClass *stmt = (StatementClass *) hstmt; + static char *func = "PGAPI_TablePrivileges"; + Int2 result_cols; + + mylog("%s: entering...\n", func); + + /* + * a statement is actually executed, so we'll have to do this + * ourselves. + */ + result_cols = 7; + extend_bindings(stmt, result_cols); + + /* set the field names */ + QR_set_num_fields(stmt->result, result_cols); + QR_set_field_info(stmt->result, 0, "TABLE_CAT", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 1, "TABLE_SCHEM", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 3, "GRANTOR", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 4, "GRANTEE", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 5, "PRIVILEGE", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 6, "IS_GRANTABLE", PG_TYPE_TEXT, MAX_INFO_STRING); + + SC_log_error(func, "Function not implemented", (StatementClass *) hstmt); + return SQL_ERROR; +} diff --git a/src/interfaces/odbc/windev/iodbc.h b/src/interfaces/odbc/windev/iodbc.h new file mode 100644 index 0000000000..f8e7d248b1 --- /dev/null +++ b/src/interfaces/odbc/windev/iodbc.h @@ -0,0 +1,68 @@ +#ifndef _IODBC_H +#define _IODBC_H + +#if !defined(WIN32) && !defined(WIN32_SYSTEM) +#define _UNIX_ + +#include +#include + +#define MEM_ALLOC(size) (malloc((size_t)(size))) +#define MEM_FREE(ptr) \ +do { \ + if(ptr) \ + free(ptr); \ +} while (0) + +#define STRCPY(t, s) (strcpy((char*)(t), (char*)(s))) +#define STRNCPY(t,s,n) (strncpy((char*)(t), (char*)(s), (size_t)(n))) +#define STRCAT(t, s) (strcat((char*)(t), (char*)(s))) +#define STRNCAT(t,s,n) (strncat((char*)(t), (char*)(s), (size_t)(n))) +#define STREQ(a, b) (strcmp((char*)(a), (char*)(b)) == 0) +#define STRLEN(str) ((str)? strlen((char*)(str)):0) + +#define EXPORT +#define CALLBACK +#define FAR + +typedef signed short SSHOR; +typedef short WORD; +typedef long DWORD; + +typedef WORD WPARAM; +typedef DWORD LPARAM; +typedef void *HWND; +typedef int BOOL; +#endif /* _UNIX_ */ + +#if defined(WIN32) || defined(WIN32_SYSTEM) + +#include +#include + +#ifdef _MSVC_ +#define MEM_ALLOC(size) (fmalloc((size_t)(size))) +#define MEM_FREE(ptr) ((ptr)? ffree((PTR)(ptr)):0)) +#define STRCPY(t, s) (fstrcpy((char FAR*)(t), (char FAR*)(s))) +#define STRNCPY(t,s,n) (fstrncpy((char FAR*)(t), (char FAR*)(s), (size_t)(n))) +#define STRLEN(str) ((str)? fstrlen((char FAR*)(str)):0) +#define STREQ(a, b) (fstrcmp((char FAR*)(a), (char FAR*)(b) == 0) +#endif + +#ifdef _BORLAND_ +#define MEM_ALLOC(size) (farmalloc((unsigned long)(size)) +#define MEM_FREE(ptr) ((ptr)? farfree((void far*)(ptr)):0) +#define STRCPY(t, s) (_fstrcpy((char FAR*)(t), (char FAR*)(s))) +#define STRNCPY(t,s,n) (_fstrncpy((char FAR*)(t), (char FAR*)(s), (size_t)(n))) +#define STRLEN(str) ((str)? _fstrlen((char FAR*)(str)):0) +#define STREQ(a, b) (_fstrcmp((char FAR*)(a), (char FAR*)(b) == 0) +#endif +#endif /* WIN32 */ + +#define SYSERR (-1) + +#ifndef NULL +#define NULL ((void FAR*)0UL) +#endif + +#endif diff --git a/src/interfaces/odbc/windev/license.txt b/src/interfaces/odbc/windev/license.txt new file mode 100644 index 0000000000..bdf8ab0cb3 --- /dev/null +++ b/src/interfaces/odbc/windev/license.txt @@ -0,0 +1,962 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + + Version 2, June 1991 + + + + Copyright (C) 1991 Free Software Foundation, Inc. + + 675 Mass Ave, Cambridge, MA 02139, USA + + Everyone is permitted to copy and distribute verbatim copies + + of this license document, but changing it is not allowed. + + + +[This is the first released version of the library GPL. It is + + numbered 2 because it goes with version 2 of the ordinary GPL.] + + + + Preamble + + + + The licenses for most software are designed to take away your + +freedom to share and change it. By contrast, the GNU General Public + +Licenses are intended to guarantee your freedom to share and change + +free software--to make sure the software is free for all its users. + + + + This license, the Library General Public License, applies to some + +specially designated Free Software Foundation software, and to any + +other libraries whose authors decide to use it. You can use it for + +your libraries, too. + + + + When we speak of free software, we are referring to freedom, not + +price. Our General Public Licenses are designed to make sure that you + +have the freedom to distribute copies of free software (and charge for + +this service if you wish), that you receive source code or can get it + +if you want it, that you can change the software or use pieces of it + +in new free programs; and that you know you can do these things. + + + + To protect your rights, we need to make restrictions that forbid + +anyone to deny you these rights or to ask you to surrender the rights. + +These restrictions translate to certain responsibilities for you if + +you distribute copies of the library, or if you modify it. + + + + For example, if you distribute copies of the library, whether gratis + +or for a fee, you must give the recipients all the rights that we gave + +you. You must make sure that they, too, receive or can get the source + +code. If you link a program with the library, you must provide + +complete object files to the recipients so that they can relink them + +with the library, after making changes to the library and recompiling + +it. And you must show them these terms so they know their rights. + + + + Our method of protecting your rights has two steps: (1) copyright + +the library, and (2) offer you this license which gives you legal + +permission to copy, distribute and/or modify the library. + + + + Also, for each distributor's protection, we want to make certain + +that everyone understands that there is no warranty for this free + +library. If the library is modified by someone else and passed on, we + +want its recipients to know that what they have is not the original + +version, so that any problems introduced by others will not reflect on + +the original authors' reputations. + + + + Finally, any free program is threatened constantly by software + +patents. We wish to avoid the danger that companies distributing free + +software will individually obtain patent licenses, thus in effect + +transforming the program into proprietary software. To prevent this, + +we have made it clear that any patent must be licensed for everyone's + +free use or not licensed at all. + + + + Most GNU software, including some libraries, is covered by the ordinary + +GNU General Public License, which was designed for utility programs. This + +license, the GNU Library General Public License, applies to certain + +designated libraries. This license is quite different from the ordinary + +one; be sure to read it in full, and don't assume that anything in it is + +the same as in the ordinary license. + + + + The reason we have a separate public license for some libraries is that + +they blur the distinction we usually make between modifying or adding to a + +program and simply using it. Linking a program with a library, without + +changing the library, is in some sense simply using the library, and is + +analogous to running a utility program or application program. However, in + +a textual and legal sense, the linked executable is a combined work, a + +derivative of the original library, and the ordinary General Public License + +treats it as such. + + + + Because of this blurred distinction, using the ordinary General + +Public License for libraries did not effectively promote software + +sharing, because most developers did not use the libraries. We + +concluded that weaker conditions might promote sharing better. + + + + However, unrestricted linking of non-free programs would deprive the + +users of those programs of all benefit from the free status of the + +libraries themselves. This Library General Public License is intended to + +permit developers of non-free programs to use free libraries, while + +preserving your freedom as a user of such programs to change the free + +libraries that are incorporated in them. (We have not seen how to achieve + +this as regards changes in header files, but we have achieved it as regards + +changes in the actual functions of the Library.) The hope is that this + +will lead to faster development of free libraries. + + + + The precise terms and conditions for copying, distribution and + +modification follow. Pay close attention to the difference between a + +"work based on the library" and a "work that uses the library". The + +former contains code derived from the library, while the latter only + +works together with the library. + + + + Note that it is possible for a library to be covered by the ordinary + +General Public License rather than by this special one. + + + + GNU LIBRARY GENERAL PUBLIC LICENSE + + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + + + 0. This License Agreement applies to any software library which + +contains a notice placed by the copyright holder or other authorized + +party saying it may be distributed under the terms of this Library + +General Public License (also called "this License"). Each licensee is + +addressed as "you". + + + + A "library" means a collection of software functions and/or data + +prepared so as to be conveniently linked with application programs + +(which use some of those functions and data) to form executables. + + + + The "Library", below, refers to any such software library or work + +which has been distributed under these terms. A "work based on the + +Library" means either the Library or any derivative work under + +copyright law: that is to say, a work containing the Library or a + +portion of it, either verbatim or with modifications and/or translated + +straightforwardly into another language. (Hereinafter, translation is + +included without limitation in the term "modification".) + + + + "Source code" for a work means the preferred form of the work for + +making modifications to it. For a library, complete source code means + +all the source code for all modules it contains, plus any associated + +interface definition files, plus the scripts used to control compilation + +and installation of the library. + + + + Activities other than copying, distribution and modification are not + +covered by this License; they are outside its scope. The act of + +running a program using the Library is not restricted, and output from + +such a program is covered only if its contents constitute a work based + +on the Library (independent of the use of the Library in a tool for + +writing it). Whether that is true depends on what the Library does + +and what the program that uses the Library does. + + + + 1. You may copy and distribute verbatim copies of the Library's + +complete source code as you receive it, in any medium, provided that + +you conspicuously and appropriately publish on each copy an + +appropriate copyright notice and disclaimer of warranty; keep intact + +all the notices that refer to this License and to the absence of any + +warranty; and distribute a copy of this License along with the + +Library. + + + + You may charge a fee for the physical act of transferring a copy, + +and you may at your option offer warranty protection in exchange for a + +fee. + + + + 2. You may modify your copy or copies of the Library or any portion + +of it, thus forming a work based on the Library, and copy and + +distribute such modifications or work under the terms of Section 1 + +above, provided that you also meet all of these conditions: + + + + a) The modified work must itself be a software library. + + + + b) You must cause the files modified to carry prominent notices + + stating that you changed the files and the date of any change. + + + + c) You must cause the whole of the work to be licensed at no + + charge to all third parties under the terms of this License. + + + + d) If a facility in the modified Library refers to a function or a + + table of data to be supplied by an application program that uses + + the facility, other than as an argument passed when the facility + + is invoked, then you must make a good faith effort to ensure that, + + in the event an application does not supply such function or + + table, the facility still operates, and performs whatever part of + + its purpose remains meaningful. + + + + (For example, a function in a library to compute square roots has + + a purpose that is entirely well-defined independent of the + + application. Therefore, Subsection 2d requires that any + + application-supplied function or table used by this function must + + be optional: if the application does not supply it, the square + + root function must still compute square roots.) + + + +These requirements apply to the modified work as a whole. If + +identifiable sections of that work are not derived from the Library, + +and can be reasonably considered independent and separate works in + +themselves, then this License, and its terms, do not apply to those + +sections when you distribute them as separate works. But when you + +distribute the same sections as part of a whole which is a work based + +on the Library, the distribution of the whole must be on the terms of + +this License, whose permissions for other licensees extend to the + +entire whole, and thus to each and every part regardless of who wrote + +it. + + + +Thus, it is not the intent of this section to claim rights or contest + +your rights to work written entirely by you; rather, the intent is to + +exercise the right to control the distribution of derivative or + +collective works based on the Library. + + + +In addition, mere aggregation of another work not based on the Library + +with the Library (or with a work based on the Library) on a volume of + +a storage or distribution medium does not bring the other work under + +the scope of this License. + + + + 3. You may opt to apply the terms of the ordinary GNU General Public + +License instead of this License to a given copy of the Library. To do + +this, you must alter all the notices that refer to this License, so + +that they refer to the ordinary GNU General Public License, version 2, + +instead of to this License. (If a newer version than version 2 of the + +ordinary GNU General Public License has appeared, then you can specify + +that version instead if you wish.) Do not make any other change in + +these notices. + + + + Once this change is made in a given copy, it is irreversible for + +that copy, so the ordinary GNU General Public License applies to all + +subsequent copies and derivative works made from that copy. + + + + This option is useful when you wish to copy part of the code of + +the Library into a program that is not a library. + + + + 4. You may copy and distribute the Library (or a portion or + +derivative of it, under Section 2) in object code or executable form + +under the terms of Sections 1 and 2 above provided that you accompany + +it with the complete corresponding machine-readable source code, which + +must be distributed under the terms of Sections 1 and 2 above on a + +medium customarily used for software interchange. + + + + If distribution of object code is made by offering access to copy + +from a designated place, then offering equivalent access to copy the + +source code from the same place satisfies the requirement to + +distribute the source code, even though third parties are not + +compelled to copy the source along with the object code. + + + + 5. A program that contains no derivative of any portion of the + +Library, but is designed to work with the Library by being compiled or + +linked with it, is called a "work that uses the Library". Such a + +work, in isolation, is not a derivative work of the Library, and + +therefore falls outside the scope of this License. + + + + However, linking a "work that uses the Library" with the Library + +creates an executable that is a derivative of the Library (because it + +contains portions of the Library), rather than a "work that uses the + +library". The executable is therefore covered by this License. + +Section 6 states terms for distribution of such executables. + + + + When a "work that uses the Library" uses material from a header file + +that is part of the Library, the object code for the work may be a + +derivative work of the Library even though the source code is not. + +Whether this is true is especially significant if the work can be + +linked without the Library, or if the work is itself a library. The + +threshold for this to be true is not precisely defined by law. + + + + If such an object file uses only numerical parameters, data + +structure layouts and accessors, and small macros and small inline + +functions (ten lines or less in length), then the use of the object + +file is unrestricted, regardless of whether it is legally a derivative + +work. (Executables containing this object code plus portions of the + +Library will still fall under Section 6.) + + + + Otherwise, if the work is a derivative of the Library, you may + +distribute the object code for the work under the terms of Section 6. + +Any executables containing that work also fall under Section 6, + +whether or not they are linked directly with the Library itself. + + + + 6. As an exception to the Sections above, you may also compile or + +link a "work that uses the Library" with the Library to produce a + +work containing portions of the Library, and distribute that work + +under terms of your choice, provided that the terms permit + +modification of the work for the customer's own use and reverse + +engineering for debugging such modifications. + + + + You must give prominent notice with each copy of the work that the + +Library is used in it and that the Library and its use are covered by + +this License. You must supply a copy of this License. If the work + +during execution displays copyright notices, you must include the + +copyright notice for the Library among them, as well as a reference + +directing the user to the copy of this License. Also, you must do one + +of these things: + + + + a) Accompany the work with the complete corresponding + + machine-readable source code for the Library including whatever + + changes were used in the work (which must be distributed under + + Sections 1 and 2 above); and, if the work is an executable linked + + with the Library, with the complete machine-readable "work that + + uses the Library", as object code and/or source code, so that the + + user can modify the Library and then relink to produce a modified + + executable containing the modified Library. (It is understood + + that the user who changes the contents of definitions files in the + + Library will not necessarily be able to recompile the application + + to use the modified definitions.) + + + + b) Accompany the work with a written offer, valid for at + + least three years, to give the same user the materials + + specified in Subsection 6a, above, for a charge no more + + than the cost of performing this distribution. + + + + c) If distribution of the work is made by offering access to copy + + from a designated place, offer equivalent access to copy the above + + specified materials from the same place. + + + + d) Verify that the user has already received a copy of these + + materials or that you have already sent this user a copy. + + + + For an executable, the required form of the "work that uses the + +Library" must include any data and utility programs needed for + +reproducing the executable from it. However, as a special exception, + +the source code distributed need not include anything that is normally + +distributed (in either source or binary form) with the major + +components (compiler, kernel, and so on) of the operating system on + +which the executable runs, unless that component itself accompanies + +the executable. + + + + It may happen that this requirement contradicts the license + +restrictions of other proprietary libraries that do not normally + +accompany the operating system. Such a contradiction means you cannot + +use both them and the Library together in an executable that you + +distribute. + + + + 7. You may place library facilities that are a work based on the + +Library side-by-side in a single library together with other library + +facilities not covered by this License, and distribute such a combined + +library, provided that the separate distribution of the work based on + +the Library and of the other library facilities is otherwise + +permitted, and provided that you do these two things: + + + + a) Accompany the combined library with a copy of the same work + + based on the Library, uncombined with any other library + + facilities. This must be distributed under the terms of the + + Sections above. + + + + b) Give prominent notice with the combined library of the fact + + that part of it is a work based on the Library, and explaining + + where to find the accompanying uncombined form of the same work. + + + + 8. You may not copy, modify, sublicense, link with, or distribute + +the Library except as expressly provided under this License. Any + +attempt otherwise to copy, modify, sublicense, link with, or + +distribute the Library is void, and will automatically terminate your + +rights under this License. However, parties who have received copies, + +or rights, from you under this License will not have their licenses + +terminated so long as such parties remain in full compliance. + + + + 9. You are not required to accept this License, since you have not + +signed it. However, nothing else grants you permission to modify or + +distribute the Library or its derivative works. These actions are + +prohibited by law if you do not accept this License. Therefore, by + +modifying or distributing the Library (or any work based on the + +Library), you indicate your acceptance of this License to do so, and + +all its terms and conditions for copying, distributing or modifying + +the Library or works based on it. + + + + 10. Each time you redistribute the Library (or any work based on the + +Library), the recipient automatically receives a license from the + +original licensor to copy, distribute, link with or modify the Library + +subject to these terms and conditions. You may not impose any further + +restrictions on the recipients' exercise of the rights granted herein. + +You are not responsible for enforcing compliance by third parties to + +this License. + + + + 11. If, as a consequence of a court judgment or allegation of patent + +infringement or for any other reason (not limited to patent issues), + +conditions are imposed on you (whether by court order, agreement or + +otherwise) that contradict the conditions of this License, they do not + +excuse you from the conditions of this License. If you cannot + +distribute so as to satisfy simultaneously your obligations under this + +License and any other pertinent obligations, then as a consequence you + +may not distribute the Library at all. For example, if a patent + +license would not permit royalty-free redistribution of the Library by + +all those who receive copies directly or indirectly through you, then + +the only way you could satisfy both it and this License would be to + +refrain entirely from distribution of the Library. + + + +If any portion of this section is held invalid or unenforceable under any + +particular circumstance, the balance of the section is intended to apply, + +and the section as a whole is intended to apply in other circumstances. + + + +It is not the purpose of this section to induce you to infringe any + +patents or other property right claims or to contest validity of any + +such claims; this section has the sole purpose of protecting the + +integrity of the free software distribution system which is + +implemented by public license practices. Many people have made + +generous contributions to the wide range of software distributed + +through that system in reliance on consistent application of that + +system; it is up to the author/donor to decide if he or she is willing + +to distribute software through any other system and a licensee cannot + +impose that choice. + + + +This section is intended to make thoroughly clear what is believed to + +be a consequence of the rest of this License. + + + + 12. If the distribution and/or use of the Library is restricted in + +certain countries either by patents or by copyrighted interfaces, the + +original copyright holder who places the Library under this License may add + +an explicit geographical distribution limitation excluding those countries, + +so that distribution is permitted only in or among countries not thus + +excluded. In such case, this License incorporates the limitation as if + +written in the body of this License. + + + + 13. The Free Software Foundation may publish revised and/or new + +versions of the Library General Public License from time to time. + +Such new versions will be similar in spirit to the present version, + +but may differ in detail to address new problems or concerns. + + + +Each version is given a distinguishing version number. If the Library + +specifies a version number of this License which applies to it and + +"any later version", you have the option of following the terms and + +conditions either of that version or of any later version published by + +the Free Software Foundation. If the Library does not specify a + +license version number, you may choose any version ever published by + +the Free Software Foundation. + + + + 14. If you wish to incorporate parts of the Library into other free + +programs whose distribution conditions are incompatible with these, + +write to the author to ask for permission. For software which is + +copyrighted by the Free Software Foundation, write to the Free + +Software Foundation; we sometimes make exceptions for this. Our + +decision will be guided by the two goals of preserving the free status + +of all derivatives of our free software and of promoting the sharing + +and reuse of software generally. + + + + NO WARRANTY + + + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO + +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY + +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE + +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE + +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME + +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU + +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR + +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE + +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING + +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A + +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF + +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + +DAMAGES. + + + + END OF TERMS AND CONDITIONS + + + + Appendix: How to Apply These Terms to Your New Libraries + + + + If you develop a new library, and you want it to be of the greatest + +possible use to the public, we recommend making it free software that + +everyone can redistribute and change. You can do so by permitting + +redistribution under these terms (or, alternatively, under the terms of the + +ordinary General Public License). + + + + To apply these terms, attach the following notices to the library. It is + +safest to attach them to the start of each source file to most effectively + +convey the exclusion of warranty; and each file should have at least the + +"copyright" line and a pointer to where the full notice is found. + + + + + + Copyright (C) + + + + This library is free software; you can redistribute it and/or + + modify it under the terms of the GNU Library General Public + + License as published by the Free Software Foundation; either + + version 2 of the License, or (at your option) any later version. + + + + This library is distributed in the hope that it will be useful, + + but WITHOUT ANY WARRANTY; without even the implied warranty of + + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + + Library General Public License for more details. + + + + You should have received a copy of the GNU Library General Public + + License along with this library; if not, write to the Free + + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + + +Also add information on how to contact you by electronic and paper mail. + + + +You should also get your employer (if you work as a programmer) or your + +school, if any, to sign a "copyright disclaimer" for the library, if + +necessary. Here is a sample; alter the names: + + + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + + + , 1 April 1990 + + Ty Coon, President of Vice + + + +That's all there is to it! + diff --git a/src/interfaces/odbc/windev/lobj.c b/src/interfaces/odbc/windev/lobj.c new file mode 100644 index 0000000000..6b55b82d54 --- /dev/null +++ b/src/interfaces/odbc/windev/lobj.c @@ -0,0 +1,186 @@ +/*-------- + * Module: lobj.c + * + * Description: This module contains routines related to manipulating + * large objects. + * + * Classes: none + * + * API functions: none + * + * Comments: See "notice.txt" for copyright and license information. + *-------- + */ + +#include "lobj.h" + +#include "connection.h" + + +Oid +lo_creat(ConnectionClass *conn, int mode) +{ + LO_ARG argv[1]; + int retval, + result_len; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = mode; + + if (!CC_send_function(conn, LO_CREAT, &retval, &result_len, 1, argv, 1)) + return 0; /* invalid oid */ + else + return retval; +} + + +int +lo_open(ConnectionClass *conn, int lobjId, int mode) +{ + int fd; + int result_len; + LO_ARG argv[2]; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = lobjId; + + argv[1].isint = 1; + argv[1].len = 4; + argv[1].u.integer = mode; + + if (!CC_send_function(conn, LO_OPEN, &fd, &result_len, 1, argv, 2)) + return -1; + + if (fd >= 0 && lo_lseek(conn, fd, 0L, SEEK_SET) < 0) + return -1; + + return fd; +} + + +int +lo_close(ConnectionClass *conn, int fd) +{ + LO_ARG argv[1]; + int retval, + result_len; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + if (!CC_send_function(conn, LO_CLOSE, &retval, &result_len, 1, argv, 1)) + return -1; + else + return retval; +} + + +int +lo_read(ConnectionClass *conn, int fd, char *buf, int len) +{ + LO_ARG argv[2]; + int result_len; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + argv[1].isint = 1; + argv[1].len = 4; + argv[1].u.integer = len; + + if (!CC_send_function(conn, LO_READ, (int *) buf, &result_len, 0, argv, 2)) + return -1; + else + return result_len; +} + + +int +lo_write(ConnectionClass *conn, int fd, char *buf, int len) +{ + LO_ARG argv[2]; + int retval, + result_len; + + if (len <= 0) + return 0; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + argv[1].isint = 0; + argv[1].len = len; + argv[1].u.ptr = (char *) buf; + + if (!CC_send_function(conn, LO_WRITE, &retval, &result_len, 1, argv, 2)) + return -1; + else + return retval; +} + + +int +lo_lseek(ConnectionClass *conn, int fd, int offset, int whence) +{ + LO_ARG argv[3]; + int retval, + result_len; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + argv[1].isint = 1; + argv[1].len = 4; + argv[1].u.integer = offset; + + argv[2].isint = 1; + argv[2].len = 4; + argv[2].u.integer = whence; + + if (!CC_send_function(conn, LO_LSEEK, &retval, &result_len, 1, argv, 3)) + return -1; + else + return retval; +} + + +int +lo_tell(ConnectionClass *conn, int fd) +{ + LO_ARG argv[1]; + int retval, + result_len; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = fd; + + if (!CC_send_function(conn, LO_TELL, &retval, &result_len, 1, argv, 1)) + return -1; + else + return retval; +} + + +int +lo_unlink(ConnectionClass *conn, Oid lobjId) +{ + LO_ARG argv[1]; + int retval, + result_len; + + argv[0].isint = 1; + argv[0].len = 4; + argv[0].u.integer = lobjId; + + if (!CC_send_function(conn, LO_UNLINK, &retval, &result_len, 1, argv, 1)) + return -1; + else + return retval; +} diff --git a/src/interfaces/odbc/windev/lobj.h b/src/interfaces/odbc/windev/lobj.h new file mode 100644 index 0000000000..4d720488a0 --- /dev/null +++ b/src/interfaces/odbc/windev/lobj.h @@ -0,0 +1,47 @@ +/* File: lobj.h + * + * Description: See "lobj.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __LOBJ_H__ +#define __LOBJ_H__ + + +#include "psqlodbc.h" + +struct lo_arg +{ + int isint; + int len; + union + { + int integer; + char *ptr; + } u; +}; + +#define LO_CREAT 957 +#define LO_OPEN 952 +#define LO_CLOSE 953 +#define LO_READ 954 +#define LO_WRITE 955 +#define LO_LSEEK 956 +#define LO_TELL 958 +#define LO_UNLINK 964 + +#define INV_WRITE 0x00020000 +#define INV_READ 0x00040000 + +Oid lo_creat(ConnectionClass *conn, int mode); +int lo_open(ConnectionClass *conn, int lobjId, int mode); +int lo_close(ConnectionClass *conn, int fd); +int lo_read(ConnectionClass *conn, int fd, char *buf, int len); +int lo_write(ConnectionClass *conn, int fd, char *buf, int len); +int lo_lseek(ConnectionClass *conn, int fd, int offset, int len); +int lo_tell(ConnectionClass *conn, int fd); +int lo_unlink(ConnectionClass *conn, Oid lobjId); + +#endif diff --git a/src/interfaces/odbc/windev/md5.c b/src/interfaces/odbc/windev/md5.c new file mode 100644 index 0000000000..da32380dde --- /dev/null +++ b/src/interfaces/odbc/windev/md5.c @@ -0,0 +1,351 @@ +/* + * md5.c + * + * Implements the MD5 Message-Digest Algorithm as specified in + * RFC 1321. This implementation is a simple one, in that it + * needs every input byte to be buffered before doing any + * calculations. I do not expect this file to be used for + * general purpose MD5'ing of large amounts of data, only for + * generating hashed passwords from limited input. + * + * Sverre H. Huseby + * + * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/interfaces/odbc/windev/Attic/md5.c,v 1.1 2002/01/11 02:50:01 inoue Exp $ + */ + + +/* + * NOTE: + * + * There are two copies of this file, one in backend/libpq and another + * in interfaces/odbc. They should be identical. This is done so ODBC + * can be compiled stand-alone. + */ + +#ifndef MD5_ODBC +#include "postgres.h" +#include "libpq/crypt.h" +#else +#include "md5.h" +#endif + +#ifdef FRONTEND +#undef palloc +#define palloc malloc +#undef pfree +#define pfree free +#endif + + +/* + * PRIVATE FUNCTIONS + */ + + +/* + * The returned array is allocated using malloc. the caller should free it + * when it is no longer needed. + */ +static uint8 * +createPaddedCopyWithLength(uint8 *b, uint32 *l) +{ + uint8 *ret; + uint32 q; + uint32 len, + newLen448; + uint32 len_high, + len_low; /* 64-bit value split into 32-bit sections */ + + len = ((b == NULL) ? 0 : *l); + newLen448 = len + 64 - (len % 64) - 8; + if (newLen448 <= len) + newLen448 += 64; + + *l = newLen448 + 8; + if ((ret = (uint8 *) malloc(sizeof(uint8) * *l)) == NULL) + return NULL; + + if (b != NULL) + memcpy(ret, b, sizeof(uint8) * len); + + /* pad */ + ret[len] = 0x80; + for (q = len + 1; q < newLen448; q++) + ret[q] = 0x00; + + /* append length as a 64 bit bitcount */ + len_low = len; + /* split into two 32-bit values */ + /* we only look at the bottom 32-bits */ + len_high = len >> 29; + len_low <<= 3; + q = newLen448; + ret[q++] = (len_low & 0xff); + len_low >>= 8; + ret[q++] = (len_low & 0xff); + len_low >>= 8; + ret[q++] = (len_low & 0xff); + len_low >>= 8; + ret[q++] = (len_low & 0xff); + ret[q++] = (len_high & 0xff); + len_high >>= 8; + ret[q++] = (len_high & 0xff); + len_high >>= 8; + ret[q++] = (len_high & 0xff); + len_high >>= 8; + ret[q] = (len_high & 0xff); + + return ret; +} + +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define ROT_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + +static void +doTheRounds(uint32 X[16], uint32 state[4]) +{ + uint32 a, + b, + c, + d; + + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + + /* round 1 */ + a = b + ROT_LEFT((a + F(b, c, d) + X[0] + 0xd76aa478), 7); /* 1 */ + d = a + ROT_LEFT((d + F(a, b, c) + X[1] + 0xe8c7b756), 12); /* 2 */ + c = d + ROT_LEFT((c + F(d, a, b) + X[2] + 0x242070db), 17); /* 3 */ + b = c + ROT_LEFT((b + F(c, d, a) + X[3] + 0xc1bdceee), 22); /* 4 */ + a = b + ROT_LEFT((a + F(b, c, d) + X[4] + 0xf57c0faf), 7); /* 5 */ + d = a + ROT_LEFT((d + F(a, b, c) + X[5] + 0x4787c62a), 12); /* 6 */ + c = d + ROT_LEFT((c + F(d, a, b) + X[6] + 0xa8304613), 17); /* 7 */ + b = c + ROT_LEFT((b + F(c, d, a) + X[7] + 0xfd469501), 22); /* 8 */ + a = b + ROT_LEFT((a + F(b, c, d) + X[8] + 0x698098d8), 7); /* 9 */ + d = a + ROT_LEFT((d + F(a, b, c) + X[9] + 0x8b44f7af), 12); /* 10 */ + c = d + ROT_LEFT((c + F(d, a, b) + X[10] + 0xffff5bb1), 17); /* 11 */ + b = c + ROT_LEFT((b + F(c, d, a) + X[11] + 0x895cd7be), 22); /* 12 */ + a = b + ROT_LEFT((a + F(b, c, d) + X[12] + 0x6b901122), 7); /* 13 */ + d = a + ROT_LEFT((d + F(a, b, c) + X[13] + 0xfd987193), 12); /* 14 */ + c = d + ROT_LEFT((c + F(d, a, b) + X[14] + 0xa679438e), 17); /* 15 */ + b = c + ROT_LEFT((b + F(c, d, a) + X[15] + 0x49b40821), 22); /* 16 */ + + /* round 2 */ + a = b + ROT_LEFT((a + G(b, c, d) + X[1] + 0xf61e2562), 5); /* 17 */ + d = a + ROT_LEFT((d + G(a, b, c) + X[6] + 0xc040b340), 9); /* 18 */ + c = d + ROT_LEFT((c + G(d, a, b) + X[11] + 0x265e5a51), 14); /* 19 */ + b = c + ROT_LEFT((b + G(c, d, a) + X[0] + 0xe9b6c7aa), 20); /* 20 */ + a = b + ROT_LEFT((a + G(b, c, d) + X[5] + 0xd62f105d), 5); /* 21 */ + d = a + ROT_LEFT((d + G(a, b, c) + X[10] + 0x02441453), 9); /* 22 */ + c = d + ROT_LEFT((c + G(d, a, b) + X[15] + 0xd8a1e681), 14); /* 23 */ + b = c + ROT_LEFT((b + G(c, d, a) + X[4] + 0xe7d3fbc8), 20); /* 24 */ + a = b + ROT_LEFT((a + G(b, c, d) + X[9] + 0x21e1cde6), 5); /* 25 */ + d = a + ROT_LEFT((d + G(a, b, c) + X[14] + 0xc33707d6), 9); /* 26 */ + c = d + ROT_LEFT((c + G(d, a, b) + X[3] + 0xf4d50d87), 14); /* 27 */ + b = c + ROT_LEFT((b + G(c, d, a) + X[8] + 0x455a14ed), 20); /* 28 */ + a = b + ROT_LEFT((a + G(b, c, d) + X[13] + 0xa9e3e905), 5); /* 29 */ + d = a + ROT_LEFT((d + G(a, b, c) + X[2] + 0xfcefa3f8), 9); /* 30 */ + c = d + ROT_LEFT((c + G(d, a, b) + X[7] + 0x676f02d9), 14); /* 31 */ + b = c + ROT_LEFT((b + G(c, d, a) + X[12] + 0x8d2a4c8a), 20); /* 32 */ + + /* round 3 */ + a = b + ROT_LEFT((a + H(b, c, d) + X[5] + 0xfffa3942), 4); /* 33 */ + d = a + ROT_LEFT((d + H(a, b, c) + X[8] + 0x8771f681), 11); /* 34 */ + c = d + ROT_LEFT((c + H(d, a, b) + X[11] + 0x6d9d6122), 16); /* 35 */ + b = c + ROT_LEFT((b + H(c, d, a) + X[14] + 0xfde5380c), 23); /* 36 */ + a = b + ROT_LEFT((a + H(b, c, d) + X[1] + 0xa4beea44), 4); /* 37 */ + d = a + ROT_LEFT((d + H(a, b, c) + X[4] + 0x4bdecfa9), 11); /* 38 */ + c = d + ROT_LEFT((c + H(d, a, b) + X[7] + 0xf6bb4b60), 16); /* 39 */ + b = c + ROT_LEFT((b + H(c, d, a) + X[10] + 0xbebfbc70), 23); /* 40 */ + a = b + ROT_LEFT((a + H(b, c, d) + X[13] + 0x289b7ec6), 4); /* 41 */ + d = a + ROT_LEFT((d + H(a, b, c) + X[0] + 0xeaa127fa), 11); /* 42 */ + c = d + ROT_LEFT((c + H(d, a, b) + X[3] + 0xd4ef3085), 16); /* 43 */ + b = c + ROT_LEFT((b + H(c, d, a) + X[6] + 0x04881d05), 23); /* 44 */ + a = b + ROT_LEFT((a + H(b, c, d) + X[9] + 0xd9d4d039), 4); /* 45 */ + d = a + ROT_LEFT((d + H(a, b, c) + X[12] + 0xe6db99e5), 11); /* 46 */ + c = d + ROT_LEFT((c + H(d, a, b) + X[15] + 0x1fa27cf8), 16); /* 47 */ + b = c + ROT_LEFT((b + H(c, d, a) + X[2] + 0xc4ac5665), 23); /* 48 */ + + /* round 4 */ + a = b + ROT_LEFT((a + I(b, c, d) + X[0] + 0xf4292244), 6); /* 49 */ + d = a + ROT_LEFT((d + I(a, b, c) + X[7] + 0x432aff97), 10); /* 50 */ + c = d + ROT_LEFT((c + I(d, a, b) + X[14] + 0xab9423a7), 15); /* 51 */ + b = c + ROT_LEFT((b + I(c, d, a) + X[5] + 0xfc93a039), 21); /* 52 */ + a = b + ROT_LEFT((a + I(b, c, d) + X[12] + 0x655b59c3), 6); /* 53 */ + d = a + ROT_LEFT((d + I(a, b, c) + X[3] + 0x8f0ccc92), 10); /* 54 */ + c = d + ROT_LEFT((c + I(d, a, b) + X[10] + 0xffeff47d), 15); /* 55 */ + b = c + ROT_LEFT((b + I(c, d, a) + X[1] + 0x85845dd1), 21); /* 56 */ + a = b + ROT_LEFT((a + I(b, c, d) + X[8] + 0x6fa87e4f), 6); /* 57 */ + d = a + ROT_LEFT((d + I(a, b, c) + X[15] + 0xfe2ce6e0), 10); /* 58 */ + c = d + ROT_LEFT((c + I(d, a, b) + X[6] + 0xa3014314), 15); /* 59 */ + b = c + ROT_LEFT((b + I(c, d, a) + X[13] + 0x4e0811a1), 21); /* 60 */ + a = b + ROT_LEFT((a + I(b, c, d) + X[4] + 0xf7537e82), 6); /* 61 */ + d = a + ROT_LEFT((d + I(a, b, c) + X[11] + 0xbd3af235), 10); /* 62 */ + c = d + ROT_LEFT((c + I(d, a, b) + X[2] + 0x2ad7d2bb), 15); /* 63 */ + b = c + ROT_LEFT((b + I(c, d, a) + X[9] + 0xeb86d391), 21); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; +} + +static int +calculateDigestFromBuffer(uint8 *b, uint32 len, uint8 sum[16]) +{ + register uint32 i, + j, + k, + newI; + uint32 l; + uint8 *input; + register uint32 *wbp; + uint32 workBuff[16], + state[4]; + + l = len; + + state[0] = 0x67452301; + state[1] = 0xEFCDAB89; + state[2] = 0x98BADCFE; + state[3] = 0x10325476; + + if ((input = createPaddedCopyWithLength(b, &l)) == NULL) + return 0; + + for (i = 0;;) + { + if ((newI = i + 16 * 4) > l) + break; + k = i + 3; + for (j = 0; j < 16; j++) + { + wbp = (workBuff + j); + *wbp = input[k--]; + *wbp <<= 8; + *wbp |= input[k--]; + *wbp <<= 8; + *wbp |= input[k--]; + *wbp <<= 8; + *wbp |= input[k]; + k += 7; + } + doTheRounds(workBuff, state); + i = newI; + } + free(input); + + j = 0; + for (i = 0; i < 4; i++) + { + k = state[i]; + sum[j++] = (k & 0xff); + k >>= 8; + sum[j++] = (k & 0xff); + k >>= 8; + sum[j++] = (k & 0xff); + k >>= 8; + sum[j++] = (k & 0xff); + } + return 1; +} + +static void +bytesToHex(uint8 b[16], char *s) +{ + static char *hex = "0123456789abcdef"; + int q, + w; + + for (q = 0, w = 0; q < 16; q++) + { + s[w++] = hex[(b[q] >> 4) & 0x0F]; + s[w++] = hex[b[q] & 0x0F]; + } + s[w] = '\0'; +} + +/* + * PUBLIC FUNCTIONS + */ + +/* + * md5_hash + * + * Calculates the MD5 sum of the bytes in a buffer. + * + * SYNOPSIS #include "crypt.h" + * int md5_hash(const void *buff, size_t len, char *hexsum) + * + * INPUT buff the buffer containing the bytes that you want + * the MD5 sum of. + * len number of bytes in the buffer. + * + * OUTPUT hexsum the MD5 sum as a '\0'-terminated string of + * hexadecimal digits. an MD5 sum is 16 bytes long. + * each byte is represented by two heaxadecimal + * characters. you thus need to provide an array + * of 33 characters, including the trailing '\0'. + * + * RETURNS 0 on failure (out of memory for internal buffers) or + * non-zero on success. + * + * STANDARDS MD5 is described in RFC 1321. + * + * AUTHOR Sverre H. Huseby + * + */ +bool +md5_hash(const void *buff, size_t len, char *hexsum) +{ + uint8 sum[16]; + + if (!calculateDigestFromBuffer((uint8 *) buff, len, sum)) + return false; + + bytesToHex(sum, hexsum); + return true; +} + + + +/* + * Computes MD5 checksum of "passwd" (a null-terminated string) followed + * by "salt" (which need not be null-terminated). + * + * Output format is "md5" followed by a 32-hex-digit MD5 checksum. + * Hence, the output buffer "buf" must be at least 36 bytes long. + * + * Returns TRUE if okay, FALSE on error (out of memory). + */ +bool +EncryptMD5(const char *passwd, const char *salt, size_t salt_len, + char *buf) +{ + size_t passwd_len = strlen(passwd); + char *crypt_buf = palloc(passwd_len + salt_len); + bool ret; + + /* + * Place salt at the end because it may be known by users trying to + * crack the MD5 output. + */ + strcpy(crypt_buf, passwd); + memcpy(crypt_buf + passwd_len, salt, salt_len); + + strcpy(buf, "md5"); + ret = md5_hash(crypt_buf, passwd_len + salt_len, buf + 3); + + pfree(crypt_buf); + + return ret; +} diff --git a/src/interfaces/odbc/windev/md5.h b/src/interfaces/odbc/windev/md5.h new file mode 100644 index 0000000000..2e2429d33d --- /dev/null +++ b/src/interfaces/odbc/windev/md5.h @@ -0,0 +1,49 @@ +/* File: md5.h + * + * Description: See "md5.h" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __MD5_H__ +#define __MD5_H__ + +#include "psqlodbc.h" + +#include +#include + +#define MD5_PASSWD_LEN 35 + +/* From c.h */ +#ifndef __BEOS__ + +#ifndef __cplusplus + +#ifndef bool +typedef char bool; +#endif + +#ifndef true +#define true ((bool) 1) +#endif + +#ifndef false +#define false ((bool) 0) +#endif +#endif /* not C++ */ +#endif /* __BEOS__ */ + +/* Also defined in include/c.h */ +#ifndef HAVE_UINT8 +typedef unsigned char uint8; /* == 8 bits */ +typedef unsigned short uint16; /* == 16 bits */ +typedef unsigned int uint32; /* == 32 bits */ +#endif /* not HAVE_UINT8 */ + +extern bool md5_hash(const void *buff, size_t len, char *hexsum); +extern bool EncryptMD5(const char *passwd, const char *salt, + size_t salt_len, char *buf); + +#endif diff --git a/src/interfaces/odbc/windev/misc.c b/src/interfaces/odbc/windev/misc.c new file mode 100644 index 0000000000..443d1f47b3 --- /dev/null +++ b/src/interfaces/odbc/windev/misc.c @@ -0,0 +1,306 @@ +/*------- + * Module: misc.c + * + * Description: This module contains miscellaneous routines + * such as for debugging/logging and string functions. + * + * Classes: n/a + * + * API functions: none + * + * Comments: See "notice.txt" for copyright and license information. + *------- + */ + +#include "psqlodbc.h" + +#include +#include +#include + +#ifndef WIN32 +#if HAVE_PWD_H +#include +#endif +#include +#include +#else +#include /* Byron: is this where Windows keeps def. + * of getpid ? */ +#endif + +extern GLOBAL_VALUES globals; +void generate_filename(const char *, const char *, char *); + + +void +generate_filename(const char *dirname, const char *prefix, char *filename) +{ + int pid = 0; + +#ifndef WIN32 + struct passwd *ptr = 0; + + ptr = getpwuid(getuid()); +#endif + pid = getpid(); + if (dirname == 0 || filename == 0) + return; + + strcpy(filename, dirname); + strcat(filename, DIRSEPARATOR); + if (prefix != 0) + strcat(filename, prefix); +#ifndef WIN32 + strcat(filename, ptr->pw_name); +#endif + sprintf(filename, "%s%u%s", filename, pid, ".log"); + return; +} + +static int mylog_on = 0, + qlog_on = 0; +void +logs_on_off(int cnopen, int mylog_onoff, int qlog_onoff) +{ + static int mylog_on_count = 0, + mylog_off_count = 0, + qlog_on_count = 0, + qlog_off_count = 0; + + if (mylog_onoff) + mylog_on_count += cnopen; + else + mylog_off_count += cnopen; + if (mylog_on_count > 0) + mylog_on = 1; + else if (mylog_off_count > 0) + mylog_on = 0; + else + mylog_on = globals.debug; + if (qlog_onoff) + qlog_on_count += cnopen; + else + qlog_off_count += cnopen; + if (qlog_on_count > 0) + qlog_on = 1; + else if (qlog_off_count > 0) + qlog_on = 0; + else + qlog_on = globals.commlog; +} + +#ifdef MY_LOG +void +mylog(char *fmt,...) +{ + va_list args; + char filebuf[80]; + static FILE *LOGFP = NULL; + + if (mylog_on) + { + va_start(args, fmt); + + if (!LOGFP) + { + generate_filename(MYLOGDIR, MYLOGFILE, filebuf); + LOGFP = fopen(filebuf, PG_BINARY_W); + setbuf(LOGFP, NULL); + } + + if (LOGFP) + vfprintf(LOGFP, fmt, args); + + va_end(args); + } +} +#endif + + +#ifdef Q_LOG +void +qlog(char *fmt,...) +{ + va_list args; + char filebuf[80]; + static FILE *LOGFP = NULL; + + if (qlog_on) + { + va_start(args, fmt); + + if (!LOGFP) + { + generate_filename(QLOGDIR, QLOGFILE, filebuf); + LOGFP = fopen(filebuf, PG_BINARY_W); + setbuf(LOGFP, NULL); + } + + if (LOGFP) + vfprintf(LOGFP, fmt, args); + + va_end(args); + } +} +#endif + + +/* + * returns STRCPY_FAIL, STRCPY_TRUNCATED, or #bytes copied + * (not including null term) + */ +int +my_strcpy(char *dst, int dst_len, const char *src, int src_len) +{ + if (dst_len <= 0) + return STRCPY_FAIL; + + if (src_len == SQL_NULL_DATA) + { + dst[0] = '\0'; + return STRCPY_NULL; + } + else if (src_len == SQL_NTS) + src_len = strlen(src); + + if (src_len <= 0) + return STRCPY_FAIL; + else + { + if (src_len < dst_len) + { + memcpy(dst, src, src_len); + dst[src_len] = '\0'; + } + else + { + memcpy(dst, src, dst_len - 1); + dst[dst_len - 1] = '\0'; /* truncated */ + return STRCPY_TRUNCATED; + } + } + + return strlen(dst); +} + + +/* + * strncpy copies up to len characters, and doesn't terminate + * the destination string if src has len characters or more. + * instead, I want it to copy up to len-1 characters and always + * terminate the destination string. + */ +char * +strncpy_null(char *dst, const char *src, int len) +{ + int i; + + + if (NULL != dst) + { + /* Just in case, check for special lengths */ + if (len == SQL_NULL_DATA) + { + dst[0] = '\0'; + return NULL; + } + else if (len == SQL_NTS) + len = strlen(src) + 1; + + for (i = 0; src[i] && i < len - 1; i++) + dst[i] = src[i]; + + if (len > 0) + dst[i] = '\0'; + } + return dst; +} + + +/*------ + * Create a null terminated string (handling the SQL_NTS thing): + * 1. If buf is supplied, place the string in there + * (assumes enough space) and return buf. + * 2. If buf is not supplied, malloc space and return this string + *------ + */ +char * +make_string(const char *s, int len, char *buf) +{ + int length; + char *str; + + if (s && (len > 0 || (len == SQL_NTS && strlen(s) > 0))) + { + length = (len > 0) ? len : strlen(s); + + if (buf) + { + strncpy_null(buf, s, length + 1); + return buf; + } + + str = malloc(length + 1); + if (!str) + return NULL; + + strncpy_null(str, s, length + 1); + return str; + } + + return NULL; +} + + +/* + * Concatenate a single formatted argument to a given buffer handling the SQL_NTS thing. + * "fmt" must contain somewhere in it the single form '%.*s'. + * This is heavily used in creating queries for info routines (SQLTables, SQLColumns). + * This routine could be modified to use vsprintf() to handle multiple arguments. + */ +char * +my_strcat(char *buf, const char *fmt, const char *s, int len) +{ + if (s && (len > 0 || (len == SQL_NTS && strlen(s) > 0))) + { + int length = (len > 0) ? len : strlen(s); + + int pos = strlen(buf); + + sprintf(&buf[pos], fmt, length, s); + return buf; + } + return NULL; +} + + +void +remove_newlines(char *string) +{ + unsigned int i; + + for (i = 0; i < strlen(string); i++) + { + if ((string[i] == '\n') || + (string[i] == '\r')) + string[i] = ' '; + } +} + + +char * +trim(char *s) +{ + int i; + + for (i = strlen(s) - 1; i >= 0; i--) + { + if (s[i] == ' ') + s[i] = '\0'; + else + break; + } + + return s; +} diff --git a/src/interfaces/odbc/windev/misc.h b/src/interfaces/odbc/windev/misc.h new file mode 100644 index 0000000000..5cedd4c147 --- /dev/null +++ b/src/interfaces/odbc/windev/misc.h @@ -0,0 +1,98 @@ +/* File: misc.h + * + * Description: See "misc.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __MISC_H__ +#define __MISC_H__ + +#include "psqlodbc.h" + +#include + +/* Uncomment MY_LOG define to compile in the mylog() statements. + Then, debug logging will occur if 'Debug' is set to 1 in the ODBCINST.INI + portion of the registry. You may have to manually add this key. + This logfile is intended for development use, not for an end user! +*/ +#define MY_LOG + + +/* Uncomment Q_LOG to compile in the qlog() statements (Communications log, i.e. CommLog). + This logfile contains serious log statements that are intended for an + end user to be able to read and understand. It is controlled by the + 'CommLog' flag in the ODBCINST.INI portion of the registry (see above), + which is manipulated on the setup/connection dialog boxes. +*/ +#define Q_LOG + + +#ifdef MY_LOG +#define MYLOGFILE "mylog_" +#ifndef WIN32 +#define MYLOGDIR "/tmp" +#else +#define MYLOGDIR "c:" +#endif +extern void mylog(char *fmt,...); + +#else +#ifndef WIN32 +#define mylog(args...) /* GNU convention for variable arguments */ +#else +#define mylog /* mylog */ +#endif +#endif + +#ifdef Q_LOG +#define QLOGFILE "psqlodbc_" +#ifndef WIN32 +#define QLOGDIR "/tmp" +#else +#define QLOGDIR "c:" +#endif +extern void qlog(char *fmt,...); + +#else +#ifndef WIN32 +#define qlog(args...) /* GNU convention for variable arguments */ +#else +#define qlog /* qlog */ +#endif +#endif + +#ifndef WIN32 +#define DIRSEPARATOR "/" +#else +#define DIRSEPARATOR "\\" +#endif + +#ifdef WIN32 +#define PG_BINARY O_BINARY +#define PG_BINARY_R "rb" +#define PG_BINARY_W "wb" +#else +#define PG_BINARY 0 +#define PG_BINARY_R "r" +#define PG_BINARY_W "w" +#endif + + +void remove_newlines(char *string); +char *strncpy_null(char *dst, const char *src, int len); +char *trim(char *string); +char *make_string(const char *s, int len, char *buf); +char *my_strcat(char *buf, const char *fmt, const char *s, int len); + +/* defines for return value of my_strcpy */ +#define STRCPY_SUCCESS 1 +#define STRCPY_FAIL 0 +#define STRCPY_TRUNCATED (-1) +#define STRCPY_NULL (-2) + +int my_strcpy(char *dst, int dst_len, const char *src, int src_len); + +#endif diff --git a/src/interfaces/odbc/windev/multibyte.c b/src/interfaces/odbc/windev/multibyte.c new file mode 100644 index 0000000000..b19af2764c --- /dev/null +++ b/src/interfaces/odbc/windev/multibyte.c @@ -0,0 +1,138 @@ +/*-------- + * Module : multibyte.c + * + * Description: Mlutibyte related additional function. + * + * Create 2001-03-03 Eiji Tokuya + *-------- + */ + +#include "multibyte.h" +#include + +int multibyte_client_encoding; /* Multibyte Client Encoding. */ +int multibyte_status; /* Multibyte Odds and ends character. */ + + +unsigned char * +multibyte_strchr(unsigned char *s, unsigned char c) +{ + int mb_st = 0, + i = 0; + + while (!(mb_st == 0 && (s[i] == c || s[i] == 0))) + { + if (s[i] == 0) + return (0); + switch (multibyte_client_encoding) + { + case SJIS: + { + if (mb_st < 2 && s[i] > 0x80 && !(s[i] > 0x9f && s[i] < 0xe0)) + mb_st = 2; + else if (mb_st == 2) + mb_st = 1; + else + mb_st = 0; + } + break; + +/* Chinese Big5 Support. */ + case BIG5: + { + if (mb_st < 2 && s[i] > 0xA0) + mb_st = 2; + else if (mb_st == 2) + mb_st = 1; + else + mb_st = 0; + } + break; + default: + mb_st = 0; + } + i++; + } +#ifdef _DEBUG + qlog("i = %d\n", i); +#endif + return (s + i); +} + + +void +multibyte_init(void) +{ + multibyte_status = 0; +} + + +unsigned char * +check_client_encoding(unsigned char *str) +{ + if (strstr(str, "%27SJIS%27") || + strstr(str, "%27Shift_JIS%27") || + strstr(str, "'SJIS'") || + strstr(str, "'sjis'") || + strstr(str, "'Shift_JIS'")) + { + multibyte_client_encoding = SJIS; + return ("SJIS"); + } + if (strstr(str, "%27BIG5%27") || + strstr(str, "%27Big5%27") || + strstr(str, "'BIG5'") || + strstr(str, "'big5'") || + strstr(str, "'Big5'")) + { + multibyte_client_encoding = BIG5; + return ("BIG5"); + } + return ("OTHER"); +} + + +/*-------- + * Multibyte Status Function. + * Input char + * Output 0 : 1 Byte Character. + * 1 : MultibyteCharacter Last Byte. + * N : MultibyteCharacter Fast or Middle Byte. + *-------- + */ +int +multibyte_char_check(unsigned char s) +{ + switch (multibyte_client_encoding) + { + /* Japanese Shift-JIS(CP932) Support. */ + case SJIS: + { + if (multibyte_status < 2 && s > 0x80 && !(s > 0x9f && s < 0xE0)) + multibyte_status = 2; + else if (multibyte_status == 2) + multibyte_status = 1; + else + multibyte_status = 0; + } + break; + + /* Chinese Big5(CP950) Support. */ + case BIG5: + { + if (multibyte_status < 2 && s > 0xA0) + multibyte_status = 2; + else if (multibyte_status == 2) + multibyte_status = 1; + else + multibyte_status = 0; + } + break; + default: + multibyte_status = 0; + } +#ifdef _DEBUG + qlog("multibyte_client_encoding = %d s = 0x%02X multibyte_stat = %d\n", multibyte_client_encoding, s, multibyte_status); +#endif + return (multibyte_status); +} diff --git a/src/interfaces/odbc/windev/multibyte.h b/src/interfaces/odbc/windev/multibyte.h new file mode 100644 index 0000000000..43870458e0 --- /dev/null +++ b/src/interfaces/odbc/windev/multibyte.h @@ -0,0 +1,39 @@ +/* + * + * Multibyte library header ( psqlODBC Only ) + * + */ +#include "psqlodbc.h" + +/* PostgreSQL client encoding */ +#define SQL_ASCII 0 /* SQL/ASCII */ +#define EUC_JP 1 /* EUC for Japanese */ +#define EUC_CN 2 /* EUC for Chinese */ +#define EUC_KR 3 /* EUC for Korean */ +#define EUC_TW 4 /* EUC for Taiwan */ +#define UNICODE 5 /* Unicode UTF-8 */ +#define MULE_INTERNAL 6 /* Mule internal code */ +#define LATIN1 7 /* ISO-8859 Latin 1 */ +#define LATIN2 8 /* ISO-8859 Latin 2 */ +#define LATIN3 9 /* ISO-8859 Latin 3 */ +#define LATIN4 10 /* ISO-8859 Latin 4 */ +#define LATIN5 11 /* ISO-8859 Latin 5 */ +#define LATIN6 12 /* ISO-8859 Latin 6 */ +#define LATIN7 13 /* ISO-8859 Latin 7 */ +#define LATIN8 14 /* ISO-8859 Latin 8 */ +#define LATIN9 15 /* ISO-8859 Latin 9 */ +#define KOI8 16 /* KOI8-R/U */ +#define WIN 17 /* windows-1251 */ +#define ALT 18 /* Alternativny Variant (MS-DOS CP866) */ +#define SJIS 32 /* Shift JIS */ +#define BIG5 33 /* Big5 */ +#define WIN1250 34 /* windows-1250 */ + + +extern int multibyte_client_encoding; /* Multibyte client encoding. */ +extern int multibyte_status; /* Multibyte charcter status. */ + +void multibyte_init(void); +unsigned char *check_client_encoding(unsigned char *str); +int multibyte_char_check(unsigned char s); +unsigned char *multibyte_strchr(unsigned char *s, unsigned char c); diff --git a/src/interfaces/odbc/windev/notice.txt b/src/interfaces/odbc/windev/notice.txt new file mode 100644 index 0000000000..70df4401cd --- /dev/null +++ b/src/interfaces/odbc/windev/notice.txt @@ -0,0 +1,35 @@ + +/******************************************************************** + + PSQLODBC.DLL - A library to talk to the PostgreSQL DBMS using ODBC. + + + Copyright (C) 1998; Insight Distribution Systems + + The code contained in this library is based on code written by + Christian Czezatke and Dan McGuirk, (C) 1996. + + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library (see "license.txt"); if not, write to + the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA + 02139, USA. + + + How to contact the author: + + email: byronn@insightdist.com (Byron Nikolaidis) + + +***********************************************************************/ + diff --git a/src/interfaces/odbc/windev/odbcapi.c b/src/interfaces/odbc/windev/odbcapi.c new file mode 100644 index 0000000000..a87c35850c --- /dev/null +++ b/src/interfaces/odbc/windev/odbcapi.c @@ -0,0 +1,661 @@ +/*------- + * Module: odbcapi.c + * + * Description: This module contains routines related to + * preparing and executing an SQL statement. + * + * Classes: n/a + * + * API functions: SQLAllocConnect, SQLAllocEnv, SQLAllocStmt, + SQLBindCol, SQLCancel, SQLColumns, SQLConnect, + SQLDataSources, SQLDescribeCol, SQLDisconnect, + SQLError, SQLExecDirect, SQLExecute, SQLFetch, + SQLFreeConnect, SQLFreeEnv, SQLFreeStmt, + SQLGetConnectOption, SQLGetCursorName, SQLGetData, + SQLGetFunctions, SQLGetInfo, SQLGetStmtOption, + SQLGetTypeInfo, SQLNumResultCols, SQLParamData, + SQLPrepare, SQLPutData, SQLRowCount, + SQLSetConnectOption, SQLSetCursorName, SQLSetParam, + SQLSetStmtOption, SQLSpecialColumns, SQLStatistics, + SQLTables, SQLTransact, SQLColAttributes, + SQLColumnPrivileges, SQLDescribeParam, SQLExtendedFetch, + SQLForeignKeys, SQLMoreResults, SQLNativeSql, + SQLNumParams, SQLParamOptions, SQLPrimaryKeys, + SQLProcedureColumns, SQLProcedures, SQLSetPos, + SQLTablePrivileges, SQLBindParameter + *------- + */ + +#include "psqlodbc.h" +#include +#include + +#include "pgapifunc.h" +#include "connection.h" +#include "statement.h" + +RETCODE SQL_API +SQLAllocConnect(HENV EnvironmentHandle, + HDBC FAR * ConnectionHandle) +{ + mylog("[SQLAllocConnect]"); + return PGAPI_AllocConnect(EnvironmentHandle, ConnectionHandle); +} + +RETCODE SQL_API +SQLAllocEnv(HENV FAR * EnvironmentHandle) +{ + mylog("[SQLAllocEnv]"); + return PGAPI_AllocEnv(EnvironmentHandle); +} + +RETCODE SQL_API +SQLAllocStmt(HDBC ConnectionHandle, + HSTMT *StatementHandle) +{ + mylog("[SQLAllocStmt]"); + return PGAPI_AllocStmt(ConnectionHandle, StatementHandle); +} + +RETCODE SQL_API +SQLBindCol(HSTMT StatementHandle, + SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, + PTR TargetValue, SQLINTEGER BufferLength, + SQLINTEGER *StrLen_or_Ind) +{ + mylog("[SQLBindCol]"); + return PGAPI_BindCol(StatementHandle, ColumnNumber, + TargetType, TargetValue, BufferLength, StrLen_or_Ind); +} + +RETCODE SQL_API +SQLCancel(HSTMT StatementHandle) +{ + mylog("[SQLCancel]"); + return PGAPI_Cancel(StatementHandle); +} + +RETCODE SQL_API +SQLColumns(HSTMT StatementHandle, + SQLCHAR *CatalogName, SQLSMALLINT NameLength1, + SQLCHAR *SchemaName, SQLSMALLINT NameLength2, + SQLCHAR *TableName, SQLSMALLINT NameLength3, + SQLCHAR *ColumnName, SQLSMALLINT NameLength4) +{ + mylog("[SQLColumns]"); + return PGAPI_Columns(StatementHandle, CatalogName, NameLength1, + SchemaName, NameLength2, TableName, NameLength3, + ColumnName, NameLength4); +} + + +RETCODE SQL_API +SQLConnect(HDBC ConnectionHandle, + SQLCHAR *ServerName, SQLSMALLINT NameLength1, + SQLCHAR *UserName, SQLSMALLINT NameLength2, + SQLCHAR *Authentication, SQLSMALLINT NameLength3) +{ + mylog("[SQLConnect]"); + return PGAPI_Connect(ConnectionHandle, ServerName, NameLength1, + UserName, NameLength2, Authentication, NameLength3); +} + +RETCODE SQL_API +SQLDriverConnect(HDBC hdbc, + HWND hwnd, + UCHAR FAR * szConnStrIn, + SWORD cbConnStrIn, + UCHAR FAR * szConnStrOut, + SWORD cbConnStrOutMax, + SWORD FAR * pcbConnStrOut, + UWORD fDriverCompletion) +{ + mylog("[SQLDriverConnect]"); + return PGAPI_DriverConnect(hdbc, hwnd, szConnStrIn, cbConnStrIn, + szConnStrOut, cbConnStrOutMax, pcbConnStrOut, fDriverCompletion); +} +RETCODE SQL_API +SQLBrowseConnect( + HDBC hdbc, + SQLCHAR *szConnStrIn, + SQLSMALLINT cbConnStrIn, + SQLCHAR *szConnStrOut, + SQLSMALLINT cbConnStrOutMax, + SQLSMALLINT *pcbConnStrOut) +{ + mylog("[SQLBrowseConnect]"); + return PGAPI_BrowseConnect(hdbc, szConnStrIn, cbConnStrIn, + szConnStrOut, cbConnStrOutMax, pcbConnStrOut); +} + +RETCODE SQL_API +SQLDataSources(HENV EnvironmentHandle, + SQLUSMALLINT Direction, SQLCHAR *ServerName, + SQLSMALLINT BufferLength1, SQLSMALLINT *NameLength1, + SQLCHAR *Description, SQLSMALLINT BufferLength2, + SQLSMALLINT *NameLength2) +{ + mylog("[SQLDataSources]"); + + /* + * return PGAPI_DataSources(EnvironmentHandle, Direction, ServerName, + * BufferLength1, NameLength1, Description, BufferLength2, + * NameLength2); + */ + return SQL_ERROR; +} + +RETCODE SQL_API +SQLDescribeCol(HSTMT StatementHandle, + SQLUSMALLINT ColumnNumber, SQLCHAR *ColumnName, + SQLSMALLINT BufferLength, SQLSMALLINT *NameLength, + SQLSMALLINT *DataType, SQLUINTEGER *ColumnSize, + SQLSMALLINT *DecimalDigits, SQLSMALLINT *Nullable) +{ + mylog("[SQLDescribeCol]"); + return PGAPI_DescribeCol(StatementHandle, ColumnNumber, + ColumnName, BufferLength, NameLength, + DataType, ColumnSize, DecimalDigits, Nullable); +} + +RETCODE SQL_API +SQLDisconnect(HDBC ConnectionHandle) +{ + mylog("[SQLDisconnect]"); + return PGAPI_Disconnect(ConnectionHandle); +} + +RETCODE SQL_API +SQLError(HENV EnvironmentHandle, + HDBC ConnectionHandle, HSTMT StatementHandle, + SQLCHAR *Sqlstate, SQLINTEGER *NativeError, + SQLCHAR *MessageText, SQLSMALLINT BufferLength, + SQLSMALLINT *TextLength) +{ + mylog("[SQLError]"); + return PGAPI_Error(EnvironmentHandle, ConnectionHandle, StatementHandle, + Sqlstate, NativeError, MessageText, BufferLength, TextLength); +} + +RETCODE SQL_API +SQLExecDirect(HSTMT StatementHandle, + SQLCHAR *StatementText, SQLINTEGER TextLength) +{ + mylog("[SQLExecDirect]"); + return PGAPI_ExecDirect(StatementHandle, StatementText, TextLength); +} + +RETCODE SQL_API +SQLExecute(HSTMT StatementHandle) +{ + mylog("[SQLExecute]"); + return PGAPI_Execute(StatementHandle); +} + +RETCODE SQL_API +SQLFetch(HSTMT StatementHandle) +{ + static char *func = "SQLFetch"; + +#if (ODBCVER >= 0x0300) + StatementClass *stmt = (StatementClass *) StatementHandle; + ConnectionClass *conn = SC_get_conn(stmt); + + if (conn->driver_version >= 0x0300) + { + SQLUSMALLINT *rowStatusArray = stmt->options.rowStatusArray; + SQLINTEGER *pcRow = stmt->options.rowsFetched; + + mylog("[[%s]]", func); + return PGAPI_ExtendedFetch(StatementHandle, SQL_FETCH_NEXT, 0, + pcRow, rowStatusArray); + } +#endif + mylog("[%s]", func); + return PGAPI_Fetch(StatementHandle); +} + +RETCODE SQL_API +SQLFreeConnect(HDBC ConnectionHandle) +{ + mylog("[SQLFreeStmt]"); + return PGAPI_FreeConnect(ConnectionHandle); +} + +RETCODE SQL_API +SQLFreeEnv(HENV EnvironmentHandle) +{ + mylog("[SQLFreeEnv]"); + return PGAPI_FreeEnv(EnvironmentHandle); +} + +RETCODE SQL_API +SQLFreeStmt(HSTMT StatementHandle, + SQLUSMALLINT Option) +{ + mylog("[SQLFreeStmt]"); + return PGAPI_FreeStmt(StatementHandle, Option); +} + +RETCODE SQL_API +SQLGetConnectOption(HDBC ConnectionHandle, + SQLUSMALLINT Option, PTR Value) +{ + mylog("[SQLGetConnectOption]"); + return PGAPI_GetConnectOption(ConnectionHandle, Option, Value); +} +RETCODE SQL_API +SQLGetCursorName(HSTMT StatementHandle, + SQLCHAR *CursorName, SQLSMALLINT BufferLength, + SQLSMALLINT *NameLength) +{ + mylog("[SQLGetCursorName]"); + return PGAPI_GetCursorName(StatementHandle, CursorName, BufferLength, + NameLength); +} + +RETCODE SQL_API +SQLGetData(HSTMT StatementHandle, + SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, + PTR TargetValue, SQLINTEGER BufferLength, + SQLINTEGER *StrLen_or_Ind) +{ + mylog("[SQLGetData]"); + return PGAPI_GetData(StatementHandle, ColumnNumber, TargetType, + TargetValue, BufferLength, StrLen_or_Ind); +} + +RETCODE SQL_API +SQLGetFunctions(HDBC ConnectionHandle, + SQLUSMALLINT FunctionId, SQLUSMALLINT *Supported) +{ + mylog("[SQLGetFunctions]"); +#if (ODBCVER >= 0x0300) + if (FunctionId == SQL_API_ODBC3_ALL_FUNCTIONS) + return PGAPI_GetFunctions30(ConnectionHandle, FunctionId, Supported); +#endif + return PGAPI_GetFunctions(ConnectionHandle, FunctionId, Supported); +} +RETCODE SQL_API +SQLGetInfo(HDBC ConnectionHandle, + SQLUSMALLINT InfoType, PTR InfoValue, + SQLSMALLINT BufferLength, SQLSMALLINT *StringLength) +{ +#if (ODBCVER >= 0x0300) + RETCODE ret; + + mylog("[SQLGetInfo(30)]"); + if ((ret = PGAPI_GetInfo(ConnectionHandle, InfoType, InfoValue, + BufferLength, StringLength)) == SQL_ERROR) + { + if (((ConnectionClass *) ConnectionHandle)->driver_version >= 0x0300) + return PGAPI_GetInfo30(ConnectionHandle, InfoType, InfoValue, + BufferLength, StringLength); + } + return ret; +#else + mylog("[SQLGetInfo]"); + return PGAPI_GetInfo(ConnectionHandle, InfoType, InfoValue, + BufferLength, StringLength); +#endif +} + +RETCODE SQL_API +SQLGetStmtOption(HSTMT StatementHandle, + SQLUSMALLINT Option, PTR Value) +{ + mylog("[SQLGetStmtOption]"); + return PGAPI_GetStmtOption(StatementHandle, Option, Value); +} + +RETCODE SQL_API +SQLGetTypeInfo(HSTMT StatementHandle, + SQLSMALLINT DataType) +{ + mylog("[SQLGetTypeInfo]"); + return PGAPI_GetTypeInfo(StatementHandle, DataType); +} + +RETCODE SQL_API +SQLNumResultCols(HSTMT StatementHandle, + SQLSMALLINT *ColumnCount) +{ + mylog("[SQLNumResultCols]"); + return PGAPI_NumResultCols(StatementHandle, ColumnCount); +} + +RETCODE SQL_API +SQLParamData(HSTMT StatementHandle, + PTR *Value) +{ + mylog("[SQLParamData]"); + return PGAPI_ParamData(StatementHandle, Value); +} + +RETCODE SQL_API +SQLPrepare(HSTMT StatementHandle, + SQLCHAR *StatementText, SQLINTEGER TextLength) +{ + mylog("[SQLPrepare]"); + return PGAPI_Prepare(StatementHandle, StatementText, TextLength); +} + +RETCODE SQL_API +SQLPutData(HSTMT StatementHandle, + PTR Data, SQLINTEGER StrLen_or_Ind) +{ + mylog("[SQLPutData]"); + return PGAPI_PutData(StatementHandle, Data, StrLen_or_Ind); +} + +RETCODE SQL_API +SQLRowCount(HSTMT StatementHandle, + SQLINTEGER *RowCount) +{ + mylog("[SQLRowCount]"); + return PGAPI_RowCount(StatementHandle, RowCount); +} + +RETCODE SQL_API +SQLSetConnectOption(HDBC ConnectionHandle, + SQLUSMALLINT Option, SQLUINTEGER Value) +{ + mylog("[SQLSetConnectionOption]"); + return PGAPI_SetConnectOption(ConnectionHandle, Option, Value); +} + +RETCODE SQL_API +SQLSetCursorName(HSTMT StatementHandle, + SQLCHAR *CursorName, SQLSMALLINT NameLength) +{ + mylog("[SQLSetCursorName]"); + return PGAPI_SetCursorName(StatementHandle, CursorName, NameLength); +} + +RETCODE SQL_API +SQLSetParam(HSTMT StatementHandle, + SQLUSMALLINT ParameterNumber, SQLSMALLINT ValueType, + SQLSMALLINT ParameterType, SQLUINTEGER LengthPrecision, + SQLSMALLINT ParameterScale, PTR ParameterValue, + SQLINTEGER *StrLen_or_Ind) +{ + mylog("[SQLSetParam]"); + + /* + * return PGAPI_SetParam(StatementHandle, ParameterNumber, ValueType, + * ParameterType, LengthPrecision, ParameterScale, ParameterValue, + * StrLen_or_Ind); + */ + return SQL_ERROR; +} + +RETCODE SQL_API +SQLSetStmtOption(HSTMT StatementHandle, + SQLUSMALLINT Option, SQLUINTEGER Value) +{ + mylog("[SQLSetStmtOption]"); + return PGAPI_SetStmtOption(StatementHandle, Option, Value); +} + +RETCODE SQL_API +SQLSpecialColumns(HSTMT StatementHandle, + SQLUSMALLINT IdentifierType, SQLCHAR *CatalogName, + SQLSMALLINT NameLength1, SQLCHAR *SchemaName, + SQLSMALLINT NameLength2, SQLCHAR *TableName, + SQLSMALLINT NameLength3, SQLUSMALLINT Scope, + SQLUSMALLINT Nullable) +{ + mylog("[SQLSpecialColumns]"); + return PGAPI_SpecialColumns(StatementHandle, IdentifierType, CatalogName, + NameLength1, SchemaName, NameLength2, TableName, NameLength3, + Scope, Nullable); +} + +RETCODE SQL_API +SQLStatistics(HSTMT StatementHandle, + SQLCHAR *CatalogName, SQLSMALLINT NameLength1, + SQLCHAR *SchemaName, SQLSMALLINT NameLength2, + SQLCHAR *TableName, SQLSMALLINT NameLength3, + SQLUSMALLINT Unique, SQLUSMALLINT Reserved) +{ + mylog("[SQLStatistics]"); + return PGAPI_Statistics(StatementHandle, CatalogName, NameLength1, + SchemaName, NameLength2, TableName, NameLength3, Unique, + Reserved); +} + +RETCODE SQL_API +SQLTables(HSTMT StatementHandle, + SQLCHAR *CatalogName, SQLSMALLINT NameLength1, + SQLCHAR *SchemaName, SQLSMALLINT NameLength2, + SQLCHAR *TableName, SQLSMALLINT NameLength3, + SQLCHAR *TableType, SQLSMALLINT NameLength4) +{ + mylog("[SQLTables]"); + return PGAPI_Tables(StatementHandle, CatalogName, NameLength1, + SchemaName, NameLength2, TableName, NameLength3, + TableType, NameLength4); +} + +RETCODE SQL_API +SQLTransact(HENV EnvironmentHandle, + HDBC ConnectionHandle, SQLUSMALLINT CompletionType) +{ + mylog("[SQLTransact]"); + return PGAPI_Transact(EnvironmentHandle, ConnectionHandle, CompletionType); +} + +RETCODE SQL_API +SQLColAttributes( + HSTMT hstmt, + SQLUSMALLINT icol, + SQLUSMALLINT fDescType, + PTR rgbDesc, + SQLSMALLINT cbDescMax, + SQLSMALLINT *pcbDesc, + SQLINTEGER *pfDesc) +{ + mylog("[SQLColAttributes]"); + return PGAPI_ColAttributes(hstmt, icol, fDescType, rgbDesc, + cbDescMax, pcbDesc, pfDesc); +} + +RETCODE SQL_API +SQLColumnPrivileges( + HSTMT hstmt, + SQLCHAR *szCatalogName, + SQLSMALLINT cbCatalogName, + SQLCHAR *szSchemaName, + SQLSMALLINT cbSchemaName, + SQLCHAR *szTableName, + SQLSMALLINT cbTableName, + SQLCHAR *szColumnName, + SQLSMALLINT cbColumnName) +{ + mylog("[SQLColumnPrivileges]"); + return PGAPI_ColumnPrivileges(hstmt, szCatalogName, cbCatalogName, + szSchemaName, cbSchemaName, szTableName, cbTableName, + szColumnName, cbColumnName); +} + +RETCODE SQL_API +SQLDescribeParam( + HSTMT hstmt, + SQLUSMALLINT ipar, + SQLSMALLINT *pfSqlType, + SQLUINTEGER *pcbParamDef, + SQLSMALLINT *pibScale, + SQLSMALLINT *pfNullable) +{ + mylog("[SQLDescribeParam]"); + return PGAPI_DescribeParam(hstmt, ipar, pfSqlType, pcbParamDef, + pibScale, pfNullable); +} + +RETCODE SQL_API +SQLExtendedFetch( + HSTMT hstmt, + SQLUSMALLINT fFetchType, + SQLINTEGER irow, + SQLUINTEGER *pcrow, + SQLUSMALLINT *rgfRowStatus) +{ + mylog("[SQLExtendedFetch]"); + return PGAPI_ExtendedFetch(hstmt, fFetchType, irow, pcrow, rgfRowStatus); +} + +RETCODE SQL_API +SQLForeignKeys( + HSTMT hstmt, + SQLCHAR *szPkCatalogName, + SQLSMALLINT cbPkCatalogName, + SQLCHAR *szPkSchemaName, + SQLSMALLINT cbPkSchemaName, + SQLCHAR *szPkTableName, + SQLSMALLINT cbPkTableName, + SQLCHAR *szFkCatalogName, + SQLSMALLINT cbFkCatalogName, + SQLCHAR *szFkSchemaName, + SQLSMALLINT cbFkSchemaName, + SQLCHAR *szFkTableName, + SQLSMALLINT cbFkTableName) +{ + mylog("[SQLForeignKeys]"); + return PGAPI_ForeignKeys(hstmt, szPkCatalogName, cbPkCatalogName, + szPkSchemaName, cbPkSchemaName, szPkTableName, + cbPkTableName, szFkCatalogName, cbFkCatalogName, + szFkSchemaName, cbFkSchemaName, szFkTableName, cbFkTableName); +} + +RETCODE SQL_API +SQLMoreResults(HSTMT hstmt) +{ + mylog("[SQLMoreResults]"); + return PGAPI_MoreResults(hstmt); +} + +RETCODE SQL_API +SQLNativeSql( + HDBC hdbc, + SQLCHAR *szSqlStrIn, + SQLINTEGER cbSqlStrIn, + SQLCHAR *szSqlStr, + SQLINTEGER cbSqlStrMax, + SQLINTEGER *pcbSqlStr) +{ + mylog("[SQLNativeSql]"); + return PGAPI_NativeSql(hdbc, szSqlStrIn, cbSqlStrIn, szSqlStr, + cbSqlStrMax, pcbSqlStr); +} + +RETCODE SQL_API +SQLNumParams( + HSTMT hstmt, + SQLSMALLINT *pcpar) +{ + mylog("[SQLNumParams]"); + return PGAPI_NumParams(hstmt, pcpar); +} + +RETCODE SQL_API +SQLParamOptions( + HSTMT hstmt, + SQLUINTEGER crow, + SQLUINTEGER *pirow) +{ + mylog("[SQLParamOptions]"); + return PGAPI_ParamOptions(hstmt, crow, pirow); +} + +RETCODE SQL_API +SQLPrimaryKeys( + HSTMT hstmt, + SQLCHAR *szCatalogName, + SQLSMALLINT cbCatalogName, + SQLCHAR *szSchemaName, + SQLSMALLINT cbSchemaName, + SQLCHAR *szTableName, + SQLSMALLINT cbTableName) +{ + mylog("[SQLPrimaryKeys]"); + return PGAPI_PrimaryKeys(hstmt, szCatalogName, cbCatalogName, + szSchemaName, cbSchemaName, szTableName, cbTableName); +} + +RETCODE SQL_API +SQLProcedureColumns( + HSTMT hstmt, + SQLCHAR *szCatalogName, + SQLSMALLINT cbCatalogName, + SQLCHAR *szSchemaName, + SQLSMALLINT cbSchemaName, + SQLCHAR *szProcName, + SQLSMALLINT cbProcName, + SQLCHAR *szColumnName, + SQLSMALLINT cbColumnName) +{ + mylog("[SQLProcedureColumns]"); + return PGAPI_ProcedureColumns(hstmt, szCatalogName, cbCatalogName, + szSchemaName, cbSchemaName, szProcName, cbProcName, + szColumnName, cbColumnName); +} + +RETCODE SQL_API +SQLProcedures( + HSTMT hstmt, + SQLCHAR *szCatalogName, + SQLSMALLINT cbCatalogName, + SQLCHAR *szSchemaName, + SQLSMALLINT cbSchemaName, + SQLCHAR *szProcName, + SQLSMALLINT cbProcName) +{ + mylog("[SQLProcedures]"); + return PGAPI_Procedures(hstmt, szCatalogName, cbCatalogName, + szSchemaName, cbSchemaName, szProcName, cbProcName); +} + +RETCODE SQL_API +SQLSetPos( + HSTMT hstmt, + SQLUSMALLINT irow, + SQLUSMALLINT fOption, + SQLUSMALLINT fLock) +{ + mylog("[SQLSetPos]"); + return PGAPI_SetPos(hstmt, irow, fOption, fLock); +} + +RETCODE SQL_API +SQLTablePrivileges( + HSTMT hstmt, + SQLCHAR *szCatalogName, + SQLSMALLINT cbCatalogName, + SQLCHAR *szSchemaName, + SQLSMALLINT cbSchemaName, + SQLCHAR *szTableName, + SQLSMALLINT cbTableName) +{ + mylog("[SQLTablePrivileges]"); + return PGAPI_TablePrivileges(hstmt, szCatalogName, cbCatalogName, + szSchemaName, cbSchemaName, szTableName, cbTableName); +} + +RETCODE SQL_API +SQLBindParameter( + HSTMT hstmt, + SQLUSMALLINT ipar, + SQLSMALLINT fParamType, + SQLSMALLINT fCType, + SQLSMALLINT fSqlType, + SQLUINTEGER cbColDef, + SQLSMALLINT ibScale, + PTR rgbValue, + SQLINTEGER cbValueMax, + SQLINTEGER *pcbValue) +{ + mylog("[SQLBindParameter]"); + return PGAPI_BindParameter(hstmt, ipar, fParamType, fCType, + fSqlType, cbColDef, ibScale, rgbValue, cbValueMax, + pcbValue); +} diff --git a/src/interfaces/odbc/windev/odbcapi30.c b/src/interfaces/odbc/windev/odbcapi30.c new file mode 100644 index 0000000000..8ad1dba7e6 --- /dev/null +++ b/src/interfaces/odbc/windev/odbcapi30.c @@ -0,0 +1,752 @@ +/*------- + * Module: odbcapi30.c + * + * Description: This module contains routines related to ODBC 3.0 + * most of their implementations are temporary + * and must be rewritten properly. + * 2001/07/23 inoue + * + * Classes: n/a + * + * API functions: SQLAllocHandle, SQLBindParam, SQLCloseCursor, + SQLColAttribute, SQLCopyDesc, SQLEndTran, + SQLFetchScroll, SQLFreeHandle, SQLGetDescField, + SQLGetDescRec, SQLGetDiagField, SQLGetDiagRec, + SQLGetEnvAttr, SQLGetConnectAttr, SQLGetStmtAttr, + SQLSetConnectAttr, SQLSetDescField, SQLSetDescRec, + SQLSetEnvAttr, SQLSetStmtAttr, SQLBulkOperations + *------- + */ + +#ifndef ODBCVER +#define ODBCVER 0x0300 +#endif +#include "psqlodbc.h" +#include +#include + +#include "environ.h" +#include "connection.h" +#include "statement.h" +#include "pgapifunc.h" + +/* SQLAllocConnect/SQLAllocEnv/SQLAllocStmt -> SQLAllocHandle */ +RETCODE SQL_API +SQLAllocHandle(SQLSMALLINT HandleType, + SQLHANDLE InputHandle, SQLHANDLE * OutputHandle) +{ + mylog("[[SQLAllocHandle]]"); + switch (HandleType) + { + case SQL_HANDLE_ENV: + return PGAPI_AllocEnv(OutputHandle); + case SQL_HANDLE_DBC: + return PGAPI_AllocConnect(InputHandle, OutputHandle); + case SQL_HANDLE_STMT: + return PGAPI_AllocStmt(InputHandle, OutputHandle); + default: + break; + } + return SQL_ERROR; +} + +/* SQLBindParameter/SQLSetParam -> SQLBindParam */ +RETCODE SQL_API +SQLBindParam(HSTMT StatementHandle, + SQLUSMALLINT ParameterNumber, SQLSMALLINT ValueType, + SQLSMALLINT ParameterType, SQLUINTEGER LengthPrecision, + SQLSMALLINT ParameterScale, PTR ParameterValue, + SQLINTEGER *StrLen_or_Ind) +{ + int BufferLength = 512; /* Is it OK ? */ + + mylog("[[SQLBindParam]]"); + return PGAPI_BindParameter(StatementHandle, ParameterNumber, SQL_PARAM_INPUT, ValueType, ParameterType, LengthPrecision, ParameterScale, ParameterValue, BufferLength, StrLen_or_Ind); +} + +/* New function */ +RETCODE SQL_API +SQLCloseCursor(HSTMT StatementHandle) +{ + mylog("[[SQLCloseCursor]]"); + return PGAPI_FreeStmt(StatementHandle, SQL_CLOSE); +} + +/* SQLColAttributes -> SQLColAttribute */ +RETCODE SQL_API +SQLColAttribute(HSTMT StatementHandle, + SQLUSMALLINT ColumnNumber, SQLUSMALLINT FieldIdentifier, + PTR CharacterAttribute, SQLSMALLINT BufferLength, + SQLSMALLINT *StringLength, PTR NumericAttribute) +{ + mylog("[[SQLColAttribute]]"); + return PGAPI_ColAttributes(StatementHandle, ColumnNumber, + FieldIdentifier, CharacterAttribute, BufferLength, + StringLength, NumericAttribute); +} + +/* new function */ +RETCODE SQL_API +SQLCopyDesc(SQLHDESC SourceDescHandle, + SQLHDESC TargetDescHandle) +{ + mylog("[[SQLCopyDesc]]\n"); + return SQL_ERROR; +} + +/* SQLTransact -> SQLEndTran */ +RETCODE SQL_API +SQLEndTran(SQLSMALLINT HandleType, SQLHANDLE Handle, + SQLSMALLINT CompletionType) +{ + mylog("[[SQLEndTran]]"); + switch (HandleType) + { + case SQL_HANDLE_ENV: + return PGAPI_Transact(Handle, SQL_NULL_HDBC, CompletionType); + case SQL_HANDLE_DBC: + return PGAPI_Transact(SQL_NULL_HENV, Handle, CompletionType); + default: + break; + } + return SQL_ERROR; /* SQLSTATE HY092 ("Invalid + * attribute/option identifier") */ + +} + +/* SQLExtendedFetch -> SQLFetchScroll */ +RETCODE SQL_API +SQLFetchScroll(HSTMT StatementHandle, + SQLSMALLINT FetchOrientation, SQLINTEGER FetchOffset) +{ + static char *func = "SQLFetchScroll"; + StatementClass *stmt = (StatementClass *) StatementHandle; + RETCODE ret; + SQLUSMALLINT *rowStatusArray = stmt->options.rowStatusArray; + SQLINTEGER *pcRow = stmt->options.rowsFetched; + + mylog("[[%s]] %d,%d\n", func, FetchOrientation, FetchOffset); + if (FetchOrientation == SQL_FETCH_BOOKMARK) + { + if (stmt->options.bookmark_ptr) + FetchOffset += *((Int4 *) stmt->options.bookmark_ptr); + else + { + stmt->errornumber = STMT_SEQUENCE_ERROR; + stmt->errormsg = "Bookmark isn't specifed yet"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + } + ret = PGAPI_ExtendedFetch(StatementHandle, FetchOrientation, FetchOffset, + pcRow, rowStatusArray); + if (ret != SQL_SUCCESS) + mylog("%s return = %d\n", func, ret); + return ret; +} + +/* SQLFree(Connect/Env/Stmt) -> SQLFreeHandle */ +RETCODE SQL_API +SQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle) +{ + mylog("[[SQLFreeHandle]]"); + switch (HandleType) + { + case SQL_HANDLE_ENV: + return PGAPI_FreeEnv(Handle); + case SQL_HANDLE_DBC: + return PGAPI_FreeConnect(Handle); + case SQL_HANDLE_STMT: + return PGAPI_FreeStmt(Handle, SQL_DROP); + default: + break; + } + return SQL_ERROR; +} + +/* new function */ +RETCODE SQL_API +SQLGetDescField(SQLHDESC DescriptorHandle, + SQLSMALLINT RecNumber, SQLSMALLINT FieldIdentifier, + PTR Value, SQLINTEGER BufferLength, + SQLINTEGER *StringLength) +{ + mylog("[[SQLGetDescField]]\n"); + return SQL_ERROR; +} + +/* new function */ +RETCODE SQL_API +SQLGetDescRec(SQLHDESC DescriptorHandle, + SQLSMALLINT RecNumber, SQLCHAR *Name, + SQLSMALLINT BufferLength, SQLSMALLINT *StringLength, + SQLSMALLINT *Type, SQLSMALLINT *SubType, + SQLINTEGER *Length, SQLSMALLINT *Precision, + SQLSMALLINT *Scale, SQLSMALLINT *Nullable) +{ + mylog("[[SQLGetDescRec]]\n"); + return SQL_ERROR; +} + +/* new function */ +RETCODE SQL_API +SQLGetDiagField(SQLSMALLINT HandleType, SQLHANDLE Handle, + SQLSMALLINT RecNumber, SQLSMALLINT DiagIdentifier, + PTR DiagInfo, SQLSMALLINT BufferLength, + SQLSMALLINT *StringLength) +{ + mylog("[[SQLGetDiagField]]\n"); + return SQL_ERROR; +} + +/* SQLError -> SQLDiagRec */ +RETCODE SQL_API +SQLGetDiagRec(SQLSMALLINT HandleType, SQLHANDLE Handle, + SQLSMALLINT RecNumber, SQLCHAR *Sqlstate, + SQLINTEGER *NativeError, SQLCHAR *MessageText, + SQLSMALLINT BufferLength, SQLSMALLINT *TextLength) +{ + RETCODE ret; + + mylog("[[SQLGetDiagRec]]\n"); + switch (HandleType) + { + case SQL_HANDLE_ENV: + ret = PGAPI_Error(Handle, NULL, NULL, Sqlstate, NativeError, + MessageText, BufferLength, TextLength); + break; + case SQL_HANDLE_DBC: + ret = PGAPI_Error(NULL, Handle, NULL, Sqlstate, NativeError, + MessageText, BufferLength, TextLength); + break; + case SQL_HANDLE_STMT: + ret = PGAPI_Error(NULL, NULL, Handle, Sqlstate, NativeError, + MessageText, BufferLength, TextLength); + break; + default: + ret = SQL_ERROR; + } + if (ret == SQL_SUCCESS_WITH_INFO && + BufferLength == 0 && + *TextLength) + { + SQLSMALLINT BufferLength = *TextLength + 4; + SQLCHAR *MessageText = malloc(BufferLength); + + ret = SQLGetDiagRec(HandleType, Handle, RecNumber, Sqlstate, + NativeError, MessageText, BufferLength, + TextLength); + free(MessageText); + } + return ret; +} + +/* new function */ +RETCODE SQL_API +SQLGetEnvAttr(HENV EnvironmentHandle, + SQLINTEGER Attribute, PTR Value, + SQLINTEGER BufferLength, SQLINTEGER *StringLength) +{ + EnvironmentClass *env = (EnvironmentClass *) EnvironmentHandle; + + mylog("[[SQLGetEnvAttr]] %d\n", Attribute); + switch (Attribute) + { + case SQL_ATTR_CONNECTION_POOLING: + *((unsigned int *) Value) = SQL_CP_OFF; + break; + case SQL_ATTR_CP_MATCH: + *((unsigned int *) Value) = SQL_CP_RELAXED_MATCH; + break; + case SQL_ATTR_ODBC_VERSION: + *((unsigned int *) Value) = SQL_OV_ODBC3; + break; + case SQL_ATTR_OUTPUT_NTS: + *((unsigned int *) Value) = SQL_TRUE; + break; + default: + env->errornumber = CONN_INVALID_ARGUMENT_NO; + return SQL_ERROR; + } + return SQL_SUCCESS; +} + +/* SQLGetConnectOption -> SQLGetconnectAttr */ +RETCODE SQL_API +SQLGetConnectAttr(HDBC ConnectionHandle, + SQLINTEGER Attribute, PTR Value, + SQLINTEGER BufferLength, SQLINTEGER *StringLength) +{ + ConnectionClass *conn = (ConnectionClass *) ConnectionHandle; + + mylog("[[SQLGetConnectAttr]] %d\n", Attribute); + switch (Attribute) + { + case SQL_ATTR_ASYNC_ENABLE: + case SQL_ATTR_AUTO_IPD: + case SQL_ATTR_CONNECTION_DEAD: + case SQL_ATTR_CONNECTION_TIMEOUT: + case SQL_ATTR_METADATA_ID: + conn->errornumber = STMT_INVALID_OPTION_IDENTIFIER; + conn->errormsg = "Unsupported connection option (Set)"; + return SQL_ERROR; + } + return PGAPI_GetConnectOption(ConnectionHandle, (UWORD) Attribute, Value); +} + +/* SQLGetStmtOption -> SQLGetStmtAttr */ +RETCODE SQL_API +SQLGetStmtAttr(HSTMT StatementHandle, + SQLINTEGER Attribute, PTR Value, + SQLINTEGER BufferLength, SQLINTEGER *StringLength) +{ + static char *func = "SQLGetStmtAttr"; + StatementClass *stmt = (StatementClass *) StatementHandle; + RETCODE ret = SQL_SUCCESS; + int len = 0; + + mylog("[[%s]] %d\n", func, Attribute); + switch (Attribute) + { + case SQL_ATTR_FETCH_BOOKMARK_PTR: /* 16 */ + Value = stmt->options.bookmark_ptr; + + len = 4; + break; + case SQL_ATTR_ROW_STATUS_PTR: /* 25 */ + Value = stmt->options.rowStatusArray; + + len = 4; + break; + case SQL_ATTR_ROWS_FETCHED_PTR: /* 26 */ + Value = stmt->options.rowsFetched; + + len = 4; + break; + case SQL_ATTR_ROW_ARRAY_SIZE: /* 27 */ + *((SQLUINTEGER *) Value) = stmt->options.rowset_size; + len = 4; + break; + case SQL_ATTR_APP_ROW_DESC: /* 10010 */ + *((HSTMT *) Value) = StatementHandle; /* this is useless */ + len = 4; + break; + case SQL_ATTR_APP_PARAM_DESC: /* 10011 */ + *((HSTMT *) Value) = StatementHandle; /* this is useless */ + len = 4; + break; + case SQL_ATTR_IMP_ROW_DESC: /* 10012 */ + *((HSTMT *) Value) = StatementHandle; /* this is useless */ + len = 4; + break; + case SQL_ATTR_IMP_PARAM_DESC: /* 10013 */ + *((HSTMT *) Value) = StatementHandle; /* this is useless */ + len = 4; + break; + case SQL_ATTR_AUTO_IPD: /* 10001 */ + /* case SQL_ATTR_ROW_BIND_TYPE: ** == SQL_BIND_TYPE(ODBC2.0) */ + case SQL_ATTR_PARAMSET_SIZE: /* 22 */ + case SQL_ATTR_PARAM_STATUS_PTR: /* 20 */ + case SQL_ATTR_PARAMS_PROCESSED_PTR: /* 21 */ + + case SQL_ATTR_CURSOR_SCROLLABLE: /* -1 */ + case SQL_ATTR_CURSOR_SENSITIVITY: /* -2 */ + + case SQL_ATTR_ENABLE_AUTO_IPD: /* 15 */ + case SQL_ATTR_METADATA_ID: /* 10014 */ + + /* + * case SQL_ATTR_PREDICATE_PTR: case + * SQL_ATTR_PREDICATE_OCTET_LENGTH_PTR: + */ + case SQL_ATTR_PARAM_BIND_OFFSET_PTR: /* 17 */ + case SQL_ATTR_PARAM_BIND_TYPE: /* 18 */ + case SQL_ATTR_PARAM_OPERATION_PTR: /* 19 */ + case SQL_ATTR_ROW_BIND_OFFSET_PTR: /* 23 */ + case SQL_ATTR_ROW_OPERATION_PTR: /* 24 */ + stmt->errornumber = STMT_INVALID_OPTION_IDENTIFIER; + stmt->errormsg = "Unsupported statement option (Get)"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + default: + len = 4; + ret = PGAPI_GetStmtOption(StatementHandle, (UWORD) Attribute, Value); + } + if (ret == SQL_SUCCESS && StringLength) + *StringLength = len; + return ret; +} + +/* SQLSetConnectOption -> SQLSetConnectAttr */ +RETCODE SQL_API +SQLSetConnectAttr(HDBC ConnectionHandle, + SQLINTEGER Attribute, PTR Value, + SQLINTEGER StringLength) +{ + ConnectionClass *conn = (ConnectionClass *) ConnectionHandle; + + mylog("[[SQLSetConnectAttr]] %d\n", Attribute); + switch (Attribute) + { + case SQL_ATTR_ASYNC_ENABLE: + case SQL_ATTR_AUTO_IPD: + case SQL_ATTR_CONNECTION_DEAD: + case SQL_ATTR_CONNECTION_TIMEOUT: + case SQL_ATTR_METADATA_ID: + conn->errornumber = STMT_INVALID_OPTION_IDENTIFIER; + conn->errormsg = "Unsupported connection option (Set)"; + return SQL_ERROR; + } + return PGAPI_SetConnectOption(ConnectionHandle, (UWORD) Attribute, (UDWORD) Value); +} + +/* new function */ +RETCODE SQL_API +SQLSetDescField(SQLHDESC DescriptorHandle, + SQLSMALLINT RecNumber, SQLSMALLINT FieldIdentifier, + PTR Value, SQLINTEGER BufferLength) +{ + mylog("[[SQLSetDescField]]\n"); + return SQL_ERROR; +} + +/* new fucntion */ +RETCODE SQL_API +SQLSetDescRec(SQLHDESC DescriptorHandle, + SQLSMALLINT RecNumber, SQLSMALLINT Type, + SQLSMALLINT SubType, SQLINTEGER Length, + SQLSMALLINT Precision, SQLSMALLINT Scale, + PTR Data, SQLINTEGER *StringLength, + SQLINTEGER *Indicator) +{ + mylog("[[SQLsetDescRec]]\n"); + return SQL_ERROR; +} + +/* new function */ +RETCODE SQL_API +SQLSetEnvAttr(HENV EnvironmentHandle, + SQLINTEGER Attribute, PTR Value, + SQLINTEGER StringLength) +{ + EnvironmentClass *env = (EnvironmentClass *) EnvironmentHandle; + + mylog("[[SQLSetEnvAttr]] att=%d,%u\n", Attribute, Value); + switch (Attribute) + { + case SQL_ATTR_CONNECTION_POOLING: + if ((SQLUINTEGER) Value == SQL_CP_OFF) + return SQL_SUCCESS; + break; + case SQL_ATTR_CP_MATCH: + /* *((unsigned int *) Value) = SQL_CP_RELAXED_MATCH; */ + return SQL_SUCCESS; + case SQL_ATTR_ODBC_VERSION: + if ((SQLUINTEGER) Value == SQL_OV_ODBC2) + return SQL_SUCCESS; + break; + case SQL_ATTR_OUTPUT_NTS: + if ((SQLUINTEGER) Value == SQL_TRUE) + return SQL_SUCCESS; + break; + default: + env->errornumber = CONN_INVALID_ARGUMENT_NO; + return SQL_ERROR; + } + env->errornumber = CONN_OPTION_VALUE_CHANGED; + env->errormsg = "SetEnv changed to "; + return SQL_SUCCESS_WITH_INFO; +} + +/* SQLSet(Param/Scroll/Stmt)Option -> SQLSetStmtAttr */ +RETCODE SQL_API +SQLSetStmtAttr(HSTMT StatementHandle, + SQLINTEGER Attribute, PTR Value, + SQLINTEGER StringLength) +{ + static char *func = "SQLSetStmtAttr"; + StatementClass *stmt = (StatementClass *) StatementHandle; + UDWORD rowcount; + + mylog("[[%s]] %d,%u\n", func, Attribute, Value); + switch (Attribute) + { + case SQL_ATTR_PARAMSET_SIZE: /* 22 */ + return PGAPI_ParamOptions(StatementHandle, (UWORD) Value, &rowcount); + case SQL_ATTR_PARAM_STATUS_PTR: /* 20 */ + case SQL_ATTR_PARAMS_PROCESSED_PTR: /* 21 */ + + case SQL_ATTR_CURSOR_SCROLLABLE: /* -1 */ + case SQL_ATTR_CURSOR_SENSITIVITY: /* -2 */ + + case SQL_ATTR_ENABLE_AUTO_IPD: /* 15 */ + + case SQL_ATTR_APP_ROW_DESC: /* 10010 */ + case SQL_ATTR_APP_PARAM_DESC: /* 10011 */ + case SQL_ATTR_AUTO_IPD: /* 10001 */ + /* case SQL_ATTR_ROW_BIND_TYPE: ** == SQL_BIND_TYPE(ODBC2.0) */ + case SQL_ATTR_IMP_ROW_DESC: /* 10012 */ + case SQL_ATTR_IMP_PARAM_DESC: /* 10013 */ + case SQL_ATTR_METADATA_ID: /* 10014 */ + + /* + * case SQL_ATTR_PREDICATE_PTR: case + * SQL_ATTR_PREDICATE_OCTET_LENGTH_PTR: + */ + case SQL_ATTR_PARAM_BIND_OFFSET_PTR: /* 17 */ + case SQL_ATTR_PARAM_BIND_TYPE: /* 18 */ + case SQL_ATTR_PARAM_OPERATION_PTR: /* 19 */ + case SQL_ATTR_ROW_BIND_OFFSET_PTR: /* 23 */ + case SQL_ATTR_ROW_OPERATION_PTR: /* 24 */ + stmt->errornumber = STMT_INVALID_OPTION_IDENTIFIER; + stmt->errormsg = "Unsupported statement option (Set)"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + + case SQL_ATTR_FETCH_BOOKMARK_PTR: /* 16 */ + stmt->options.bookmark_ptr = Value; + + break; + case SQL_ATTR_ROW_STATUS_PTR: /* 25 */ + stmt->options.rowStatusArray = (SQLUSMALLINT *) Value; + + break; + case SQL_ATTR_ROWS_FETCHED_PTR: /* 26 */ + stmt->options.rowsFetched = (SQLUINTEGER *) Value; + + break; + case SQL_ATTR_ROW_ARRAY_SIZE: /* 27 */ + stmt->options.rowset_size = (SQLUINTEGER) Value; + + break; + default: + return PGAPI_SetStmtOption(StatementHandle, (UWORD) Attribute, (UDWORD) Value); + } + return SQL_SUCCESS; +} + +#define SQL_FUNC_ESET(pfExists, uwAPI) \ + (*(((UWORD*) (pfExists)) + ((uwAPI) >> 4)) \ + |= (1 << ((uwAPI) & 0x000F)) \ + ) +RETCODE SQL_API +PGAPI_GetFunctions30(HDBC hdbc, UWORD fFunction, UWORD FAR * pfExists) +{ + if (fFunction != SQL_API_ODBC3_ALL_FUNCTIONS) + return SQL_ERROR; + memset(pfExists, 0, sizeof(UWORD) * SQL_API_ODBC3_ALL_FUNCTIONS_SIZE); + + /* SQL_FUNC_ESET(pfExists, SQL_API_SQLALLOCCONNECT); 1 deprecated */ + /* SQL_FUNC_ESET(pfExists, SQL_API_SQLALLOCENV); 2 deprecated */ + /* SQL_FUNC_ESET(pfExists, SQL_API_SQLALLOCSTMT); 3 deprecated */ + + /* + * for (i = SQL_API_SQLBINDCOL; i <= 23; i++) SQL_FUNC_ESET(pfExists, + * i); + */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLBINDCOL); /* 4 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLCANCEL); /* 5 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLCOLATTRIBUTE); /* 6 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLCONNECT); /* 7 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLDESCRIBECOL); /* 8 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLDISCONNECT); /* 9 */ + /* SQL_FUNC_ESET(pfExists, SQL_API_SQLERROR); 10 deprecated */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLEXECDIRECT); /* 11 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLEXECUTE); /* 12 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLFETCH); /* 13 */ + /* SQL_FUNC_ESET(pfExists, SQL_API_SQLFREECONNECT); 14 deprecated */ + /* SQL_FUNC_ESET(pfExists, SQL_API_SQLFREEENV); 15 deprecated */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLFREESTMT); /* 16 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLGETCURSORNAME); /* 17 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLNUMRESULTCOLS); /* 18 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLPREPARE); /* 19 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLROWCOUNT); /* 20 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLSETCURSORNAME); /* 21 */ + /* SQL_FUNC_ESET(pfExists, SQL_API_SQLSETPARAM); 22 deprecated */ + /* SQL_FUNC_ESET(pfExists, SQL_API_SQLTRANSACT); 23 deprecated */ + + /* + * for (i = 40; i < SQL_API_SQLEXTENDEDFETCH; i++) + * SQL_FUNC_ESET(pfExists, i); + */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLCOLUMNS); /* 40 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLDRIVERCONNECT); /* 41 */ + /* SQL_FUNC_ESET(pfExists, SQL_API_SQLGETCONNECTOPTION); 42 deprecated */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLGETDATA); /* 43 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLGETFUNCTIONS); /* 44 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLGETINFO); /* 45 */ + /* SQL_FUNC_ESET(pfExists, SQL_API_SQLGETSTMTOPTION); 46 deprecated */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLGETTYPEINFO); /* 47 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLPARAMDATA); /* 48 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLPUTDATA); /* 49 */ + + /* + * SQL_FUNC_ESET(pfExists, SQL_API_SQLSETCONNECTIONOPTION); 50 + * deprecated + */ + /* SQL_FUNC_ESET(pfExists, SQL_API_SQLSETSTMTOPTION); 51 deprecated */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLSPECIALCOLUMNS); /* 52 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLSTATISTICS); /* 53 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLTABLES); /* 54 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLBROWSECONNECT); /* 55 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLCOLUMNPRIVILEGES); /* 56 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLDATASOURCES); /* 57 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLDESCRIBEPARAM); /* 58 */ + /* SQL_FUNC_ESET(pfExists, SQL_API_SQLEXTENDEDFETCH); 59 deprecated */ + + /* + * for (++i; i < SQL_API_SQLBINDPARAMETER; i++) + * SQL_FUNC_ESET(pfExists, i); + */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLFOREIGNKEYS); /* 60 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLMORERESULTS); /* 61 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLNATIVESQL); /* 62 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLNUMPARAMS); /* 63 */ + /* SQL_FUNC_ESET(pfExists, SQL_API_SQLPARAMOPTIONS); 64 deprecated */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLPRIMARYKEYS); /* 65 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLPROCEDURECOLUMNS); /* 66 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLPROCEDURES); /* 67 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLSETPOS); /* 68 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLSETSCROLLOPTIONS); /* 69 deprecated */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLTABLEPRIVILEGES); /* 70 */ + /* SQL_FUNC_ESET(pfExists, SQL_API_SQLDRIVERS); */ /* 71 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLBINDPARAMETER); /* 72 */ + + SQL_FUNC_ESET(pfExists, SQL_API_SQLALLOCHANDLE); /* 1001 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLBINDPARAM); /* 1002 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLCLOSECURSOR); /* 1003 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLCOPYDESC); /* 1004 not implemented + * yet */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLENDTRAN); /* 1005 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLFREEHANDLE); /* 1006 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLGETCONNECTATTR); /* 1007 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLGETDESCFIELD); /* 1008 not implemented + * yet */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLGETDESCREC); /* 1009 not implemented + * yet */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLGETDIAGFIELD); /* 1010 not implemented + * yet */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLGETDIAGREC); /* 1011 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLGETENVATTR); /* 1012 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLGETSTMTATTR); /* 1014 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLSETCONNECTATTR); /* 1016 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLSETDESCFIELD); /* 1017 not implemeted + * yet */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLSETDESCREC); /* 1018 not implemented + * yet */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLSETENVATTR); /* 1019 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLSETSTMTATTR); /* 1020 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLFETCHSCROLL); /* 1021 */ + SQL_FUNC_ESET(pfExists, SQL_API_SQLBULKOPERATIONS); /* 24 not implemented + * yet */ + + return SQL_SUCCESS; +} +RETCODE SQL_API +PGAPI_GetInfo30(HDBC hdbc, UWORD fInfoType, PTR rgbInfoValue, + SWORD cbInfoValueMax, SWORD FAR * pcbInfoValue) +{ + static char *func = "PGAPI_GetInfo30"; + ConnectionClass *conn = (ConnectionClass *) hdbc; + char *p = NULL; + int len = 0, + value = 0; + RETCODE result; + + switch (fInfoType) + { + case SQL_DYNAMIC_CURSOR_ATTRIBUTES1: + len = 4; + value = 0; + break; + case SQL_DYNAMIC_CURSOR_ATTRIBUTES2: + len = 4; + value = 0; + break; + + case SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1: + len = 4; + value = SQL_CA1_NEXT | SQL_CA1_ABSOLUTE | + SQL_CA1_RELATIVE | SQL_CA1_BOOKMARK; + break; + case SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES2: + len = 4; + value = 0; + break; + case SQL_KEYSET_CURSOR_ATTRIBUTES1: + len = 4; + value = SQL_CA1_NEXT | SQL_CA1_ABSOLUTE + | SQL_CA1_RELATIVE | SQL_CA1_BOOKMARK + | SQL_CA1_LOCK_NO_CHANGE | SQL_CA1_POS_POSITION + | SQL_CA1_POS_UPDATE | SQL_CA1_POS_DELETE + | SQL_CA1_POS_REFRESH + | SQL_CA1_BULK_ADD + | SQL_CA1_BULK_UPDATE_BY_BOOKMARK + | SQL_CA1_BULK_DELETE_BY_BOOKMARK + | SQL_CA1_BULK_FETCH_BY_BOOKMARK + ; + break; + case SQL_KEYSET_CURSOR_ATTRIBUTES2: + len = 4; + value = SQL_CA2_OPT_ROWVER_CONCURRENCY | + SQL_CA2_SENSITIVITY_ADDITIONS | + SQL_CA2_SENSITIVITY_DELETIONS | + SQL_CA2_SENSITIVITY_UPDATES; + break; + + case SQL_STATIC_CURSOR_ATTRIBUTES1: + len = 4; + value = SQL_CA1_NEXT | SQL_CA1_ABSOLUTE | + SQL_CA1_RELATIVE | SQL_CA1_BOOKMARK | + SQL_CA1_LOCK_NO_CHANGE | SQL_CA1_POS_POSITION | + SQL_CA1_POS_UPDATE | SQL_CA1_POS_DELETE | + SQL_CA1_POS_REFRESH; + break; + case SQL_STATIC_CURSOR_ATTRIBUTES2: + len = 4; + value = SQL_CA2_OPT_ROWVER_CONCURRENCY | + SQL_CA2_SENSITIVITY_ADDITIONS | + SQL_CA2_SENSITIVITY_DELETIONS | + SQL_CA2_SENSITIVITY_UPDATES; + break; + default: + /* unrecognized key */ + conn->errormsg = "Unrecognized key passed to SQLGetInfo."; + conn->errornumber = CONN_NOT_IMPLEMENTED_ERROR; + CC_log_error(func, "", conn); + return SQL_ERROR; + } + result = SQL_SUCCESS; + if (p) + { + /* char/binary data */ + len = strlen(p); + + if (rgbInfoValue) + { + strncpy_null((char *) rgbInfoValue, p, (size_t) cbInfoValueMax); + + if (len >= cbInfoValueMax) + { + result = SQL_SUCCESS_WITH_INFO; + conn->errornumber = STMT_TRUNCATED; + conn->errormsg = "The buffer was too small for tthe InfoValue."; + } + } + } + else + { + /* numeric data */ + if (rgbInfoValue) + { + if (len == 2) + *((WORD *) rgbInfoValue) = (WORD) value; + else if (len == 4) + *((DWORD *) rgbInfoValue) = (DWORD) value; + } + } + + if (pcbInfoValue) + *pcbInfoValue = len; + return result; +} diff --git a/src/interfaces/odbc/windev/options.c b/src/interfaces/odbc/windev/options.c new file mode 100644 index 0000000000..bf2707f4da --- /dev/null +++ b/src/interfaces/odbc/windev/options.c @@ -0,0 +1,668 @@ +/*-------- + * Module: options.c + * + * Description: This module contains routines for getting/setting + * connection and statement options. + * + * Classes: n/a + * + * API functions: SQLSetConnectOption, SQLSetStmtOption, SQLGetConnectOption, + * SQLGetStmtOption + * + * Comments: See "notice.txt" for copyright and license information. + *-------- + */ + +#include "psqlodbc.h" +#include + +#include "environ.h" +#include "connection.h" +#include "statement.h" +#include "qresult.h" +#include "pgapifunc.h" + + + +RETCODE set_statement_option(ConnectionClass *conn, + StatementClass *stmt, + UWORD fOption, + UDWORD vParam); + + +RETCODE +set_statement_option(ConnectionClass *conn, + StatementClass *stmt, + UWORD fOption, + UDWORD vParam) +{ + static char *func = "set_statement_option"; + char changed = FALSE; + ConnInfo *ci = NULL; + + if (conn) + ci = &(conn->connInfo); + else if (stmt) + ci = &(SC_get_conn(stmt)->connInfo); + switch (fOption) + { + case SQL_ASYNC_ENABLE: /* ignored */ + break; + + case SQL_BIND_TYPE: + /* now support multi-column and multi-row binding */ + if (conn) + conn->stmtOptions.bind_size = vParam; + if (stmt) + stmt->options.bind_size = vParam; + break; + + case SQL_CONCURRENCY: + + /* + * positioned update isn't supported so cursor concurrency is + * read-only + */ + mylog("SetStmtOption(): SQL_CONCURRENCY = %d\n", vParam); + if (ci->drivers.lie || vParam == SQL_CONCUR_READ_ONLY || vParam == SQL_CONCUR_ROWVER) + { + if (conn) + conn->stmtOptions.scroll_concurrency = vParam; + if (stmt) + stmt->options.scroll_concurrency = vParam; + } + else + { + if (conn) + conn->stmtOptions.scroll_concurrency = SQL_CONCUR_ROWVER; + if (stmt) + stmt->options.scroll_concurrency = SQL_CONCUR_ROWVER; + changed = TRUE; + } + break; + + case SQL_CURSOR_TYPE: + + /* + * if declare/fetch, then type can only be forward. otherwise, + * it can only be forward or static. + */ + mylog("SetStmtOption(): SQL_CURSOR_TYPE = %d\n", vParam); + + if (ci->drivers.lie) + { + if (conn) + conn->stmtOptions.cursor_type = vParam; + if (stmt) + stmt->options.cursor_type = vParam; + } + else + { + if (ci->drivers.use_declarefetch) + { + if (conn) + conn->stmtOptions.cursor_type = SQL_CURSOR_FORWARD_ONLY; + if (stmt) + stmt->options.cursor_type = SQL_CURSOR_FORWARD_ONLY; + + if (vParam != SQL_CURSOR_FORWARD_ONLY) + changed = TRUE; + } + else + { + if (vParam == SQL_CURSOR_FORWARD_ONLY || vParam == SQL_CURSOR_STATIC) + { + if (conn) + conn->stmtOptions.cursor_type = vParam; /* valid type */ + if (stmt) + stmt->options.cursor_type = vParam; /* valid type */ + } + else + { + if (conn) + conn->stmtOptions.cursor_type = SQL_CURSOR_STATIC; + if (stmt) + stmt->options.cursor_type = SQL_CURSOR_STATIC; + + changed = TRUE; + } + } + } + break; + + case SQL_KEYSET_SIZE: /* ignored, but saved and returned */ + mylog("SetStmtOption(): SQL_KEYSET_SIZE, vParam = %d\n", vParam); + + if (conn) + conn->stmtOptions.keyset_size = vParam; + if (stmt) + stmt->options.keyset_size = vParam; + + break; + + /*------- + * if (ci->drivers.lie) + * stmt->keyset_size = vParam; + * else + * { + * stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + * stmt->errormsg = "Driver does not support keyset size option"; + * SC_log_error(func, "", stmt); + * return SQL_ERROR; + * } + *------- + */ + + case SQL_MAX_LENGTH: /* ignored, but saved */ + mylog("SetStmtOption(): SQL_MAX_LENGTH, vParam = %d\n", vParam); + if (conn) + conn->stmtOptions.maxLength = vParam; + if (stmt) + stmt->options.maxLength = vParam; + break; + + case SQL_MAX_ROWS: /* ignored, but saved */ + mylog("SetStmtOption(): SQL_MAX_ROWS, vParam = %d\n", vParam); + if (conn) + conn->stmtOptions.maxRows = vParam; + if (stmt) + stmt->options.maxRows = vParam; + break; + + case SQL_NOSCAN: /* ignored */ + mylog("SetStmtOption: SQL_NOSCAN, vParam = %d\n", vParam); + break; + + case SQL_QUERY_TIMEOUT: /* ignored */ + mylog("SetStmtOption: SQL_QUERY_TIMEOUT, vParam = %d\n", vParam); + /* "0" returned in SQLGetStmtOption */ + break; + + case SQL_RETRIEVE_DATA: + mylog("SetStmtOption(): SQL_RETRIEVE_DATA, vParam = %d\n", vParam); + if (conn) + conn->stmtOptions.retrieve_data = vParam; + if (stmt) + stmt->options.retrieve_data = vParam; + break; + + case SQL_ROWSET_SIZE: + mylog("SetStmtOption(): SQL_ROWSET_SIZE, vParam = %d\n", vParam); + + /* + * Save old rowset size for SQLExtendedFetch purposes If the + * rowset_size is being changed since the last call to fetch + * rows. + */ + + if (stmt && stmt->save_rowset_size <= 0 && stmt->last_fetch_count > 0) + stmt->save_rowset_size = stmt->options.rowset_size; + + if (vParam < 1) + { + vParam = 1; + changed = TRUE; + } + + if (conn) + conn->stmtOptions.rowset_size = vParam; + if (stmt) + stmt->options.rowset_size = vParam; + break; + + case SQL_SIMULATE_CURSOR: /* NOT SUPPORTED */ + if (stmt) + { + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + stmt->errormsg = "Simulated positioned update/delete not supported. Use the cursor library."; + SC_log_error(func, "", stmt); + } + if (conn) + { + conn->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + conn->errormsg = "Simulated positioned update/delete not supported. Use the cursor library."; + CC_log_error(func, "", conn); + } + return SQL_ERROR; + + case SQL_USE_BOOKMARKS: + if (stmt) + stmt->options.use_bookmarks = vParam; + if (conn) + conn->stmtOptions.use_bookmarks = vParam; + break; + + default: + { + char option[64]; + + if (stmt) + { + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + stmt->errormsg = "Unknown statement option (Set)"; + sprintf(option, "fOption=%d, vParam=%ld", fOption, vParam); + SC_log_error(func, option, stmt); + } + if (conn) + { + conn->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + conn->errormsg = "Unknown statement option (Set)"; + sprintf(option, "fOption=%d, vParam=%ld", fOption, vParam); + CC_log_error(func, option, conn); + } + + return SQL_ERROR; + } + } + + if (changed) + { + if (stmt) + { + stmt->errormsg = "Requested value changed."; + stmt->errornumber = STMT_OPTION_VALUE_CHANGED; + } + if (conn) + { + conn->errormsg = "Requested value changed."; + conn->errornumber = STMT_OPTION_VALUE_CHANGED; + } + return SQL_SUCCESS_WITH_INFO; + } + else + return SQL_SUCCESS; +} + + +/* Implements only SQL_AUTOCOMMIT */ +RETCODE SQL_API +PGAPI_SetConnectOption( + HDBC hdbc, + UWORD fOption, + UDWORD vParam) +{ + static char *func = "PGAPI_SetConnectOption"; + ConnectionClass *conn = (ConnectionClass *) hdbc; + char changed = FALSE; + RETCODE retval; + int i; + + mylog("%s: entering fOption = %d vParam = %d\n", func, fOption, vParam); + if (!conn) + { + CC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + switch (fOption) + { + /* + * Statement Options (apply to all stmts on the connection and + * become defaults for new stmts) + */ + case SQL_ASYNC_ENABLE: + case SQL_BIND_TYPE: + case SQL_CONCURRENCY: + case SQL_CURSOR_TYPE: + case SQL_KEYSET_SIZE: + case SQL_MAX_LENGTH: + case SQL_MAX_ROWS: + case SQL_NOSCAN: + case SQL_QUERY_TIMEOUT: + case SQL_RETRIEVE_DATA: + case SQL_ROWSET_SIZE: + case SQL_SIMULATE_CURSOR: + case SQL_USE_BOOKMARKS: + + /* Affect all current Statements */ + for (i = 0; i < conn->num_stmts; i++) + { + if (conn->stmts[i]) + set_statement_option(NULL, conn->stmts[i], fOption, vParam); + } + + /* + * Become the default for all future statements on this + * connection + */ + retval = set_statement_option(conn, NULL, fOption, vParam); + + if (retval == SQL_SUCCESS_WITH_INFO) + changed = TRUE; + else if (retval == SQL_ERROR) + return SQL_ERROR; + + break; + + /* + * Connection Options + */ + + case SQL_ACCESS_MODE: /* ignored */ + break; + + case SQL_AUTOCOMMIT: + if (CC_is_in_trans(conn)) + { + conn->errormsg = "Cannot switch commit mode while a transaction is in progress"; + conn->errornumber = CONN_TRANSACT_IN_PROGRES; + CC_log_error(func, "", conn); + return SQL_ERROR; + } + + mylog("PGAPI_SetConnectOption: AUTOCOMMIT: transact_status=%d, vparam=%d\n", conn->transact_status, vParam); + + switch (vParam) + { + case SQL_AUTOCOMMIT_OFF: + CC_set_autocommit_off(conn); + break; + + case SQL_AUTOCOMMIT_ON: + CC_set_autocommit_on(conn); + break; + + default: + conn->errormsg = "Illegal parameter value for SQL_AUTOCOMMIT"; + conn->errornumber = CONN_INVALID_ARGUMENT_NO; + CC_log_error(func, "", conn); + return SQL_ERROR; + } + break; + + case SQL_CURRENT_QUALIFIER: /* ignored */ + break; + + case SQL_LOGIN_TIMEOUT: /* ignored */ + break; + + case SQL_PACKET_SIZE: /* ignored */ + break; + + case SQL_QUIET_MODE: /* ignored */ + break; + + case SQL_TXN_ISOLATION: /* ignored */ + break; + + /* These options should be handled by driver manager */ + case SQL_ODBC_CURSORS: + case SQL_OPT_TRACE: + case SQL_OPT_TRACEFILE: + case SQL_TRANSLATE_DLL: + case SQL_TRANSLATE_OPTION: + CC_log_error(func, "This connect option (Set) is only used by the Driver Manager", conn); + break; + + default: + { + char option[64]; + + conn->errormsg = "Unknown connect option (Set)"; + conn->errornumber = CONN_UNSUPPORTED_OPTION; + sprintf(option, "fOption=%d, vParam=%ld", fOption, vParam); + if (fOption == 30002 && vParam) + { + if (strcmp((char *) vParam, "Microsoft Jet") == 0) + { + conn->errornumber = 0; + conn->ms_jet = 1; + return SQL_SUCCESS; + } + } + CC_log_error(func, option, conn); + return SQL_ERROR; + } + } + + if (changed) + { + conn->errornumber = CONN_OPTION_VALUE_CHANGED; + conn->errormsg = "Requested value changed."; + return SQL_SUCCESS_WITH_INFO; + } + else + return SQL_SUCCESS; +} + + +/* This function just can tell you whether you are in Autcommit mode or not */ +RETCODE SQL_API +PGAPI_GetConnectOption( + HDBC hdbc, + UWORD fOption, + PTR pvParam) +{ + static char *func = "PGAPI_GetConnectOption"; + ConnectionClass *conn = (ConnectionClass *) hdbc; + ConnInfo *ci = &(conn->connInfo); + + mylog("%s: entering...\n", func); + + if (!conn) + { + CC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + switch (fOption) + { + case SQL_ACCESS_MODE: /* NOT SUPPORTED */ + *((UDWORD *) pvParam) = SQL_MODE_READ_WRITE; + break; + + case SQL_AUTOCOMMIT: + *((UDWORD *) pvParam) = (UDWORD) (CC_is_in_autocommit(conn) ? + SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF); + break; + + case SQL_CURRENT_QUALIFIER: /* don't use qualifiers */ + if (pvParam) + strcpy(pvParam, ""); + + break; + + case SQL_LOGIN_TIMEOUT: /* NOT SUPPORTED */ + *((UDWORD *) pvParam) = 0; + break; + + case SQL_PACKET_SIZE: /* NOT SUPPORTED */ + *((UDWORD *) pvParam) = ci->drivers.socket_buffersize; + break; + + case SQL_QUIET_MODE: /* NOT SUPPORTED */ + *((UDWORD *) pvParam) = (UDWORD) NULL; + break; + + case SQL_TXN_ISOLATION: /* NOT SUPPORTED */ + *((UDWORD *) pvParam) = SQL_TXN_SERIALIZABLE; + break; + + /* These options should be handled by driver manager */ + case SQL_ODBC_CURSORS: + case SQL_OPT_TRACE: + case SQL_OPT_TRACEFILE: + case SQL_TRANSLATE_DLL: + case SQL_TRANSLATE_OPTION: + CC_log_error(func, "This connect option (Get) is only used by the Driver Manager", conn); + break; + + default: + { + char option[64]; + + conn->errormsg = "Unknown connect option (Get)"; + conn->errornumber = CONN_UNSUPPORTED_OPTION; + sprintf(option, "fOption=%d", fOption); + CC_log_error(func, option, conn); + return SQL_ERROR; + break; + } + } + + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_SetStmtOption( + HSTMT hstmt, + UWORD fOption, + UDWORD vParam) +{ + static char *func = "PGAPI_SetStmtOption"; + StatementClass *stmt = (StatementClass *) hstmt; + + mylog("%s: entering...\n", func); + + /* + * Though we could fake Access out by just returning SQL_SUCCESS all + * the time, but it tries to set a huge value for SQL_MAX_LENGTH and + * expects the driver to reduce it to the real value. + */ + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + return set_statement_option(NULL, stmt, fOption, vParam); +} + + +RETCODE SQL_API +PGAPI_GetStmtOption( + HSTMT hstmt, + UWORD fOption, + PTR pvParam) +{ + static char *func = "PGAPI_GetStmtOption"; + StatementClass *stmt = (StatementClass *) hstmt; + QResultClass *res; + ConnInfo *ci = &(SC_get_conn(stmt)->connInfo); + + mylog("%s: entering...\n", func); + + /* + * thought we could fake Access out by just returning SQL_SUCCESS all + * the time, but it tries to set a huge value for SQL_MAX_LENGTH and + * expects the driver to reduce it to the real value + */ + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + switch (fOption) + { + case SQL_GET_BOOKMARK: + case SQL_ROW_NUMBER: + + res = stmt->result; + + if (stmt->manual_result || !ci->drivers.use_declarefetch) + { + /* make sure we're positioned on a valid row */ + if ((stmt->currTuple < 0) || + (stmt->currTuple >= QR_get_num_tuples(res))) + { + stmt->errormsg = "Not positioned on a valid row."; + stmt->errornumber = STMT_INVALID_CURSOR_STATE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + } + else + { + if (stmt->currTuple == -1 || !res || !res->tupleField) + { + stmt->errormsg = "Not positioned on a valid row."; + stmt->errornumber = STMT_INVALID_CURSOR_STATE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + } + + if (fOption == SQL_GET_BOOKMARK && stmt->options.use_bookmarks == SQL_UB_OFF) + { + stmt->errormsg = "Operation invalid because use bookmarks not enabled."; + stmt->errornumber = STMT_OPERATION_INVALID; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + *((UDWORD *) pvParam) = SC_get_bookmark(stmt); + + break; + + case SQL_ASYNC_ENABLE: /* NOT SUPPORTED */ + *((SDWORD *) pvParam) = SQL_ASYNC_ENABLE_OFF; + break; + + case SQL_BIND_TYPE: + *((SDWORD *) pvParam) = stmt->options.bind_size; + break; + + case SQL_CONCURRENCY: /* NOT REALLY SUPPORTED */ + mylog("GetStmtOption(): SQL_CONCURRENCY\n"); + *((SDWORD *) pvParam) = stmt->options.scroll_concurrency; + break; + + case SQL_CURSOR_TYPE: /* PARTIAL SUPPORT */ + mylog("GetStmtOption(): SQL_CURSOR_TYPE\n"); + *((SDWORD *) pvParam) = stmt->options.cursor_type; + break; + + case SQL_KEYSET_SIZE: /* NOT SUPPORTED, but saved */ + mylog("GetStmtOption(): SQL_KEYSET_SIZE\n"); + *((SDWORD *) pvParam) = stmt->options.keyset_size; + break; + + case SQL_MAX_LENGTH: /* NOT SUPPORTED, but saved */ + *((SDWORD *) pvParam) = stmt->options.maxLength; + break; + + case SQL_MAX_ROWS: /* NOT SUPPORTED, but saved */ + *((SDWORD *) pvParam) = stmt->options.maxRows; + mylog("GetSmtOption: MAX_ROWS, returning %d\n", stmt->options.maxRows); + break; + + case SQL_NOSCAN: /* NOT SUPPORTED */ + *((SDWORD *) pvParam) = SQL_NOSCAN_ON; + break; + + case SQL_QUERY_TIMEOUT: /* NOT SUPPORTED */ + *((SDWORD *) pvParam) = 0; + break; + + case SQL_RETRIEVE_DATA: + *((SDWORD *) pvParam) = stmt->options.retrieve_data; + break; + + case SQL_ROWSET_SIZE: + *((SDWORD *) pvParam) = stmt->options.rowset_size; + break; + + case SQL_SIMULATE_CURSOR: /* NOT SUPPORTED */ + *((SDWORD *) pvParam) = SQL_SC_NON_UNIQUE; + break; + + case SQL_USE_BOOKMARKS: + *((SDWORD *) pvParam) = stmt->options.use_bookmarks; + break; + + default: + { + char option[64]; + + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + stmt->errormsg = "Unknown statement option (Get)"; + sprintf(option, "fOption=%d", fOption); + SC_log_error(func, option, stmt); + return SQL_ERROR; + } + } + + return SQL_SUCCESS; +} diff --git a/src/interfaces/odbc/windev/parse.c b/src/interfaces/odbc/windev/parse.c new file mode 100644 index 0000000000..e73cb82a32 --- /dev/null +++ b/src/interfaces/odbc/windev/parse.c @@ -0,0 +1,954 @@ +/*-------- + * Module: parse.c + * + * Description: This module contains routines related to parsing SQL + * statements. This can be useful for two reasons: + * + * 1. So the query does not actually have to be executed + * to return data about it + * + * 2. To be able to return information about precision, + * nullability, aliases, etc. in the functions + * SQLDescribeCol and SQLColAttributes. Currently, + * Postgres doesn't return any information about + * these things in a query. + * + * Classes: none + * + * API functions: none + * + * Comments: See "notice.txt" for copyright and license information. + *-------- + */ +/* Multibyte support Eiji Tokuya 2001-03-15 */ + +#include "psqlodbc.h" + +#include +#include +#include + +#include "statement.h" +#include "connection.h" +#include "qresult.h" +#include "pgtypes.h" +#include "pgapifunc.h" + +#ifdef MULTIBYTE +#include "multibyte.h" +#endif + +#define FLD_INCR 32 +#define TAB_INCR 8 +#define COL_INCR 16 + +char *getNextToken(char *s, char *token, int smax, char *delim, char *quote, char *dquote, char *numeric); +void getColInfo(COL_INFO *col_info, FIELD_INFO *fi, int k); +char searchColInfo(COL_INFO *col_info, FIELD_INFO *fi); + + +char * +getNextToken(char *s, char *token, int smax, char *delim, char *quote, char *dquote, char *numeric) +{ + int i = 0; + int out = 0; + char qc, + in_escape = FALSE; + + if (smax <= 1) + return NULL; + + smax--; + + /* skip leading delimiters */ + while (isspace((unsigned char) s[i]) || s[i] == ',') + { + /* mylog("skipping '%c'\n", s[i]); */ + i++; + } + + if (s[i] == '\0') + { + token[0] = '\0'; + return NULL; + } + + if (quote) + *quote = FALSE; + if (dquote) + *dquote = FALSE; + if (numeric) + *numeric = FALSE; + + /* get the next token */ + while (!isspace((unsigned char) s[i]) && s[i] != ',' && + s[i] != '\0' && out != smax) + { +#ifdef MULTIBYTE + if (multibyte_char_check(s[i]) != 0) + { + token[out++] = s[i++]; + continue; + } +#endif + /* Handle quoted stuff */ + if (out == 0 && (s[i] == '\"' || s[i] == '\'')) + { + qc = s[i]; + if (qc == '\"') + { + if (dquote) + *dquote = TRUE; + } + if (qc == '\'') + { + if (quote) + *quote = TRUE; + } + + i++; /* dont return the quote */ + while (s[i] != '\0' && out != smax) + { +#ifdef MULTIBYTE + if (multibyte_char_check(s[i]) != 0) + { + token[out++] = s[i++]; + continue; + } +#endif + if (s[i] == qc && !in_escape) + break; + if (s[i] == '\\' && !in_escape) + in_escape = TRUE; + else + { + in_escape = FALSE; + token[out++] = s[i]; + } + i++; + } + if (s[i] == qc) + i++; + break; + } + + /* Check for numeric literals */ + if (out == 0 && isdigit((unsigned char) s[i])) + { + if (numeric) + *numeric = TRUE; + token[out++] = s[i++]; + while (isalnum((unsigned char) s[i]) || s[i] == '.') + token[out++] = s[i++]; + + break; + } + + if (ispunct((unsigned char) s[i]) && s[i] != '_') + { + mylog("got ispunct: s[%d] = '%c'\n", i, s[i]); + + if (out == 0) + { + token[out++] = s[i++]; + break; + } + else + break; + } + + if (out != smax) + token[out++] = s[i]; + + i++; + } + + /* mylog("done -- s[%d] = '%c'\n", i, s[i]); */ + + token[out] = '\0'; + + /* find the delimiter */ + while (isspace((unsigned char) s[i])) + i++; + + /* return the most priority delimiter */ + if (s[i] == ',') + { + if (delim) + *delim = s[i]; + } + else if (s[i] == '\0') + { + if (delim) + *delim = '\0'; + } + else + { + if (delim) + *delim = ' '; + } + + /* skip trailing blanks */ + while (isspace((unsigned char) s[i])) + i++; + + return &s[i]; +} + + +#if 0 +QR_set_num_fields(stmt->result, 14); +QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING); +QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING); +QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); +QR_set_field_info(stmt->result, 3, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); +QR_set_field_info(stmt->result, 4, "DATA_TYPE", PG_TYPE_INT2, 2); +QR_set_field_info(stmt->result, 5, "TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); +QR_set_field_info(stmt->result, 6, "PRECISION", PG_TYPE_INT4, 4); +QR_set_field_info(stmt->result, 7, "LENGTH", PG_TYPE_INT4, 4); +QR_set_field_info(stmt->result, 8, "SCALE", PG_TYPE_INT2, 2); +QR_set_field_info(stmt->result, 9, "RADIX", PG_TYPE_INT2, 2); +QR_set_field_info(stmt->result, 10, "NULLABLE", PG_TYPE_INT2, 2); +QR_set_field_info(stmt->result, 11, "REMARKS", PG_TYPE_TEXT, 254); +/* User defined fields */ +QR_set_field_info(stmt->result, 12, "DISPLAY_SIZE", PG_TYPE_INT4, 4); +QR_set_field_info(stmt->result, 13, "FIELD_TYPE", PG_TYPE_INT4, 4); +#endif + +void +getColInfo(COL_INFO *col_info, FIELD_INFO *fi, int k) +{ + char *str; + + if (fi->name[0] == '\0') + strcpy(fi->name, QR_get_value_manual(col_info->result, k, 3)); + + fi->type = atoi(QR_get_value_manual(col_info->result, k, 13)); + fi->precision = atoi(QR_get_value_manual(col_info->result, k, 6)); + fi->length = atoi(QR_get_value_manual(col_info->result, k, 7)); + if (str = QR_get_value_manual(col_info->result, k, 8), str) + fi->scale = atoi(str); + else + fi->scale = -1; + fi->nullable = atoi(QR_get_value_manual(col_info->result, k, 10)); + fi->display_size = atoi(QR_get_value_manual(col_info->result, k, 12)); +} + + +char +searchColInfo(COL_INFO *col_info, FIELD_INFO *fi) +{ + int k, + cmp; + char *col; + + for (k = 0; k < QR_get_num_tuples(col_info->result); k++) + { + col = QR_get_value_manual(col_info->result, k, 3); + if (fi->dquote) + cmp = strcmp(col, fi->name); + else + cmp = stricmp(col, fi->name); + if (!cmp) + { + if (!fi->dquote) + strcpy(fi->name, col); + getColInfo(col_info, fi, k); + + mylog("PARSE: searchColInfo: \n"); + return TRUE; + } + } + + return FALSE; +} + + +char +parse_statement(StatementClass *stmt) +{ + static char *func = "parse_statement"; + char token[256]; + char delim, + quote, + dquote, + numeric, + unquoted; + char *ptr, + *pptr = NULL; + char in_select = FALSE, + in_distinct = FALSE, + in_on = FALSE, + in_from = FALSE, + from_found = FALSE, + in_where = FALSE, + in_table = FALSE; + char in_field = FALSE, + in_expr = FALSE, + in_func = FALSE, + in_dot = FALSE, + in_as = FALSE; + int j, + i, + k = 0, + n, + first_where = 0, + blevel = 0; + FIELD_INFO **fi; + TABLE_INFO **ti; + char parse; + ConnectionClass *conn = stmt->hdbc; + HSTMT hcol_stmt; + StatementClass *col_stmt; + RETCODE result; + + mylog("%s: entering...\n", func); + + ptr = stmt->statement; + fi = stmt->fi; + ti = stmt->ti; + + stmt->nfld = 0; + stmt->ntab = 0; + +#ifdef MULTIBYTE + multibyte_init(); +#endif + while (pptr = ptr, (ptr = getNextToken(pptr, token, sizeof(token), &delim, "e, &dquote, &numeric)) != NULL) + { + unquoted = !(quote || dquote); + + mylog("unquoted=%d, quote=%d, dquote=%d, numeric=%d, delim='%c', token='%s', ptr='%s'\n", unquoted, quote, dquote, numeric, delim, token, ptr); + + if (in_select && unquoted && blevel == 0) + { + if (!stricmp(token, "distinct")) + { + in_distinct = TRUE; + + mylog("DISTINCT\n"); + continue; + } + if (!stricmp(token, "into")) + { + in_select = FALSE; + mylog("INTO\n"); + stmt->statement_type = STMT_TYPE_CREATE; + stmt->parse_status = STMT_PARSE_FATAL; + return FALSE; + } + if (!stricmp(token, "from")) + { + in_select = FALSE; + in_from = TRUE; + if (!from_found && + (!strnicmp(pptr, "from", 4))) + { + mylog("First "); + from_found = TRUE; + } + + mylog("FROM\n"); + continue; + } + } + if (unquoted && blevel == 0) + { + if ((!stricmp(token, "where") || + !stricmp(token, "union") || + !stricmp(token, "intersect") || + !stricmp(token, "except") || + !stricmp(token, "order") || + !stricmp(token, "group") || + !stricmp(token, "having"))) + { + in_select = FALSE; + in_from = FALSE; + in_where = TRUE; + + if (!first_where && + (!stricmp(token, "where"))) + first_where = ptr - stmt->statement; + + mylog("WHERE...\n"); + break; + } + } + if (in_select && (in_expr || in_func)) + { + /* just eat the expression */ + mylog("in_expr=%d or func=%d\n", in_expr, in_func); + + if (unquoted) + { + if (token[0] == '(') + { + blevel++; + mylog("blevel++ = %d\n", blevel); + } + else if (token[0] == ')') + { + blevel--; + mylog("blevel-- = %d\n", blevel); + } + } + if (blevel == 0) + { + if (delim == ',') + { + mylog("**** Got comma in_expr/func\n"); + in_func = FALSE; + in_expr = FALSE; + in_field = FALSE; + } + else if (unquoted && !stricmp(token, "as")) + { + mylog("got AS in_expr\n"); + in_func = FALSE; + in_expr = FALSE; + in_as = TRUE; + in_field = TRUE; + } + } + continue; + } + + if (unquoted && !stricmp(token, "select")) + { + in_select = TRUE; + + mylog("SELECT\n"); + continue; + } + if (in_select) + { + if (in_distinct) + { + mylog("in distinct\n"); + + if (unquoted && !stricmp(token, "on")) + { + in_on = TRUE; + mylog("got on\n"); + continue; + } + if (in_on) + { + in_distinct = FALSE; + in_on = FALSE; + continue; /* just skip the unique on field */ + } + mylog("done distinct\n"); + in_distinct = FALSE; + } + + if (!in_field) + { + if (!token[0]) + continue; + + if (!(stmt->nfld % FLD_INCR)) + { + mylog("reallocing at nfld=%d\n", stmt->nfld); + fi = (FIELD_INFO **) realloc(fi, (stmt->nfld + FLD_INCR) * sizeof(FIELD_INFO *)); + if (!fi) + { + stmt->parse_status = STMT_PARSE_FATAL; + return FALSE; + } + stmt->fi = fi; + } + + fi[stmt->nfld] = (FIELD_INFO *) malloc(sizeof(FIELD_INFO)); + if (fi[stmt->nfld] == NULL) + { + stmt->parse_status = STMT_PARSE_FATAL; + return FALSE; + } + + /* Initialize the field info */ + memset(fi[stmt->nfld], 0, sizeof(FIELD_INFO)); + + /* double quotes are for qualifiers */ + if (dquote) + fi[stmt->nfld]->dquote = TRUE; + + if (quote) + { + fi[stmt->nfld]->quote = TRUE; + fi[stmt->nfld]->precision = strlen(token); + } + else if (numeric) + { + mylog("**** got numeric: nfld = %d\n", stmt->nfld); + fi[stmt->nfld]->numeric = TRUE; + } + else if (token[0] == '(') + { /* expression */ + mylog("got EXPRESSION\n"); + fi[stmt->nfld++]->expr = TRUE; + in_expr = TRUE; + blevel = 1; + continue; + } + else + { + strcpy(fi[stmt->nfld]->name, token); + fi[stmt->nfld]->dot[0] = '\0'; + } + mylog("got field='%s', dot='%s'\n", fi[stmt->nfld]->name, fi[stmt->nfld]->dot); + + if (delim == ',') + mylog("comma (1)\n"); + else + in_field = TRUE; + stmt->nfld++; + continue; + } + + /* + * We are in a field now + */ + if (in_dot) + { + stmt->nfld--; + strcpy(fi[stmt->nfld]->dot, fi[stmt->nfld]->name); + strcpy(fi[stmt->nfld]->name, token); + stmt->nfld++; + in_dot = FALSE; + + if (delim == ',') + { + mylog("in_dot: got comma\n"); + in_field = FALSE; + } + continue; + } + + if (in_as) + { + stmt->nfld--; + strcpy(fi[stmt->nfld]->alias, token); + mylog("alias for field '%s' is '%s'\n", fi[stmt->nfld]->name, fi[stmt->nfld]->alias); + in_as = FALSE; + in_field = FALSE; + + stmt->nfld++; + + if (delim == ',') + mylog("comma(2)\n"); + continue; + } + + /* Function */ + if (token[0] == '(') + { + in_func = TRUE; + blevel = 1; + fi[stmt->nfld - 1]->func = TRUE; + + /* + * name will have the function name -- maybe useful some + * day + */ + mylog("**** got function = '%s'\n", fi[stmt->nfld - 1]->name); + continue; + } + + if (token[0] == '.') + { + in_dot = TRUE; + mylog("got dot\n"); + continue; + } + + if (!stricmp(token, "as")) + { + in_as = TRUE; + mylog("got AS\n"); + continue; + } + + /* otherwise, it's probably an expression */ + in_expr = TRUE; + fi[stmt->nfld - 1]->expr = TRUE; + fi[stmt->nfld - 1]->name[0] = '\0'; + fi[stmt->nfld - 1]->precision = 0; + mylog("*** setting expression\n"); + } + + if (in_from) + { + if (!in_table) + { + if (!token[0]) + continue; + + if (!(stmt->ntab % TAB_INCR)) + { + ti = (TABLE_INFO **) realloc(ti, (stmt->ntab + TAB_INCR) * sizeof(TABLE_INFO *)); + if (!ti) + { + stmt->parse_status = STMT_PARSE_FATAL; + return FALSE; + } + stmt->ti = ti; + } + ti[stmt->ntab] = (TABLE_INFO *) malloc(sizeof(TABLE_INFO)); + if (ti[stmt->ntab] == NULL) + { + stmt->parse_status = STMT_PARSE_FATAL; + return FALSE; + } + + ti[stmt->ntab]->alias[0] = '\0'; + + strcpy(ti[stmt->ntab]->name, token); + if (!dquote) + { + char *ptr; + + /* lower case table name */ + for (ptr = ti[stmt->ntab]->name; *ptr; ptr++) + { +#ifdef MULTIBYTE + if ((unsigned char) *ptr >= 0x80) + ptr++; + else +#endif /* MULTIBYTE */ + *ptr = tolower((unsigned char) *ptr); + } + } + mylog("got table = '%s'\n", ti[stmt->ntab]->name); + + if (delim == ',') + mylog("more than 1 tables\n"); + else + in_table = TRUE; + stmt->ntab++; + continue; + } + + strcpy(ti[stmt->ntab - 1]->alias, token); + mylog("alias for table '%s' is '%s'\n", ti[stmt->ntab - 1]->name, ti[stmt->ntab - 1]->alias); + in_table = FALSE; + if (delim == ',') + mylog("more than 1 tables\n"); + } + } + + /* + * Resolve any possible field names with tables + */ + + parse = TRUE; + + /* Resolve field names with tables */ + for (i = 0; i < stmt->nfld; i++) + { + if (fi[i]->func || fi[i]->expr || fi[i]->numeric) + { + fi[i]->ti = NULL; + fi[i]->type = -1; + parse = FALSE; + continue; + } + else if (fi[i]->quote) + { /* handle as text */ + fi[i]->ti = NULL; + + /* + * fi[i]->type = PG_TYPE_TEXT; fi[i]->precision = 0; the + * following may be better + */ + fi[i]->type = PG_TYPE_UNKNOWN; + if (fi[i]->precision == 0) + { + fi[i]->type = PG_TYPE_VARCHAR; + fi[i]->precision = 254; + } + fi[i]->length = fi[i]->precision; + continue; + } + /* it's a dot, resolve to table or alias */ + else if (fi[i]->dot[0]) + { + for (k = 0; k < stmt->ntab; k++) + { + if (!stricmp(ti[k]->name, fi[i]->dot)) + { + fi[i]->ti = ti[k]; + break; + } + else if (!stricmp(ti[k]->alias, fi[i]->dot)) + { + fi[i]->ti = ti[k]; + break; + } + } + } + else if (stmt->ntab == 1) + fi[i]->ti = ti[0]; + } + + mylog("--------------------------------------------\n"); + mylog("nfld=%d, ntab=%d\n", stmt->nfld, stmt->ntab); + + for (i = 0; i < stmt->nfld; i++) + { + mylog("Field %d: expr=%d, func=%d, quote=%d, dquote=%d, numeric=%d, name='%s', alias='%s', dot='%s'\n", i, fi[i]->expr, fi[i]->func, fi[i]->quote, fi[i]->dquote, fi[i]->numeric, fi[i]->name, fi[i]->alias, fi[i]->dot); + if (fi[i]->ti) + mylog(" ----> table_name='%s', table_alias='%s'\n", fi[i]->ti->name, fi[i]->ti->alias); + } + + for (i = 0; i < stmt->ntab; i++) + mylog("Table %d: name='%s', alias='%s'\n", i, ti[i]->name, ti[i]->alias); + + + /* + * Now save the SQLColumns Info for the parse tables + */ + + /* Call SQLColumns for each table and store the result */ + for (i = 0; i < stmt->ntab; i++) + { + /* See if already got it */ + char found = FALSE; + + for (k = 0; k < conn->ntables; k++) + { + if (!stricmp(conn->col_info[k]->name, ti[i]->name)) + { + mylog("FOUND col_info table='%s'\n", ti[i]->name); + found = TRUE; + break; + } + } + + if (!found) + { + mylog("PARSE: Getting PG_Columns for table[%d]='%s'\n", i, ti[i]->name); + + result = PGAPI_AllocStmt(stmt->hdbc, &hcol_stmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = "PGAPI_AllocStmt failed in parse_statement for columns."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->parse_status = STMT_PARSE_FATAL; + return FALSE; + } + + col_stmt = (StatementClass *) hcol_stmt; + col_stmt->internal = TRUE; + + result = PGAPI_Columns(hcol_stmt, "", 0, "", 0, + ti[i]->name, (SWORD) strlen(ti[i]->name), "", 0); + + mylog(" Past PG_Columns\n"); + if (result == SQL_SUCCESS) + { + mylog(" Success\n"); + if (!(conn->ntables % COL_INCR)) + { + mylog("PARSE: Allocing col_info at ntables=%d\n", conn->ntables); + + conn->col_info = (COL_INFO **) realloc(conn->col_info, (conn->ntables + COL_INCR) * sizeof(COL_INFO *)); + if (!conn->col_info) + { + stmt->parse_status = STMT_PARSE_FATAL; + return FALSE; + } + } + + mylog("PARSE: malloc at conn->col_info[%d]\n", conn->ntables); + conn->col_info[conn->ntables] = (COL_INFO *) malloc(sizeof(COL_INFO)); + if (!conn->col_info[conn->ntables]) + { + stmt->parse_status = STMT_PARSE_FATAL; + return FALSE; + } + + /* + * Store the table name and the SQLColumns result + * structure + */ + strcpy(conn->col_info[conn->ntables]->name, ti[i]->name); + conn->col_info[conn->ntables]->result = col_stmt->result; + + /* + * The connection will now free the result structures, so + * make sure that the statement doesn't free it + */ + col_stmt->result = NULL; + + conn->ntables++; + + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + mylog("Created col_info table='%s', ntables=%d\n", ti[i]->name, conn->ntables); + } + else + { + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + break; + } + } + + /* Associate a table from the statement with a SQLColumn info */ + ti[i]->col_info = conn->col_info[k]; + mylog("associate col_info: i=%d, k=%d\n", i, k); + } + + mylog("Done PG_Columns\n"); + + /* + * Now resolve the fields to point to column info + */ + for (i = 0; i < stmt->nfld;) + { + /* Dont worry about functions or quotes */ + if (fi[i]->func || fi[i]->quote || fi[i]->numeric) + { + i++; + continue; + } + + /* Stars get expanded to all fields in the table */ + else if (fi[i]->name[0] == '*') + { + char do_all_tables; + int total_cols, + old_alloc, + new_size, + cols; + int increased_cols; + + mylog("expanding field %d\n", i); + + total_cols = 0; + + if (fi[i]->ti) /* The star represents only the qualified + * table */ + total_cols = QR_get_num_tuples(fi[i]->ti->col_info->result); + + else + { /* The star represents all tables */ + + /* Calculate the total number of columns after expansion */ + for (k = 0; k < stmt->ntab; k++) + total_cols += QR_get_num_tuples(ti[k]->col_info->result); + } + increased_cols = total_cols - 1; + + /* Allocate some more field pointers if necessary */ + old_alloc = ((stmt->nfld - 1) / FLD_INCR + 1) * FLD_INCR; + new_size = stmt->nfld + increased_cols; + + mylog("k=%d, increased_cols=%d, old_alloc=%d, new_size=%d\n", k, increased_cols, old_alloc, new_size); + + if (new_size > old_alloc) + { + int new_alloc = ((new_size / FLD_INCR) + 1) * FLD_INCR; + + mylog("need more cols: new_alloc = %d\n", new_alloc); + fi = (FIELD_INFO **) realloc(fi, new_alloc * sizeof(FIELD_INFO *)); + if (!fi) + { + stmt->parse_status = STMT_PARSE_FATAL; + return FALSE; + } + stmt->fi = fi; + } + + /* + * copy any other fields (if there are any) up past the + * expansion + */ + for (j = stmt->nfld - 1; j > i; j--) + { + mylog("copying field %d to %d\n", j, increased_cols + j); + fi[increased_cols + j] = fi[j]; + } + mylog("done copying fields\n"); + + /* Set the new number of fields */ + stmt->nfld += increased_cols; + mylog("stmt->nfld now at %d\n", stmt->nfld); + + + /* copy the new field info */ + do_all_tables = (fi[i]->ti ? FALSE : TRUE); + + for (k = 0; k < (do_all_tables ? stmt->ntab : 1); k++) + { + TABLE_INFO *the_ti = do_all_tables ? ti[k] : fi[i]->ti; + + cols = QR_get_num_tuples(the_ti->col_info->result); + + for (n = 0; n < cols; n++) + { + mylog("creating field info: n=%d\n", n); + /* skip malloc (already did it for the Star) */ + if (k > 0 || n > 0) + { + mylog("allocating field info at %d\n", n + i); + fi[n + i] = (FIELD_INFO *) malloc(sizeof(FIELD_INFO)); + if (fi[n + i] == NULL) + { + stmt->parse_status = STMT_PARSE_FATAL; + return FALSE; + } + } + /* Initialize the new space (or the * field) */ + memset(fi[n + i], 0, sizeof(FIELD_INFO)); + fi[n + i]->ti = the_ti; + + mylog("about to copy at %d\n", n + i); + + getColInfo(the_ti->col_info, fi[n + i], n); + + mylog("done copying\n"); + } + + i += cols; + mylog("i now at %d\n", i); + } + } + + /* + * We either know which table the field was in because it was + * qualified with a table name or alias -OR- there was only 1 + * table. + */ + else if (fi[i]->ti) + { + if (!searchColInfo(fi[i]->ti->col_info, fi[i])) + parse = FALSE; + + i++; + } + + /* Don't know the table -- search all tables in "from" list */ + else + { + parse = FALSE; + for (k = 0; k < stmt->ntab; k++) + { + if (searchColInfo(ti[k]->col_info, fi[i])) + { + fi[i]->ti = ti[k]; /* now know the table */ + parse = TRUE; + break; + } + } + i++; + } + } + + if (!parse) + stmt->parse_status = STMT_PARSE_INCOMPLETE; + else + stmt->parse_status = STMT_PARSE_COMPLETE; + + mylog("done parse_statement: parse=%d, parse_status=%d\n", parse, stmt->parse_status); + return parse; +} diff --git a/src/interfaces/odbc/windev/pgapifunc.h b/src/interfaces/odbc/windev/pgapifunc.h new file mode 100644 index 0000000000..2ebf20371a --- /dev/null +++ b/src/interfaces/odbc/windev/pgapifunc.h @@ -0,0 +1,244 @@ +/*------- + * Module: pgapifunc.h + * + *------- + */ +#ifndef _PG_API_FUNC_H__ +#define _PG_API_FUNC_H__ + +#include "psqlodbc.h" +#include +#include + + +RETCODE SQL_API PGAPI_AllocConnect(HENV EnvironmentHandle, + HDBC FAR * ConnectionHandle); +RETCODE SQL_API PGAPI_AllocEnv(HENV FAR * EnvironmentHandle); +RETCODE SQL_API PGAPI_AllocStmt(HDBC ConnectionHandle, + HSTMT *StatementHandle); +RETCODE SQL_API PGAPI_BindCol(HSTMT StatementHandle, + SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, + PTR TargetValue, SQLINTEGER BufferLength, + SQLINTEGER *StrLen_or_Ind); +RETCODE SQL_API PGAPI_Cancel(HSTMT StatementHandle); +RETCODE SQL_API PGAPI_Columns(HSTMT StatementHandle, + SQLCHAR *CatalogName, SQLSMALLINT NameLength1, + SQLCHAR *SchemaName, SQLSMALLINT NameLength2, + SQLCHAR *TableName, SQLSMALLINT NameLength3, + SQLCHAR *ColumnName, SQLSMALLINT NameLength4); +RETCODE SQL_API PGAPI_Connect(HDBC ConnectionHandle, + SQLCHAR *ServerName, SQLSMALLINT NameLength1, + SQLCHAR *UserName, SQLSMALLINT NameLength2, + SQLCHAR *Authentication, SQLSMALLINT NameLength3); +RETCODE SQL_API PGAPI_DriverConnect(HDBC hdbc, HWND hwnd, + UCHAR FAR * szConnStrIn, SWORD cbConnStrIn, + UCHAR FAR * szConnStrOut, SWORD cbConnStrOutMax, + SWORD FAR * pcbConnStrOut, UWORD fDriverCompletion); +RETCODE SQL_API PGAPI_BrowseConnect(HDBC hdbc, + SQLCHAR *szConnStrIn, SQLSMALLINT cbConnStrIn, + SQLCHAR *szConnStrOut, SQLSMALLINT cbConnStrOutMax, + SQLSMALLINT *pcbConnStrOut); +RETCODE SQL_API PGAPI_DataSources(HENV EnvironmentHandle, + SQLUSMALLINT Direction, SQLCHAR *ServerName, + SQLSMALLINT BufferLength1, SQLSMALLINT *NameLength1, + SQLCHAR *Description, SQLSMALLINT BufferLength2, + SQLSMALLINT *NameLength2); +RETCODE SQL_API PGAPI_DescribeCol(HSTMT StatementHandle, + SQLUSMALLINT ColumnNumber, SQLCHAR *ColumnName, + SQLSMALLINT BufferLength, SQLSMALLINT *NameLength, + SQLSMALLINT *DataType, SQLUINTEGER *ColumnSize, + SQLSMALLINT *DecimalDigits, SQLSMALLINT *Nullable); +RETCODE SQL_API PGAPI_Disconnect(HDBC ConnectionHandle); +RETCODE SQL_API PGAPI_Error(HENV EnvironmentHandle, + HDBC ConnectionHandle, HSTMT StatementHandle, + SQLCHAR *Sqlstate, SQLINTEGER *NativeError, + SQLCHAR *MessageText, SQLSMALLINT BufferLength, + SQLSMALLINT *TextLength); +RETCODE SQL_API PGAPI_ExecDirect(HSTMT StatementHandle, + SQLCHAR *StatementText, SQLINTEGER TextLength); +RETCODE SQL_API PGAPI_Execute(HSTMT StatementHandle); +RETCODE SQL_API PGAPI_Fetch(HSTMT StatementHandle); +RETCODE SQL_API PGAPI_FreeConnect(HDBC ConnectionHandle); +RETCODE SQL_API PGAPI_FreeEnv(HENV EnvironmentHandle); +RETCODE SQL_API PGAPI_FreeStmt(HSTMT StatementHandle, + SQLUSMALLINT Option); +RETCODE SQL_API PGAPI_GetConnectOption(HDBC ConnectionHandle, + SQLUSMALLINT Option, PTR Value); +RETCODE SQL_API PGAPI_GetCursorName(HSTMT StatementHandle, + SQLCHAR *CursorName, SQLSMALLINT BufferLength, + SQLSMALLINT *NameLength); +RETCODE SQL_API PGAPI_GetData(HSTMT StatementHandle, + SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, + PTR TargetValue, SQLINTEGER BufferLength, + SQLINTEGER *StrLen_or_Ind); +RETCODE SQL_API PGAPI_GetFunctions(HDBC ConnectionHandle, + SQLUSMALLINT FunctionId, SQLUSMALLINT *Supported); +RETCODE SQL_API PGAPI_GetFunctions30(HDBC ConnectionHandle, + SQLUSMALLINT FunctionId, SQLUSMALLINT *Supported); +RETCODE SQL_API PGAPI_GetInfo(HDBC ConnectionHandle, + SQLUSMALLINT InfoType, PTR InfoValue, + SQLSMALLINT BufferLength, SQLSMALLINT *StringLength); +RETCODE SQL_API PGAPI_GetInfo30(HDBC ConnectionHandle, + SQLUSMALLINT InfoType, PTR InfoValue, + SQLSMALLINT BufferLength, SQLSMALLINT *StringLength); +RETCODE SQL_API PGAPI_GetStmtOption(HSTMT StatementHandle, + SQLUSMALLINT Option, PTR Value); +RETCODE SQL_API PGAPI_GetTypeInfo(HSTMT StatementHandle, + SQLSMALLINT DataType); +RETCODE SQL_API PGAPI_NumResultCols(HSTMT StatementHandle, + SQLSMALLINT *ColumnCount); +RETCODE SQL_API PGAPI_ParamData(HSTMT StatementHandle, + PTR *Value); +RETCODE SQL_API PGAPI_Prepare(HSTMT StatementHandle, + SQLCHAR *StatementText, SQLINTEGER TextLength); +RETCODE SQL_API PGAPI_PutData(HSTMT StatementHandle, + PTR Data, SQLINTEGER StrLen_or_Ind); +RETCODE SQL_API PGAPI_RowCount(HSTMT StatementHandle, + SQLINTEGER *RowCount); +RETCODE SQL_API PGAPI_SetConnectOption(HDBC ConnectionHandle, + SQLUSMALLINT Option, SQLUINTEGER Value); +RETCODE SQL_API PGAPI_SetCursorName(HSTMT StatementHandle, + SQLCHAR *CursorName, SQLSMALLINT NameLength); +RETCODE SQL_API PGAPI_SetParam(HSTMT StatementHandle, + SQLUSMALLINT ParameterNumber, SQLSMALLINT ValueType, + SQLSMALLINT ParameterType, SQLUINTEGER LengthPrecision, + SQLSMALLINT ParameterScale, PTR ParameterValue, + SQLINTEGER *StrLen_or_Ind); +RETCODE SQL_API PGAPI_SetStmtOption(HSTMT StatementHandle, + SQLUSMALLINT Option, SQLUINTEGER Value); +RETCODE SQL_API PGAPI_SpecialColumns(HSTMT StatementHandle, + SQLUSMALLINT IdentifierType, SQLCHAR *CatalogName, + SQLSMALLINT NameLength1, SQLCHAR *SchemaName, + SQLSMALLINT NameLength2, SQLCHAR *TableName, + SQLSMALLINT NameLength3, SQLUSMALLINT Scope, + SQLUSMALLINT Nullable); +RETCODE SQL_API PGAPI_Statistics(HSTMT StatementHandle, + SQLCHAR *CatalogName, SQLSMALLINT NameLength1, + SQLCHAR *SchemaName, SQLSMALLINT NameLength2, + SQLCHAR *TableName, SQLSMALLINT NameLength3, + SQLUSMALLINT Unique, SQLUSMALLINT Reserved); +RETCODE SQL_API PGAPI_Tables(HSTMT StatementHandle, + SQLCHAR *CatalogName, SQLSMALLINT NameLength1, + SQLCHAR *SchemaName, SQLSMALLINT NameLength2, + SQLCHAR *TableName, SQLSMALLINT NameLength3, + SQLCHAR *TableType, SQLSMALLINT NameLength4); +RETCODE SQL_API PGAPI_Transact(HENV EnvironmentHandle, + HDBC ConnectionHandle, SQLUSMALLINT CompletionType); +RETCODE SQL_API PGAPI_ColAttributes( + HSTMT hstmt, + SQLUSMALLINT icol, + SQLUSMALLINT fDescType, + PTR rgbDesc, + SQLSMALLINT cbDescMax, + SQLSMALLINT *pcbDesc, + SQLINTEGER *pfDesc); +RETCODE SQL_API PGAPI_ColumnPrivileges( + HSTMT hstmt, + SQLCHAR *szCatalogName, + SQLSMALLINT cbCatalogName, + SQLCHAR *szSchemaName, + SQLSMALLINT cbSchemaName, + SQLCHAR *szTableName, + SQLSMALLINT cbTableName, + SQLCHAR *szColumnName, + SQLSMALLINT cbColumnName); +RETCODE SQL_API PGAPI_DescribeParam( + HSTMT hstmt, + SQLUSMALLINT ipar, + SQLSMALLINT *pfSqlType, + SQLUINTEGER *pcbParamDef, + SQLSMALLINT *pibScale, + SQLSMALLINT *pfNullable); +RETCODE SQL_API PGAPI_ExtendedFetch( + HSTMT hstmt, + SQLUSMALLINT fFetchType, + SQLINTEGER irow, + SQLUINTEGER *pcrow, + SQLUSMALLINT *rgfRowStatus); +RETCODE SQL_API PGAPI_ForeignKeys( + HSTMT hstmt, + SQLCHAR *szPkCatalogName, + SQLSMALLINT cbPkCatalogName, + SQLCHAR *szPkSchemaName, + SQLSMALLINT cbPkSchemaName, + SQLCHAR *szPkTableName, + SQLSMALLINT cbPkTableName, + SQLCHAR *szFkCatalogName, + SQLSMALLINT cbFkCatalogName, + SQLCHAR *szFkSchemaName, + SQLSMALLINT cbFkSchemaName, + SQLCHAR *szFkTableName, + SQLSMALLINT cbFkTableName); +RETCODE SQL_API PGAPI_MoreResults( + HSTMT hstmt); +RETCODE SQL_API PGAPI_NativeSql( + HDBC hdbc, + SQLCHAR *szSqlStrIn, + SQLINTEGER cbSqlStrIn, + SQLCHAR *szSqlStr, + SQLINTEGER cbSqlStrMax, + SQLINTEGER *pcbSqlStr); +RETCODE SQL_API PGAPI_NumParams( + HSTMT hstmt, + SQLSMALLINT *pcpar); +RETCODE SQL_API PGAPI_ParamOptions( + HSTMT hstmt, + SQLUINTEGER crow, + SQLUINTEGER *pirow); +RETCODE SQL_API PGAPI_PrimaryKeys( + HSTMT hstmt, + SQLCHAR *szCatalogName, + SQLSMALLINT cbCatalogName, + SQLCHAR *szSchemaName, + SQLSMALLINT cbSchemaName, + SQLCHAR *szTableName, + SQLSMALLINT cbTableName); +RETCODE SQL_API PGAPI_ProcedureColumns( + HSTMT hstmt, + SQLCHAR *szCatalogName, + SQLSMALLINT cbCatalogName, + SQLCHAR *szSchemaName, + SQLSMALLINT cbSchemaName, + SQLCHAR *szProcName, + SQLSMALLINT cbProcName, + SQLCHAR *szColumnName, + SQLSMALLINT cbColumnName); +RETCODE SQL_API PGAPI_Procedures( + HSTMT hstmt, + SQLCHAR *szCatalogName, + SQLSMALLINT cbCatalogName, + SQLCHAR *szSchemaName, + SQLSMALLINT cbSchemaName, + SQLCHAR *szProcName, + SQLSMALLINT cbProcName); +RETCODE SQL_API PGAPI_SetPos( + HSTMT hstmt, + SQLUSMALLINT irow, + SQLUSMALLINT fOption, + SQLUSMALLINT fLock); +RETCODE SQL_API PGAPI_TablePrivileges( + HSTMT hstmt, + SQLCHAR *szCatalogName, + SQLSMALLINT cbCatalogName, + SQLCHAR *szSchemaName, + SQLSMALLINT cbSchemaName, + SQLCHAR *szTableName, + SQLSMALLINT cbTableName); +RETCODE SQL_API PGAPI_BindParameter( + HSTMT hstmt, + SQLUSMALLINT ipar, + SQLSMALLINT fParamType, + SQLSMALLINT fCType, + SQLSMALLINT fSqlType, + SQLUINTEGER cbColDef, + SQLSMALLINT ibScale, + PTR rgbValue, + SQLINTEGER cbValueMax, + SQLINTEGER *pcbValue); +RETCODE SQL_API PGAPI_SetScrollOptions( + HSTMT hstmt, + UWORD fConcurrency, + SDWORD crowKeyset, + UWORD crowRowset); + +#endif /* define_PG_API_FUNC_H__ */ diff --git a/src/interfaces/odbc/windev/pgtypes.c b/src/interfaces/odbc/windev/pgtypes.c new file mode 100644 index 0000000000..1388650a65 --- /dev/null +++ b/src/interfaces/odbc/windev/pgtypes.c @@ -0,0 +1,1109 @@ +/*-------- + * Module: pgtypes.c + * + * Description: This module contains routines for getting information + * about the supported Postgres data types. Only the + * function pgtype_to_sqltype() returns an unknown condition. + * All other functions return a suitable default so that + * even data types that are not directly supported can be + * used (it is handled as char data). + * + * Classes: n/a + * + * API functions: none + * + * Comments: See "notice.txt" for copyright and license information. + *-------- + */ + +#include "pgtypes.h" + +#include "dlg_specific.h" +#include "statement.h" +#include "connection.h" +#include "qresult.h" + + + +Int4 getCharPrecision(StatementClass *stmt, Int4 type, int col, int handle_unknown_size_as); + +/* + * these are the types we support. all of the pgtype_ functions should + * return values for each one of these. + * Even types not directly supported are handled as character types + * so all types should work (points, etc.) + */ + +/* + * ALL THESE TYPES ARE NO LONGER REPORTED in SQLGetTypeInfo. Instead, all + * the SQL TYPES are reported and mapped to a corresponding Postgres Type + */ + +/* +Int4 pgtypes_defined[] = { + PG_TYPE_CHAR, + PG_TYPE_CHAR2, + PG_TYPE_CHAR4, + PG_TYPE_CHAR8, + PG_TYPE_CHAR16, + PG_TYPE_NAME, + PG_TYPE_VARCHAR, + PG_TYPE_BPCHAR, + PG_TYPE_DATE, + PG_TYPE_TIME, + PG_TYPE_DATETIME, + PG_TYPE_ABSTIME, + PG_TYPE_TIMESTAMP, + PG_TYPE_TEXT, + PG_TYPE_INT2, + PG_TYPE_INT4, + PG_TYPE_FLOAT4, + PG_TYPE_FLOAT8, + PG_TYPE_OID, + PG_TYPE_MONEY, + PG_TYPE_BOOL, + PG_TYPE_BYTEA, + PG_TYPE_LO, + 0 }; +*/ + + +/* These are NOW the SQL Types reported in SQLGetTypeInfo. */ +Int2 sqlTypes[] = { + SQL_BIGINT, + /* SQL_BINARY, -- Commented out because VarBinary is more correct. */ + SQL_BIT, + SQL_CHAR, + SQL_DATE, + SQL_DECIMAL, + SQL_DOUBLE, + SQL_FLOAT, + SQL_INTEGER, + SQL_LONGVARBINARY, + SQL_LONGVARCHAR, + SQL_NUMERIC, + SQL_REAL, + SQL_SMALLINT, + SQL_TIME, + SQL_TIMESTAMP, + SQL_TINYINT, + SQL_VARBINARY, + SQL_VARCHAR, + 0 +}; + + +Int4 +sqltype_to_pgtype(StatementClass *stmt, SWORD fSqlType) +{ + Int4 pgType; + ConnInfo *ci = &(SC_get_conn(stmt)->connInfo); + + switch (fSqlType) + { + case SQL_BINARY: + pgType = PG_TYPE_BYTEA; + break; + + case SQL_CHAR: + pgType = PG_TYPE_BPCHAR; + break; + + case SQL_BIT: + pgType = ci->drivers.bools_as_char ? PG_TYPE_CHAR : PG_TYPE_BOOL; + break; + + case SQL_DATE: + pgType = PG_TYPE_DATE; + break; + + case SQL_DOUBLE: + case SQL_FLOAT: + pgType = PG_TYPE_FLOAT8; + break; + + case SQL_DECIMAL: + case SQL_NUMERIC: + pgType = PG_TYPE_NUMERIC; + break; + + case SQL_BIGINT: + pgType = PG_TYPE_INT8; + break; + + case SQL_INTEGER: + pgType = PG_TYPE_INT4; + break; + + case SQL_LONGVARBINARY: + pgType = PG_TYPE_LO; + break; + + case SQL_LONGVARCHAR: + pgType = ci->drivers.text_as_longvarchar ? PG_TYPE_TEXT : PG_TYPE_VARCHAR; + break; + + case SQL_REAL: + pgType = PG_TYPE_FLOAT4; + break; + + case SQL_SMALLINT: + case SQL_TINYINT: + pgType = PG_TYPE_INT2; + break; + + case SQL_TIME: + pgType = PG_TYPE_TIME; + break; + + case SQL_TIMESTAMP: + pgType = PG_TYPE_DATETIME; + break; + + case SQL_VARBINARY: + pgType = PG_TYPE_BYTEA; + break; + + case SQL_VARCHAR: + pgType = PG_TYPE_VARCHAR; + break; + + default: + pgType = 0; /* ??? */ + break; + } + + return pgType; +} + + +/* + * There are two ways of calling this function: + * + * 1. When going through the supported PG types (SQLGetTypeInfo) + * + * 2. When taking any type id (SQLColumns, SQLGetData) + * + * The first type will always work because all the types defined are returned here. + * The second type will return a default based on global parameter when it does not + * know. This allows for supporting + * types that are unknown. All other pg routines in here return a suitable default. + */ +Int2 +pgtype_to_sqltype(StatementClass *stmt, Int4 type) +{ + ConnInfo *ci = &(SC_get_conn(stmt)->connInfo); + + switch (type) + { + case PG_TYPE_CHAR: + case PG_TYPE_CHAR2: + case PG_TYPE_CHAR4: + case PG_TYPE_CHAR8: + case PG_TYPE_NAME: + return SQL_CHAR; + + case PG_TYPE_BPCHAR: + return SQL_CHAR; + + case PG_TYPE_VARCHAR: + return SQL_VARCHAR; + + case PG_TYPE_TEXT: + return ci->drivers.text_as_longvarchar ? SQL_LONGVARCHAR : SQL_VARCHAR; + + case PG_TYPE_BYTEA: + return SQL_VARBINARY; + case PG_TYPE_LO: + return SQL_LONGVARBINARY; + + case PG_TYPE_INT2: + return SQL_SMALLINT; + + case PG_TYPE_OID: + case PG_TYPE_XID: + case PG_TYPE_INT4: + return SQL_INTEGER; + + /* Change this to SQL_BIGINT for ODBC v3 bjm 2001-01-23 */ + case PG_TYPE_INT8: + return SQL_CHAR; + + case PG_TYPE_NUMERIC: + return SQL_NUMERIC; + + case PG_TYPE_FLOAT4: + return SQL_REAL; + case PG_TYPE_FLOAT8: + return SQL_FLOAT; + case PG_TYPE_DATE: + return SQL_DATE; + case PG_TYPE_TIME: + return SQL_TIME; + case PG_TYPE_ABSTIME: + case PG_TYPE_DATETIME: + case PG_TYPE_TIMESTAMP: + return SQL_TIMESTAMP; + case PG_TYPE_MONEY: + return SQL_FLOAT; + case PG_TYPE_BOOL: + return ci->drivers.bools_as_char ? SQL_CHAR : SQL_BIT; + + default: + + /* + * first, check to see if 'type' is in list. If not, look up + * with query. Add oid, name to list. If it's already in + * list, just return. + */ + /* hack until permanent type is available */ + if (type == stmt->hdbc->lobj_type) + return SQL_LONGVARBINARY; + + return ci->drivers.unknowns_as_longvarchar ? SQL_LONGVARCHAR : SQL_VARCHAR; + } +} + + +Int2 +pgtype_to_ctype(StatementClass *stmt, Int4 type) +{ + ConnInfo *ci = &(SC_get_conn(stmt)->connInfo); + + switch (type) + { + case PG_TYPE_INT8: + return SQL_C_CHAR; + case PG_TYPE_NUMERIC: + return SQL_C_CHAR; + case PG_TYPE_INT2: + return SQL_C_SSHORT; + case PG_TYPE_OID: + case PG_TYPE_XID: + case PG_TYPE_INT4: + return SQL_C_SLONG; + case PG_TYPE_FLOAT4: + return SQL_C_FLOAT; + case PG_TYPE_FLOAT8: + return SQL_C_DOUBLE; + case PG_TYPE_DATE: + return SQL_C_DATE; + case PG_TYPE_TIME: + return SQL_C_TIME; + case PG_TYPE_ABSTIME: + case PG_TYPE_DATETIME: + case PG_TYPE_TIMESTAMP: + return SQL_C_TIMESTAMP; + case PG_TYPE_MONEY: + return SQL_C_FLOAT; + case PG_TYPE_BOOL: + return ci->drivers.bools_as_char ? SQL_C_CHAR : SQL_C_BIT; + + case PG_TYPE_BYTEA: + return SQL_C_BINARY; + case PG_TYPE_LO: + return SQL_C_BINARY; + + default: + /* hack until permanent type is available */ + if (type == stmt->hdbc->lobj_type) + return SQL_C_BINARY; + + return SQL_C_CHAR; + } +} + + +char * +pgtype_to_name(StatementClass *stmt, Int4 type) +{ + switch (type) + { + case PG_TYPE_CHAR: + return "char"; + case PG_TYPE_CHAR2: + return "char2"; + case PG_TYPE_CHAR4: + return "char4"; + case PG_TYPE_CHAR8: + return "char8"; + case PG_TYPE_INT8: + return "int8"; + case PG_TYPE_NUMERIC: + return "numeric"; + case PG_TYPE_VARCHAR: + return "varchar"; + case PG_TYPE_BPCHAR: + return "char"; + case PG_TYPE_TEXT: + return "text"; + case PG_TYPE_NAME: + return "name"; + case PG_TYPE_INT2: + return "int2"; + case PG_TYPE_OID: + return "oid"; + case PG_TYPE_INT4: + return "int4"; + case PG_TYPE_FLOAT4: + return "float4"; + case PG_TYPE_FLOAT8: + return "float8"; + case PG_TYPE_DATE: + return "date"; + case PG_TYPE_TIME: + return "time"; + case PG_TYPE_ABSTIME: + return "abstime"; + case PG_TYPE_DATETIME: + return "datetime"; + case PG_TYPE_TIMESTAMP: + return "timestamp"; + case PG_TYPE_MONEY: + return "money"; + case PG_TYPE_BOOL: + return "bool"; + case PG_TYPE_BYTEA: + return "bytea"; + + case PG_TYPE_LO: + return PG_TYPE_LO_NAME; + + default: + /* hack until permanent type is available */ + if (type == stmt->hdbc->lobj_type) + return PG_TYPE_LO_NAME; + + /* + * "unknown" can actually be used in alter table because it is + * a real PG type! + */ + return "unknown"; + } +} + + +static Int2 +getNumericScale(StatementClass *stmt, Int4 type, int col) +{ + Int4 atttypmod; + QResultClass *result; + ColumnInfoClass *flds; + + mylog("getNumericScale: type=%d, col=%d\n", type, col); + + if (col < 0) + return PG_NUMERIC_MAX_SCALE; + + result = SC_get_Result(stmt); + + /* + * Manual Result Sets -- use assigned column width (i.e., from + * set_tuplefield_string) + */ + if (stmt->manual_result) + { + flds = result->fields; + if (flds) + return flds->adtsize[col]; + else + return PG_NUMERIC_MAX_SCALE; + } + + atttypmod = QR_get_atttypmod(result, col); + if (atttypmod > -1) + return (atttypmod & 0xffff); + else + return (QR_get_display_size(result, col) ? + QR_get_display_size(result, col) : + PG_NUMERIC_MAX_SCALE); +} + + +static Int4 +getNumericPrecision(StatementClass *stmt, Int4 type, int col) +{ + Int4 atttypmod; + QResultClass *result; + ColumnInfoClass *flds; + + mylog("getNumericPrecision: type=%d, col=%d\n", type, col); + + if (col < 0) + return PG_NUMERIC_MAX_PRECISION; + + result = SC_get_Result(stmt); + + /* + * Manual Result Sets -- use assigned column width (i.e., from + * set_tuplefield_string) + */ + if (stmt->manual_result) + { + flds = result->fields; + if (flds) + return flds->adtsize[col]; + else + return PG_NUMERIC_MAX_PRECISION; + } + + atttypmod = QR_get_atttypmod(result, col); + if (atttypmod > -1) + return (atttypmod >> 16) & 0xffff; + else + return (QR_get_display_size(result, col) >= 0 ? + QR_get_display_size(result, col) : + PG_NUMERIC_MAX_PRECISION); +} + + +Int4 +getCharPrecision(StatementClass *stmt, Int4 type, int col, int handle_unknown_size_as) +{ + int p = -1, + maxsize; + QResultClass *result; + ColumnInfoClass *flds; + ConnInfo *ci = &(SC_get_conn(stmt)->connInfo); + + mylog("getCharPrecision: type=%d, col=%d, unknown = %d\n", type, col, handle_unknown_size_as); + + /* Assign Maximum size based on parameters */ + switch (type) + { + case PG_TYPE_TEXT: + if (ci->drivers.text_as_longvarchar) + maxsize = ci->drivers.max_longvarchar_size; + else + maxsize = ci->drivers.max_varchar_size; + break; + + case PG_TYPE_VARCHAR: + case PG_TYPE_BPCHAR: + maxsize = ci->drivers.max_varchar_size; + break; + + default: + if (ci->drivers.unknowns_as_longvarchar) + maxsize = ci->drivers.max_longvarchar_size; + else + maxsize = ci->drivers.max_varchar_size; + break; + } + + /* + * Static Precision (i.e., the Maximum Precision of the datatype) This + * has nothing to do with a result set. + */ + if (maxsize == TEXT_FIELD_SIZE + 1) /* magic length for testing */ + { + if (PG_VERSION_GE(SC_get_conn(stmt), 7.1)) + maxsize = 0; + else + maxsize = TEXT_FIELD_SIZE; + } + if (col < 0) + return maxsize; + + result = SC_get_Result(stmt); + + /* + * Manual Result Sets -- use assigned column width (i.e., from + * set_tuplefield_string) + */ + if (stmt->manual_result) + { + flds = result->fields; + if (flds) + return flds->adtsize[col]; + else + return maxsize; + } + + /* Size is unknown -- handle according to parameter */ + if (QR_get_atttypmod(result, col) > -1) + return QR_get_atttypmod(result, col); + + if (type == PG_TYPE_BPCHAR || handle_unknown_size_as == UNKNOWNS_AS_LONGEST) + { + p = QR_get_display_size(result, col); + mylog("getCharPrecision: LONGEST: p = %d\n", p); + } + + if (p < 0 && handle_unknown_size_as == UNKNOWNS_AS_MAX) + return maxsize; + else + return p; +} + +static Int2 +getTimestampScale(StatementClass *stmt, Int4 type, int col) +{ + ConnectionClass *conn = SC_get_conn(stmt); + Int4 atttypmod; + QResultClass *result; + ColumnInfoClass *flds; + + mylog("getTimestampScale: type=%d, col=%d\n", type, col); + + if (col < 0) + return 0; + if (PG_VERSION_LT(conn, 7.2)) + return 0; + + result = SC_get_Result(stmt); + + /* + * Manual Result Sets -- use assigned column width (i.e., from + * set_tuplefield_string) + */ + atttypmod = 0; + if (stmt->manual_result) + { + flds = result->fields; + if (flds) + atttypmod = flds->atttypmod[col]; + mylog("atttypmod1=%d\n", atttypmod); + } + else + atttypmod = QR_get_atttypmod(result, col); + mylog("atttypmod2=%d\n", atttypmod); + return (atttypmod > -1 ? atttypmod : 0); +} + + +static Int4 +getTimestampPrecision(StatementClass *stmt, Int4 type, int col) +{ + Int4 fixed, + scale; + + mylog("getTimestampPrecision: type=%d, col=%d\n", type, col); + + switch (type) + { + case PG_TYPE_TIME: + fixed = 8; + break; + case PG_TYPE_TIME_WITH_TMZONE: + fixed = 11; + break; + case PG_TYPE_TIMESTAMP_NO_TMZONE: + fixed = 19; + break; + default: + fixed = 22; + break; + } + scale = getTimestampScale(stmt, type, col); + return (scale > 0) ? fixed + 1 + scale : fixed; +} + +/* + * For PG_TYPE_VARCHAR, PG_TYPE_BPCHAR, PG_TYPE_NUMERIC, SQLColumns will + * override this length with the atttypmod length from pg_attribute . + * + * If col >= 0, then will attempt to get the info from the result set. + * This is used for functions SQLDescribeCol and SQLColAttributes. + */ +Int4 +pgtype_precision(StatementClass *stmt, Int4 type, int col, int handle_unknown_size_as) +{ + switch (type) + { + case PG_TYPE_CHAR: + return 1; + case PG_TYPE_CHAR2: + return 2; + case PG_TYPE_CHAR4: + return 4; + case PG_TYPE_CHAR8: + return 8; + + case PG_TYPE_NAME: + return NAME_FIELD_SIZE; + + case PG_TYPE_INT2: + return 5; + + case PG_TYPE_OID: + case PG_TYPE_XID: + case PG_TYPE_INT4: + return 10; + + case PG_TYPE_INT8: + return 19; /* signed */ + + case PG_TYPE_NUMERIC: + return getNumericPrecision(stmt, type, col); + + case PG_TYPE_FLOAT4: + case PG_TYPE_MONEY: + return 7; + + case PG_TYPE_FLOAT8: + return 15; + + case PG_TYPE_DATE: + return 10; + case PG_TYPE_TIME: + return 8; + + case PG_TYPE_ABSTIME: + case PG_TYPE_TIMESTAMP: + return 22; + case PG_TYPE_DATETIME: + /* return 22; */ + return getTimestampPrecision(stmt, type, col); + + case PG_TYPE_BOOL: + return 1; + + case PG_TYPE_LO: + return SQL_NO_TOTAL; + + default: + + if (type == stmt->hdbc->lobj_type) /* hack until permanent + * type is available */ + return SQL_NO_TOTAL; + + /* Handle Character types and unknown types */ + return getCharPrecision(stmt, type, col, handle_unknown_size_as); + } +} + + +Int4 +pgtype_display_size(StatementClass *stmt, Int4 type, int col, int handle_unknown_size_as) +{ + switch (type) + { + case PG_TYPE_INT2: + return 6; + + case PG_TYPE_OID: + case PG_TYPE_XID: + return 10; + + case PG_TYPE_INT4: + return 11; + + case PG_TYPE_INT8: + return 20; /* signed: 19 digits + sign */ + + case PG_TYPE_NUMERIC: + return getNumericPrecision(stmt, type, col) + 2; + + case PG_TYPE_MONEY: + return 15; /* ($9,999,999.99) */ + + case PG_TYPE_FLOAT4: + return 13; + + case PG_TYPE_FLOAT8: + return 22; + + /* Character types use regular precision */ + default: + return pgtype_precision(stmt, type, col, handle_unknown_size_as); + } +} + + +/* + * For PG_TYPE_VARCHAR, PG_TYPE_BPCHAR, SQLColumns will + * override this length with the atttypmod length from pg_attribute + */ +Int4 +pgtype_length(StatementClass *stmt, Int4 type, int col, int handle_unknown_size_as) +{ + switch (type) + { + case PG_TYPE_INT2: + return 2; + + case PG_TYPE_OID: + case PG_TYPE_XID: + case PG_TYPE_INT4: + return 4; + + case PG_TYPE_INT8: + return 20; /* signed: 19 digits + sign */ + + case PG_TYPE_NUMERIC: + return getNumericPrecision(stmt, type, col) + 2; + + case PG_TYPE_FLOAT4: + case PG_TYPE_MONEY: + return 4; + + case PG_TYPE_FLOAT8: + return 8; + + case PG_TYPE_DATE: + case PG_TYPE_TIME: + return 6; /* sizeof(DATE(TIME)_STRUCT) */ + + case PG_TYPE_ABSTIME: + case PG_TYPE_DATETIME: + case PG_TYPE_TIMESTAMP: + return 16; /* sizeof(TIMESTAMP_STRUCT) */ + + /* Character types (and NUMERIC) use the default precision */ + case PG_TYPE_VARCHAR: + case PG_TYPE_BPCHAR: +#ifdef MULTIBYTE + /* after 7.2 */ + if (PG_VERSION_GE(SC_get_conn(stmt), 7.2)) + return 3 * pgtype_precision(stmt, type, col, handle_unknown_size_as); + else +#else + /* CR -> CR/LF */ + return 2 * pgtype_precision(stmt, type, col, handle_unknown_size_as); +#endif /* MULTIBYTE */ + default: + return pgtype_precision(stmt, type, col, handle_unknown_size_as); + } +} + + +Int2 +pgtype_scale(StatementClass *stmt, Int4 type, int col) +{ + switch (type) + { + case PG_TYPE_INT2: + case PG_TYPE_OID: + case PG_TYPE_XID: + case PG_TYPE_INT4: + case PG_TYPE_INT8: + case PG_TYPE_FLOAT4: + case PG_TYPE_FLOAT8: + case PG_TYPE_MONEY: + case PG_TYPE_BOOL: + + /* + * Number of digits to the right of the decimal point in + * "yyyy-mm=dd hh:mm:ss[.f...]" + */ + case PG_TYPE_ABSTIME: + case PG_TYPE_TIMESTAMP: + return 0; + case PG_TYPE_DATETIME: + /* return 0; */ + return getTimestampScale(stmt, type, col); + + case PG_TYPE_NUMERIC: + return getNumericScale(stmt, type, col); + + default: + return -1; + } +} + + +Int2 +pgtype_radix(StatementClass *stmt, Int4 type) +{ + switch (type) + { + case PG_TYPE_INT2: + case PG_TYPE_OID: + case PG_TYPE_INT4: + case PG_TYPE_INT8: + case PG_TYPE_NUMERIC: + case PG_TYPE_FLOAT4: + case PG_TYPE_MONEY: + case PG_TYPE_FLOAT8: + return 10; + default: + return -1; + } +} + + +Int2 +pgtype_nullable(StatementClass *stmt, Int4 type) +{ + return SQL_NULLABLE; /* everything should be nullable */ +} + + +Int2 +pgtype_auto_increment(StatementClass *stmt, Int4 type) +{ + switch (type) + { + case PG_TYPE_INT2: + case PG_TYPE_OID: + case PG_TYPE_XID: + case PG_TYPE_INT4: + case PG_TYPE_FLOAT4: + case PG_TYPE_MONEY: + case PG_TYPE_BOOL: + case PG_TYPE_FLOAT8: + case PG_TYPE_INT8: + case PG_TYPE_NUMERIC: + + case PG_TYPE_DATE: + case PG_TYPE_TIME: + case PG_TYPE_ABSTIME: + case PG_TYPE_DATETIME: + case PG_TYPE_TIMESTAMP: + return FALSE; + + default: + return -1; + } +} + + +Int2 +pgtype_case_sensitive(StatementClass *stmt, Int4 type) +{ + switch (type) + { + case PG_TYPE_CHAR: + + case PG_TYPE_CHAR2: + case PG_TYPE_CHAR4: + case PG_TYPE_CHAR8: + + case PG_TYPE_VARCHAR: + case PG_TYPE_BPCHAR: + case PG_TYPE_TEXT: + case PG_TYPE_NAME: + return TRUE; + + default: + return FALSE; + } +} + + +Int2 +pgtype_money(StatementClass *stmt, Int4 type) +{ + switch (type) + { + case PG_TYPE_MONEY: + return TRUE; + default: + return FALSE; + } +} + + +Int2 +pgtype_searchable(StatementClass *stmt, Int4 type) +{ + switch (type) + { + case PG_TYPE_CHAR: + case PG_TYPE_CHAR2: + case PG_TYPE_CHAR4: + case PG_TYPE_CHAR8: + + case PG_TYPE_VARCHAR: + case PG_TYPE_BPCHAR: + case PG_TYPE_TEXT: + case PG_TYPE_NAME: + return SQL_SEARCHABLE; + + default: + return SQL_ALL_EXCEPT_LIKE; + } +} + + +Int2 +pgtype_unsigned(StatementClass *stmt, Int4 type) +{ + switch (type) + { + case PG_TYPE_OID: + case PG_TYPE_XID: + return TRUE; + + case PG_TYPE_INT2: + case PG_TYPE_INT4: + case PG_TYPE_INT8: + case PG_TYPE_NUMERIC: + case PG_TYPE_FLOAT4: + case PG_TYPE_FLOAT8: + case PG_TYPE_MONEY: + return FALSE; + + default: + return -1; + } +} + + +char * +pgtype_literal_prefix(StatementClass *stmt, Int4 type) +{ + switch (type) + { + case PG_TYPE_INT2: + case PG_TYPE_OID: + case PG_TYPE_XID: + case PG_TYPE_INT4: + case PG_TYPE_INT8: + case PG_TYPE_NUMERIC: + case PG_TYPE_FLOAT4: + case PG_TYPE_FLOAT8: + case PG_TYPE_MONEY: + return NULL; + + default: + return "'"; + } +} + + +char * +pgtype_literal_suffix(StatementClass *stmt, Int4 type) +{ + switch (type) + { + case PG_TYPE_INT2: + case PG_TYPE_OID: + case PG_TYPE_XID: + case PG_TYPE_INT4: + case PG_TYPE_INT8: + case PG_TYPE_NUMERIC: + case PG_TYPE_FLOAT4: + case PG_TYPE_FLOAT8: + case PG_TYPE_MONEY: + return NULL; + + default: + return "'"; + } +} + + +char * +pgtype_create_params(StatementClass *stmt, Int4 type) +{ + switch (type) + { + case PG_TYPE_CHAR: + case PG_TYPE_VARCHAR: + return "max. length"; + default: + return NULL; + } +} + + +Int2 +sqltype_to_default_ctype(Int2 sqltype) +{ + /* + * from the table on page 623 of ODBC 2.0 Programmer's Reference + * (Appendix D) + */ + switch (sqltype) + { + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_LONGVARCHAR: + case SQL_DECIMAL: + case SQL_NUMERIC: + case SQL_BIGINT: + return SQL_C_CHAR; + + case SQL_BIT: + return SQL_C_BIT; + + case SQL_TINYINT: + return SQL_C_STINYINT; + + case SQL_SMALLINT: + return SQL_C_SSHORT; + + case SQL_INTEGER: + return SQL_C_SLONG; + + case SQL_REAL: + return SQL_C_FLOAT; + + case SQL_FLOAT: + case SQL_DOUBLE: + return SQL_C_DOUBLE; + + case SQL_BINARY: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: + return SQL_C_BINARY; + + case SQL_DATE: + return SQL_C_DATE; + + case SQL_TIME: + return SQL_C_TIME; + + case SQL_TIMESTAMP: + return SQL_C_TIMESTAMP; + + default: + /* should never happen */ + return SQL_C_CHAR; + } +} + +Int4 +ctype_length(Int2 ctype) +{ + switch (ctype) + { + case SQL_C_SSHORT: + case SQL_C_SHORT: + return sizeof(SWORD); + + case SQL_C_USHORT: + return sizeof(UWORD); + + case SQL_C_SLONG: + case SQL_C_LONG: + return sizeof(SDWORD); + + case SQL_C_ULONG: + return sizeof(UDWORD); + + case SQL_C_FLOAT: + return sizeof(SFLOAT); + + case SQL_C_DOUBLE: + return sizeof(SDOUBLE); + + case SQL_C_BIT: + return sizeof(UCHAR); + + case SQL_C_STINYINT: + case SQL_C_TINYINT: + return sizeof(SCHAR); + + case SQL_C_UTINYINT: + return sizeof(UCHAR); + + case SQL_C_DATE: + return sizeof(DATE_STRUCT); + + case SQL_C_TIME: + return sizeof(TIME_STRUCT); + + case SQL_C_TIMESTAMP: + return sizeof(TIMESTAMP_STRUCT); + + case SQL_C_BINARY: + case SQL_C_CHAR: + return 0; + + default: /* should never happen */ + return 0; + } +} diff --git a/src/interfaces/odbc/windev/pgtypes.h b/src/interfaces/odbc/windev/pgtypes.h new file mode 100644 index 0000000000..1d276432ce --- /dev/null +++ b/src/interfaces/odbc/windev/pgtypes.h @@ -0,0 +1,99 @@ +/* File: pgtypes.h + * + * Description: See "pgtypes.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __PGTYPES_H__ +#define __PGTYPES_H__ + +#include "psqlodbc.h" + +/* the type numbers are defined by the OID's of the types' rows */ +/* in table pg_type */ + + +#if 0 +#define PG_TYPE_LO ???? /* waiting for permanent type */ +#endif + +#define PG_TYPE_BOOL 16 +#define PG_TYPE_BYTEA 17 +#define PG_TYPE_CHAR 18 +#define PG_TYPE_NAME 19 +#define PG_TYPE_INT8 20 +#define PG_TYPE_INT2 21 +#define PG_TYPE_INT2VECTOR 22 +#define PG_TYPE_INT4 23 +#define PG_TYPE_REGPROC 24 +#define PG_TYPE_TEXT 25 +#define PG_TYPE_OID 26 +#define PG_TYPE_TID 27 +#define PG_TYPE_XID 28 +#define PG_TYPE_CID 29 +#define PG_TYPE_OIDVECTOR 30 +#define PG_TYPE_SET 32 +#define PG_TYPE_CHAR2 409 +#define PG_TYPE_CHAR4 410 +#define PG_TYPE_CHAR8 411 +#define PG_TYPE_POINT 600 +#define PG_TYPE_LSEG 601 +#define PG_TYPE_PATH 602 +#define PG_TYPE_BOX 603 +#define PG_TYPE_POLYGON 604 +#define PG_TYPE_FILENAME 605 +#define PG_TYPE_FLOAT4 700 +#define PG_TYPE_FLOAT8 701 +#define PG_TYPE_ABSTIME 702 +#define PG_TYPE_RELTIME 703 +#define PG_TYPE_TINTERVAL 704 +#define PG_TYPE_UNKNOWN 705 +#define PG_TYPE_MONEY 790 +#define PG_TYPE_OIDINT2 810 +#define PG_TYPE_OIDINT4 910 +#define PG_TYPE_OIDNAME 911 +#define PG_TYPE_BPCHAR 1042 +#define PG_TYPE_VARCHAR 1043 +#define PG_TYPE_DATE 1082 +#define PG_TYPE_TIME 1083 +#define PG_TYPE_TIMESTAMP_NO_TMZONE 1114 /* since 7.2 */ +#define PG_TYPE_DATETIME 1184 +#define PG_TYPE_TIME_WITH_TMZONE 1266 /* since 7.1 */ +#define PG_TYPE_TIMESTAMP 1296 /* deprecated since 7.0 */ +#define PG_TYPE_NUMERIC 1700 + +/* extern Int4 pgtypes_defined[]; */ +extern Int2 sqlTypes[]; + +/* Defines for pgtype_precision */ +#define PG_STATIC (-1) + +Int4 sqltype_to_pgtype(StatementClass *stmt, Int2 fSqlType); + +Int2 pgtype_to_sqltype(StatementClass *stmt, Int4 type); +Int2 pgtype_to_ctype(StatementClass *stmt, Int4 type); +char *pgtype_to_name(StatementClass *stmt, Int4 type); + +/* These functions can use static numbers or result sets(col parameter) */ +Int4 pgtype_precision(StatementClass *stmt, Int4 type, int col, int handle_unknown_size_as); +Int4 pgtype_display_size(StatementClass *stmt, Int4 type, int col, int handle_unknown_size_as); +Int4 pgtype_length(StatementClass *stmt, Int4 type, int col, int handle_unknown_size_as); + +Int2 pgtype_scale(StatementClass *stmt, Int4 type, int col); +Int2 pgtype_radix(StatementClass *stmt, Int4 type); +Int2 pgtype_nullable(StatementClass *stmt, Int4 type); +Int2 pgtype_auto_increment(StatementClass *stmt, Int4 type); +Int2 pgtype_case_sensitive(StatementClass *stmt, Int4 type); +Int2 pgtype_money(StatementClass *stmt, Int4 type); +Int2 pgtype_searchable(StatementClass *stmt, Int4 type); +Int2 pgtype_unsigned(StatementClass *stmt, Int4 type); +char *pgtype_literal_prefix(StatementClass *stmt, Int4 type); +char *pgtype_literal_suffix(StatementClass *stmt, Int4 type); +char *pgtype_create_params(StatementClass *stmt, Int4 type); + +Int2 sqltype_to_default_ctype(Int2 sqltype); +Int4 ctype_length(Int2 ctype); + +#endif diff --git a/src/interfaces/odbc/windev/psqlodbc.c b/src/interfaces/odbc/windev/psqlodbc.c new file mode 100644 index 0000000000..cc8d8b7bfb --- /dev/null +++ b/src/interfaces/odbc/windev/psqlodbc.c @@ -0,0 +1,132 @@ +/*-------- + * Module: psqlodbc.c + * + * Description: This module contains the main entry point (DllMain) + * for the library. It also contains functions to get + * and set global variables for the driver in the registry. + * + * Classes: n/a + * + * API functions: none + * + * Comments: See "notice.txt" for copyright and license information. + *-------- + */ + +#include "psqlodbc.h" +#include "dlg_specific.h" + +#ifdef WIN32 +#include +#endif + +GLOBAL_VALUES globals; + +RETCODE SQL_API SQLDummyOrdinal(void); + +#ifdef WIN32 +HINSTANCE NEAR s_hModule; /* Saved module handle. */ + +/* This is where the Driver Manager attaches to this Driver */ +BOOL WINAPI +DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) +{ + WORD wVersionRequested; + WSADATA wsaData; + + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + s_hModule = hInst; /* Save for dialog boxes */ + + /* Load the WinSock Library */ + wVersionRequested = MAKEWORD(1, 1); + + if (WSAStartup(wVersionRequested, &wsaData)) + return FALSE; + + /* Verify that this is the minimum version of WinSock */ + if (LOBYTE(wsaData.wVersion) != 1 || + HIBYTE(wsaData.wVersion) != 1) + { + WSACleanup(); + return FALSE; + } + + getCommonDefaults(DBMS_NAME, ODBCINST_INI, NULL); + break; + + case DLL_THREAD_ATTACH: + break; + + case DLL_PROCESS_DETACH: + WSACleanup(); + return TRUE; + + case DLL_THREAD_DETACH: + break; + + default: + break; + } + + return TRUE; + + UNREFERENCED_PARAMETER(lpReserved); +} + +#else /* not WIN32 */ + +#ifndef TRUE +#define TRUE (BOOL)1 +#endif +#ifndef FALSE +#define FALSE (BOOL)0 +#endif + +#ifdef __GNUC__ + +/* This function is called at library initialization time. */ + +static BOOL +__attribute__((constructor)) +init(void) +{ + getCommonDefaults(DBMS_NAME, ODBCINST_INI, NULL); + return TRUE; +} + +#else /* not __GNUC__ */ + +/* + * These two functions do shared library initialziation on UNIX, well at least + * on Linux. I don't know about other systems. + */ +BOOL +_init(void) +{ + getCommonDefaults(DBMS_NAME, ODBCINST_INI, NULL); + return TRUE; +} + +BOOL +_fini(void) +{ + return TRUE; +} +#endif /* not __GNUC__ */ +#endif /* not WIN32 */ + + +/* + * This function is used to cause the Driver Manager to + * call functions by number rather than name, which is faster. + * The ordinal value of this function must be 199 to have the + * Driver Manager do this. Also, the ordinal values of the + * functions must match the value of fFunction in SQLGetFunctions() + */ +RETCODE SQL_API +SQLDummyOrdinal(void) +{ + return SQL_SUCCESS; +} diff --git a/src/interfaces/odbc/windev/psqlodbc.h b/src/interfaces/odbc/windev/psqlodbc.h new file mode 100644 index 0000000000..fa87918bd9 --- /dev/null +++ b/src/interfaces/odbc/windev/psqlodbc.h @@ -0,0 +1,268 @@ +/* File: psqlodbc.h + * + * Description: This file contains defines and declarations that are related to + * the entire driver. + * + * Comments: See "notice.txt" for copyright and license information. + * + * $Id: psqlodbc.h,v 1.1 2002/01/11 02:50:01 inoue Exp $ + * + */ + +#ifndef __PSQLODBC_H__ +#define __PSQLODBC_H__ + +#ifndef WIN32 +#include "pg_config.h" +#else +#include +#endif + +#include /* for FILE* pointers: see GLOBAL_VALUES */ + +/* Must come before sql.h */ +#ifndef ODBCVER +#define ODBCVER 0x0250 +#endif /* ODBCVER_REP */ + + +#if defined(WIN32) || defined(WITH_UNIXODBC) || defined(WITH_IODBC) +#include +#include +#else +#include "iodbc.h" +#include "isql.h" +#include "isqlext.h" +#endif + +#if defined(WIN32) +#include +#elif defined(WITH_UNIXODBC) +#include +#elif defined(WITH_IODBC) +#include +#else +#include "gpps.h" +#endif + +#ifndef WIN32 +#define Int4 long int +#define UInt4 unsigned int +#define Int2 short +#define UInt2 unsigned short + +#if !defined(WITH_UNIXODBC) && !defined(WITH_IODBC) +typedef float SFLOAT; +typedef double SDOUBLE; +#endif + +#ifndef CALLBACK +#define CALLBACK +#endif + +#else +#define Int4 int +#define UInt4 unsigned int +#define Int2 short +#define UInt2 unsigned short +#endif + +typedef UInt4 Oid; + +#ifndef WIN32 +#define stricmp strcasecmp +#define strnicmp strncasecmp +#endif + +/* Driver stuff */ + +#define DRIVERNAME "PostgreSQL ODBC" +#if (ODBCVER >= 0x0300) +#define DRIVER_ODBC_VER "03.00" +#define DBMS_NAME "PostgreSQL30" +#else +#define DRIVER_ODBC_VER "02.50" +#define DBMS_NAME "PostgreSQL" +#endif /* ODBCVER */ + +#define POSTGRESDRIVERVERSION "07.01.0009" + +#ifdef WIN32 +#if (ODBCVER >= 0x0300) +#define DRIVER_FILE_NAME "PSQLODBC30.DLL" +#else +#define DRIVER_FILE_NAME "PSQLODBC.DLL" +#endif /* ODBCVER */ +#else +#define DRIVER_FILE_NAME "libpsqlodbc.so" +#endif /* WIN32 */ + +/* Limits */ +#ifdef WIN32 +#define BLCKSZ 4096 +#endif + +#define MAX_MESSAGE_LEN 65536 /* This puts a limit on + * query size but I don't */ + /* see an easy way round this - DJP 24-1-2001 */ +#define MAX_CONNECT_STRING 4096 +#define ERROR_MSG_LENGTH 4096 +#define FETCH_MAX 100 /* default number of rows to cache + * for declare/fetch */ +#define TUPLE_MALLOC_INC 100 +#define SOCK_BUFFER_SIZE 4096 /* default socket buffer + * size */ +#define MAX_CONNECTIONS 128 /* conns per environment + * (arbitrary) */ +#define MAX_FIELDS 512 +#define BYTELEN 8 +#define VARHDRSZ sizeof(Int4) + +#define MAX_TABLE_LEN 32 +#define MAX_COLUMN_LEN 32 +#define MAX_CURSOR_LEN 32 + +/* Registry length limits */ +#define LARGE_REGISTRY_LEN 4096 /* used for special cases */ +#define MEDIUM_REGISTRY_LEN 256 /* normal size for + * user,database,etc. */ +#define SMALL_REGISTRY_LEN 10 /* for 1/0 settings */ + + +/* These prefixes denote system tables */ +#define POSTGRES_SYS_PREFIX "pg_" +#define KEYS_TABLE "dd_fkey" + +/* Info limits */ +#define MAX_INFO_STRING 128 +#define MAX_KEYPARTS 20 +#define MAX_KEYLEN 512 /* max key of the form + * "date+outlet+invoice" */ +#define MAX_ROW_SIZE 0 /* Unlimited rowsize with the + * Tuple Toaster */ +#define MAX_STATEMENT_LEN 0 /* Unlimited statement size with + * 7.0 */ + +/* Previously, numerous query strings were defined of length MAX_STATEMENT_LEN */ +/* Now that's 0, lets use this instead. DJP 24-1-2001 */ +#define STD_STATEMENT_LEN MAX_MESSAGE_LEN + +#define PG62 "6.2" /* "Protocol" key setting + * to force Postgres 6.2 */ +#define PG63 "6.3" /* "Protocol" key setting + * to force postgres 6.3 */ +#define PG64 "6.4" + +typedef struct ConnectionClass_ ConnectionClass; +typedef struct StatementClass_ StatementClass; +typedef struct QResultClass_ QResultClass; +typedef struct SocketClass_ SocketClass; +typedef struct BindInfoClass_ BindInfoClass; +typedef struct ParameterInfoClass_ ParameterInfoClass; +typedef struct ColumnInfoClass_ ColumnInfoClass; +typedef struct TupleListClass_ TupleListClass; +typedef struct EnvironmentClass_ EnvironmentClass; +typedef struct TupleNode_ TupleNode; +typedef struct TupleField_ TupleField; + +typedef struct col_info COL_INFO; +typedef struct lo_arg LO_ARG; + +typedef struct GlobalValues_ +{ + int fetch_max; + int socket_buffersize; + int unknown_sizes; + int max_varchar_size; + int max_longvarchar_size; + char debug; + char commlog; + char disable_optimizer; + char ksqo; + char unique_index; + char onlyread; /* readonly is reserved on Digital C++ + * compiler */ + char use_declarefetch; + char text_as_longvarchar; + char unknowns_as_longvarchar; + char bools_as_char; + char lie; + char parse; + char cancel_as_freestmt; + char extra_systable_prefixes[MEDIUM_REGISTRY_LEN]; + char conn_settings[LARGE_REGISTRY_LEN]; + char protocol[SMALL_REGISTRY_LEN]; +} GLOBAL_VALUES; + +typedef struct StatementOptions_ +{ + int maxRows; + int maxLength; + int rowset_size; + int keyset_size; + int cursor_type; + int scroll_concurrency; + int retrieve_data; + int bind_size; /* size of each structure if using Row + * Binding */ + int use_bookmarks; + UInt4 *rowsFetched; + UInt2 *rowStatusArray; + void *bookmark_ptr; + UInt2 *row_operation_ptr; + UInt4 *row_offset_ptr; + UInt4 paramset_size; + UInt4 param_bind_type; + UInt4 *param_processed_ptr; + UInt2 *param_status_ptr; + UInt2 *param_operation_ptr; + UInt4 *param_offset_ptr; +} StatementOptions; + +/* Used to pass extra query info to send_query */ +typedef struct QueryInfo_ +{ + int row_size; + QResultClass *result_in; + char *cursor; +} QueryInfo; + +void logs_on_off(int cnopen, int, int); + +#define PG_TYPE_LO (-999) /* hack until permanent + * type available */ +#define PG_TYPE_LO_NAME "lo" +#define OID_ATTNUM (-2) /* the attnum in pg_index + * of the oid */ + +/* sizes */ +#define TEXT_FIELD_SIZE 8190 /* size of text fields + * (not including null + * term) */ +#define NAME_FIELD_SIZE 32 /* size of name fields */ +#define MAX_VARCHAR_SIZE 254 /* maximum size of a varchar (not + * including null term) */ + +#define PG_NUMERIC_MAX_PRECISION 1000 +#define PG_NUMERIC_MAX_SCALE 1000 + +#define INFO_INQUIRY_LEN 8192 /* this seems sufficiently big for + * queries used in info.c inoue + * 2001/05/17 */ + +#include "misc.h" + +#ifdef _MEMORY_DEBUG_ +void *debug_alloc(size_t); +void *debug_realloc(void *, size_t); +char *debug_strdup(const char *); +void debug_free(void *); +void debug_memory_check(void); + +#define malloc debug_alloc +#define realloc debug_realloc +#define strdup debug_strdup +#define free debug_free +#endif /* _MEMORY_DEBUG_ */ + +#endif diff --git a/src/interfaces/odbc/windev/psqlodbc.rc b/src/interfaces/odbc/windev/psqlodbc.rc new file mode 100644 index 0000000000..30a7ff7203 --- /dev/null +++ b/src/interfaces/odbc/windev/psqlodbc.rc @@ -0,0 +1,425 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +#ifdef MULTIBYTE +DLG_CONFIG DIALOG DISCARDABLE 65, 43, 299, 113 +STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | + WS_SYSMENU +CAPTION "PostgreSQL Driver Setup" +FONT 10, "Terminal" +BEGIN + RTEXT "&Data Source:",IDC_DSNAMETEXT,3,9,49,8,NOT WS_GROUP + EDITTEXT IDC_DSNAME,59,9,72,12,ES_AUTOHSCROLL | WS_GROUP + RTEXT "Des&cription:",IDC_DESCTEXT,135,10,49,8,NOT WS_GROUP + EDITTEXT IDC_DESC,185,10,110,25,ES_AUTOHSCROLL + RTEXT "Data&base:",IDC_STATIC,15,24,37,8,NOT WS_GROUP + EDITTEXT IDC_DATABASE,59,24,72,12,ES_AUTOHSCROLL + RTEXT "&Server:",IDC_STATIC,23,38,29,8,NOT WS_GROUP + EDITTEXT IDC_SERVER,59,38,72,12,ES_AUTOHSCROLL + RTEXT "&Port:",IDC_STATIC,161,38,21,8 + EDITTEXT IDC_PORT,185,38,37,12,ES_AUTOHSCROLL + RTEXT "&User Name:",IDC_STATIC,11,53,41,8 + EDITTEXT IDC_USER,59,53,72,12,ES_AUTOHSCROLL + RTEXT "Pass&word:",IDC_STATIC,145,53,37,8 + EDITTEXT IDC_PASSWORD,185,53,72,12,ES_PASSWORD | ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,27,88,40,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,81,88,40,14 + GROUPBOX "Options (Advanced):",IDC_OPTIONS,141,72,140,35, + BS_CENTER + PUSHBUTTON "Driver",IDC_DRIVER,149,89,50,14 + PUSHBUTTON "DataSource",IDC_DATASOURCE,221,88,50,14 + CTEXT "Please supply any missing information needed to connect.", + DRV_MSG_LABEL,25,4,238,10 +END + +DLG_OPTIONS_DRV DIALOG DISCARDABLE 0, 0, 306, 226 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Advanced Options (Driver)" +FONT 10, "Terminal" +BEGIN + CONTROL "Disable Genetic &Optimizer",DRV_OPTIMIZER,"Button", + BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP,13,11,116,10 + CONTROL "Comm&Log (C:\\psqlodbc.log)",DRV_COMMLOG,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,164,11,120,10 + CONTROL "&KSQO (Keyset Query Optimization)",DRV_KSQO,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,13,23,144,10 + CONTROL "&ReadOnly (Default)",DRV_READONLY,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,164,24,88,10 + CONTROL "Recognize Unique &Indexes",DRV_UNIQUEINDEX,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,13,35,112,10 + CONTROL "P&arse Statements",DRV_PARSE,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,164,37,80,10 + CONTROL "&Use Declare/Fetch",DRV_USEDECLAREFETCH,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,13,47,84,10 + CONTROL "Cancel as FreeStmt (Exp)",DRV_CANCELASFREESTMT,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,164,50,112,10 + CONTROL "Mylog(Debug ouput)",DRV_DEBUG,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,164,63,112,10 + GROUPBOX "Unknown Sizes",IDC_STATIC,13,76,175,24 + CONTROL "Maximum",DRV_UNKNOWN_MAX,"Button",BS_AUTORADIOBUTTON | + WS_GROUP | WS_TABSTOP,21,84,44,10 + CONTROL "Don't Know",DRV_UNKNOWN_DONTKNOW,"Button", + BS_AUTORADIOBUTTON | WS_TABSTOP,72,84,56,10 + CONTROL "Longest",DRV_UNKNOWN_LONGEST,"Button", + BS_AUTORADIOBUTTON | WS_TABSTOP,135,84,44,10 + GROUPBOX "Data Type Options",IDC_STATIC,13,104,282,23 + CONTROL "Text as LongVarChar",DRV_TEXT_LONGVARCHAR,"Button", + BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP,15,115,92,10 + CONTROL "Unknowns as LongVarChar",DRV_UNKNOWNS_LONGVARCHAR, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,112,115,108,10 + CONTROL "Bools as Char",DRV_BOOLS_CHAR,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,225,115,68,10 + LTEXT "&Cache Size:",IDC_STATIC,15,133,45,8 + EDITTEXT DRV_CACHE_SIZE,61,129,35,12,ES_AUTOHSCROLL + LTEXT "Max &Varchar:",IDC_STATIC,99,133,49,8 + EDITTEXT DRV_VARCHAR_SIZE,149,129,35,12,ES_AUTOHSCROLL + LTEXT "Max Lon&gVarChar:",IDC_STATIC,192,133,65,8 + EDITTEXT DRV_LONGVARCHAR_SIZE,259,129,35,12,ES_AUTOHSCROLL + LTEXT "SysTable &Prefixes:",IDC_STATIC,23,144,36,20 + EDITTEXT DRV_EXTRASYSTABLEPREFIXES,61,153,75,12,ES_AUTOHSCROLL + LTEXT "Connect &Settings:",IDC_STATIC,22,165,35,20 + EDITTEXT DRV_CONNSETTINGS,61,165,225,25,ES_MULTILINE | + ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN + DEFPUSHBUTTON "OK",IDOK,59,201,50,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,124,201,50,14 + PUSHBUTTON "Defaults",IDDEFAULTS,189,201,50,15 + CONTROL "Default",DRV_OR_DSN,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | + BS_NOTIFY | WS_TABSTOP,247,205,40,10 +END + +DLG_OPTIONS_DS DIALOG DISCARDABLE 0, 0, 267, 161 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Advanced Options (DataSource)" +FONT 10, "Terminal" +BEGIN + CONTROL "&ReadOnly",DS_READONLY,"Button",BS_AUTOCHECKBOX | + WS_GROUP | WS_TABSTOP,45,13,48,10 + CONTROL "Row &Versioning",DS_ROWVERSIONING,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,149,13,72,10 + CONTROL "Show System &Tables",DS_SHOWSYSTEMTABLES,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,45,28,88,10 + CONTROL "Disallow &Premature",DS_DISALLOWPREMATURE,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,149,28,86,10 + GROUPBOX "Protocol",IDC_STATIC,43,44,180,25 + CONTROL "7.X,6.4+",DS_PG64,"Button",BS_AUTORADIOBUTTON | + WS_GROUP,53,54,47,10 + CONTROL "6.3",DS_PG63,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP, + 131,54,26,10 + CONTROL "6.2",DS_PG62,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP, + 187,54,26,10 + GROUPBOX "OID Options",IDC_STATIC,43,74,180,25 + CONTROL "Show &Column",DS_SHOWOIDCOLUMN,"Button",BS_AUTOCHECKBOX | + WS_GROUP | WS_TABSTOP,53,85,59,10 + CONTROL "Fake &Index",DS_FAKEOIDINDEX,"Button",BS_AUTOCHECKBOX | + WS_GROUP | WS_TABSTOP,161,85,55,10 + LTEXT "Connect &Settings:",IDC_STATIC,10,105,35,25 + EDITTEXT DS_CONNSETTINGS,50,105,200,20,ES_MULTILINE | + ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN + DEFPUSHBUTTON "OK",IDOK,71,135,50,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,146,135,50,14 +END +#else +DLG_CONFIG DIALOG DISCARDABLE 65, 43, 292, 116 +STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | + WS_SYSMENU +CAPTION "PostgreSQL Driver Setup" +FONT 8, "MS Sans Serif" +BEGIN + RTEXT "&Data Source:",IDC_DSNAMETEXT,5,10,50,12,NOT WS_GROUP + EDITTEXT IDC_DSNAME,57,10,72,12,ES_AUTOHSCROLL | WS_GROUP + RTEXT "Des&cription:",IDC_DESCTEXT,135,10,39,12,NOT WS_GROUP + EDITTEXT IDC_DESC,175,10,108,12,ES_AUTOHSCROLL + RTEXT "Data&base:",IDC_STATIC,17,25,38,12,NOT WS_GROUP + EDITTEXT IDC_DATABASE,57,25,72,12,ES_AUTOHSCROLL + RTEXT "&Server:",IDC_STATIC,27,40,29,12,NOT WS_GROUP + EDITTEXT IDC_SERVER,57,40,72,12,ES_AUTOHSCROLL + RTEXT "&Port:",IDC_STATIC,153,40,22,12 + EDITTEXT IDC_PORT,175,40,37,12,ES_AUTOHSCROLL + RTEXT "&User Name:",IDC_STATIC,17,55,39,12 + EDITTEXT IDC_USER,57,55,72,12,ES_AUTOHSCROLL + RTEXT "Pass&word:",IDC_STATIC,141,55,34,12 + EDITTEXT IDC_PASSWORD,175,55,72,12,ES_PASSWORD | ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,25,90,40,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,80,90,40,14 + GROUPBOX "Options (Advanced):",IDC_OPTIONS,140,74,140,35, + BS_CENTER + PUSHBUTTON "Driver",IDC_DRIVER,160,90,50,14 + PUSHBUTTON "DataSource",IDC_DATASOURCE,220,90,50,14 + CTEXT "Please supply any missing information needed to connect.", + DRV_MSG_LABEL,36,5,220,15 +END + +DLG_OPTIONS_DRV DIALOG DISCARDABLE 0, 0, 287, 241 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Advanced Options (Driver)" +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Disable Genetic &Optimizer",DRV_OPTIMIZER,"Button", + BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP,15,5,97,10 + CONTROL "Comm&Log (C:\\psqlodbc.log)",DRV_COMMLOG,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,140,5,113,10 + CONTROL "&KSQO (Keyset Query Optimization)",DRV_KSQO,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,15,20,124,10 + CONTROL "&ReadOnly (Default)",DRV_READONLY,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,140,20,80,10 + CONTROL "Recognize Unique &Indexes",DRV_UNIQUEINDEX,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,15,35,101,10 + CONTROL "P&arse Statements",DRV_PARSE,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,140,35,80,10 + CONTROL "&Use Declare/Fetch",DRV_USEDECLAREFETCH,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,15,50,80,10 + CONTROL "Cancel as FreeStmt (Exp)",DRV_CANCELASFREESTMT,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,140,50,105,10 + CONTROL "Mylog(Debug ouput)",DRV_DEBUG,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,140,65,112,10 + GROUPBOX "Unknown Sizes",IDC_STATIC,10,80,175,25 + CONTROL "Maximum",DRV_UNKNOWN_MAX,"Button",BS_AUTORADIOBUTTON | + WS_GROUP | WS_TABSTOP,15,91,45,10 + CONTROL "Don't Know",DRV_UNKNOWN_DONTKNOW,"Button", + BS_AUTORADIOBUTTON | WS_TABSTOP,70,91,53,10 + CONTROL "Longest",DRV_UNKNOWN_LONGEST,"Button", + BS_AUTORADIOBUTTON | WS_TABSTOP,130,91,50,10 + GROUPBOX "Data Type Options",IDC_STATIC,10,110,270,25 + CONTROL "Text as LongVarChar",DRV_TEXT_LONGVARCHAR,"Button", + BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP,15,120,80,10 + CONTROL "Unknowns as LongVarChar",DRV_UNKNOWNS_LONGVARCHAR, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,105,120,100,10 + CONTROL "Bools as Char",DRV_BOOLS_CHAR,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,215,120,60,10 + LTEXT "&Cache Size:",IDC_STATIC,10,145,40,10 + EDITTEXT DRV_CACHE_SIZE,50,145,35,12,ES_AUTOHSCROLL + LTEXT "Max &Varchar:",IDC_STATIC,90,145,45,10 + EDITTEXT DRV_VARCHAR_SIZE,135,145,35,12,ES_AUTOHSCROLL + LTEXT "Max Lon&gVarChar:",IDC_STATIC,180,145,60,10 + EDITTEXT DRV_LONGVARCHAR_SIZE,240,145,35,12,ES_AUTOHSCROLL + LTEXT "SysTable &Prefixes:",IDC_STATIC,15,160,35,20 + EDITTEXT DRV_EXTRASYSTABLEPREFIXES,50,166,75,12,ES_AUTOHSCROLL + RTEXT "Connect &Settings:",IDC_STATIC,10,185,35,20 + EDITTEXT DRV_CONNSETTINGS,50,185,225,25,ES_MULTILINE | + ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN + DEFPUSHBUTTON "OK",IDOK,45,220,50,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,110,220,50,14 + PUSHBUTTON "Defaults",IDDEFAULTS,175,220,50,15 + CONTROL "Default",DRV_OR_DSN,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | + BS_NOTIFY | WS_TABSTOP,233,224,40,10 +END + +DLG_OPTIONS_DS DIALOG DISCARDABLE 0, 0, 267, 161 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Advanced Options (DataSource)" +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "&ReadOnly",DS_READONLY,"Button",BS_AUTOCHECKBOX | + WS_GROUP | WS_TABSTOP,25,10,53,10 + CONTROL "Row &Versioning",DS_ROWVERSIONING,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,130,10,85,10 + CONTROL "Show System &Tables",DS_SHOWSYSTEMTABLES,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,25,25,85,10 + CONTROL "Disallow &Premature",DS_DISALLOWPREMATURE,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,130,25,85,10 + GROUPBOX "Protocol",IDC_STATIC,15,40,180,25 + CONTROL "7.X,6.4+",DS_PG64,"Button",BS_AUTORADIOBUTTON | WS_GROUP,25, + 50,35,10 + CONTROL "6.3",DS_PG63,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP, + 75,50,26,10 + CONTROL "6.2",DS_PG62,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP, + 130,50,26,10 + GROUPBOX "OID Options",IDC_STATIC,15,70,180,25 + CONTROL "Show &Column",DS_SHOWOIDCOLUMN,"Button",BS_AUTOCHECKBOX | + WS_GROUP | WS_TABSTOP,25,81,59,10 + CONTROL "Fake &Index",DS_FAKEOIDINDEX,"Button",BS_AUTOCHECKBOX | + WS_GROUP | WS_TABSTOP,115,81,51,10 + RTEXT "Connect &Settings:",IDC_STATIC,10,105,35,25 + EDITTEXT DS_CONNSETTINGS,50,105,200,20,ES_MULTILINE | + ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN + DEFPUSHBUTTON "OK",IDOK,71,135,50,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,146,135,50,14 +END +#endif + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +#ifdef MULTIBYTE +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + DLG_CONFIG, DIALOG + BEGIN + BOTTOMMARGIN, 112 + END + + DLG_OPTIONS_DRV, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 301 + TOPMARGIN, 5 + BOTTOMMARGIN, 206 + END + + DLG_OPTIONS_DS, DIALOG + BEGIN + LEFTMARGIN, 5 + RIGHTMARGIN, 260 + TOPMARGIN, 7 + BOTTOMMARGIN, 154 + END +END +#else +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + DLG_CONFIG, DIALOG + BEGIN + BOTTOMMARGIN, 115 + END + + DLG_OPTIONS_DRV, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 280 + TOPMARGIN, 7 + BOTTOMMARGIN, 219 + END + + DLG_OPTIONS_DS, DIALOG + BEGIN + LEFTMARGIN, 5 + RIGHTMARGIN, 260 + VERTGUIDE, 55 + TOPMARGIN, 7 + BOTTOMMARGIN, 154 + END +END +#endif // MULTIBYTE +#endif // APSTUDIO_INVOKED + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 7,1,0,9 + PRODUCTVERSION 7,1,0,9 + FILEFLAGSMASK 0x3L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "Comments", "PostgreSQL ODBC driver\0" +#ifdef MULTIBYTE + VALUE "CompanyName", "Insight Distribution Systems & Sankyo Unyu Service (MULTIBYTE support)\0" +#else + VALUE "CompanyName", "Insight Distribution Systems\0" +#endif + VALUE "FileDescription", "PostgreSQL Driver\0" + VALUE "FileVersion", " 07.01.0009\0" + VALUE "InternalName", "psqlodbc\0" + VALUE "LegalCopyright", "\0" + VALUE "LegalTrademarks", "ODBC(TM) is a trademark of Microsoft Corporation. Microsoft® is a registered trademark of Microsoft Corporation. Windows(TM) is a trademark of Microsoft Corporation.\0" + VALUE "OriginalFilename", "psqlodbc.dll\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "Microsoft Open Database Connectivity\0" + VALUE "ProductVersion", " 07.01.0009\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_BADDSN "Invalid DSN entry, please recheck." + IDS_MSGTITLE "Invalid DSN" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/src/interfaces/odbc/windev/psqlodbc.reg b/src/interfaces/odbc/windev/psqlodbc.reg new file mode 100644 index 0000000000..ac4322f942 --- /dev/null +++ b/src/interfaces/odbc/windev/psqlodbc.reg @@ -0,0 +1,17 @@ +REGEDIT4 + +[HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBCINST.INI] + +[HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBCINST.INI\ODBC Drivers] +"PostgreSQL"="Installed" + +[HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBCINST.INI\PostgreSQL] +"APILevel"="1" +"ConnectFunctions"="YYN" +"Driver"="PSQLODBC.DLL" +"DriverODBCVer"="02.50" +"FileUsage"="0" +"Setup"="PSQLODBC.DLL" +"SQLLevel"="1" +"UsageCount"=dword:00000001 + diff --git a/src/interfaces/odbc/windev/psqlodbc_win32.def b/src/interfaces/odbc/windev/psqlodbc_win32.def new file mode 100644 index 0000000000..23a5a82b39 --- /dev/null +++ b/src/interfaces/odbc/windev/psqlodbc_win32.def @@ -0,0 +1,60 @@ +LIBRARY psqlodbc +EXPORTS +SQLAllocConnect @1 +SQLAllocEnv @2 +SQLAllocStmt @3 +SQLBindCol @4 +SQLCancel @5 +SQLColAttributes @6 +SQLConnect @7 +SQLDescribeCol @8 +SQLDisconnect @9 +SQLError @10 +SQLExecDirect @11 +SQLExecute @12 +SQLFetch @13 +SQLFreeConnect @14 +SQLFreeEnv @15 +SQLFreeStmt @16 +SQLGetCursorName @17 +SQLNumResultCols @18 +SQLPrepare @19 +SQLRowCount @20 +SQLSetCursorName @21 +SQLTransact @23 +SQLColumns @40 +SQLDriverConnect @41 +SQLGetConnectOption @42 +SQLGetData @43 +SQLGetFunctions @44 +SQLGetInfo @45 +SQLGetStmtOption @46 +SQLGetTypeInfo @47 +SQLParamData @48 +SQLPutData @49 +SQLSetConnectOption @50 +SQLSetStmtOption @51 +SQLSpecialColumns @52 +SQLStatistics @53 +SQLTables @54 +SQLBrowseConnect @55 +SQLColumnPrivileges @56 +SQLDescribeParam @58 +SQLExtendedFetch @59 +SQLForeignKeys @60 +SQLMoreResults @61 +SQLNativeSql @62 +SQLNumParams @63 +SQLParamOptions @64 +SQLPrimaryKeys @65 +SQLProcedureColumns @66 +SQLProcedures @67 +SQLSetPos @68 +SQLSetScrollOptions @69 +SQLTablePrivileges @70 +SQLBindParameter @72 +SQLDummyOrdinal @199 +dconn_FDriverConnectProc @200 +DllMain @201 +ConfigDSN @202 + diff --git a/src/interfaces/odbc/windev/qresult.c b/src/interfaces/odbc/windev/qresult.c new file mode 100644 index 0000000000..ca7a6dee9e --- /dev/null +++ b/src/interfaces/odbc/windev/qresult.c @@ -0,0 +1,712 @@ +/*--------- + * Module: qresult.c + * + * Description: This module contains functions related to + * managing result information (i.e, fetching rows + * from the backend, managing the tuple cache, etc.) + * and retrieving it. Depending on the situation, a + * QResultClass will hold either data from the backend + * or a manually built result (see "qresult.h" to + * see which functions/macros are for manual or backend + * results. For manually built results, the + * QResultClass simply points to TupleList and + * ColumnInfo structures, which actually hold the data. + * + * Classes: QResultClass (Functions prefix: "QR_") + * + * API functions: none + * + * Comments: See "notice.txt" for copyright and license information. + *--------- + */ + +#include "qresult.h" + +#include "misc.h" +#include +#include + +#ifndef TRUE +#define TRUE (BOOL)1 +#endif +#ifndef FALSE +#define FALSE (BOOL)0 +#endif + + +/* + * Used for building a Manual Result only + * All info functions call this function to create the manual result set. + */ +void +QR_set_num_fields(QResultClass *self, int new_num_fields) +{ + mylog("in QR_set_num_fields\n"); + + CI_set_num_fields(self->fields, new_num_fields); + if (self->manual_tuples) + TL_Destructor(self->manual_tuples); + + self->manual_tuples = TL_Constructor(new_num_fields); + + mylog("exit QR_set_num_fields\n"); +} + + +void +QR_set_position(QResultClass *self, int pos) +{ + self->tupleField = self->backend_tuples + ((self->base + pos) * self->num_fields); +} + + +void +QR_set_cache_size(QResultClass *self, int cache_size) +{ + self->cache_size = cache_size; +} + + +void +QR_set_rowset_size(QResultClass *self, int rowset_size) +{ + self->rowset_size = rowset_size; +} + + +void +QR_inc_base(QResultClass *self, int base_inc) +{ + self->base += base_inc; +} + + +/* + * CLASS QResult + */ +QResultClass * +QR_Constructor() +{ + QResultClass *rv; + + mylog("in QR_Constructor\n"); + rv = (QResultClass *) malloc(sizeof(QResultClass)); + + if (rv != NULL) + { + rv->status = PGRES_EMPTY_QUERY; + + /* construct the column info */ + if (!(rv->fields = CI_Constructor())) + { + free(rv); + return NULL; + } + rv->manual_tuples = NULL; + rv->backend_tuples = NULL; + rv->message = NULL; + rv->command = NULL; + rv->notice = NULL; + rv->conn = NULL; + rv->inTuples = FALSE; + rv->fcount = 0; + rv->fetch_count = 0; + rv->base = 0; + rv->currTuple = -1; + rv->num_fields = 0; + rv->tupleField = NULL; + rv->cursor = NULL; + rv->aborted = FALSE; + + rv->cache_size = 0; + rv->rowset_size = 1; + } + + mylog("exit QR_Constructor\n"); + return rv; +} + + +void +QR_Destructor(QResultClass *self) +{ + mylog("QResult: in DESTRUCTOR\n"); + + /* manual result set tuples */ + if (self->manual_tuples) + TL_Destructor(self->manual_tuples); + + /* + * If conn is defined, then we may have used "backend_tuples", so in + * case we need to, free it up. Also, close the cursor. + */ + if (self->conn && self->conn->sock && CC_is_in_trans(self->conn)) + QR_close(self); /* close the cursor if there is one */ + + QR_free_memory(self); /* safe to call anyway */ + + /* Should have been freed in the close() but just in case... */ + if (self->cursor) + free(self->cursor); + + /* Free up column info */ + if (self->fields) + CI_Destructor(self->fields); + + /* Free command info (this is from strdup()) */ + if (self->command) + free(self->command); + + /* Free notice info (this is from strdup()) */ + if (self->notice) + free(self->notice); + + free(self); + + mylog("QResult: exit DESTRUCTOR\n"); +} + + +void +QR_set_command(QResultClass *self, char *msg) +{ + if (self->command) + free(self->command); + + self->command = msg ? strdup(msg) : NULL; +} + + +void +QR_set_notice(QResultClass *self, char *msg) +{ + if (self->notice) + free(self->notice); + + self->notice = msg ? strdup(msg) : NULL; +} + + +void +QR_free_memory(QResultClass *self) +{ + register int lf, + row; + register TupleField *tuple = self->backend_tuples; + int fcount = self->fcount; + int num_fields = self->num_fields; + + mylog("QResult: free memory in, fcount=%d\n", fcount); + + if (self->backend_tuples) + { + for (row = 0; row < fcount; row++) + { + mylog("row = %d, num_fields = %d\n", row, num_fields); + for (lf = 0; lf < num_fields; lf++) + { + if (tuple[lf].value != NULL) + { + mylog("free [lf=%d] %u\n", lf, tuple[lf].value); + free(tuple[lf].value); + } + } + tuple += num_fields; /* next row */ + } + + free(self->backend_tuples); + self->backend_tuples = NULL; + } + + self->fcount = 0; + + mylog("QResult: free memory out\n"); +} + + +/* This function is called by send_query() */ +char +QR_fetch_tuples(QResultClass *self, ConnectionClass *conn, char *cursor) +{ + int tuple_size; + + /* + * If called from send_query the first time (conn != NULL), then set + * the inTuples state, and read the tuples. If conn is NULL, it + * implies that we are being called from next_tuple(), like to get + * more rows so don't call next_tuple again! + */ + if (conn != NULL) + { + ConnInfo *ci = &(conn->connInfo); + BOOL fetch_cursor = (ci->drivers.use_declarefetch && cursor && cursor[0]); + + self->conn = conn; + + mylog("QR_fetch_tuples: cursor = '%s', self->cursor=%u\n", (cursor == NULL) ? "" : cursor, self->cursor); + + if (self->cursor) + free(self->cursor); + + if (fetch_cursor) + { + if (!cursor || cursor[0] == '\0') + { + self->status = PGRES_INTERNAL_ERROR; + QR_set_message(self, "Internal Error -- no cursor for fetch"); + return FALSE; + } + self->cursor = strdup(cursor); + } + + /* + * Read the field attributes. + * + * $$$$ Should do some error control HERE! $$$$ + */ + if (CI_read_fields(self->fields, self->conn)) + { + self->status = PGRES_FIELDS_OK; + self->num_fields = CI_get_num_fields(self->fields); + } + else + { + self->status = PGRES_BAD_RESPONSE; + QR_set_message(self, "Error reading field information"); + return FALSE; + } + + mylog("QR_fetch_tuples: past CI_read_fields: num_fields = %d\n", self->num_fields); + + if (fetch_cursor) + tuple_size = self->cache_size; + else + tuple_size = TUPLE_MALLOC_INC; + + /* allocate memory for the tuple cache */ + mylog("MALLOC: tuple_size = %d, size = %d\n", tuple_size, self->num_fields * sizeof(TupleField) * tuple_size); + self->count_allocated = 0; + self->backend_tuples = (TupleField *) malloc(self->num_fields * sizeof(TupleField) * tuple_size); + if (!self->backend_tuples) + { + self->status = PGRES_FATAL_ERROR; + QR_set_message(self, "Could not get memory for tuple cache."); + return FALSE; + } + self->count_allocated = tuple_size; + + self->inTuples = TRUE; + + /* Force a read to occur in next_tuple */ + self->fcount = tuple_size + 1; + self->fetch_count = tuple_size + 1; + self->base = 0; + + return QR_next_tuple(self); + } + else + { + /* + * Always have to read the field attributes. But we dont have to + * reallocate memory for them! + */ + + if (!CI_read_fields(NULL, self->conn)) + { + self->status = PGRES_BAD_RESPONSE; + QR_set_message(self, "Error reading field information"); + return FALSE; + } + return TRUE; + } +} + + +/* + * Close the cursor and end the transaction (if no cursors left) + * We only close cursor/end the transaction if a cursor was used. + */ +int +QR_close(QResultClass *self) +{ + QResultClass *res; + + if (self->conn && self->cursor && self->conn->connInfo.drivers.use_declarefetch) + { + char buf[64]; + + sprintf(buf, "close %s", self->cursor); + mylog("QResult: closing cursor: '%s'\n", buf); + + res = CC_send_query(self->conn, buf, NULL); + + self->inTuples = FALSE; + self->currTuple = -1; + + free(self->cursor); + self->cursor = NULL; + + if (res == NULL) + { + self->status = PGRES_FATAL_ERROR; + QR_set_message(self, "Error closing cursor."); + return FALSE; + } + QR_Destructor(res); + + /* End the transaction if there are no cursors left on this conn */ + if (CC_is_in_autocommit(self->conn) && CC_cursor_count(self->conn) == 0) + { + mylog("QResult: END transaction on conn=%u\n", self->conn); + + res = CC_send_query(self->conn, "END", NULL); + + CC_set_no_trans(self->conn); + + if (res == NULL) + { + self->status = PGRES_FATAL_ERROR; + QR_set_message(self, "Error ending transaction."); + return FALSE; + } + QR_Destructor(res); + } + } + + return TRUE; +} + + +/* This function is called by fetch_tuples() AND SQLFetch() */ +int +QR_next_tuple(QResultClass *self) +{ + int id; + QResultClass *res; + SocketClass *sock; + + /* Speed up access */ + int fetch_count = self->fetch_count; + int fcount = self->fcount; + int fetch_size, + offset = 0; + int end_tuple = self->rowset_size + self->base; + char corrected = FALSE; + TupleField *the_tuples = self->backend_tuples; + + /* ERROR_MSG_LENGTH is sufficient */ + static char msgbuffer[ERROR_MSG_LENGTH + 1]; + + /* QR_set_command() dups this string so doesn't need static */ + char cmdbuffer[ERROR_MSG_LENGTH + 1]; + char fetch[128]; + QueryInfo qi; + ConnInfo *ci = NULL; + + if (fetch_count < fcount) + { + /* return a row from cache */ + mylog("next_tuple: fetch_count < fcount: returning tuple %d, fcount = %d\n", fetch_count, fcount); + self->tupleField = the_tuples + (fetch_count * self->num_fields); /* next row */ + self->fetch_count++; + return TRUE; + } + else if (self->fcount < self->cache_size) + { + /* last row from cache */ + /* We are done because we didn't even get CACHE_SIZE tuples */ + mylog("next_tuple: fcount < CACHE_SIZE: fcount = %d, fetch_count = %d\n", fcount, fetch_count); + self->tupleField = NULL; + self->status = PGRES_END_TUPLES; + /* end of tuples */ + return -1; + } + else + { + /* + * See if we need to fetch another group of rows. We may be being + * called from send_query(), and if so, don't send another fetch, + * just fall through and read the tuples. + */ + self->tupleField = NULL; + + if (!self->inTuples) + { + ci = &(self->conn->connInfo); + if (!self->cursor || !ci->drivers.use_declarefetch) + { + mylog("next_tuple: ALL_ROWS: done, fcount = %d, fetch_count = %d\n", fcount, fetch_count); + self->tupleField = NULL; + self->status = PGRES_END_TUPLES; + return -1; /* end of tuples */ + } + + if (self->base == fcount) + { + /* not a correction */ + /* Determine the optimum cache size. */ + if (ci->drivers.fetch_max % self->rowset_size == 0) + fetch_size = ci->drivers.fetch_max; + else if (self->rowset_size < ci->drivers.fetch_max) + fetch_size = (ci->drivers.fetch_max / self->rowset_size) * self->rowset_size; + else + fetch_size = self->rowset_size; + + self->cache_size = fetch_size; + self->fetch_count = 1; + } + else + { + /* need to correct */ + corrected = TRUE; + + fetch_size = end_tuple - fcount; + + self->cache_size += fetch_size; + + offset = self->fetch_count; + self->fetch_count++; + } + + if (!self->backend_tuples || self->cache_size > self->count_allocated) + { + self->count_allocated = 0; + self->backend_tuples = (TupleField *) realloc(self->backend_tuples, self->num_fields * sizeof(TupleField) * self->cache_size); + if (!self->backend_tuples) + { + self->status = PGRES_FATAL_ERROR; + QR_set_message(self, "Out of memory while reading tuples."); + return FALSE; + } + self->count_allocated = self->cache_size; + } + sprintf(fetch, "fetch %d in %s", fetch_size, self->cursor); + + mylog("next_tuple: sending actual fetch (%d) query '%s'\n", fetch_size, fetch); + + /* don't read ahead for the next tuple (self) ! */ + qi.row_size = self->cache_size; + qi.result_in = self; + qi.cursor = NULL; + res = CC_send_query(self->conn, fetch, &qi); + if (res == NULL || QR_get_aborted(res)) + { + self->status = PGRES_FATAL_ERROR; + QR_set_message(self, "Error fetching next group."); + if (res) + QR_Destructor(res); + return FALSE; + } + self->inTuples = TRUE; + } + else + { + mylog("next_tuple: inTuples = true, falling through: fcount = %d, fetch_count = %d\n", self->fcount, self->fetch_count); + + /* + * This is a pre-fetch (fetching rows right after query but + * before any real SQLFetch() calls. This is done so the + * field attributes are available. + */ + self->fetch_count = 0; + } + } + + if (!corrected) + { + self->base = 0; + self->fcount = 0; + } + + sock = CC_get_socket(self->conn); + self->tupleField = NULL; + ci = &(self->conn->connInfo); + + for (;;) + { + id = SOCK_get_char(sock); + + switch (id) + { + + case 'T': /* Tuples within tuples cannot be handled */ + self->status = PGRES_BAD_RESPONSE; + QR_set_message(self, "Tuples within tuples cannot be handled"); + return FALSE; + case 'B': /* Tuples in binary format */ + case 'D': /* Tuples in ASCII format */ + + if ((!self->cursor || !ci->drivers.use_declarefetch) && self->fcount >= self->count_allocated) + { + int tuple_size = self->count_allocated; + + mylog("REALLOC: old_count = %d, size = %d\n", tuple_size, self->num_fields * sizeof(TupleField) * tuple_size); + tuple_size *= 2; + self->backend_tuples = (TupleField *) realloc(self->backend_tuples, + tuple_size * self->num_fields * sizeof(TupleField)); + if (!self->backend_tuples) + { + self->status = PGRES_FATAL_ERROR; + QR_set_message(self, "Out of memory while reading tuples."); + return FALSE; + } + self->count_allocated = tuple_size; + } + + if (!QR_read_tuple(self, (char) (id == 0))) + { + self->status = PGRES_BAD_RESPONSE; + QR_set_message(self, "Error reading the tuple"); + return FALSE; + } + self->fcount++; + break; /* continue reading */ + + case 'C': /* End of tuple list */ + SOCK_get_string(sock, cmdbuffer, ERROR_MSG_LENGTH); + QR_set_command(self, cmdbuffer); + + mylog("end of tuple list -- setting inUse to false: this = %u\n", self); + + self->inTuples = FALSE; + if (self->fcount > 0) + { + qlog(" [ fetched %d rows ]\n", self->fcount); + mylog("_next_tuple: 'C' fetch_max && fcount = %d\n", self->fcount); + + /* set to first row */ + self->tupleField = self->backend_tuples + (offset * self->num_fields); + return TRUE; + } + else + { + /* We are surely done here (we read 0 tuples) */ + qlog(" [ fetched 0 rows ]\n"); + mylog("_next_tuple: 'C': DONE (fcount == 0)\n"); + return -1; /* end of tuples */ + } + + case 'E': /* Error */ + SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH); + QR_set_message(self, msgbuffer); + self->status = PGRES_FATAL_ERROR; + + if (!strncmp(msgbuffer, "FATAL", 5)) + CC_set_no_trans(self->conn); + + qlog("ERROR from backend in next_tuple: '%s'\n", msgbuffer); + + return FALSE; + + case 'N': /* Notice */ + SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH); + QR_set_message(self, msgbuffer); + self->status = PGRES_NONFATAL_ERROR; + qlog("NOTICE from backend in next_tuple: '%s'\n", msgbuffer); + continue; + + default: /* this should only happen if the backend + * dumped core */ + mylog("QR_next_tuple: Unexpected result from backend: id = '%c' (%d)\n", id, id); + qlog("QR_next_tuple: Unexpected result from backend: id = '%c' (%d)\n", id, id); + QR_set_message(self, "Unexpected result from backend. It probably crashed"); + self->status = PGRES_FATAL_ERROR; + CC_set_no_trans(self->conn); + return FALSE; + } + } + return TRUE; +} + + +char +QR_read_tuple(QResultClass *self, char binary) +{ + Int2 field_lf; + TupleField *this_tuplefield; + char bmp, + bitmap[MAX_FIELDS]; /* Max. len of the bitmap */ + Int2 bitmaplen; /* len of the bitmap in bytes */ + Int2 bitmap_pos; + Int2 bitcnt; + Int4 len; + char *buffer; + int num_fields = self->num_fields; /* speed up access */ + SocketClass *sock = CC_get_socket(self->conn); + ColumnInfoClass *flds; + + /* set the current row to read the fields into */ + this_tuplefield = self->backend_tuples + (self->fcount * num_fields); + + bitmaplen = (Int2) num_fields / BYTELEN; + if ((num_fields % BYTELEN) > 0) + bitmaplen++; + + /* + * At first the server sends a bitmap that indicates which database + * fields are null + */ + SOCK_get_n_char(sock, bitmap, bitmaplen); + + bitmap_pos = 0; + bitcnt = 0; + bmp = bitmap[bitmap_pos]; + + for (field_lf = 0; field_lf < num_fields; field_lf++) + { + /* Check if the current field is NULL */ + if (!(bmp & 0200)) + { + /* YES, it is NULL ! */ + this_tuplefield[field_lf].len = 0; + this_tuplefield[field_lf].value = 0; + } + else + { + /* + * NO, the field is not null. so get at first the length of + * the field (four bytes) + */ + len = SOCK_get_int(sock, VARHDRSZ); + if (!binary) + len -= VARHDRSZ; + + buffer = (char *) malloc(len + 1); + SOCK_get_n_char(sock, buffer, len); + buffer[len] = '\0'; + + mylog("qresult: len=%d, buffer='%s'\n", len, buffer); + + this_tuplefield[field_lf].len = len; + this_tuplefield[field_lf].value = buffer; + + /* + * This can be used to set the longest length of the column + * for any row in the tuple cache. It would not be accurate + * for varchar and text fields to use this since a tuple cache + * is only 100 rows. Bpchar can be handled since the strlen of + * all rows is fixed, assuming there are not 100 nulls in a + * row! + */ + + flds = self->fields; + if (flds->display_size[field_lf] < len) + flds->display_size[field_lf] = len; + } + + /* + * Now adjust for the next bit to be scanned in the next loop. + */ + bitcnt++; + if (BYTELEN == bitcnt) + { + bitmap_pos++; + bmp = bitmap[bitmap_pos]; + bitcnt = 0; + } + else + bmp <<= 1; + } + self->currTuple++; + return TRUE; +} diff --git a/src/interfaces/odbc/windev/qresult.h b/src/interfaces/odbc/windev/qresult.h new file mode 100644 index 0000000000..251bc449c1 --- /dev/null +++ b/src/interfaces/odbc/windev/qresult.h @@ -0,0 +1,131 @@ +/* File: qresult.h + * + * Description: See "qresult.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __QRESULT_H__ +#define __QRESULT_H__ + +#include "psqlodbc.h" + +#include "connection.h" +#include "socket.h" +#include "columninfo.h" +#include "tuplelist.h" +#include "tuple.h" + +enum QueryResultCode_ +{ + PGRES_EMPTY_QUERY = 0, + PGRES_COMMAND_OK, /* a query command that doesn't return */ + /* anything was executed properly by the backend */ + PGRES_TUPLES_OK, /* a query command that returns tuples */ + /* was executed properly by the backend, PGresult */ + /* contains the resulttuples */ + PGRES_COPY_OUT, + PGRES_COPY_IN, + PGRES_BAD_RESPONSE, /* an unexpected response was recv'd from + * the backend */ + PGRES_NONFATAL_ERROR, + PGRES_FATAL_ERROR, + PGRES_FIELDS_OK, /* field information from a query was + * successful */ + PGRES_END_TUPLES, + PGRES_INTERNAL_ERROR +}; +typedef enum QueryResultCode_ QueryResultCode; + + +struct QResultClass_ +{ + ColumnInfoClass *fields; /* the Column information */ + TupleListClass *manual_tuples; /* manual result tuple list */ + ConnectionClass *conn; /* the connection this result is using + * (backend) */ + + /* Stuff for declare/fetch tuples */ + int count_allocated; /* m(re)alloced count */ + int fetch_count; /* logical rows read so far */ + int fcount; /* actual rows read in the fetch */ + int currTuple; + int base; + + int num_fields; /* number of fields in the result */ + int cache_size; + int rowset_size; + + QueryResultCode status; + + char *message; + char *cursor; /* The name of the cursor for select + * statements */ + char *command; + char *notice; + + TupleField *backend_tuples; /* data from the backend (the tuple cache) */ + TupleField *tupleField; /* current backend tuple being retrieved */ + + char inTuples; /* is a fetch of rows from the backend in + * progress? */ + char aborted; /* was aborted? */ +}; + +#define QR_get_fields(self) (self->fields) + + +/* These functions are for retrieving data from the qresult */ +#define QR_get_value_manual(self, tupleno, fieldno) (TL_get_fieldval(self->manual_tuples, tupleno, fieldno)) +#define QR_get_value_backend(self, fieldno) (self->tupleField[fieldno].value) +#define QR_get_value_backend_row(self, tupleno, fieldno) ((self->backend_tuples + (tupleno * self->num_fields))[fieldno].value) + +/* These functions are used by both manual and backend results */ +#define QR_NumResultCols(self) (CI_get_num_fields(self->fields)) +#define QR_get_fieldname(self, fieldno_) (CI_get_fieldname(self->fields, fieldno_)) +#define QR_get_fieldsize(self, fieldno_) (CI_get_fieldsize(self->fields, fieldno_)) +#define QR_get_display_size(self, fieldno_) (CI_get_display_size(self->fields, fieldno_)) +#define QR_get_atttypmod(self, fieldno_) (CI_get_atttypmod(self->fields, fieldno_)) +#define QR_get_field_type(self, fieldno_) (CI_get_oid(self->fields, fieldno_)) + +/* These functions are used only for manual result sets */ +#define QR_get_num_tuples(self) (self->manual_tuples ? TL_get_num_tuples(self->manual_tuples) : self->fcount) +#define QR_add_tuple(self, new_tuple) (TL_add_tuple(self->manual_tuples, new_tuple)) +#define QR_set_field_info(self, field_num, name, adtid, adtsize) (CI_set_field_info(self->fields, field_num, name, adtid, adtsize, -1)) + +/* status macros */ +#define QR_command_successful(self) ( !(self->status == PGRES_BAD_RESPONSE || self->status == PGRES_NONFATAL_ERROR || self->status == PGRES_FATAL_ERROR)) +#define QR_command_nonfatal(self) ( self->status == PGRES_NONFATAL_ERROR) +#define QR_end_tuples(self) ( self->status == PGRES_END_TUPLES) +#define QR_set_status(self, condition) ( self->status = condition ) +#define QR_set_message(self, message_) ( self->message = message_) +#define QR_set_aborted(self, aborted_) ( self->aborted = aborted_) + +#define QR_get_message(self) (self->message) +#define QR_get_command(self) (self->command) +#define QR_get_notice(self) (self->notice) +#define QR_get_status(self) (self->status) +#define QR_get_aborted(self) (self->aborted) + +#define QR_aborted(self) (!self || self->aborted) + +/* Core Functions */ +QResultClass *QR_Constructor(void); +void QR_Destructor(QResultClass *self); +char QR_read_tuple(QResultClass *self, char binary); +int QR_next_tuple(QResultClass *self); +int QR_close(QResultClass *self); +char QR_fetch_tuples(QResultClass *self, ConnectionClass *conn, char *cursor); +void QR_free_memory(QResultClass *self); +void QR_set_command(QResultClass *self, char *msg); +void QR_set_notice(QResultClass *self, char *msg); + +void QR_set_num_fields(QResultClass *self, int new_num_fields); /* manual result only */ + +void QR_inc_base(QResultClass *self, int base_inc); +void QR_set_cache_size(QResultClass *self, int cache_size); +void QR_set_rowset_size(QResultClass *self, int rowset_size); +void QR_set_position(QResultClass *self, int pos); + +#endif diff --git a/src/interfaces/odbc/windev/resource.h b/src/interfaces/odbc/windev/resource.h new file mode 100644 index 0000000000..b8bbd4fcfc --- /dev/null +++ b/src/interfaces/odbc/windev/resource.h @@ -0,0 +1,66 @@ +/* {{NO_DEPENDENCIES}} */ +/* Microsoft Developer Studio generated include file. */ +/* Used by psqlodbc.rc */ +/* */ +#define IDS_BADDSN 1 +#define IDS_MSGTITLE 2 +#define DLG_OPTIONS_DRV 102 +#define DLG_OPTIONS_DS 103 +#define IDC_DSNAME 400 +#define IDC_DSNAMETEXT 401 +#define IDC_DESC 404 +#define IDC_SERVER 407 +#define IDC_DATABASE 408 +#define DLG_CONFIG 1001 +#define IDC_PORT 1002 +#define IDC_USER 1006 +#define IDC_PASSWORD 1009 +#define DS_READONLY 1011 +#define DS_SHOWOIDCOLUMN 1012 +#define DS_FAKEOIDINDEX 1013 +#define DRV_COMMLOG 1014 +#define DS_PG62 1016 +#define IDC_DATASOURCE 1018 +#define DRV_OPTIMIZER 1019 +#define DS_CONNSETTINGS 1020 +#define IDC_DRIVER 1021 +#define DRV_CONNSETTINGS 1031 +#define DRV_UNIQUEINDEX 1032 +#define DRV_UNKNOWN_MAX 1035 +#define DRV_UNKNOWN_DONTKNOW 1036 +#define DRV_READONLY 1037 +#define IDC_DESCTEXT 1039 +#define DRV_MSG_LABEL 1040 +#define DRV_UNKNOWN_LONGEST 1041 +#define DRV_TEXT_LONGVARCHAR 1043 +#define DRV_UNKNOWNS_LONGVARCHAR 1044 +#define DRV_CACHE_SIZE 1045 +#define DRV_VARCHAR_SIZE 1046 +#define DRV_LONGVARCHAR_SIZE 1047 +#define IDDEFAULTS 1048 +#define DRV_USEDECLAREFETCH 1049 +#define DRV_BOOLS_CHAR 1050 +#define DS_SHOWSYSTEMTABLES 1051 +#define DRV_EXTRASYSTABLEPREFIXES 1051 +#define DS_ROWVERSIONING 1052 +#define DRV_PARSE 1052 +#define DRV_CANCELASFREESTMT 1053 +#define IDC_OPTIONS 1054 +#define DRV_KSQO 1055 +#define DS_PG64 1057 +#define DS_PG63 1058 +#define DRV_OR_DSN 1059 +#define DRV_DEBUG 1060 +#define DS_DISALLOWPREMATURE 1061 + +/* Next default values for new objects */ +/* */ +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 105 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1062 +#define _APS_NEXT_SYMED_VALUE 101 +#endif /* */ + +#endif /* */ diff --git a/src/interfaces/odbc/windev/results.c b/src/interfaces/odbc/windev/results.c new file mode 100644 index 0000000000..3bc5a5c0e1 --- /dev/null +++ b/src/interfaces/odbc/windev/results.c @@ -0,0 +1,1937 @@ +/*------- + * Module: results.c + * + * Description: This module contains functions related to + * retrieving result information through the ODBC API. + * + * Classes: n/a + * + * API functions: SQLRowCount, SQLNumResultCols, SQLDescribeCol, + * SQLColAttributes, SQLGetData, SQLFetch, SQLExtendedFetch, + * SQLMoreResults(NI), SQLSetPos, SQLSetScrollOptions(NI), + * SQLSetCursorName, SQLGetCursorName + * + * Comments: See "notice.txt" for copyright and license information. + *------- + */ + +#include "psqlodbc.h" + +#include +#include "dlg_specific.h" +#include "environ.h" +#include "connection.h" +#include "statement.h" +#include "bind.h" +#include "qresult.h" +#include "convert.h" +#include "pgtypes.h" + +#include + +#include "pgapifunc.h" + + + +RETCODE SQL_API +PGAPI_RowCount( + HSTMT hstmt, + SDWORD FAR * pcrow) +{ + static char *func = "PGAPI_RowCount"; + StatementClass *stmt = (StatementClass *) hstmt; + QResultClass *res; + char *msg, + *ptr; + ConnInfo *ci; + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + ci = &(SC_get_conn(stmt)->connInfo); + if (stmt->manual_result) + { + if (pcrow) + *pcrow = -1; + return SQL_SUCCESS; + } + + if (stmt->statement_type == STMT_TYPE_SELECT) + { + if (stmt->status == STMT_FINISHED) + { + res = SC_get_Result(stmt); + + if (res && pcrow) + { + *pcrow = SC_is_fetchcursor(stmt) ? -1 : QR_get_num_tuples(res); + return SQL_SUCCESS; + } + } + } + else + { + res = SC_get_Result(stmt); + if (res && pcrow) + { + msg = QR_get_command(res); + mylog("*** msg = '%s'\n", msg); + trim(msg); /* get rid of trailing spaces */ + ptr = strrchr(msg, ' '); + if (ptr) + { + *pcrow = atoi(ptr + 1); + mylog("**** PGAPI_RowCount(): THE ROWS: *pcrow = %d\n", *pcrow); + } + else + { + *pcrow = -1; + mylog("**** PGAPI_RowCount(): NO ROWS: *pcrow = %d\n", *pcrow); + } + + return SQL_SUCCESS; + } + } + + SC_log_error(func, "Bad return value", stmt); + return SQL_ERROR; +} + + +/* + * This returns the number of columns associated with the database + * attached to "hstmt". + */ +RETCODE SQL_API +PGAPI_NumResultCols( + HSTMT hstmt, + SWORD FAR * pccol) +{ + static char *func = "PGAPI_NumResultCols"; + StatementClass *stmt = (StatementClass *) hstmt; + QResultClass *result; + char parse_ok; + ConnInfo *ci; + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + ci = &(SC_get_conn(stmt)->connInfo); + + SC_clear_error(stmt); + + parse_ok = FALSE; + if (ci->drivers.parse && stmt->statement_type == STMT_TYPE_SELECT) + { + if (stmt->parse_status == STMT_PARSE_NONE) + { + mylog("PGAPI_NumResultCols: calling parse_statement on stmt=%u\n", stmt); + parse_statement(stmt); + } + + if (stmt->parse_status != STMT_PARSE_FATAL) + { + parse_ok = TRUE; + *pccol = stmt->nfld; + mylog("PARSE: PGAPI_NumResultCols: *pccol = %d\n", *pccol); + } + } + + if (!parse_ok) + { + SC_pre_execute(stmt); + result = SC_get_Result(stmt); + + mylog("PGAPI_NumResultCols: result = %u, status = %d, numcols = %d\n", result, stmt->status, result != NULL ? QR_NumResultCols(result) : -1); + if ((!result) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE))) + { + /* no query has been executed on this statement */ + stmt->errornumber = STMT_SEQUENCE_ERROR; + stmt->errormsg = "No query has been executed with that handle"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + *pccol = QR_NumResultCols(result); + /* updatable cursors */ + if (ci->updatable_cursors && + stmt->options.scroll_concurrency != SQL_CONCUR_READ_ONLY) + *pccol -= 2; + } + + return SQL_SUCCESS; +} + + +/* + * Return information about the database column the user wants + * information about. + */ +RETCODE SQL_API +PGAPI_DescribeCol( + HSTMT hstmt, + UWORD icol, + UCHAR FAR * szColName, + SWORD cbColNameMax, + SWORD FAR * pcbColName, + SWORD FAR * pfSqlType, + UDWORD FAR * pcbColDef, + SWORD FAR * pibScale, + SWORD FAR * pfNullable) +{ + static char *func = "PGAPI_DescribeCol"; + + /* gets all the information about a specific column */ + StatementClass *stmt = (StatementClass *) hstmt; + QResultClass *res; + char *col_name = NULL; + Int4 fieldtype = 0; + int precision = 0, + scale = 0; + ConnInfo *ci; + char parse_ok; + char buf[255]; + int len = 0; + RETCODE result; + + mylog("%s: entering...\n", func); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + ci = &(SC_get_conn(stmt)->connInfo); + + SC_clear_error(stmt); + + /* + * Dont check for bookmark column. This is the responsibility of the + * driver manager. + */ + + icol--; /* use zero based column numbers */ + + parse_ok = FALSE; + if (ci->drivers.parse && stmt->statement_type == STMT_TYPE_SELECT) + { + if (stmt->parse_status == STMT_PARSE_NONE) + { + mylog("PGAPI_DescribeCol: calling parse_statement on stmt=%u\n", stmt); + parse_statement(stmt); + } + + mylog("PARSE: DescribeCol: icol=%d, stmt=%u, stmt->nfld=%d, stmt->fi=%u\n", icol, stmt, stmt->nfld, stmt->fi); + + if (stmt->parse_status != STMT_PARSE_FATAL && stmt->fi && stmt->fi[icol]) + { + if (icol >= stmt->nfld) + { + stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR; + stmt->errormsg = "Invalid column number in DescribeCol."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + mylog("DescribeCol: getting info for icol=%d\n", icol); + + fieldtype = stmt->fi[icol]->type; + if (stmt->fi[icol]->alias[0]) + col_name = stmt->fi[icol]->alias; + else + col_name = stmt->fi[icol]->name; + precision = stmt->fi[icol]->precision; + scale = stmt->fi[icol]->scale; + + mylog("PARSE: fieldtype=%d, col_name='%s', precision=%d\n", fieldtype, col_name, precision); + if (fieldtype > 0) + parse_ok = TRUE; + } + } + + /* + * If couldn't parse it OR the field being described was not parsed + * (i.e., because it was a function or expression, etc, then do it the + * old fashioned way. + */ + if (!parse_ok) + { + SC_pre_execute(stmt); + + res = SC_get_Result(stmt); + + mylog("**** PGAPI_DescribeCol: res = %u, stmt->status = %d, !finished=%d, !premature=%d\n", res, stmt->status, stmt->status != STMT_FINISHED, stmt->status != STMT_PREMATURE); + if ((NULL == res) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE))) + { + /* no query has been executed on this statement */ + stmt->errornumber = STMT_SEQUENCE_ERROR; + stmt->errormsg = "No query has been assigned to this statement."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + if (icol >= QR_NumResultCols(res)) + { + stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR; + stmt->errormsg = "Invalid column number in DescribeCol."; + sprintf(buf, "Col#=%d, #Cols=%d", icol, QR_NumResultCols(res)); + SC_log_error(func, buf, stmt); + return SQL_ERROR; + } + + col_name = QR_get_fieldname(res, icol); + fieldtype = QR_get_field_type(res, icol); + + /* atoi(ci->unknown_sizes) */ + precision = pgtype_precision(stmt, fieldtype, icol, ci->drivers.unknown_sizes); + scale = pgtype_scale(stmt, fieldtype, icol); + } + + mylog("describeCol: col %d fieldname = '%s'\n", icol, col_name); + mylog("describeCol: col %d fieldtype = %d\n", icol, fieldtype); + mylog("describeCol: col %d precision = %d\n", icol, precision); + + result = SQL_SUCCESS; + + /* + * COLUMN NAME + */ + len = strlen(col_name); + + if (pcbColName) + *pcbColName = len; + + if (szColName) + { + strncpy_null(szColName, col_name, cbColNameMax); + + if (len >= cbColNameMax) + { + result = SQL_SUCCESS_WITH_INFO; + stmt->errornumber = STMT_TRUNCATED; + stmt->errormsg = "The buffer was too small for the colName."; + } + } + + /* + * SQL TYPE + */ + if (pfSqlType) + { + *pfSqlType = pgtype_to_sqltype(stmt, fieldtype); + + mylog("describeCol: col %d *pfSqlType = %d\n", icol, *pfSqlType); + } + + /* + * PRECISION + */ + if (pcbColDef) + { + if (precision < 0) + precision = 0; /* "I dont know" */ + + *pcbColDef = precision; + + mylog("describeCol: col %d *pcbColDef = %d\n", icol, *pcbColDef); + } + + /* + * SCALE + */ + if (pibScale) + { + if (scale < 0) + scale = 0; + + *pibScale = scale; + mylog("describeCol: col %d *pibScale = %d\n", icol, *pibScale); + } + + /* + * NULLABILITY + */ + if (pfNullable) + { + *pfNullable = (parse_ok) ? stmt->fi[icol]->nullable : pgtype_nullable(stmt, fieldtype); + + mylog("describeCol: col %d *pfNullable = %d\n", icol, *pfNullable); + } + + return result; +} + + +/* Returns result column descriptor information for a result set. */ +RETCODE SQL_API +PGAPI_ColAttributes( + HSTMT hstmt, + UWORD icol, + UWORD fDescType, + PTR rgbDesc, + SWORD cbDescMax, + SWORD FAR * pcbDesc, + SDWORD FAR * pfDesc) +{ + static char *func = "PGAPI_ColAttributes"; + StatementClass *stmt = (StatementClass *) hstmt; + Int4 field_type = 0; + ConnInfo *ci; + int unknown_sizes; + int cols = 0; + char parse_ok; + RETCODE result; + char *p = NULL; + int len = 0, + value = 0; + + mylog("%s: entering...\n", func); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + ci = &(SC_get_conn(stmt)->connInfo); + + /* + * Dont check for bookmark column. This is the responsibility of the + * driver manager. For certain types of arguments, the column number + * is ignored anyway, so it may be 0. + */ + + icol--; + + /* atoi(ci->unknown_sizes); */ + unknown_sizes = ci->drivers.unknown_sizes; + + /* not appropriate for SQLColAttributes() */ + if (unknown_sizes == UNKNOWNS_AS_DONTKNOW) + unknown_sizes = UNKNOWNS_AS_MAX; + + parse_ok = FALSE; + if (ci->drivers.parse && stmt->statement_type == STMT_TYPE_SELECT) + { + if (stmt->parse_status == STMT_PARSE_NONE) + { + mylog("PGAPI_ColAttributes: calling parse_statement\n"); + parse_statement(stmt); + } + + cols = stmt->nfld; + + /* + * Column Count is a special case. The Column number is ignored + * in this case. + */ + if (fDescType == SQL_COLUMN_COUNT) + { + if (pfDesc) + *pfDesc = cols; + + return SQL_SUCCESS; + } + + if (stmt->parse_status != STMT_PARSE_FATAL && stmt->fi && stmt->fi[icol]) + { + if (icol >= cols) + { + stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR; + stmt->errormsg = "Invalid column number in ColAttributes."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + field_type = stmt->fi[icol]->type; + if (field_type > 0) + parse_ok = TRUE; + } + } + + if (!parse_ok) + { + SC_pre_execute(stmt); + + mylog("**** PGAPI_ColAtt: result = %u, status = %d, numcols = %d\n", stmt->result, stmt->status, stmt->result != NULL ? QR_NumResultCols(stmt->result) : -1); + + if ((NULL == stmt->result) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE))) + { + stmt->errormsg = "Can't get column attributes: no result found."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + cols = QR_NumResultCols(stmt->result); + + /* + * Column Count is a special case. The Column number is ignored + * in this case. + */ + if (fDescType == SQL_COLUMN_COUNT) + { + if (pfDesc) + *pfDesc = cols; + + return SQL_SUCCESS; + } + + if (icol >= cols) + { + stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR; + stmt->errormsg = "Invalid column number in ColAttributes."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + field_type = QR_get_field_type(stmt->result, icol); + } + + mylog("colAttr: col %d field_type = %d\n", icol, field_type); + + switch (fDescType) + { + case SQL_COLUMN_AUTO_INCREMENT: + value = pgtype_auto_increment(stmt, field_type); + if (value == -1) /* non-numeric becomes FALSE (ODBC Doc) */ + value = FALSE; + + break; + + case SQL_COLUMN_CASE_SENSITIVE: + value = pgtype_case_sensitive(stmt, field_type); + break; + + /* + * This special case is handled above. + * + * case SQL_COLUMN_COUNT: + */ + case SQL_COLUMN_DISPLAY_SIZE: + value = (parse_ok) ? stmt->fi[icol]->display_size : pgtype_display_size(stmt, field_type, icol, unknown_sizes); + + mylog("PGAPI_ColAttributes: col %d, display_size= %d\n", icol, value); + + break; + + case SQL_COLUMN_LABEL: + if (parse_ok && stmt->fi[icol]->alias[0] != '\0') + { + p = stmt->fi[icol]->alias; + + mylog("PGAPI_ColAttr: COLUMN_LABEL = '%s'\n", p); + break; + + } + /* otherwise same as column name -- FALL THROUGH!!! */ + + case SQL_COLUMN_NAME: + p = (parse_ok) ? stmt->fi[icol]->name : QR_get_fieldname(stmt->result, icol); + + mylog("PGAPI_ColAttr: COLUMN_NAME = '%s'\n", p); + break; + + case SQL_COLUMN_LENGTH: + value = (parse_ok) ? stmt->fi[icol]->length : pgtype_length(stmt, field_type, icol, unknown_sizes); + + mylog("PGAPI_ColAttributes: col %d, length = %d\n", icol, value); + break; + + case SQL_COLUMN_MONEY: + value = pgtype_money(stmt, field_type); + break; + + case SQL_COLUMN_NULLABLE: + value = (parse_ok) ? stmt->fi[icol]->nullable : pgtype_nullable(stmt, field_type); + break; + + case SQL_COLUMN_OWNER_NAME: + p = ""; + break; + + case SQL_COLUMN_PRECISION: + value = (parse_ok) ? stmt->fi[icol]->precision : pgtype_precision(stmt, field_type, icol, unknown_sizes); + + mylog("PGAPI_ColAttributes: col %d, precision = %d\n", icol, value); + break; + + case SQL_COLUMN_QUALIFIER_NAME: + p = ""; + break; + + case SQL_COLUMN_SCALE: + value = pgtype_scale(stmt, field_type, icol); + break; + + case SQL_COLUMN_SEARCHABLE: + value = pgtype_searchable(stmt, field_type); + break; + + case SQL_COLUMN_TABLE_NAME: + p = (parse_ok && stmt->fi[icol]->ti) ? stmt->fi[icol]->ti->name : ""; + + mylog("PGAPI_ColAttr: TABLE_NAME = '%s'\n", p); + break; + + case SQL_COLUMN_TYPE: + value = pgtype_to_sqltype(stmt, field_type); + break; + + case SQL_COLUMN_TYPE_NAME: + p = pgtype_to_name(stmt, field_type); + break; + + case SQL_COLUMN_UNSIGNED: + value = pgtype_unsigned(stmt, field_type); + if (value == -1) /* non-numeric becomes TRUE (ODBC Doc) */ + value = TRUE; + + break; + + case SQL_COLUMN_UPDATABLE: + + /* + * Neither Access or Borland care about this. + * + * if (field_type == PG_TYPE_OID) pfDesc = SQL_ATTR_READONLY; + * else + */ + value = SQL_ATTR_WRITE; + + mylog("PGAPI_ColAttr: UPDATEABLE = %d\n", value); + break; + } + + result = SQL_SUCCESS; + + if (p) + { /* char/binary data */ + len = strlen(p); + + if (rgbDesc) + { + strncpy_null((char *) rgbDesc, p, (size_t) cbDescMax); + + if (len >= cbDescMax) + { + result = SQL_SUCCESS_WITH_INFO; + stmt->errornumber = STMT_TRUNCATED; + stmt->errormsg = "The buffer was too small for the rgbDesc."; + } + } + + if (pcbDesc) + *pcbDesc = len; + } + else + { + /* numeric data */ + if (pfDesc) + *pfDesc = value; + } + + return result; +} + + +/* Returns result data for a single column in the current row. */ +RETCODE SQL_API +PGAPI_GetData( + HSTMT hstmt, + UWORD icol, + SWORD fCType, + PTR rgbValue, + SDWORD cbValueMax, + SDWORD FAR * pcbValue) +{ + static char *func = "PGAPI_GetData"; + QResultClass *res; + StatementClass *stmt = (StatementClass *) hstmt; + int num_cols, + num_rows; + Int4 field_type; + void *value = NULL; + int result; + char get_bookmark = FALSE; + ConnInfo *ci; + + mylog("PGAPI_GetData: enter, stmt=%u\n", stmt); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + ci = &(SC_get_conn(stmt)->connInfo); + res = stmt->result; + + if (STMT_EXECUTING == stmt->status) + { + stmt->errormsg = "Can't get data while statement is still executing."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + if (stmt->status != STMT_FINISHED) + { + stmt->errornumber = STMT_STATUS_ERROR; + stmt->errormsg = "GetData can only be called after the successful execution on a SQL statement"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + if (icol == 0) + { + if (stmt->options.use_bookmarks == SQL_UB_OFF) + { + stmt->errornumber = STMT_COLNUM_ERROR; + stmt->errormsg = "Attempt to retrieve bookmark with bookmark usage disabled"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* Make sure it is the bookmark data type */ + if (fCType != SQL_C_BOOKMARK) + { + stmt->errormsg = "Column 0 is not of type SQL_C_BOOKMARK"; + stmt->errornumber = STMT_PROGRAM_TYPE_OUT_OF_RANGE; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + get_bookmark = TRUE; + } + else + { + /* use zero-based column numbers */ + icol--; + + /* make sure the column number is valid */ + num_cols = QR_NumResultCols(res); + if (icol >= num_cols) + { + stmt->errormsg = "Invalid column number."; + stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + } + + if (stmt->manual_result || !SC_is_fetchcursor(stmt)) + { + /* make sure we're positioned on a valid row */ + num_rows = QR_get_num_tuples(res); + if ((stmt->currTuple < 0) || + (stmt->currTuple >= num_rows)) + { + stmt->errormsg = "Not positioned on a valid row for GetData."; + stmt->errornumber = STMT_INVALID_CURSOR_STATE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + mylog(" num_rows = %d\n", num_rows); + + if (!get_bookmark) + { + if (stmt->manual_result) + value = QR_get_value_manual(res, stmt->currTuple, icol); + else + value = QR_get_value_backend_row(res, stmt->currTuple, icol); + mylog(" value = '%s'\n", value); + } + } + else + { + /* it's a SOCKET result (backend data) */ + if (stmt->currTuple == -1 || !res || !res->tupleField) + { + stmt->errormsg = "Not positioned on a valid row for GetData."; + stmt->errornumber = STMT_INVALID_CURSOR_STATE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + if (!get_bookmark) + value = QR_get_value_backend(res, icol); + + mylog(" socket: value = '%s'\n", value); + } + + if (get_bookmark) + { + *((UDWORD *) rgbValue) = SC_get_bookmark(stmt); + + if (pcbValue) + *pcbValue = 4; + + return SQL_SUCCESS; + } + + field_type = QR_get_field_type(res, icol); + + mylog("**** PGAPI_GetData: icol = %d, fCType = %d, field_type = %d, value = '%s'\n", icol, fCType, field_type, value); + + stmt->current_col = icol; + + result = copy_and_convert_field(stmt, field_type, value, + fCType, rgbValue, cbValueMax, pcbValue); + + stmt->current_col = -1; + + switch (result) + { + case COPY_OK: + return SQL_SUCCESS; + + case COPY_UNSUPPORTED_TYPE: + stmt->errormsg = "Received an unsupported type from Postgres."; + stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + + case COPY_UNSUPPORTED_CONVERSION: + stmt->errormsg = "Couldn't handle the necessary data type conversion."; + stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + + case COPY_RESULT_TRUNCATED: + stmt->errornumber = STMT_TRUNCATED; + stmt->errormsg = "The buffer was too small for the GetData."; + return SQL_SUCCESS_WITH_INFO; + + case COPY_GENERAL_ERROR: /* error msg already filled in */ + SC_log_error(func, "", stmt); + return SQL_ERROR; + + case COPY_NO_DATA_FOUND: + /* SC_log_error(func, "no data found", stmt); */ + return SQL_NO_DATA_FOUND; + + default: + stmt->errormsg = "Unrecognized return value from copy_and_convert_field."; + stmt->errornumber = STMT_INTERNAL_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } +} + + +/* + * Returns data for bound columns in the current row ("hstmt->iCursor"), + * advances the cursor. + */ +RETCODE SQL_API +PGAPI_Fetch( + HSTMT hstmt) +{ + static char *func = "PGAPI_Fetch"; + StatementClass *stmt = (StatementClass *) hstmt; + QResultClass *res; + + mylog("PGAPI_Fetch: stmt = %u, stmt->result= %u\n", stmt, stmt->result); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + SC_clear_error(stmt); + + if (!(res = stmt->result)) + { + stmt->errormsg = "Null statement result in PGAPI_Fetch."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* Not allowed to bind a bookmark column when using SQLFetch. */ + if (stmt->bookmark.buffer) + { + stmt->errornumber = STMT_COLNUM_ERROR; + stmt->errormsg = "Not allowed to bind a bookmark column when using PGAPI_Fetch"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + if (stmt->status == STMT_EXECUTING) + { + stmt->errormsg = "Can't fetch while statement is still executing."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + if (stmt->status != STMT_FINISHED) + { + stmt->errornumber = STMT_STATUS_ERROR; + stmt->errormsg = "Fetch can only be called after the successful execution on a SQL statement"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + if (stmt->bindings == NULL) + { + /* just to avoid a crash if the user insists on calling this */ + /* function even if SQL_ExecDirect has reported an Error */ + stmt->errormsg = "Bindings were not allocated properly."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + QR_set_rowset_size(res, 1); + QR_inc_base(res, stmt->last_fetch_count); + + return SC_fetch(stmt); +} + + +/* This fetchs a block of data (rowset). */ +RETCODE SQL_API +PGAPI_ExtendedFetch( + HSTMT hstmt, + UWORD fFetchType, + SDWORD irow, + UDWORD FAR * pcrow, + UWORD FAR * rgfRowStatus) +{ + static char *func = "PGAPI_ExtendedFetch"; + StatementClass *stmt = (StatementClass *) hstmt; + QResultClass *res; + int num_tuples, + i, + save_rowset_size; + RETCODE result; + char truncated, + error; + ConnInfo *ci; + + mylog("PGAPI_ExtendedFetch: stmt=%u\n", stmt); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + ci = &(SC_get_conn(stmt)->connInfo); + + if (SC_is_fetchcursor(stmt) && !stmt->manual_result) + { + if (fFetchType != SQL_FETCH_NEXT) + { + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + stmt->errormsg = "Unsupported fetch type for PGAPI_ExtendedFetch with UseDeclareFetch option."; + return SQL_ERROR; + } + } + + SC_clear_error(stmt); + + if (!(res = stmt->result)) + { + stmt->errormsg = "Null statement result in PGAPI_ExtendedFetch."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* + * If a bookmark colunmn is bound but bookmark usage is off, then + * error + */ + if (stmt->bookmark.buffer && stmt->options.use_bookmarks == SQL_UB_OFF) + { + stmt->errornumber = STMT_COLNUM_ERROR; + stmt->errormsg = "Attempt to retrieve bookmark with bookmark usage disabled"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + if (stmt->status == STMT_EXECUTING) + { + stmt->errormsg = "Can't fetch while statement is still executing."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + if (stmt->status != STMT_FINISHED) + { + stmt->errornumber = STMT_STATUS_ERROR; + stmt->errormsg = "ExtendedFetch can only be called after the successful execution on a SQL statement"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + if (stmt->bindings == NULL) + { + /* just to avoid a crash if the user insists on calling this */ + /* function even if SQL_ExecDirect has reported an Error */ + stmt->errormsg = "Bindings were not allocated properly."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* Initialize to no rows fetched */ + if (rgfRowStatus) + for (i = 0; i < stmt->options.rowset_size; i++) + *(rgfRowStatus + i) = SQL_ROW_NOROW; + + if (pcrow) + *pcrow = 0; + + num_tuples = QR_get_num_tuples(res); + + /* Save and discard the saved rowset size */ + save_rowset_size = stmt->save_rowset_size; + stmt->save_rowset_size = -1; + + switch (fFetchType) + { + case SQL_FETCH_NEXT: + + /* + * From the odbc spec... If positioned before the start of the + * RESULT SET, then this should be equivalent to + * SQL_FETCH_FIRST. + */ + + if (stmt->rowset_start < 0) + stmt->rowset_start = 0; + + else + stmt->rowset_start += (save_rowset_size > 0 ? save_rowset_size : stmt->options.rowset_size); + + mylog("SQL_FETCH_NEXT: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple); + break; + + case SQL_FETCH_PRIOR: + mylog("SQL_FETCH_PRIOR: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple); + + /* + * From the odbc spec... If positioned after the end of the + * RESULT SET, then this should be equivalent to + * SQL_FETCH_LAST. + */ + if (stmt->rowset_start >= num_tuples) + { + if (stmt->options.rowset_size > num_tuples) + { + stmt->errornumber = STMT_POS_BEFORE_RECORDSET; + stmt->errormsg = "fetch prior from eof and before the beggining"; + } + stmt->rowset_start = num_tuples <= 0 ? 0 : (num_tuples - stmt->options.rowset_size); + + } + else + { + if (stmt->rowset_start < stmt->options.rowset_size) + { + stmt->errormsg = "fetch prior and before the beggining"; + stmt->errornumber = STMT_POS_BEFORE_RECORDSET; + } + stmt->rowset_start -= stmt->options.rowset_size; + } + break; + + case SQL_FETCH_FIRST: + mylog("SQL_FETCH_FIRST: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple); + + stmt->rowset_start = 0; + break; + + case SQL_FETCH_LAST: + mylog("SQL_FETCH_LAST: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple); + + stmt->rowset_start = num_tuples <= 0 ? 0 : (num_tuples - stmt->options.rowset_size); + break; + + case SQL_FETCH_ABSOLUTE: + mylog("SQL_FETCH_ABSOLUTE: num_tuples=%d, currtuple=%d, irow=%d\n", num_tuples, stmt->currTuple, irow); + + /* Position before result set, but dont fetch anything */ + if (irow == 0) + { + stmt->rowset_start = -1; + stmt->currTuple = -1; + return SQL_NO_DATA_FOUND; + } + /* Position before the desired row */ + else if (irow > 0) + stmt->rowset_start = irow - 1; + /* Position with respect to the end of the result set */ + else + stmt->rowset_start = num_tuples + irow; + break; + + case SQL_FETCH_RELATIVE: + + /* + * Refresh the current rowset -- not currently implemented, + * but lie anyway + */ + if (irow == 0) + break; + + stmt->rowset_start += irow; + break; + + case SQL_FETCH_BOOKMARK: + stmt->rowset_start = irow - 1; + break; + + default: + SC_log_error(func, "Unsupported PGAPI_ExtendedFetch Direction", stmt); + return SQL_ERROR; + } + + /* + * CHECK FOR PROPER CURSOR STATE + */ + + /* + * Handle Declare Fetch style specially because the end is not really + * the end... + */ + if (SC_is_fetchcursor(stmt) && !stmt->manual_result) + { + if (QR_end_tuples(res)) + return SQL_NO_DATA_FOUND; + } + else + { + /* If *new* rowset is after the result_set, return no data found */ + if (stmt->rowset_start >= num_tuples) + { + stmt->rowset_start = num_tuples; + return SQL_NO_DATA_FOUND; + } + } + + /* If *new* rowset is prior to result_set, return no data found */ + if (stmt->rowset_start < 0) + { + if (stmt->rowset_start + stmt->options.rowset_size <= 0) + { + stmt->rowset_start = -1; + return SQL_NO_DATA_FOUND; + } + else + { /* overlap with beginning of result set, + * so get first rowset */ + stmt->rowset_start = 0; + } + } + + /* currTuple is always 1 row prior to the rowset */ + stmt->currTuple = stmt->rowset_start - 1; + + /* increment the base row in the tuple cache */ + QR_set_rowset_size(res, stmt->options.rowset_size); + /* QR_inc_base(res, stmt->last_fetch_count); */ + /* Is inc_base right ? */ + res->base = stmt->rowset_start; + + /* Physical Row advancement occurs for each row fetched below */ + + mylog("PGAPI_ExtendedFetch: new currTuple = %d\n", stmt->currTuple); + + truncated = error = FALSE; + for (i = 0; i < stmt->options.rowset_size; i++) + { + stmt->bind_row = i; /* set the binding location */ + result = SC_fetch(stmt); + + /* Determine Function status */ + if (result == SQL_NO_DATA_FOUND) + break; + else if (result == SQL_SUCCESS_WITH_INFO) + truncated = TRUE; + else if (result == SQL_ERROR) + error = TRUE; + + /* Determine Row Status */ + if (rgfRowStatus) + { + if (result == SQL_ERROR) + *(rgfRowStatus + i) = SQL_ROW_ERROR; +#ifdef DRIVER_CURSOR_IMPLEMENT + /* this should be refined */ + else if (result > 10 && result < 20) + *(rgfRowStatus + i) = result - 10; +#endif /* DRIVER_CURSOR_IMPLEMENT */ + else + *(rgfRowStatus + i) = SQL_ROW_SUCCESS; + } + } + + /* Save the fetch count for SQLSetPos */ + stmt->last_fetch_count = i; + + /* Reset next binding row */ + stmt->bind_row = 0; + + /* Move the cursor position to the first row in the result set. */ + stmt->currTuple = stmt->rowset_start; + + /* For declare/fetch, need to reset cursor to beginning of rowset */ + if (SC_is_fetchcursor(stmt) && !stmt->manual_result) + QR_set_position(res, 0); + + /* Set the number of rows retrieved */ + if (pcrow) + *pcrow = i; + + if (i == 0) + /* Only DeclareFetch should wind up here */ + return SQL_NO_DATA_FOUND; + else if (error) + return SQL_ERROR; + else if (truncated) + return SQL_SUCCESS_WITH_INFO; + else if (stmt->errornumber == STMT_POS_BEFORE_RECORDSET) + return SQL_SUCCESS_WITH_INFO; + else + return SQL_SUCCESS; +} + + +/* + * This determines whether there are more results sets available for + * the "hstmt". + */ +/* CC: return SQL_NO_DATA_FOUND since we do not support multiple result sets */ +RETCODE SQL_API +PGAPI_MoreResults( + HSTMT hstmt) +{ + return SQL_NO_DATA_FOUND; +} + + +#ifdef DRIVER_CURSOR_IMPLEMENT +/* + * Stuff for updatable cursors. + */ +static QResultClass * +positioned_load(StatementClass *stmt, BOOL latest, int res_cols, UInt4 oid, const char *tidval) +{ + int i; + QResultClass *qres; + char selstr[4096]; + + sprintf(selstr, "select"); + for (i = 0; i < res_cols; i++) + sprintf(selstr, "%s \"%s\",", selstr, stmt->fi[i]->name); + sprintf(selstr, "%s CTID, OID from \"%s\" where", selstr, stmt->ti[0]->name); + if (tidval) + { + if (latest) + sprintf(selstr, "%s ctid = currtid2('%s', '%s') and", + selstr, stmt->ti[0]->name, tidval); + else + sprintf(selstr, "%s ctid = '%s' and", selstr, tidval); + } + sprintf(selstr, "%s oid = %u", selstr, oid), + mylog("selstr=%s\n", selstr); + qres = CC_send_query(SC_get_conn(stmt), selstr, NULL); + if (qres && QR_aborted(qres)) + { + QR_Destructor(qres); + qres = (QResultClass *) 0; + } + return qres; +} + +RETCODE SQL_API +SC_pos_reload(StatementClass *stmt, UWORD irow, UWORD *count) +{ + int i, + res_cols; + UWORD rcnt, + global_ridx; + UInt4 oid; + QResultClass *res, + *qres; + RETCODE ret = SQL_ERROR; + char *tidval, + *oidval; + + mylog("positioned load fi=%x ti=%x\n", stmt->fi, stmt->ti); + rcnt = 0; + if (count) + *count = 0; + if (!(res = stmt->result)) + return SQL_ERROR; + if (!stmt->ti) + parse_statement(stmt); /* not preferable */ + if (!stmt->ti || stmt->ntab != 1) + { + stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; + return SQL_ERROR; + } + global_ridx = irow + stmt->rowset_start; + res_cols = QR_NumResultCols(res); + if (!(oidval = QR_get_value_backend_row(res, global_ridx, res_cols - 1))) + return SQL_SUCCESS_WITH_INFO; + sscanf(oidval, "%u", &oid); + tidval = QR_get_value_backend_row(res, global_ridx, res_cols - 2); + res_cols -= 2; + if (qres = positioned_load(stmt, TRUE, res_cols, oid, tidval), qres) + { + TupleField *tupleo, + *tuplen; + + rcnt = QR_get_num_tuples(qres); + tupleo = res->backend_tuples + res->num_fields * global_ridx; + if (rcnt == 1) + { + QR_set_position(qres, 0); + tuplen = res->tupleField; + for (i = 0; i < res->num_fields; i++) + { + if (tupleo[i].value) + free(tupleo[i].value); + tupleo[i].len = tuplen[i].len; + tuplen[i].len = 0; + tupleo[i].value = tuplen[i].value; + tuplen[i].value = NULL; + } + ret = SQL_SUCCESS; + } + else + { + stmt->errornumber = STMT_ROW_VERSION_CHANGED; + stmt->errormsg = "the content was deleted after last fetch"; + ret = SQL_SUCCESS_WITH_INFO; + if (stmt->options.cursor_type == SQL_CURSOR_KEYSET_DRIVEN) + { + if (tupleo[res_cols + 1].value) + free(tupleo[res_cols + 1].value); + tupleo[res_cols + 1].value = NULL; + tupleo[res_cols + 1].len = 0; + } + } + QR_Destructor(qres); + } + else if (stmt->errornumber == 0) + stmt->errornumber = STMT_ERROR_TAKEN_FROM_BACKEND; + if (count) + *count = rcnt; + return ret; +} + +RETCODE SQL_API +SC_pos_newload(StatementClass *stmt, UInt4 oid, const char *tidval) +{ + int i; + QResultClass *res, + *qres; + RETCODE ret = SQL_ERROR; + + mylog("positioned new fi=%x ti=%x\n", stmt->fi, stmt->ti); + if (!(res = stmt->result)) + return SQL_ERROR; + if (!stmt->ti) + parse_statement(stmt); /* not preferable */ + if (!stmt->ti || stmt->ntab != 1) + { + stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; + return SQL_ERROR; + } + if (qres = positioned_load(stmt, TRUE, QR_NumResultCols(res) - 2, oid, tidval), qres) + { + TupleField *tupleo, + *tuplen; + int count = QR_get_num_tuples(qres); + + QR_set_position(qres, 0); + if (count == 1) + { + tuplen = qres->tupleField; + if (res->fcount >= res->count_allocated) + { + int tuple_size; + + if (!res->count_allocated) + tuple_size = TUPLE_MALLOC_INC; + else + tuple_size = res->count_allocated * 2; + res->backend_tuples = (TupleField *) realloc( + res->backend_tuples, + res->num_fields * sizeof(TupleField) * tuple_size); + if (!res->backend_tuples) + { + stmt->errornumber = res->status = PGRES_FATAL_ERROR; + stmt->errormsg = "Out of memory while reading tuples."; + QR_Destructor(qres); + return SQL_ERROR; + } + res->count_allocated = tuple_size; + } + tupleo = res->backend_tuples + res->num_fields * res->fcount; + for (i = 0; i < res->num_fields; i++) + { + tupleo[i].len = tuplen[i].len; + tuplen[i].len = 0; + tupleo[i].value = tuplen[i].value; + tuplen[i].value = NULL; + } + res->fcount++; + ret = SQL_SUCCESS; + } + else + { + stmt->errornumber = STMT_ROW_VERSION_CHANGED; + stmt->errormsg = "the content was changed before updation"; + ret = SQL_SUCCESS_WITH_INFO; + } + QR_Destructor(qres); + /* stmt->currTuple = stmt->rowset_start + irow; */ + } + return ret; +} + +RETCODE SQL_API +SC_pos_update(StatementClass *stmt, + UWORD irow) +{ + int i, + res_cols, + num_cols, + upd_cols; + UWORD global_ridx; + QResultClass *res; + BindInfoClass *bindings = stmt->bindings; + char updstr[4096]; + RETCODE ret; + char *tidval, + *oidval; + + mylog("POS UPDATE %d+%d fi=%x ti=%x\n", irow, stmt->result->base, stmt->fi, stmt->ti); + if (!(res = stmt->result)) + return SQL_ERROR; + if (!stmt->ti) + parse_statement(stmt); /* not preferable */ + if (!stmt->ti || stmt->ntab != 1) + { + stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; + return SQL_ERROR; + } + global_ridx = irow + stmt->rowset_start; + res_cols = QR_NumResultCols(res); + if (!(oidval = QR_get_value_backend_row(res, global_ridx, res_cols - 1))) + { + stmt->errormsg = "The row is already deleted"; + return SQL_ERROR; + } + tidval = QR_get_value_backend_row(res, global_ridx, res_cols - 2); + + sprintf(updstr, "update \"%s\" set", stmt->ti[0]->name); + num_cols = stmt->nfld; + for (i = upd_cols = 0; i < num_cols; i++) + { + if (bindings[i].used) + { + mylog("%d used=%d\n", i, *bindings[i].used); + if (*bindings[i].used != SQL_IGNORE) + { + if (upd_cols) + sprintf(updstr, "%s, \"%s\" = ?", updstr, stmt->fi[i]->name); + else + sprintf(updstr, "%s \"%s\" = ?", updstr, stmt->fi[i]->name); + upd_cols++; + } + } + else + mylog("%d null bind\n", i); + } + if (upd_cols > 0) + { + HSTMT hstmt; + int j; + int res_cols = QR_NumResultCols(res); + StatementClass *qstmt; + + sprintf(updstr, "%s where ctid = '%s' and oid = %s", updstr, + tidval, oidval); + mylog("updstr=%s\n", updstr); + if (PGAPI_AllocStmt(SC_get_conn(stmt), &hstmt) != SQL_SUCCESS) + return SQL_ERROR; + qstmt = (StatementClass *) hstmt; + for (i = j = 0; i < num_cols; i++) + { + if (bindings[i].used) + { + mylog("%d used=%d\n", i, *bindings[i].used); + if (*bindings[i].used != SQL_IGNORE) + { + PGAPI_BindParameter(hstmt, (SQLUSMALLINT) ++j, + SQL_PARAM_INPUT, bindings[i].returntype, + pgtype_to_sqltype(stmt, QR_get_field_type(res, i)), + QR_get_fieldsize(res, i), + (SQLSMALLINT) stmt->fi[i]->precision, + bindings[i].buffer, + bindings[i].buflen, + bindings[i].used); + } + } + } + ret = PGAPI_ExecDirect(hstmt, updstr, strlen(updstr)); + if (ret == SQL_ERROR) + { + stmt->errornumber = qstmt->errornumber; + stmt->errormsg = qstmt->errormsg; + } + else if (ret == SQL_NEED_DATA) /* must be fixed */ + { + stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; + stmt->errornumber = STMT_INVALID_CURSOR_STATE_ERROR; + stmt->errormsg = "SetPos with data_at_exec not yet supported"; + ret = SQL_ERROR; + } + if (ret != SQL_ERROR) + { + int updcnt; + const char *cmdstr = QR_get_command(qstmt->result); + + if (cmdstr && + sscanf(cmdstr, "UPDATE %d", &updcnt) == 1) + { + if (updcnt == 1) + SC_pos_reload(stmt, irow, (UWORD *) 0); + else if (updcnt == 0) + { + stmt->errornumber = STMT_ROW_VERSION_CHANGED; + stmt->errormsg = "the content was changed before updation"; + ret = SQL_ERROR; + if (stmt->options.cursor_type == SQL_CURSOR_KEYSET_DRIVEN) + SC_pos_reload(stmt, irow, (UWORD *) 0); + } + else + ret = SQL_ERROR; + stmt->currTuple = stmt->rowset_start + irow; + } + else + ret = SQL_ERROR; + if (ret == SQL_ERROR && stmt->errornumber == 0) + { + stmt->errornumber = STMT_ERROR_TAKEN_FROM_BACKEND; + stmt->errormsg = "SetPos update return error"; + } + } + PGAPI_FreeStmt(hstmt, SQL_DROP); + } + else + ret = SQL_SUCCESS_WITH_INFO; + return ret; +} +RETCODE SQL_API +SC_pos_delete(StatementClass *stmt, + UWORD irow) +{ + int res_cols; + UWORD global_ridx; + QResultClass *res, + *qres; + BindInfoClass *bindings = stmt->bindings; + char dltstr[4096]; + RETCODE ret; + char *oidval; + + mylog("POS DELETE fi=%x ti=%x\n", stmt->fi, stmt->ti); + if (!(res = stmt->result)) + return SQL_ERROR; + if (!stmt->ti) + parse_statement(stmt); /* not preferable */ + if (!stmt->ti || stmt->ntab != 1) + { + stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; + return SQL_ERROR; + } + res_cols = QR_NumResultCols(res); + global_ridx = irow + stmt->rowset_start; + if (!(oidval = QR_get_value_backend_row(res, global_ridx, res_cols - 1))) + { + stmt->errormsg = "The row is already deleted"; + return SQL_ERROR; + } + sprintf(dltstr, "delete from \"%s\" where ctid = '%s' and oid = %s", + stmt->ti[0]->name, + QR_get_value_backend_row(stmt->result, global_ridx, res_cols - 2), + oidval); + + mylog("dltstr=%s\n", dltstr); + qres = CC_send_query(SC_get_conn(stmt), dltstr, NULL); + if (qres && QR_command_successful(qres)) + { + int dltcnt; + const char *cmdstr = QR_get_command(qres); + + if (cmdstr && + sscanf(cmdstr, "DELETE %d", &dltcnt) == 1) + { + if (dltcnt == 1) + SC_pos_reload(stmt, irow, (UWORD *) 0); + else if (dltcnt == 0) + { + stmt->errornumber = STMT_ROW_VERSION_CHANGED; + stmt->errormsg = "the content was changed before deletion"; + ret = SQL_ERROR; + if (stmt->options.cursor_type == SQL_CURSOR_KEYSET_DRIVEN) + SC_pos_reload(stmt, irow, (UWORD *) 0); + } + else + ret = SQL_ERROR; + stmt->currTuple = stmt->rowset_start + irow; + } + else + ret = SQL_ERROR; + } + else + ret = SQL_ERROR; + if (ret == SQL_ERROR && stmt->errornumber == 0) + { + stmt->errornumber = STMT_ERROR_TAKEN_FROM_BACKEND; + stmt->errormsg = "SetPos delete return error"; + } + if (qres) + QR_Destructor(qres); + return ret; +} +RETCODE SQL_API +SC_pos_add(StatementClass *stmt, + UWORD irow) +{ + int num_cols, + add_cols, + i; + HSTMT hstmt; + QResultClass *res; + BindInfoClass *bindings = stmt->bindings; + char addstr[4096]; + RETCODE ret; + + mylog("POS ADD fi=%x ti=%x\n", stmt->fi, stmt->ti); + if (!(res = stmt->result)) + return SQL_ERROR; + if (!stmt->ti) + parse_statement(stmt); /* not preferable */ + if (!stmt->ti || stmt->ntab != 1) + { + stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; + return SQL_ERROR; + } + num_cols = stmt->nfld; + sprintf(addstr, "insert into \"%s\" (", stmt->ti[0]->name); + if (PGAPI_AllocStmt(SC_get_conn(stmt), &hstmt) != SQL_SUCCESS) + return SQL_ERROR; + for (i = add_cols = 0; i < num_cols; i++) + { + if (bindings[i].used) + { + mylog("%d used=%d\n", i, *bindings[i].used); + if (*bindings[i].used != SQL_IGNORE) + { + if (add_cols) + sprintf(addstr, "%s, \"%s\"", addstr, stmt->fi[i]->name); + else + sprintf(addstr, "%s\"%s\"", addstr, stmt->fi[i]->name); + PGAPI_BindParameter(hstmt, (SQLUSMALLINT) ++add_cols, + SQL_PARAM_INPUT, bindings[i].returntype, + pgtype_to_sqltype(stmt, QR_get_field_type(res, i)), + QR_get_fieldsize(res, i), + (SQLSMALLINT) stmt->fi[i]->precision, + bindings[i].buffer, + bindings[i].buflen, + bindings[i].used); + } + } + else + mylog("%d null bind\n", i); + } + if (add_cols > 0) + { + StatementClass *qstmt = (StatementClass *) hstmt; + + sprintf(addstr, "%s) values (", addstr); + for (i = 0; i < add_cols; i++) + { + if (i) + strcat(addstr, ", ?"); + else + strcat(addstr, "?"); + } + strcat(addstr, ")"); + mylog("addstr=%s\n", addstr); + ret = PGAPI_ExecDirect(hstmt, addstr, strlen(addstr)); + if (ret == SQL_NEED_DATA) /* must be fixed */ + { + stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; + stmt->errornumber = STMT_INVALID_CURSOR_STATE_ERROR; + stmt->errormsg = "SetPos with data_at_exec not yet supported"; + ret = SQL_ERROR; + } + if (ret == SQL_ERROR) + { + stmt->errornumber = qstmt->errornumber; + stmt->errormsg = qstmt->errormsg; + } + else + { + int addcnt; + UInt4 oid; + const char *cmdstr = QR_get_command(qstmt->result); + + if (cmdstr && + sscanf(cmdstr, "INSERT %u %d", &oid, &addcnt) == 2 && + addcnt == 1) + { + SC_pos_newload(stmt, oid, NULL); + if (stmt->bookmark.buffer) + { + char buf[32]; + + sprintf(buf, "%ld", res->fcount); + copy_and_convert_field(stmt, 0, buf, + SQL_C_ULONG, stmt->bookmark.buffer, + 0, stmt->bookmark.used); + } + } + else + { + stmt->errornumber = STMT_ERROR_TAKEN_FROM_BACKEND; + stmt->errormsg = "SetPos insert return error"; + ret = SQL_ERROR; + } + } + } + else + ret = SQL_SUCCESS_WITH_INFO; + PGAPI_FreeStmt(hstmt, SQL_DROP); + return ret; +} + +/* + * Stuff for updatable cursors end. + */ +#endif /* DRIVER_CURSOR_IMPLEMENT */ + +/* + * This positions the cursor within a rowset, that was positioned using SQLExtendedFetch. + * This will be useful (so far) only when using SQLGetData after SQLExtendedFetch. + */ +RETCODE SQL_API +PGAPI_SetPos( + HSTMT hstmt, + UWORD irow, + UWORD fOption, + UWORD fLock) +{ + static char *func = "PGAPI_SetPos"; + StatementClass *stmt = (StatementClass *) hstmt; + QResultClass *res; + int num_cols, + i; + BindInfoClass *bindings = stmt->bindings; + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + +#ifdef DRIVER_CURSOR_IMPLEMENT + mylog("SetPos fOption=%d irow=%d lock=%d currt=%d\n", fOption, irow, fLock, stmt->currTuple); + if (stmt->options.scroll_concurrency != SQL_CONCUR_READ_ONLY) + ; + else +#endif /* DRIVER_CURSOR_IMPLEMENT */ + if (fOption != SQL_POSITION && fOption != SQL_REFRESH) + { + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + stmt->errormsg = "Only SQL_POSITION/REFRESH is supported for PGAPI_SetPos"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + if (!(res = stmt->result)) + { + stmt->errormsg = "Null statement result in PGAPI_SetPos."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + num_cols = QR_NumResultCols(res); + + if (irow == 0) + { + stmt->errornumber = STMT_ROW_OUT_OF_RANGE; + stmt->errormsg = "Driver does not support Bulk operations."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + if (irow > stmt->last_fetch_count) + { + stmt->errornumber = STMT_ROW_OUT_OF_RANGE; + stmt->errormsg = "Row value out of range"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + irow--; + +#ifdef DRIVER_CURSOR_IMPLEMENT + switch (fOption) + { + case SQL_UPDATE: + return SC_pos_update(stmt, irow); + case SQL_DELETE: + return SC_pos_delete(stmt, irow); + case SQL_ADD: + return SC_pos_add(stmt, irow); + } +#endif /* DRIVER_CURSOR_IMPLEMENT */ + /* Reset for SQLGetData */ + for (i = 0; i < num_cols; i++) + bindings[i].data_left = -1; + + if (fOption == SQL_REFRESH) + { + /* save the last_fetch_count */ + int last_fetch = stmt->last_fetch_count; + int bind_save = stmt->bind_row; + +#ifdef DRIVER_CURSOR_IMPLEMENT + if (stmt->options.cursor_type == SQL_CURSOR_KEYSET_DRIVEN) + SC_pos_reload(stmt, irow, (UWORD *) 0); +#endif /* DRIVER_CURSOR_IMPLEMENT */ + stmt->currTuple = stmt->rowset_start + irow - 1; + stmt->bind_row = irow; + SC_fetch(stmt); + /* restore the last_fetch_count */ + stmt->last_fetch_count = last_fetch; + stmt->bind_row = bind_save; + } + else + stmt->currTuple = stmt->rowset_start + irow; + QR_set_position(res, irow); + + return SQL_SUCCESS; +} + + +/* Sets options that control the behavior of cursors. */ +RETCODE SQL_API +PGAPI_SetScrollOptions( + HSTMT hstmt, + UWORD fConcurrency, + SDWORD crowKeyset, + UWORD crowRowset) +{ + static char *func = "PGAPI_SetScrollOptions"; + StatementClass *stmt = (StatementClass *) hstmt; + + mylog("PGAPI_SetScrollOptions fConcurrency=%d crowKeyset=%d crowRowset=%d\n", + fConcurrency, crowKeyset, crowRowset); + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + stmt->errormsg = "SetScroll option not implemeted"; + + SC_log_error(func, "Function not implemented", hstmt); + return SQL_ERROR; +} + + +/* Set the cursor name on a statement handle */ +RETCODE SQL_API +PGAPI_SetCursorName( + HSTMT hstmt, + UCHAR FAR * szCursor, + SWORD cbCursor) +{ + static char *func = "PGAPI_SetCursorName"; + StatementClass *stmt = (StatementClass *) hstmt; + int len; + + mylog("PGAPI_SetCursorName: hstmt=%u, szCursor=%u, cbCursorMax=%d\n", hstmt, szCursor, cbCursor); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + len = (cbCursor == SQL_NTS) ? strlen(szCursor) : cbCursor; + + if (len <= 0 || len > sizeof(stmt->cursor_name) - 1) + { + stmt->errornumber = STMT_INVALID_CURSOR_NAME; + stmt->errormsg = "Invalid Cursor Name"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + strncpy_null(stmt->cursor_name, szCursor, len + 1); + return SQL_SUCCESS; +} + + +/* Return the cursor name for a statement handle */ +RETCODE SQL_API +PGAPI_GetCursorName( + HSTMT hstmt, + UCHAR FAR * szCursor, + SWORD cbCursorMax, + SWORD FAR * pcbCursor) +{ + static char *func = "PGAPI_GetCursorName"; + StatementClass *stmt = (StatementClass *) hstmt; + int len = 0; + RETCODE result; + + mylog("PGAPI_GetCursorName: hstmt=%u, szCursor=%u, cbCursorMax=%d, pcbCursor=%u\n", hstmt, szCursor, cbCursorMax, pcbCursor); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + if (stmt->cursor_name[0] == '\0') + { + stmt->errornumber = STMT_NO_CURSOR_NAME; + stmt->errormsg = "No Cursor name available"; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + result = SQL_SUCCESS; + len = strlen(stmt->cursor_name); + + if (szCursor) + { + strncpy_null(szCursor, stmt->cursor_name, cbCursorMax); + + if (len >= cbCursorMax) + { + result = SQL_SUCCESS_WITH_INFO; + stmt->errornumber = STMT_TRUNCATED; + stmt->errormsg = "The buffer was too small for the GetCursorName."; + } + } + + if (pcbCursor) + *pcbCursor = len; + + return result; +} diff --git a/src/interfaces/odbc/windev/setup.c b/src/interfaces/odbc/windev/setup.c new file mode 100644 index 0000000000..6bcc01bd89 --- /dev/null +++ b/src/interfaces/odbc/windev/setup.c @@ -0,0 +1,425 @@ +/*------- + * Module: setup.c + * + * Description: This module contains the setup functions for + * adding/modifying a Data Source in the ODBC.INI portion + * of the registry. + * + * Classes: n/a + * + * API functions: ConfigDSN + * + * Comments: See "notice.txt" for copyright and license information. + *------- + */ + +#include "psqlodbc.h" + +#include "connection.h" +#include +#include +#include +#include "resource.h" +#include "dlg_specific.h" + + +#define INTFUNC __stdcall + +extern HINSTANCE NEAR s_hModule; /* Saved module handle. */ + +/* Constants */ +#define MIN(x,y) ((x) < (y) ? (x) : (y)) + +#ifdef WIN32 +#define MAXPGPATH (255+1) +#endif + +#define MAXKEYLEN (15+1) /* Max keyword length */ +#define MAXDESC (255+1) /* Max description length */ +#define MAXDSNAME (32+1) /* Max data source name length */ + + +/* Globals */ +/* NOTE: All these are used by the dialog procedures */ +typedef struct tagSETUPDLG +{ + HWND hwndParent; /* Parent window handle */ + LPCSTR lpszDrvr; /* Driver description */ + ConnInfo ci; + char szDSN[MAXDSNAME]; /* Original data source name */ + BOOL fNewDSN; /* New data source flag */ + BOOL fDefault; /* Default data source flag */ + +} SETUPDLG, FAR * LPSETUPDLG; + + + +/* Prototypes */ +void INTFUNC CenterDialog(HWND hdlg); +int CALLBACK ConfigDlgProc(HWND hdlg, WORD wMsg, WPARAM wParam, LPARAM lParam); +void INTFUNC ParseAttributes(LPCSTR lpszAttributes, LPSETUPDLG lpsetupdlg); +BOOL INTFUNC SetDSNAttributes(HWND hwnd, LPSETUPDLG lpsetupdlg); + + +/*-------- + * ConfigDSN + * + * Description: ODBC Setup entry point + * This entry point is called by the ODBC Installer + * (see file header for more details) + * Input : hwnd ----------- Parent window handle + * fRequest ------- Request type (i.e., add, config, or remove) + * lpszDriver ----- Driver name + * lpszAttributes - data source attribute string + * Output : TRUE success, FALSE otherwise + *-------- + */ +BOOL CALLBACK +ConfigDSN(HWND hwnd, + WORD fRequest, + LPCSTR lpszDriver, + LPCSTR lpszAttributes) +{ + BOOL fSuccess; /* Success/fail flag */ + GLOBALHANDLE hglbAttr; + LPSETUPDLG lpsetupdlg; + + + /* Allocate attribute array */ + hglbAttr = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof(SETUPDLG)); + if (!hglbAttr) + return FALSE; + lpsetupdlg = (LPSETUPDLG) GlobalLock(hglbAttr); + + /* Parse attribute string */ + if (lpszAttributes) + ParseAttributes(lpszAttributes, lpsetupdlg); + + /* Save original data source name */ + if (lpsetupdlg->ci.dsn[0]) + lstrcpy(lpsetupdlg->szDSN, lpsetupdlg->ci.dsn); + else + lpsetupdlg->szDSN[0] = '\0'; + + /* Remove data source */ + if (ODBC_REMOVE_DSN == fRequest) + { + /* Fail if no data source name was supplied */ + if (!lpsetupdlg->ci.dsn[0]) + fSuccess = FALSE; + + /* Otherwise remove data source from ODBC.INI */ + else + fSuccess = SQLRemoveDSNFromIni(lpsetupdlg->ci.dsn); + } + /* Add or Configure data source */ + else + { + /* Save passed variables for global access (e.g., dialog access) */ + lpsetupdlg->hwndParent = hwnd; + lpsetupdlg->lpszDrvr = lpszDriver; + lpsetupdlg->fNewDSN = (ODBC_ADD_DSN == fRequest); + lpsetupdlg->fDefault = !lstrcmpi(lpsetupdlg->ci.dsn, INI_DSN); + + /* + * Display the appropriate dialog (if parent window handle + * supplied) + */ + if (hwnd) + { + /* Display dialog(s) */ + fSuccess = (IDOK == DialogBoxParam(s_hModule, + MAKEINTRESOURCE(DLG_CONFIG), + hwnd, + ConfigDlgProc, + (LONG) (LPSTR) lpsetupdlg)); + } + else if (lpsetupdlg->ci.dsn[0]) + fSuccess = SetDSNAttributes(hwnd, lpsetupdlg); + else + fSuccess = FALSE; + } + + GlobalUnlock(hglbAttr); + GlobalFree(hglbAttr); + + return fSuccess; +} + + +/*------- + * CenterDialog + * + * Description: Center the dialog over the frame window + * Input : hdlg -- Dialog window handle + * Output : None + *------- + */ +void INTFUNC +CenterDialog(HWND hdlg) +{ + HWND hwndFrame; + RECT rcDlg, + rcScr, + rcFrame; + int cx, + cy; + + hwndFrame = GetParent(hdlg); + + GetWindowRect(hdlg, &rcDlg); + cx = rcDlg.right - rcDlg.left; + cy = rcDlg.bottom - rcDlg.top; + + GetClientRect(hwndFrame, &rcFrame); + ClientToScreen(hwndFrame, (LPPOINT) (&rcFrame.left)); + ClientToScreen(hwndFrame, (LPPOINT) (&rcFrame.right)); + rcDlg.top = rcFrame.top + (((rcFrame.bottom - rcFrame.top) - cy) >> 1); + rcDlg.left = rcFrame.left + (((rcFrame.right - rcFrame.left) - cx) >> 1); + rcDlg.bottom = rcDlg.top + cy; + rcDlg.right = rcDlg.left + cx; + + GetWindowRect(GetDesktopWindow(), &rcScr); + if (rcDlg.bottom > rcScr.bottom) + { + rcDlg.bottom = rcScr.bottom; + rcDlg.top = rcDlg.bottom - cy; + } + if (rcDlg.right > rcScr.right) + { + rcDlg.right = rcScr.right; + rcDlg.left = rcDlg.right - cx; + } + + if (rcDlg.left < 0) + rcDlg.left = 0; + if (rcDlg.top < 0) + rcDlg.top = 0; + + MoveWindow(hdlg, rcDlg.left, rcDlg.top, cx, cy, TRUE); + return; +} + +/*------- + * ConfigDlgProc + * Description: Manage add data source name dialog + * Input : hdlg --- Dialog window handle + * wMsg --- Message + * wParam - Message parameter + * lParam - Message parameter + * Output : TRUE if message processed, FALSE otherwise + *------- + */ +int CALLBACK +ConfigDlgProc(HWND hdlg, + WORD wMsg, + WPARAM wParam, + LPARAM lParam) +{ + LPSETUPDLG lpsetupdlg; + ConnInfo *ci; + + switch (wMsg) + { + /* Initialize the dialog */ + case WM_INITDIALOG: + lpsetupdlg = (LPSETUPDLG) lParam; + ci = &lpsetupdlg->ci; + + /* Hide the driver connect message */ + ShowWindow(GetDlgItem(hdlg, DRV_MSG_LABEL), SW_HIDE); + + SetWindowLong(hdlg, DWL_USER, lParam); + CenterDialog(hdlg); /* Center dialog */ + + /* + * NOTE: Values supplied in the attribute string will always + */ + /* override settings in ODBC.INI */ + + /* Get the rest of the common attributes */ + getDSNinfo(ci, CONN_DONT_OVERWRITE); + + /* Fill in any defaults */ + getDSNdefaults(ci); + + /* Initialize dialog fields */ + SetDlgStuff(hdlg, ci); + + if (lpsetupdlg->fDefault) + { + EnableWindow(GetDlgItem(hdlg, IDC_DSNAME), FALSE); + EnableWindow(GetDlgItem(hdlg, IDC_DSNAMETEXT), FALSE); + } + else + SendDlgItemMessage(hdlg, IDC_DSNAME, + EM_LIMITTEXT, (WPARAM) (MAXDSNAME - 1), 0L); + + SendDlgItemMessage(hdlg, IDC_DESC, + EM_LIMITTEXT, (WPARAM) (MAXDESC - 1), 0L); + return TRUE; /* Focus was not set */ + + /* Process buttons */ + case WM_COMMAND: + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + /* + * Ensure the OK button is enabled only when a data + * source name + */ + /* is entered */ + case IDC_DSNAME: + if (GET_WM_COMMAND_CMD(wParam, lParam) == EN_CHANGE) + { + char szItem[MAXDSNAME]; /* Edit control text */ + + /* Enable/disable the OK button */ + EnableWindow(GetDlgItem(hdlg, IDOK), + GetDlgItemText(hdlg, IDC_DSNAME, + szItem, sizeof(szItem))); + return TRUE; + } + break; + + /* Accept results */ + case IDOK: + lpsetupdlg = (LPSETUPDLG) GetWindowLong(hdlg, DWL_USER); + /* Retrieve dialog values */ + if (!lpsetupdlg->fDefault) + GetDlgItemText(hdlg, IDC_DSNAME, + lpsetupdlg->ci.dsn, + sizeof(lpsetupdlg->ci.dsn)); + /* Get Dialog Values */ + GetDlgStuff(hdlg, &lpsetupdlg->ci); + + /* Update ODBC.INI */ + SetDSNAttributes(hdlg, lpsetupdlg); + + /* Return to caller */ + case IDCANCEL: + EndDialog(hdlg, wParam); + return TRUE; + + case IDC_DRIVER: + lpsetupdlg = (LPSETUPDLG) GetWindowLong(hdlg, DWL_USER); + DialogBoxParam(s_hModule, MAKEINTRESOURCE(DLG_OPTIONS_DRV), + hdlg, driver_optionsProc, (LPARAM) &lpsetupdlg->ci); + return TRUE; + + case IDC_DATASOURCE: + lpsetupdlg = (LPSETUPDLG) GetWindowLong(hdlg, DWL_USER); + + DialogBoxParam(s_hModule, MAKEINTRESOURCE(DLG_OPTIONS_DS), + hdlg, ds_optionsProc, (LPARAM) &lpsetupdlg->ci); + + return TRUE; + } + break; + } + + /* Message not processed */ + return FALSE; +} + + +/*------- + * ParseAttributes + * + * Description: Parse attribute string moving values into the aAttr array + * Input : lpszAttributes - Pointer to attribute string + * Output : None (global aAttr normally updated) + *------- + */ +void INTFUNC +ParseAttributes(LPCSTR lpszAttributes, LPSETUPDLG lpsetupdlg) +{ + LPCSTR lpsz; + LPCSTR lpszStart; + char aszKey[MAXKEYLEN]; + int cbKey; + char value[MAXPGPATH]; + + memset(&lpsetupdlg->ci, 0, sizeof(ConnInfo)); + + for (lpsz = lpszAttributes; *lpsz; lpsz++) + { + /* + * Extract key name (e.g., DSN), it must be terminated by an + * equals + */ + lpszStart = lpsz; + for (;; lpsz++) + { + if (!*lpsz) + return; /* No key was found */ + else if (*lpsz == '=') + break; /* Valid key found */ + } + /* Determine the key's index in the key table (-1 if not found) */ + cbKey = lpsz - lpszStart; + if (cbKey < sizeof(aszKey)) + { + _fmemcpy(aszKey, lpszStart, cbKey); + aszKey[cbKey] = '\0'; + } + + /* Locate end of key value */ + lpszStart = ++lpsz; + for (; *lpsz; lpsz++) + ; + + /* lpsetupdlg->aAttr[iElement].fSupplied = TRUE; */ + _fmemcpy(value, lpszStart, MIN(lpsz - lpszStart + 1, MAXPGPATH)); + + mylog("aszKey='%s', value='%s'\n", aszKey, value); + + /* Copy the appropriate value to the conninfo */ + copyAttributes(&lpsetupdlg->ci, aszKey, value); + } + return; +} + + +/*-------- + * SetDSNAttributes + * + * Description: Write data source attributes to ODBC.INI + * Input : hwnd - Parent window handle (plus globals) + * Output : TRUE if successful, FALSE otherwise + *-------- + */ +BOOL INTFUNC +SetDSNAttributes(HWND hwndParent, LPSETUPDLG lpsetupdlg) +{ + LPCSTR lpszDSN; /* Pointer to data source name */ + + lpszDSN = lpsetupdlg->ci.dsn; + + /* Validate arguments */ + if (lpsetupdlg->fNewDSN && !*lpsetupdlg->ci.dsn) + return FALSE; + + /* Write the data source name */ + if (!SQLWriteDSNToIni(lpszDSN, lpsetupdlg->lpszDrvr)) + { + if (hwndParent) + { + char szBuf[MAXPGPATH]; + char szMsg[MAXPGPATH]; + + LoadString(s_hModule, IDS_BADDSN, szBuf, sizeof(szBuf)); + wsprintf(szMsg, szBuf, lpszDSN); + LoadString(s_hModule, IDS_MSGTITLE, szBuf, sizeof(szBuf)); + MessageBox(hwndParent, szMsg, szBuf, MB_ICONEXCLAMATION | MB_OK); + } + return FALSE; + } + + /* Update ODBC.INI */ + writeDSNinfo(&lpsetupdlg->ci); + + /* If the data source name has changed, remove the old name */ + if (lstrcmpi(lpsetupdlg->szDSN, lpsetupdlg->ci.dsn)) + SQLRemoveDSNFromIni(lpsetupdlg->szDSN); + return TRUE; +} diff --git a/src/interfaces/odbc/windev/setup.rul b/src/interfaces/odbc/windev/setup.rul new file mode 100644 index 0000000000..a4f52a03f7 --- /dev/null +++ b/src/interfaces/odbc/windev/setup.rul @@ -0,0 +1,495 @@ +/* +# Insight Distribution Systems - System V - Apr 1998 +#ident "@(#)setup.rul 1.13 :/sccs/sql/odbc/s.setup.rul 1/6/99 14:47:48" +*/ + +/*----------------------------------------------------------------------------*\ + * + * PostgreSQL ODBC Driver Installation Script for InstallShield + * +\*----------------------------------------------------------------------------*/ + + +#define APP_NAME "PostgreSQL ODBC Driver" +#define DRIVER_NAME "PostgreSQL" +#define DRIVER_FILE "PSQLODBC.DLL" +#define OLD_DRIVER_FILE "PODBC32.DLL" +#define OLD_DRIVER_FILE_RENAMED "podbc32_sav.dll" + +#define COMPANY_NAME "Insight" +#define PRODUCT_NAME "PostgreSQL ODBC Driver" +#define PRODUCT_VERSION "6.3" +#define PRODUCT_KEY "PSQLODBC.DLL" +#define UNINSTALL_KEY "PSQLODBCv6.3" + +#define ODBC_DM_KEY "\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\SharedDLLs" +#define ODBC_COMP_KEY "\\SOFTWARE\\ODBC\\ODBCINST.INI" +#define ODBC_CORE_KEY "\\SOFTWARE\\ODBC\\ODBCINST.INI\\ODBC Core" +#define ODBC_DRIVERS_KEY "\\SOFTWARE\\ODBC\\ODBCINST.INI\\ODBC Drivers" + + +declare + // functions + prototype SetupScreen(); + prototype FileCompare(STRING, STRING, STRING, STRING); + + // variables + STRING svMainDirectory [_MAX_STRING], svGrp, svUninstLogFile, svPath; + STRING svValue, szName, szKey, szMessage; + STRING szMsg, szTmp, szTmp2, szFileSet, szProgram; + NUMBER nResult, pos, nvType, nvSize, nStartup, ComponentUsageCount; + + NUMBER nvDoNot, nvVersion, nvInstall, nCore, nDM; + STRING dm, core, szFileName, svFileName; + NUMBER options, nvInfo, nvResult; + LONG lResult; + STRING svCompVersion, svFileVersion, svCompDate, svCompTime, svFileDate, svFileTime; + +program + +StartHere: + Disable( BACKGROUND ); + + // Set up the installation screen. + SetupScreen(); + InstallationInfo(COMPANY_NAME, PRODUCT_NAME, PRODUCT_VERSION, PRODUCT_KEY); + RegDBSetAppInfo("Location", REGDB_STRING, WINSYSDIR ^ DRIVER_FILE, -1); + +// Create a Welcome dialog. +WelcomeDlg: + Disable( BACKBUTTON ); + Welcome( "Welcome to the PostgreSQL Odbc Driver Installation", 0 ); + Enable( BACKBUTTON ); + Enable( NEXTBUTTON ); + +GetTargetDirectory: + svMainDirectory = WINSYSDIR; + +OptionsDlg: + RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE); + szKey = ODBC_DM_KEY; + nCore = RegDBKeyExist(szKey); + + szName = WINSYSDIR ^ "ODBC32.DLL"; + nDM = RegDBGetKeyValueEx(szKey, szName, nvType, svValue, nvSize); + + szMessage = "Select options for installing the ODBC Driver Manager.\n" + + "Analysis of your system suggests that the ODBC Driver Manager\n"; + + nvDoNot = FALSE; + nvInstall = FALSE; + nvVersion = FALSE; + if (nCore >= 0 && nDM >= 0) then + nvDoNot = TRUE; + szMessage = szMessage + "is already installed. Therefore, you may choose not to install it."; + else + nvInstall = TRUE; + szMessage = szMessage + "is not installed. Therefore, you should install it now."; + endif; + + Enable(FINISHBUTTON); + nResult = AskOptions(EXCLUSIVE, szMessage, + "Do not install Driver Manager", nvDoNot, + "Install Driver Manager ", nvInstall, + "Install Driver Manager (with version checking)", nvVersion); + + if (nResult = BACK) then + Disable(FINISHBUTTON); + goto WelcomeDlg; + endif; + +Version: + CompressInfo("driver.z", DRIVER_FILE, COMP_INFO_VERSIONMS|COMP_INFO_VERSIONLS, nvInfo, svCompVersion); + + szFileName = WINSYSDIR ^ DRIVER_FILE; + nResult = VerGetFileVersion(szFileName, svFileVersion); + + // MessageBox("System file PSQLODBC.dll version is " + svFileVersion, INFORMATION); + + lResult = VerCompare(svCompVersion, svFileVersion, VERSION); + + if (lResult = EQUALS) then + //date + CompressInfo("driver.z", DRIVER_FILE, COMP_INFO_DATE, nvInfo, svCompDate); + GetFileInfo(szFileName, FILE_DATE, nvResult, svFileDate); + + //time + CompressInfo("driver.z", DRIVER_FILE, COMP_INFO_TIME, nvInfo, svCompTime); + GetFileInfo(szFileName, FILE_TIME, nvResult, svFileTime); + + // If compressed file date/time is earlier than system file date/time + // then + nResult = FileCompare(svCompDate, svCompTime, svFileDate, svFileTime); + if (nResult < 0) then + lResult = LESS_THAN; + endif; + + NumToStr(szTmp, nResult); + // MessageBox("File Compare = " + szTmp, INFORMATION); + endif; + + if (lResult = LESS_THAN) then + MessageBeep(0); + nResult = AskYesNo("The " + PRODUCT_NAME + " is already installed on your system \nand is a newer version than the one that is about to be installed.\n\n" + + "Would you like to continue the installation anyway (not recommended)?", NO); + if (nResult = NO) then + MessageBeep(0); + MessageBox("Installation has been aborted.\nNo changes have been made to your system.", WARNING); + exit; + endif; + else + /* + nResult = AskYesNo("Ready to install " + PRODUCT_NAME + ".\n\nPress Yes to proceed with the installation.\nPress No to abort the installation.", YES); + if (nResult = NO) then + MessageBeep(0); + MessageBox("Installation has been aborted.\nNo changes have been made to your system.", WARNING); + exit; + endif; + */ + endif; + +CheckRegistry: + Enable(STATUSDLG); + + SetStatusWindow(5, "Checking registry entries..."); + Delay(1); + + RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE); + szKey = ODBC_DM_KEY; + nResult = RegDBKeyExist(szKey); + if (nResult < 0 && nvDoNot = TRUE) then + MessageBeep(0); + MessageBox("ODBC Core Components are not installed!", SEVERE); + Disable(STATUSDLG); + MessageBeep(0); + MessageBox("Please install the ODBC Core Components\nand rerun this setup program.", INFORMATION); + exit; + endif; + + szName = WINSYSDIR ^ "ODBC32.DLL"; + nResult = RegDBGetKeyValueEx(szKey, szName, nvType, svValue, nvSize); + if (nResult < 0 && nvDoNot = TRUE) then + MessageBeep(0); + MessageBox("ODBC Driver Manager (ODBC32.DLL) is not installed!", SEVERE); + Disable(STATUSDLG); + MessageBeep(0); + MessageBox("Please install the ODBC Driver Manager\nand rerun this setup program.", INFORMATION); + exit; + endif; + + +FileSetup: + + SetStatusWindow( 10, "Copying program files..."); + StatusUpdate(ON, 90); + + DeinstallStart(svMainDirectory, svUninstLogFile, UNINSTALL_KEY, 0); + + // Show the uninstall under Add/Remove Programs in Control Panel + RegDBSetItem(REGDB_UNINSTALL_NAME, PRODUCT_NAME); + + szFileSet = "psqlodbc"; + + TARGETDIR = svMainDirectory; // winsys + + FileSetBeginDefine(szFileSet); + + + nResult = CompressGet("driver.z", "*.*", COMP_NORMAL); + if (nResult < 0) then + NumToStr(szTmp, nResult); + MessageBox("Compress Get Error on driver.z files.\n\nError # " + szTmp, SEVERE); + exit; + endif; + + TARGETDIR = svMainDirectory; // winsys + + // Driver Manager stuff + if (! nvDoNot) then + if (nvVersion) then + options = COMP_UPDATE_VERSION; + else + options = COMP_NORMAL; + endif; + + // The File usage count increments are handled by CompressGet + // with the SHAREDFILE option. + + nResult = CompressGet("redist.z", "*.*", options|SHAREDFILE); + if (nResult < 0) then + NumToStr(szTmp, nResult); + MessageBox("Compress Get Error on redist.z files.\n\nError # " + szTmp, SEVERE); + exit; + endif; + endif; + + + FileSetEndDefine(szFileSet); + +FileTransfer: + nResult = FileSetPerformEz(szFileSet, 0); + + switch(nResult) + case FS_DONE: + case FS_CREATEDIR: + MessageBeep(0); + MessageBox("Unable to create a required subdirectory under " + TARGETDIR + "." + + "\nPlease check write access to this directory.", SEVERE); + + abort; + default: + NumToStr(szTmp, nResult); + MessageBeep(0); + MessageBox("Error copying files to " + TARGETDIR + "." + + "\nPlease check this location and try again." + + "\n\nError Number:"+szTmp, SEVERE); + + abort; + + endswitch; + + +UpdateRegistry: + SetStatusWindow(95, "Creating registry entries..."); + Delay(2); + + RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE); + + Disable(LOGGING); + + // Create ODBC Core Subkey (if it doesn't exist) + // (But don't create uninstall information for it) + szKey = ODBC_CORE_KEY; + nResult = RegDBCreateKeyEx(szKey, ""); + if (nResult < 0) then + MessageBeep(0); + MessageBox("Unable to create ODBC Core subkey.", SEVERE); + exit; + endif; + + // Create Installed Driver Key (if it doesn't exist) + // (But don't create uninstall information for it) + szKey = ODBC_DRIVERS_KEY; + nResult = RegDBCreateKeyEx(szKey, ""); + if (nResult < 0) then + MessageBeep(0); + MessageBox("Unable to create ODBC Drivers subkey.", SEVERE); + exit; + endif; + + + // Increment Driver Manager Component UsageCount + szKey = ODBC_CORE_KEY; + szName = "UsageCount"; + if (RegDBGetKeyValueEx(szKey, szName, nvType, svValue, nvSize) < 0) then + ComponentUsageCount = 0; + endif; + + // MessageBox("Current Driver Manager Component Usage Count = " + svValue, INFORMATION); + + StrToNum(ComponentUsageCount, svValue); + ComponentUsageCount = ComponentUsageCount + 1; + NumToStr(szTmp, ComponentUsageCount); + // MessageBox("New Driver Manager Component Usage Count = " + szTmp, INFORMATION); + + nResult = RegDBSetKeyValueEx(szKey, szName, REGDB_NUMBER, szTmp, -1); + if (nResult < 0) then + MessageBeep(0); + MessageBox("Unable to increment Driver Manager component usage count.", SEVERE); + exit; + endif; + + // Re-enable logging now + Enable(LOGGING); + + // set ODBC Drivers Subkey (installed) + szKey = ODBC_DRIVERS_KEY; + nResult = RegDBSetKeyValueEx(szKey, DRIVER_NAME, REGDB_STRING, "Installed", -1); + if (nResult < 0) then + MessageBeep(0); + MessageBox("Unable to create 'Installed' key value.", SEVERE); + exit; + endif; + + + // Driver Specification Subkey (PostgreSQL) + szKey = ODBC_COMP_KEY + "\\" + DRIVER_NAME; + nResult = RegDBCreateKeyEx(szKey, ""); + if (nResult < 0) then + MessageBeep(0); + MessageBox("Unable to create ODBC Driver Key.", SEVERE); + exit; + endif; + + nResult = RegDBSetKeyValueEx(szKey, "APILevel", REGDB_STRING, "1", -1); + if (nResult < 0) then + MessageBeep(0); + MessageBox("Unable to create 'APILevel' key value.", SEVERE); + exit; + endif; + + nResult = RegDBSetKeyValueEx(szKey, "ConnectFunctions", REGDB_STRING, "YYN", -1); + if (nResult < 0) then + MessageBeep(0); + MessageBox("Unable to create 'ConnectFunctions' key value.", SEVERE); + exit; + endif; + + nResult = RegDBSetKeyValueEx(szKey, "Driver", REGDB_STRING, WINSYSDIR ^ DRIVER_FILE, -1); + if (nResult < 0) then + MessageBeep(0); + MessageBox("Unable to create 'Driver' key value.", SEVERE); + exit; + endif; + + nResult = RegDBSetKeyValueEx(szKey, "DriverODBCVer", REGDB_STRING, "02.00", -1); + if (nResult < 0) then + MessageBeep(0); + MessageBox("Unable to create 'DriverODBCVer' key value.", SEVERE); + exit; + endif; + + nResult = RegDBSetKeyValueEx(szKey, "FileUsage", REGDB_STRING, "0", -1); + if (nResult < 0) then + MessageBeep(0); + MessageBox("Unable to create 'FileUsage' key value.", SEVERE); + exit; + endif; + + nResult = RegDBSetKeyValueEx(szKey, "Setup", REGDB_STRING, WINSYSDIR ^ DRIVER_FILE, -1); + if (nResult < 0) then + MessageBeep(0); + MessageBox("Unable to create 'Setup' key value.", SEVERE); + exit; + endif; + + nResult = RegDBSetKeyValueEx(szKey, "SQLLevel", REGDB_STRING, "1", -1); + if (nResult < 0) then + MessageBeep(0); + MessageBox("Unable to create 'SQLLevel' key value.", SEVERE); + exit; + endif; + + nResult = RegDBSetKeyValueEx(szKey, "UsageCount", REGDB_NUMBER, "1", -1); + if (nResult < 0) then + MessageBeep(0); + MessageBox("Unable to create 'UsageCount' key value.", SEVERE); + exit; + endif; + + pos = StrFind(CMDLINE, "UseDeclareFetch="); + if (pos >= 0) then + StrSub(svValue, CMDLINE, pos + 16, 1); + nResult = RegDBSetKeyValueEx(szKey, "UseDeclareFetch", REGDB_STRING, svValue, -1); + if (nResult < 0) then + MessageBeep(0); + MessageBox("Unable to create 'UseDeclareFetch' key value.", SEVERE); + exit; + endif; + endif; + + pos = StrFind(CMDLINE, "Protocol="); + if (pos >= 0) then + StrSub(svValue, CMDLINE, pos + 9, 3); + nResult = RegDBSetKeyValueEx(szKey, "Protocol", REGDB_STRING, svValue, -1); + if (nResult < 0) then + MessageBeep(0); + MessageBox("Unable to create 'Protocol' key value.", SEVERE); + exit; + endif; + endif; + +RenameOld: + if (FindFile(WINSYSDIR, OLD_DRIVER_FILE, svFileName) = 0) then + szMessage = "Renaming old driver to " + OLD_DRIVER_FILE_RENAMED + " ..."; + SetStatusWindow(98, szMessage); + Delay(1); + + Disable(LOGGING); + + SRCDIR= WINSYSDIR; + TARGETDIR = WINSYSDIR; + + RenameFile(OLD_DRIVER_FILE, OLD_DRIVER_FILE_RENAMED); + + Enable(LOGGING); + endif; + +Done: + Delay(1); + SetStatusWindow(100, "Installation complete"); + + Delay(1); + Disable(STATUSDLG); + + if (BATCH_INSTALL = TRUE) then + szMsg = "Some files could not be updated because they are " + + "currently in use by other programs on the system. " + + "Files in use will be updated the next time you restart " + + "your system."; + RebootDialog("Restart Windows", szMsg, SYS_BOOTMACHINE); + CommitSharedFiles(0); + szMsg = "Driver setup complete.\n\nReboot your system to complete the installation."; + MessageBeep(0); + MessageBox(szMsg, INFORMATION); + else + + szMsg = "Driver installation completed successfully."; + MessageBeep(0); + MessageBox(szMsg, INFORMATION); + endif; + + exit; + +/*---------------------------------------------------------------------------*\ + * + * Function: SetupScreen + * + * Purpose: This function will set up the screen look. This includes + * colors, fonts, text to be displayed, etc. + * + * + * Input: + * + * Returns: + * + * Comments: +\*---------------------------------------------------------------------------*/ + +function SetupScreen() + begin + + Enable( INDVFILESTATUS ); + + SetTitle( APP_NAME + " Setup", 28, WHITE ); + + SetTitle( "Setup", 0, BACKGROUNDCAPTION ); // Caption bar text. + + Enable( BACKGROUND ); + + end; + +function FileCompare(szCompInfoDate, szCompInfoTime, szFileInfoDate, szFileInfoTime) + STRING year, month, day, file_date, file_time; + NUMBER nResult; + begin + StrSub(year, szFileInfoDate, 2, 2); + StrSub(month, szFileInfoDate, 5, 2); + StrSub(day, szFileInfoDate, 8, 2); + file_date = month + "-" + day + "-" + year; + + nResult = StrCompare(szCompInfoDate, file_date); + if (nResult != 0) then + return nResult; + endif; + + StrSub(file_time, szFileInfoTime, 0, 5); + + // MessageBox("Comp = " + szCompInfoDate + " " + szCompInfoTime + ", File = " + file_date + " " + file_time, INFORMATION); + nResult = StrCompare(szCompInfoTime, file_time); + + return nResult; + end; + + + diff --git a/src/interfaces/odbc/windev/socket.c b/src/interfaces/odbc/windev/socket.c new file mode 100644 index 0000000000..031a6779cb --- /dev/null +++ b/src/interfaces/odbc/windev/socket.c @@ -0,0 +1,355 @@ +/*------- + * Module: socket.c + * + * Description: This module contains functions for low level socket + * operations (connecting/reading/writing to the backend) + * + * Classes: SocketClass (Functions prefix: "SOCK_") + * + * API functions: none + * + * Comments: See "notice.txt" for copyright and license information. + *------- + */ + +#include "socket.h" + +#include "connection.h" + +#ifndef WIN32 +#include +#include /* for memset */ +#endif + +extern GLOBAL_VALUES globals; + +#ifndef BOOL +#define BOOL int +#endif +#ifndef TRUE +#define TRUE (BOOL)1 +#endif +#ifndef FALSE +#define FALSE (BOOL)0 +#endif + + +void +SOCK_clear_error(SocketClass *self) +{ + self->errornumber = 0; + self->errormsg = NULL; +} + + +SocketClass * +SOCK_Constructor(const ConnectionClass *conn) +{ + SocketClass *rv; + + rv = (SocketClass *) malloc(sizeof(SocketClass)); + + if (rv != NULL) + { + rv->socket = (SOCKETFD) - 1; + rv->buffer_filled_in = 0; + rv->buffer_filled_out = 0; + rv->buffer_read_in = 0; + + if (rv) + rv->buffer_size = conn->connInfo.drivers.socket_buffersize; + else + rv->buffer_size = globals.socket_buffersize; + rv->buffer_in = (unsigned char *) malloc(rv->buffer_size); + if (!rv->buffer_in) + { + free(rv); + return NULL; + } + + rv->buffer_out = (unsigned char *) malloc(rv->buffer_size); + if (!rv->buffer_out) + { + free(rv->buffer_in); + free(rv); + return NULL; + } + rv->errormsg = NULL; + rv->errornumber = 0; + rv->reverse = FALSE; + } + return rv; +} + + +void +SOCK_Destructor(SocketClass *self) +{ + mylog("SOCK_Destructor\n"); + if (self->socket != -1) + { + SOCK_put_char(self, 'X'); + SOCK_flush_output(self); + closesocket(self->socket); + } + + if (self->buffer_in) + free(self->buffer_in); + + if (self->buffer_out) + free(self->buffer_out); + + free(self); +} + + +char +SOCK_connect_to(SocketClass *self, unsigned short port, char *hostname) +{ + struct hostent *host; + struct sockaddr_in sadr; + unsigned long iaddr; + + if (self->socket != -1) + { + self->errornumber = SOCKET_ALREADY_CONNECTED; + self->errormsg = "Socket is already connected"; + return 0; + } + + memset((char *) &sadr, 0, sizeof(sadr)); + + /* + * If it is a valid IP address, use it. Otherwise use hostname lookup. + */ + iaddr = inet_addr(hostname); + if (iaddr == INADDR_NONE) + { + host = gethostbyname(hostname); + if (host == NULL) + { + self->errornumber = SOCKET_HOST_NOT_FOUND; + self->errormsg = "Could not resolve hostname."; + return 0; + } + memcpy(&(sadr.sin_addr), host->h_addr, host->h_length); + } + else + memcpy(&(sadr.sin_addr), (struct in_addr *) & iaddr, sizeof(iaddr)); + + sadr.sin_family = AF_INET; + sadr.sin_port = htons(port); + + self->socket = socket(AF_INET, SOCK_STREAM, 0); + if (self->socket == -1) + { + self->errornumber = SOCKET_COULD_NOT_CREATE_SOCKET; + self->errormsg = "Could not create Socket."; + return 0; + } + + if (connect(self->socket, (struct sockaddr *) & (sadr), + sizeof(sadr)) < 0) + { + self->errornumber = SOCKET_COULD_NOT_CONNECT; + self->errormsg = "Could not connect to remote socket."; + closesocket(self->socket); + self->socket = (SOCKETFD) - 1; + return 0; + } + return 1; +} + + +void +SOCK_get_n_char(SocketClass *self, char *buffer, int len) +{ + int lf; + + if (!buffer) + { + self->errornumber = SOCKET_NULLPOINTER_PARAMETER; + self->errormsg = "get_n_char was called with NULL-Pointer"; + return; + } + + for (lf = 0; lf < len; lf++) + buffer[lf] = SOCK_get_next_byte(self); +} + + +void +SOCK_put_n_char(SocketClass *self, char *buffer, int len) +{ + int lf; + + if (!buffer) + { + self->errornumber = SOCKET_NULLPOINTER_PARAMETER; + self->errormsg = "put_n_char was called with NULL-Pointer"; + return; + } + + for (lf = 0; lf < len; lf++) + SOCK_put_next_byte(self, (unsigned char) buffer[lf]); +} + + +/* + * bufsize must include room for the null terminator + * will read at most bufsize-1 characters + null. + * returns TRUE if truncation occurs. + */ +BOOL +SOCK_get_string(SocketClass *self, char *buffer, int bufsize) +{ + register int lf = 0; + + for (lf = 0; lf < bufsize - 1; lf++) + if (!(buffer[lf] = SOCK_get_next_byte(self))) + return FALSE; + + buffer[bufsize - 1] = '\0'; + return TRUE; +} + + +void +SOCK_put_string(SocketClass *self, char *string) +{ + register int lf; + int len; + + len = strlen(string) + 1; + + for (lf = 0; lf < len; lf++) + SOCK_put_next_byte(self, (unsigned char) string[lf]); +} + + +int +SOCK_get_int(SocketClass *self, short len) +{ + switch (len) + { + case 2: + { + unsigned short buf; + + SOCK_get_n_char(self, (char *) &buf, len); + if (self->reverse) + return buf; + else + return ntohs(buf); + } + + case 4: + { + unsigned int buf; + + SOCK_get_n_char(self, (char *) &buf, len); + if (self->reverse) + return buf; + else + return ntohl(buf); + } + + default: + self->errornumber = SOCKET_GET_INT_WRONG_LENGTH; + self->errormsg = "Cannot read ints of that length"; + return 0; + } +} + + +void +SOCK_put_int(SocketClass *self, int value, short len) +{ + unsigned int rv; + + switch (len) + { + case 2: + rv = self->reverse ? value : htons((unsigned short) value); + SOCK_put_n_char(self, (char *) &rv, 2); + return; + + case 4: + rv = self->reverse ? value : htonl((unsigned int) value); + SOCK_put_n_char(self, (char *) &rv, 4); + return; + + default: + self->errornumber = SOCKET_PUT_INT_WRONG_LENGTH; + self->errormsg = "Cannot write ints of that length"; + return; + } +} + + +void +SOCK_flush_output(SocketClass *self) +{ + int written; + + written = send(self->socket, (char *) self->buffer_out, self->buffer_filled_out, 0); + if (written != self->buffer_filled_out) + { + self->errornumber = SOCKET_WRITE_ERROR; + self->errormsg = "Could not flush socket buffer."; + } + self->buffer_filled_out = 0; +} + + +unsigned char +SOCK_get_next_byte(SocketClass *self) +{ + if (self->buffer_read_in >= self->buffer_filled_in) + { + /* + * there are no more bytes left in the buffer so reload the buffer + */ + self->buffer_read_in = 0; + self->buffer_filled_in = recv(self->socket, (char *) self->buffer_in, self->buffer_size, 0); + + mylog("read %d, global_socket_buffersize=%d\n", self->buffer_filled_in, self->buffer_size); + + if (self->buffer_filled_in < 0) + { + self->errornumber = SOCKET_READ_ERROR; + self->errormsg = "Error while reading from the socket."; + self->buffer_filled_in = 0; + return 0; + } + if (self->buffer_filled_in == 0) + { + self->errornumber = SOCKET_CLOSED; + self->errormsg = "Socket has been closed."; + self->buffer_filled_in = 0; + return 0; + } + } + return self->buffer_in[self->buffer_read_in++]; +} + + +void +SOCK_put_next_byte(SocketClass *self, unsigned char next_byte) +{ + int bytes_sent; + + self->buffer_out[self->buffer_filled_out++] = next_byte; + + if (self->buffer_filled_out == self->buffer_size) + { + /* buffer is full, so write it out */ + bytes_sent = send(self->socket, (char *) self->buffer_out, self->buffer_size, 0); + if (bytes_sent != self->buffer_size) + { + self->errornumber = SOCKET_WRITE_ERROR; + self->errormsg = "Error while writing to the socket."; + } + self->buffer_filled_out = 0; + } +} diff --git a/src/interfaces/odbc/windev/socket.h b/src/interfaces/odbc/windev/socket.h new file mode 100644 index 0000000000..c49d9fe88d --- /dev/null +++ b/src/interfaces/odbc/windev/socket.h @@ -0,0 +1,94 @@ +/* File: socket.h + * + * Description: See "socket.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __SOCKET_H__ +#define __SOCKET_H__ + +#include "psqlodbc.h" + +#ifndef WIN32 +#include +#include +#include +#include +#include +#include + +#define closesocket(xxx) close(xxx) +#define SOCKETFD int + +#ifndef INADDR_NONE +#ifndef _IN_ADDR_T +#define _IN_ADDR_T +typedef unsigned int in_addr_t; +#endif +#define INADDR_NONE ((in_addr_t)-1) +#endif + +#else +#include +#define SOCKETFD SOCKET +#endif + +#define SOCKET_ALREADY_CONNECTED 1 +#define SOCKET_HOST_NOT_FOUND 2 +#define SOCKET_COULD_NOT_CREATE_SOCKET 3 +#define SOCKET_COULD_NOT_CONNECT 4 +#define SOCKET_READ_ERROR 5 +#define SOCKET_WRITE_ERROR 6 +#define SOCKET_NULLPOINTER_PARAMETER 7 +#define SOCKET_PUT_INT_WRONG_LENGTH 8 +#define SOCKET_GET_INT_WRONG_LENGTH 9 +#define SOCKET_CLOSED 10 + + +struct SocketClass_ +{ + + int buffer_size; + int buffer_filled_in; + int buffer_filled_out; + int buffer_read_in; + unsigned char *buffer_in; + unsigned char *buffer_out; + + SOCKETFD socket; + + char *errormsg; + int errornumber; + + char reverse; /* used to handle Postgres 6.2 protocol + * (reverse byte order) */ + +}; + +#define SOCK_get_char(self) (SOCK_get_next_byte(self)) +#define SOCK_put_char(self, c) (SOCK_put_next_byte(self, c)) + + +/* error functions */ +#define SOCK_get_errcode(self) (self->errornumber) +#define SOCK_get_errmsg(self) (self->errormsg) + + +/* Socket prototypes */ +SocketClass *SOCK_Constructor(const ConnectionClass *conn); +void SOCK_Destructor(SocketClass *self); +char SOCK_connect_to(SocketClass *self, unsigned short port, char *hostname); +void SOCK_get_n_char(SocketClass *self, char *buffer, int len); +void SOCK_put_n_char(SocketClass *self, char *buffer, int len); +BOOL SOCK_get_string(SocketClass *self, char *buffer, int bufsize); +void SOCK_put_string(SocketClass *self, char *string); +int SOCK_get_int(SocketClass *self, short len); +void SOCK_put_int(SocketClass *self, int value, short len); +void SOCK_flush_output(SocketClass *self); +unsigned char SOCK_get_next_byte(SocketClass *self); +void SOCK_put_next_byte(SocketClass *self, unsigned char next_byte); +void SOCK_clear_error(SocketClass *self); + +#endif diff --git a/src/interfaces/odbc/windev/statement.c b/src/interfaces/odbc/windev/statement.c new file mode 100644 index 0000000000..bfdb8a2fc0 --- /dev/null +++ b/src/interfaces/odbc/windev/statement.c @@ -0,0 +1,1161 @@ +/*------- + * Module: statement.c + * + * Description: This module contains functions related to creating + * and manipulating a statement. + * + * Classes: StatementClass (Functions prefix: "SC_") + * + * API functions: SQLAllocStmt, SQLFreeStmt + * + * Comments: See "notice.txt" for copyright and license information. + *------- + */ + +#include "statement.h" + +#include "bind.h" +#include "connection.h" +#include "qresult.h" +#include "convert.h" +#include "environ.h" + +#include +#include +#include + +#include "pgapifunc.h" + + +#define PRN_NULLCHECK + + +/* Map sql commands to statement types */ +static struct +{ + int type; + char *s; +} Statement_Type[] = + +{ + { + STMT_TYPE_SELECT, "SELECT" + }, + { + STMT_TYPE_INSERT, "INSERT" + }, + { + STMT_TYPE_UPDATE, "UPDATE" + }, + { + STMT_TYPE_DELETE, "DELETE" + }, + { + STMT_TYPE_CREATE, "CREATE" + }, + { + STMT_TYPE_ALTER, "ALTER" + }, + { + STMT_TYPE_DROP, "DROP" + }, + { + STMT_TYPE_GRANT, "GRANT" + }, + { + STMT_TYPE_REVOKE, "REVOKE" + }, + { + STMT_TYPE_PROCCALL, "{" + }, + { + 0, NULL + } +}; + + +RETCODE SQL_API +PGAPI_AllocStmt(HDBC hdbc, + HSTMT FAR * phstmt) +{ + static char *func = "PGAPI_AllocStmt"; + ConnectionClass *conn = (ConnectionClass *) hdbc; + StatementClass *stmt; + + mylog("%s: entering...\n", func); + + if (!conn) + { + CC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + stmt = SC_Constructor(); + + mylog("**** PGAPI_AllocStmt: hdbc = %u, stmt = %u\n", hdbc, stmt); + + if (!stmt) + { + conn->errornumber = CONN_STMT_ALLOC_ERROR; + conn->errormsg = "No more memory to allocate a further SQL-statement"; + *phstmt = SQL_NULL_HSTMT; + CC_log_error(func, "", conn); + return SQL_ERROR; + } + + if (!CC_add_statement(conn, stmt)) + { + conn->errormsg = "Maximum number of connections exceeded."; + conn->errornumber = CONN_STMT_ALLOC_ERROR; + CC_log_error(func, "", conn); + SC_Destructor(stmt); + *phstmt = SQL_NULL_HSTMT; + return SQL_ERROR; + } + + *phstmt = (HSTMT) stmt; + + /* Copy default statement options based from Connection options */ + stmt->options = conn->stmtOptions; + + stmt->stmt_size_limit = CC_get_max_query_len(conn); + /* Save the handle for later */ + stmt->phstmt = phstmt; + + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_FreeStmt(HSTMT hstmt, + UWORD fOption) +{ + static char *func = "PGAPI_FreeStmt"; + StatementClass *stmt = (StatementClass *) hstmt; + + mylog("%s: entering...hstmt=%u, fOption=%d\n", func, hstmt, fOption); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + SC_clear_error(stmt); + + if (fOption == SQL_DROP) + { + ConnectionClass *conn = stmt->hdbc; + + /* Remove the statement from the connection's statement list */ + if (conn) + { + if (!CC_remove_statement(conn, stmt)) + { + stmt->errornumber = STMT_SEQUENCE_ERROR; + stmt->errormsg = "Statement is currently executing a transaction."; + SC_log_error(func, "", stmt); + return SQL_ERROR; /* stmt may be executing a + * transaction */ + } + + /* Free any cursors and discard any result info */ + if (stmt->result) + { + QR_Destructor(stmt->result); + stmt->result = NULL; + } + } + + /* Destroy the statement and free any results, cursors, etc. */ + SC_Destructor(stmt); + } + else if (fOption == SQL_UNBIND) + SC_unbind_cols(stmt); + else if (fOption == SQL_CLOSE) + { + /* + * this should discard all the results, but leave the statement + * itself in place (it can be executed again) + */ + if (!SC_recycle_statement(stmt)) + { + /* errormsg passed in above */ + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + } + else if (fOption == SQL_RESET_PARAMS) + SC_free_params(stmt, STMT_FREE_PARAMS_ALL); + else + { + stmt->errormsg = "Invalid option passed to PGAPI_FreeStmt."; + stmt->errornumber = STMT_OPTION_OUT_OF_RANGE_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + return SQL_SUCCESS; +} + + +/* + * StatementClass implementation + */ +void +InitializeStatementOptions(StatementOptions *opt) +{ + memset(opt, 0, sizeof(StatementOptions)); + opt->maxRows = 0; /* driver returns all rows */ + opt->maxLength = 0; /* driver returns all data for char/binary */ + opt->rowset_size = 1; + opt->keyset_size = 0; /* fully keyset driven is the default */ + opt->scroll_concurrency = SQL_CONCUR_READ_ONLY; + opt->cursor_type = SQL_CURSOR_FORWARD_ONLY; + opt->bind_size = 0; /* default is to bind by column */ + opt->retrieve_data = SQL_RD_ON; + opt->use_bookmarks = SQL_UB_OFF; + opt->paramset_size = 1; + opt->param_bind_type = 0; /* default is column-wise binding */ +} + + +StatementClass * +SC_Constructor(void) +{ + StatementClass *rv; + + rv = (StatementClass *) malloc(sizeof(StatementClass)); + if (rv) + { + rv->hdbc = NULL; /* no connection associated yet */ + rv->phstmt = NULL; + rv->result = NULL; + rv->manual_result = FALSE; + rv->prepare = FALSE; + rv->status = STMT_ALLOCATED; + rv->internal = FALSE; + + rv->errormsg = NULL; + rv->errornumber = 0; + rv->errormsg_created = FALSE; + rv->errormsg_malloced = FALSE; + + rv->statement = NULL; + rv->stmt_with_params = NULL; + rv->stmt_size_limit = -1; + rv->statement_type = STMT_TYPE_UNKNOWN; + + rv->bindings = NULL; + rv->bindings_allocated = 0; + + rv->bookmark.buffer = NULL; + rv->bookmark.used = NULL; + + rv->parameters_allocated = 0; + rv->parameters = 0; + + rv->currTuple = -1; + rv->rowset_start = -1; + rv->current_col = -1; + rv->bind_row = 0; + rv->last_fetch_count = 0; + rv->save_rowset_size = -1; + + rv->data_at_exec = -1; + rv->current_exec_param = -1; + rv->put_data = FALSE; + + rv->lobj_fd = -1; + rv->cursor_name[0] = '\0'; + + /* Parse Stuff */ + rv->ti = NULL; + rv->fi = NULL; + rv->ntab = 0; + rv->nfld = 0; + rv->parse_status = STMT_PARSE_NONE; + + /* Clear Statement Options -- defaults will be set in AllocStmt */ + memset(&rv->options, 0, sizeof(StatementOptions)); + + rv->pre_executing = FALSE; + rv->inaccurate_result = FALSE; + rv->miscinfo = 0; + } + return rv; +} + + +char +SC_Destructor(StatementClass *self) +{ + mylog("SC_Destructor: self=%u, self->result=%u, self->hdbc=%u\n", self, self->result, self->hdbc); + SC_clear_error(self); + if (STMT_EXECUTING == self->status) + { + self->errornumber = STMT_SEQUENCE_ERROR; + self->errormsg = "Statement is currently executing a transaction."; + return FALSE; + } + + if (self->result) + { + if (!self->hdbc) + self->result->conn = NULL; /* prevent any dbase activity */ + + QR_Destructor(self->result); + } + + if (self->statement) + free(self->statement); + if (self->stmt_with_params) + { + free(self->stmt_with_params); + self->stmt_with_params = NULL; + } + + SC_free_params(self, STMT_FREE_PARAMS_ALL); + + /* + * the memory pointed to by the bindings is not deallocated by the + * driver but by the application that uses that driver, so we don't + * have to care + */ + /* about that here. */ + if (self->bindings) + { + int lf; + + for (lf = 0; lf < self->bindings_allocated; lf++) + { + if (self->bindings[lf].ttlbuf != NULL) + free(self->bindings[lf].ttlbuf); + } + free(self->bindings); + } + + /* Free the parsed table information */ + if (self->ti) + { + int i; + + for (i = 0; i < self->ntab; i++) + free(self->ti[i]); + + free(self->ti); + } + + /* Free the parsed field information */ + if (self->fi) + { + int i; + + for (i = 0; i < self->nfld; i++) + free(self->fi[i]); + free(self->fi); + } + + free(self); + + mylog("SC_Destructor: EXIT\n"); + + return TRUE; +} + + +/* + * Free parameters and free the memory from the + * data-at-execution parameters that was allocated in SQLPutData. + */ +void +SC_free_params(StatementClass *self, char option) +{ + int i; + + mylog("SC_free_params: ENTER, self=%d\n", self); + + if (!self->parameters) + return; + + for (i = 0; i < self->parameters_allocated; i++) + { + if (self->parameters[i].data_at_exec == TRUE) + { + if (self->parameters[i].EXEC_used) + { + free(self->parameters[i].EXEC_used); + self->parameters[i].EXEC_used = NULL; + } + + if (self->parameters[i].EXEC_buffer) + { + if (self->parameters[i].SQLType != SQL_LONGVARBINARY) + free(self->parameters[i].EXEC_buffer); + self->parameters[i].EXEC_buffer = NULL; + } + } + } + self->data_at_exec = -1; + self->current_exec_param = -1; + self->put_data = FALSE; + + if (option == STMT_FREE_PARAMS_ALL) + { + free(self->parameters); + self->parameters = NULL; + self->parameters_allocated = 0; + } + + mylog("SC_free_params: EXIT\n"); +} + + +int +statement_type(char *statement) +{ + int i; + + /* ignore leading whitespace in query string */ + while (*statement && isspace((unsigned char) *statement)) + statement++; + + for (i = 0; Statement_Type[i].s; i++) + if (!strnicmp(statement, Statement_Type[i].s, strlen(Statement_Type[i].s))) + return Statement_Type[i].type; + + return STMT_TYPE_OTHER; +} + + +/* + * Called from SQLPrepare if STMT_PREMATURE, or + * from SQLExecute if STMT_FINISHED, or + * from SQLFreeStmt(SQL_CLOSE) + */ +char +SC_recycle_statement(StatementClass *self) +{ + ConnectionClass *conn; + + mylog("recycle statement: self= %u\n", self); + + SC_clear_error(self); + /* This would not happen */ + if (self->status == STMT_EXECUTING) + { + self->errornumber = STMT_SEQUENCE_ERROR; + self->errormsg = "Statement is currently executing a transaction."; + return FALSE; + } + + switch (self->status) + { + case STMT_ALLOCATED: + /* this statement does not need to be recycled */ + return TRUE; + + case STMT_READY: + break; + + case STMT_PREMATURE: + + /* + * Premature execution of the statement might have caused the + * start of a transaction. If so, we have to rollback that + * transaction. + */ + conn = SC_get_conn(self); + if (!CC_is_in_autocommit(conn) && CC_is_in_trans(conn)) + { + if (SC_is_pre_executable(self) && !conn->connInfo.disallow_premature) + CC_abort(conn); + } + break; + + case STMT_FINISHED: + break; + + default: + self->errormsg = "An internal error occured while recycling statements"; + self->errornumber = STMT_INTERNAL_ERROR; + return FALSE; + } + + /* Free the parsed table information */ + if (self->ti) + { + int i; + + for (i = 0; i < self->ntab; i++) + free(self->ti[i]); + + free(self->ti); + self->ti = NULL; + self->ntab = 0; + } + + /* Free the parsed field information */ + if (self->fi) + { + int i; + + for (i = 0; i < self->nfld; i++) + free(self->fi[i]); + free(self->fi); + self->fi = NULL; + self->nfld = 0; + } + self->parse_status = STMT_PARSE_NONE; + + /* Free any cursors */ + if (self->result) + { + QR_Destructor(self->result); + self->result = NULL; + } + self->inaccurate_result = FALSE; + + /* + * Reset only parameters that have anything to do with results + */ + self->status = STMT_READY; + self->manual_result = FALSE; /* very important */ + + self->currTuple = -1; + self->rowset_start = -1; + self->current_col = -1; + self->bind_row = 0; + self->last_fetch_count = 0; + + if (self->errormsg_malloced && self->errormsg) + free(self->errormsg); + self->errormsg = NULL; + self->errornumber = 0; + self->errormsg_created = FALSE; + self->errormsg_malloced = FALSE; + + self->lobj_fd = -1; + + /* + * Free any data at exec params before the statement is executed + * again. If not, then there will be a memory leak when the next + * SQLParamData/SQLPutData is called. + */ + SC_free_params(self, STMT_FREE_PARAMS_DATA_AT_EXEC_ONLY); + + return TRUE; +} + + +/* Pre-execute a statement (SQLPrepare/SQLDescribeCol) */ +void +SC_pre_execute(StatementClass *self) +{ + mylog("SC_pre_execute: status = %d\n", self->status); + + if (self->status == STMT_READY) + { + mylog(" preprocess: status = READY\n"); + + self->miscinfo = 0; + if (self->statement_type == STMT_TYPE_SELECT) + { + char old_pre_executing = self->pre_executing; + + self->pre_executing = TRUE; + self->inaccurate_result = FALSE; + + PGAPI_Execute(self); + + self->pre_executing = old_pre_executing; + + if (self->status == STMT_FINISHED) + { + mylog(" preprocess: after status = FINISHED, so set PREMATURE\n"); + self->status = STMT_PREMATURE; + } + } + if (!SC_is_pre_executable(self)) + { + self->result = QR_Constructor(); + QR_set_status(self->result, PGRES_TUPLES_OK); + self->inaccurate_result = TRUE; + self->status = STMT_PREMATURE; + } + } +} + + +/* This is only called from SQLFreeStmt(SQL_UNBIND) */ +char +SC_unbind_cols(StatementClass *self) +{ + Int2 lf; + + for (lf = 0; lf < self->bindings_allocated; lf++) + { + self->bindings[lf].data_left = -1; + self->bindings[lf].buflen = 0; + self->bindings[lf].buffer = NULL; + self->bindings[lf].used = NULL; + self->bindings[lf].returntype = SQL_C_CHAR; + } + + self->bookmark.buffer = NULL; + self->bookmark.used = NULL; + + return 1; +} + + +void +SC_clear_error(StatementClass *self) +{ + if (self->errormsg_malloced && self->errormsg) + free(self->errormsg); + self->errornumber = 0; + self->errormsg = NULL; + self->errormsg_created = FALSE; + self->errormsg_malloced = FALSE; +} + + +/* + * This function creates an error msg which is the concatenation + * of the result, statement, connection, and socket messages. + */ +char * +SC_create_errormsg(StatementClass *self) +{ + QResultClass *res = self->result; + ConnectionClass *conn = self->hdbc; + int pos; + static char msg[4096]; + + msg[0] = '\0'; + + if (res && res->message) + strcpy(msg, res->message); + + else if (self->errormsg) + strcpy(msg, self->errormsg); + + if (conn) + { + SocketClass *sock = conn->sock; + + if (conn->errormsg && conn->errormsg[0] != '\0') + { + pos = strlen(msg); + sprintf(&msg[pos], ";\n%s", conn->errormsg); + } + + if (sock && sock->errormsg && sock->errormsg[0] != '\0') + { + pos = strlen(msg); + sprintf(&msg[pos], ";\n%s", sock->errormsg); + } + } + if (!msg[0] && res && QR_get_notice(res)) + return QR_get_notice(res); + + return msg; +} + + +char +SC_get_error(StatementClass *self, int *number, char **message) +{ + char rv; + + /* Create a very informative errormsg if it hasn't been done yet. */ + if (!self->errormsg_created) + { + self->errormsg = SC_create_errormsg(self); + self->errormsg_created = TRUE; + } + + if (self->errornumber) + { + *number = self->errornumber; + *message = self->errormsg; + if (!self->errormsg_malloced) + self->errormsg = NULL; + } + + rv = (self->errornumber != 0); + self->errornumber = 0; + + return rv; +} + + +/* + * Currently, the driver offers very simple bookmark support -- it is + * just the current row number. But it could be more sophisticated + * someday, such as mapping a key to a 32 bit value + */ +unsigned long +SC_get_bookmark(StatementClass *self) +{ + return (self->currTuple + 1); +} + + +RETCODE +SC_fetch(StatementClass *self) +{ + static char *func = "SC_fetch"; + QResultClass *res = self->result; + int retval, + result; + +#ifdef DRIVER_CURSOR_IMPLEMENT + int updret; +#endif /* DRIVER_CURSOR_IMPLEMENT */ + Int2 num_cols, + lf; + Oid type; + char *value; + ColumnInfoClass *coli; + + /* TupleField *tupleField; */ + ConnInfo *ci = &(SC_get_conn(self)->connInfo); + + self->last_fetch_count = 0; + coli = QR_get_fields(res); /* the column info */ + + mylog("manual_result = %d, use_declarefetch = %d\n", self->manual_result, ci->drivers.use_declarefetch); + + if (self->manual_result || !SC_is_fetchcursor(self)) + { + if (self->currTuple >= QR_get_num_tuples(res) - 1 || + (self->options.maxRows > 0 && self->currTuple == self->options.maxRows - 1)) + { + /* + * if at the end of the tuples, return "no data found" and set + * the cursor past the end of the result set + */ + self->currTuple = QR_get_num_tuples(res); + return SQL_NO_DATA_FOUND; + } + + mylog("**** SC_fetch: manual_result\n"); + (self->currTuple)++; + } + else + { + /* read from the cache or the physical next tuple */ + retval = QR_next_tuple(res); + if (retval < 0) + { + mylog("**** SC_fetch: end_tuples\n"); + return SQL_NO_DATA_FOUND; + } + else if (retval > 0) + (self->currTuple)++; /* all is well */ + else + { + mylog("SC_fetch: error\n"); + self->errornumber = STMT_EXEC_ERROR; + self->errormsg = "Error fetching next row"; + SC_log_error(func, "", self); + return SQL_ERROR; + } + } + + num_cols = QR_NumResultCols(res); + + result = SQL_SUCCESS; + self->last_fetch_count = 1; + + /* + * If the bookmark column was bound then return a bookmark. Since this + * is used with SQLExtendedFetch, and the rowset size may be greater + * than 1, and an application can use row or column wise binding, use + * the code in copy_and_convert_field() to handle that. + */ + if (self->bookmark.buffer) + { + char buf[32]; + + sprintf(buf, "%ld", SC_get_bookmark(self)); + result = copy_and_convert_field(self, 0, buf, + SQL_C_ULONG, self->bookmark.buffer, 0, self->bookmark.used); + } + +#ifdef DRIVER_CURSOR_IMPLEMENT + updret = 0; + if (self->options.scroll_concurrency != SQL_CONCUR_READ_ONLY) + { + if (!QR_get_value_backend_row(res, self->currTuple, num_cols - 1)) + updret = SQL_ROW_DELETED; + num_cols -= 2; + } +#endif /* DRIVER_CURSOR_IMPLEMENT */ + if (self->options.retrieve_data == SQL_RD_OFF) /* data isn't required */ +#ifdef DRIVER_CURSOR_IMPLEMENT + return updret ? updret + 10 : SQL_SUCCESS; +#else + return SQL_SUCCESS; +#endif /* DRIVER_CURSOR_IMPLEMENT */ + for (lf = 0; lf < num_cols; lf++) + { + mylog("fetch: cols=%d, lf=%d, self = %u, self->bindings = %u, buffer[] = %u\n", num_cols, lf, self, self->bindings, self->bindings[lf].buffer); + + /* reset for SQLGetData */ + self->bindings[lf].data_left = -1; + + if (self->bindings[lf].buffer != NULL) + { + /* this column has a binding */ + + /* type = QR_get_field_type(res, lf); */ + type = CI_get_oid(coli, lf); /* speed things up */ + + mylog("type = %d\n", type); + + if (self->manual_result) + { + value = QR_get_value_manual(res, self->currTuple, lf); + mylog("manual_result\n"); + } + else if (SC_is_fetchcursor(self)) + value = QR_get_value_backend(res, lf); + else + value = QR_get_value_backend_row(res, self->currTuple, lf); + + mylog("value = '%s'\n", (value == NULL) ? "" : value); + + retval = copy_and_convert_field_bindinfo(self, type, value, lf); + + mylog("copy_and_convert: retval = %d\n", retval); + + switch (retval) + { + case COPY_OK: + break; /* OK, do next bound column */ + + case COPY_UNSUPPORTED_TYPE: + self->errormsg = "Received an unsupported type from Postgres."; + self->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR; + SC_log_error(func, "", self); + result = SQL_ERROR; + break; + + case COPY_UNSUPPORTED_CONVERSION: + self->errormsg = "Couldn't handle the necessary data type conversion."; + self->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR; + SC_log_error(func, "", self); + result = SQL_ERROR; + break; + + case COPY_RESULT_TRUNCATED: + self->errornumber = STMT_TRUNCATED; + self->errormsg = "Fetched item was truncated."; + qlog("The %dth item was truncated\n", lf + 1); + qlog("The buffer size = %d", self->bindings[lf].buflen); + qlog(" and the value is '%s'\n", value); + result = SQL_SUCCESS_WITH_INFO; + break; + + /* error msg already filled in */ + case COPY_GENERAL_ERROR: + SC_log_error(func, "", self); + result = SQL_ERROR; + break; + + /* This would not be meaningful in SQLFetch. */ + case COPY_NO_DATA_FOUND: + break; + + default: + self->errormsg = "Unrecognized return value from copy_and_convert_field."; + self->errornumber = STMT_INTERNAL_ERROR; + SC_log_error(func, "", self); + result = SQL_ERROR; + break; + } + } + } + +#ifdef DRIVER_CURSOR_IMPLEMENT + if (updret) + result = updret + 10; +#endif /* DRIVER_CURSOR_IMPLEMENT */ + return result; +} + + +RETCODE +SC_execute(StatementClass *self) +{ + static char *func = "SC_execute"; + ConnectionClass *conn; + QResultClass *res; + char ok, + was_ok, + was_nonfatal; + Int2 oldstatus, + numcols; + QueryInfo qi; + ConnInfo *ci; + + + conn = SC_get_conn(self); + ci = &(conn->connInfo); + + /* Begin a transaction if one is not already in progress */ + + /* + * Basically we don't have to begin a transaction in autocommit mode + * because Postgres backend runs in autocomit mode. We issue "BEGIN" + * in the following cases. 1) we use declare/fetch and the statement + * is SELECT (because declare/fetch must be called in a transaction). + * 2) we are in autocommit off state and the statement isn't of type + * OTHER. + */ + if (!self->internal && !CC_is_in_trans(conn) && + (SC_is_fetchcursor(self) || + (!CC_is_in_autocommit(conn) && self->statement_type != STMT_TYPE_OTHER))) + { + mylog(" about to begin a transaction on statement = %u\n", self); + res = CC_send_query(conn, "BEGIN", NULL); + if (QR_aborted(res)) + { + self->errormsg = "Could not begin a transaction"; + self->errornumber = STMT_EXEC_ERROR; + SC_log_error(func, "", self); + return SQL_ERROR; + } + + ok = QR_command_successful(res); + + mylog("SC_exec: begin ok = %d, status = %d\n", ok, QR_get_status(res)); + + QR_Destructor(res); + + if (!ok) + { + self->errormsg = "Could not begin a transaction"; + self->errornumber = STMT_EXEC_ERROR; + SC_log_error(func, "", self); + return SQL_ERROR; + } + else + CC_set_in_trans(conn); + } + + oldstatus = conn->status; + conn->status = CONN_EXECUTING; + self->status = STMT_EXECUTING; + + /* If it's a SELECT statement, use a cursor. */ + + /* + * Note that the declare cursor has already been prepended to the + * statement + */ + /* in copy_statement... */ + if (self->statement_type == STMT_TYPE_SELECT) + { + char fetch[128]; + + mylog(" Sending SELECT statement on stmt=%u, cursor_name='%s'\n", self, self->cursor_name); + + /* send the declare/select */ + self->result = CC_send_query(conn, self->stmt_with_params, NULL); + + if (SC_is_fetchcursor(self) && self->result != NULL && + QR_command_successful(self->result)) + { + QR_Destructor(self->result); + + /* + * That worked, so now send the fetch to start getting data + * back + */ + qi.result_in = NULL; + qi.cursor = self->cursor_name; + qi.row_size = ci->drivers.fetch_max; + + /* + * Most likely the rowset size will not be set by the + * application until after the statement is executed, so might + * as well use the cache size. The qr_next_tuple() function + * will correct for any discrepancies in sizes and adjust the + * cache accordingly. + */ + sprintf(fetch, "fetch %d in %s", qi.row_size, self->cursor_name); + + self->result = CC_send_query(conn, fetch, &qi); + } + mylog(" done sending the query:\n"); + } + else + { + /* not a SELECT statement so don't use a cursor */ + mylog(" it's NOT a select statement: stmt=%u\n", self); + self->result = CC_send_query(conn, self->stmt_with_params, NULL); + + /* + * We shouldn't send COMMIT. Postgres backend does the autocommit + * if neccessary. (Zoltan, 04/26/2000) + */ + + /* + * Above seems wrong. Even in case of autocommit, started + * transactions must be committed. (Hiroshi, 02/11/2001) + */ + if (!self->internal && CC_is_in_autocommit(conn) && CC_is_in_trans(conn)) + { + res = CC_send_query(conn, "COMMIT", NULL); + QR_Destructor(res); + CC_set_no_trans(conn); + } + } + + conn->status = oldstatus; + self->status = STMT_FINISHED; + + /* Check the status of the result */ + if (self->result) + { + was_ok = QR_command_successful(self->result); + was_nonfatal = QR_command_nonfatal(self->result); + + if (was_ok) + self->errornumber = STMT_OK; + else + self->errornumber = was_nonfatal ? STMT_INFO_ONLY : STMT_ERROR_TAKEN_FROM_BACKEND; + + /* set cursor before the first tuple in the list */ + self->currTuple = -1; + self->current_col = -1; + self->rowset_start = -1; + + /* see if the query did return any result columns */ + numcols = QR_NumResultCols(self->result); + + /* now allocate the array to hold the binding info */ + if (numcols > 0) + { + extend_bindings(self, numcols); + if (self->bindings == NULL) + { + self->errornumber = STMT_NO_MEMORY_ERROR; + self->errormsg = "Could not get enough free memory to store the binding information"; + SC_log_error(func, "", self); + return SQL_ERROR; + } + } + /* issue "ABORT" when query aborted */ + if (QR_get_aborted(self->result) && !self->internal) + CC_abort(conn); + } + else + { + /* Bad Error -- The error message will be in the Connection */ + if (self->statement_type == STMT_TYPE_CREATE) + { + self->errornumber = STMT_CREATE_TABLE_ERROR; + self->errormsg = "Error creating the table"; + + /* + * This would allow the table to already exists, thus + * appending rows to it. BUT, if the table didn't have the + * same attributes, it would fail. return + * SQL_SUCCESS_WITH_INFO; + */ + } + else + { + self->errornumber = STMT_EXEC_ERROR; + self->errormsg = "Error while executing the query"; + } + + if (!self->internal) + CC_abort(conn); + } + + if (self->statement_type == STMT_TYPE_PROCCALL && + (self->errornumber == STMT_OK || + self->errornumber == STMT_INFO_ONLY) && + self->parameters && + self->parameters[0].buffer && + self->parameters[0].paramType == SQL_PARAM_OUTPUT) + { /* get the return value of the procedure + * call */ + RETCODE ret; + HSTMT hstmt = (HSTMT) self; + + ret = SC_fetch(hstmt); + if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) + { + ret = PGAPI_GetData(hstmt, 1, self->parameters[0].CType, self->parameters[0].buffer, self->parameters[0].buflen, self->parameters[0].used); + if (ret != SQL_SUCCESS) + { + self->errornumber = STMT_EXEC_ERROR; + self->errormsg = "GetData to Procedure return failed."; + } + } + else + { + self->errornumber = STMT_EXEC_ERROR; + self->errormsg = "SC_fetch to get a Procedure return failed."; + } + } + if (self->errornumber == STMT_OK) + return SQL_SUCCESS; + else if (self->errornumber == STMT_INFO_ONLY) + return SQL_SUCCESS_WITH_INFO; + else + { + self->errormsg = "Error while executing the query"; + SC_log_error(func, "", self); + return SQL_ERROR; + } +} + + +void +SC_log_error(char *func, char *desc, StatementClass *self) +{ +#ifdef PRN_NULLCHECK +#define nullcheck(a) (a ? a : "(NULL)") +#endif + if (self) + { + qlog("STATEMENT ERROR: func=%s, desc='%s', errnum=%d, errmsg='%s'\n", func, desc, self->errornumber, nullcheck(self->errormsg)); + mylog("STATEMENT ERROR: func=%s, desc='%s', errnum=%d, errmsg='%s'\n", func, desc, self->errornumber, nullcheck(self->errormsg)); + qlog(" ------------------------------------------------------------\n"); + qlog(" hdbc=%u, stmt=%u, result=%u\n", self->hdbc, self, self->result); + qlog(" manual_result=%d, prepare=%d, internal=%d\n", self->manual_result, self->prepare, self->internal); + qlog(" bindings=%u, bindings_allocated=%d\n", self->bindings, self->bindings_allocated); + qlog(" parameters=%u, parameters_allocated=%d\n", self->parameters, self->parameters_allocated); + qlog(" statement_type=%d, statement='%s'\n", self->statement_type, nullcheck(self->statement)); + qlog(" stmt_with_params='%s'\n", nullcheck(self->stmt_with_params)); + qlog(" data_at_exec=%d, current_exec_param=%d, put_data=%d\n", self->data_at_exec, self->current_exec_param, self->put_data); + qlog(" currTuple=%d, current_col=%d, lobj_fd=%d\n", self->currTuple, self->current_col, self->lobj_fd); + qlog(" maxRows=%d, rowset_size=%d, keyset_size=%d, cursor_type=%d, scroll_concurrency=%d\n", self->options.maxRows, self->options.rowset_size, self->options.keyset_size, self->options.cursor_type, self->options.scroll_concurrency); + qlog(" cursor_name='%s'\n", nullcheck(self->cursor_name)); + + qlog(" ----------------QResult Info -------------------------------\n"); + + if (self->result) + { + QResultClass *res = self->result; + + qlog(" fields=%u, manual_tuples=%u, backend_tuples=%u, tupleField=%d, conn=%u\n", res->fields, res->manual_tuples, res->backend_tuples, res->tupleField, res->conn); + qlog(" fetch_count=%d, fcount=%d, num_fields=%d, cursor='%s'\n", res->fetch_count, res->fcount, res->num_fields, nullcheck(res->cursor)); + qlog(" message='%s', command='%s', notice='%s'\n", nullcheck(res->message), nullcheck(res->command), nullcheck(res->notice)); + qlog(" status=%d, inTuples=%d\n", res->status, res->inTuples); + } + + /* Log the connection error if there is one */ + CC_log_error(func, desc, self->hdbc); + } + else + qlog("INVALID STATEMENT HANDLE ERROR: func=%s, desc='%s'\n", func, desc); +#undef PRN_NULLCHECK +} diff --git a/src/interfaces/odbc/windev/statement.h b/src/interfaces/odbc/windev/statement.h new file mode 100644 index 0000000000..9b52f25694 --- /dev/null +++ b/src/interfaces/odbc/windev/statement.h @@ -0,0 +1,254 @@ +/* File: statement.h + * + * Description: See "statement.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __STATEMENT_H__ +#define __STATEMENT_H__ + +#include "psqlodbc.h" + +#include "bind.h" + + +#ifndef FALSE +#define FALSE (BOOL)0 +#endif +#ifndef TRUE +#define TRUE (BOOL)1 +#endif + +typedef enum +{ + STMT_ALLOCATED, /* The statement handle is allocated, but + * not used so far */ + STMT_READY, /* the statement is waiting to be executed */ + STMT_PREMATURE, /* ODBC states that it is legal to call + * e.g. SQLDescribeCol before a call to + * SQLExecute, but after SQLPrepare. To + * get all the necessary information in + * such a case, we simply execute the + * query _before_ the actual call to + * SQLExecute, so that statement is + * considered to be "premature". */ + STMT_FINISHED, /* statement execution has finished */ + STMT_EXECUTING /* statement execution is still going on */ +} STMT_Status; + +#define STMT_ROW_VERSION_CHANGED (-4) +#define STMT_POS_BEFORE_RECORDSET (-3) +#define STMT_TRUNCATED (-2) +#define STMT_INFO_ONLY (-1) /* not an error message, + * just a notification + * to be returned by + * SQLError */ +#define STMT_OK 0 /* will be interpreted + * as "no error pending" */ +#define STMT_EXEC_ERROR 1 +#define STMT_STATUS_ERROR 2 +#define STMT_SEQUENCE_ERROR 3 +#define STMT_NO_MEMORY_ERROR 4 +#define STMT_COLNUM_ERROR 5 +#define STMT_NO_STMTSTRING 6 +#define STMT_ERROR_TAKEN_FROM_BACKEND 7 +#define STMT_INTERNAL_ERROR 8 +#define STMT_STILL_EXECUTING 9 +#define STMT_NOT_IMPLEMENTED_ERROR 10 +#define STMT_BAD_PARAMETER_NUMBER_ERROR 11 +#define STMT_OPTION_OUT_OF_RANGE_ERROR 12 +#define STMT_INVALID_COLUMN_NUMBER_ERROR 13 +#define STMT_RESTRICTED_DATA_TYPE_ERROR 14 +#define STMT_INVALID_CURSOR_STATE_ERROR 15 +#define STMT_OPTION_VALUE_CHANGED 16 +#define STMT_CREATE_TABLE_ERROR 17 +#define STMT_NO_CURSOR_NAME 18 +#define STMT_INVALID_CURSOR_NAME 19 +#define STMT_INVALID_ARGUMENT_NO 20 +#define STMT_ROW_OUT_OF_RANGE 21 +#define STMT_OPERATION_CANCELLED 22 +#define STMT_INVALID_CURSOR_POSITION 23 +#define STMT_VALUE_OUT_OF_RANGE 24 +#define STMT_OPERATION_INVALID 25 +#define STMT_PROGRAM_TYPE_OUT_OF_RANGE 26 +#define STMT_BAD_ERROR 27 +#define STMT_INVALID_OPTION_IDENTIFIER 28 + +/* statement types */ +enum +{ + STMT_TYPE_UNKNOWN = -2, + STMT_TYPE_OTHER = -1, + STMT_TYPE_SELECT = 0, + STMT_TYPE_INSERT, + STMT_TYPE_UPDATE, + STMT_TYPE_DELETE, + STMT_TYPE_CREATE, + STMT_TYPE_ALTER, + STMT_TYPE_DROP, + STMT_TYPE_GRANT, + STMT_TYPE_REVOKE, + STMT_TYPE_PROCCALL +}; + +#define STMT_UPDATE(stmt) (stmt->statement_type > STMT_TYPE_SELECT) + + +/* Parsing status */ +enum +{ + STMT_PARSE_NONE = 0, + STMT_PARSE_COMPLETE, + STMT_PARSE_INCOMPLETE, + STMT_PARSE_FATAL, +}; + +/* Result style */ +enum +{ + STMT_FETCH_NONE = 0, + STMT_FETCH_NORMAL, + STMT_FETCH_EXTENDED, +}; + +typedef struct +{ + COL_INFO *col_info; /* cached SQLColumns info for this table */ + char name[MAX_TABLE_LEN + 1]; + char alias[MAX_TABLE_LEN + 1]; +} TABLE_INFO; + +typedef struct +{ + TABLE_INFO *ti; /* resolve to explicit table names */ + int precision; + int scale; + int display_size; + int length; + int type; + char nullable; + char func; + char expr; + char quote; + char dquote; + char numeric; + char dot[MAX_TABLE_LEN + 1]; + char name[MAX_COLUMN_LEN + 1]; + char alias[MAX_COLUMN_LEN + 1]; +} FIELD_INFO; + + +/******** Statement Handle ***********/ +struct StatementClass_ +{ + ConnectionClass *hdbc; /* pointer to ConnectionClass this + * statement belongs to */ + QResultClass *result; /* result of the current statement */ + HSTMT FAR *phstmt; + StatementOptions options; + + STMT_Status status; + char *errormsg; + int errornumber; + + /* information on bindings */ + BindInfoClass *bindings; /* array to store the binding information */ + BindInfoClass bookmark; + int bindings_allocated; + + /* information on statement parameters */ + int parameters_allocated; + ParameterInfoClass *parameters; + + Int4 currTuple; /* current absolute row number (GetData, + * SetPos, SQLFetch) */ + int save_rowset_size; /* saved rowset size in case of + * change/FETCH_NEXT */ + int rowset_start; /* start of rowset (an absolute row + * number) */ + int bind_row; /* current offset for Multiple row/column + * binding */ + int last_fetch_count; /* number of rows retrieved in + * last fetch/extended fetch */ + int current_col; /* current column for GetData -- used to + * handle multiple calls */ + int lobj_fd; /* fd of the current large object */ + + char *statement; /* if non--null pointer to the SQL + * statement that has been executed */ + + TABLE_INFO **ti; + FIELD_INFO **fi; + int nfld; + int ntab; + + int parse_status; + + int statement_type; /* According to the defines above */ + int data_at_exec; /* Number of params needing SQLPutData */ + int current_exec_param; /* The current parameter for + * SQLPutData */ + + char put_data; /* Has SQLPutData been called yet? */ + + char errormsg_created; /* has an informative error msg + * been created? */ + char manual_result; /* Is the statement result manually built? */ + char prepare; /* is this statement a prepared statement + * or direct */ + + char internal; /* Is this statement being called + * internally? */ + + char cursor_name[MAX_CURSOR_LEN + 1]; + + char *stmt_with_params; /* statement after parameter + * substitution */ + int stmt_size_limit; + + char pre_executing; /* This statement is prematurely executing */ + char inaccurate_result; /* Current status is PREMATURE but + * result is inaccurate */ + char errormsg_malloced; /* Current error message is + * malloed (not in a static + * variable) ? */ + char miscinfo; +}; + +#define SC_get_conn(a) (a->hdbc) +#define SC_get_Result(a) (a->result); + +/* options for SC_free_params() */ +#define STMT_FREE_PARAMS_ALL 0 +#define STMT_FREE_PARAMS_DATA_AT_EXEC_ONLY 1 + +/* misc info */ +#define SC_set_pre_executable(a) (a->miscinfo |= 1L) +#define SC_no_pre_executable(a) (a->miscinfo &= ~1L) +#define SC_is_pre_executable(a) ((a->miscinfo & 1L) != 0) +#define SC_set_fetchcursor(a) (a->miscinfo |= 2L) +#define SC_no_fetchcursor(a) (a->miscinfo &= ~2L) +#define SC_is_fetchcursor(a) ((a->miscinfo & 2L) != 0) + +/* Statement prototypes */ +StatementClass *SC_Constructor(void); +void InitializeStatementOptions(StatementOptions *opt); +char SC_Destructor(StatementClass *self); +int statement_type(char *statement); +char parse_statement(StatementClass *stmt); +void SC_pre_execute(StatementClass *self); +char SC_unbind_cols(StatementClass *self); +char SC_recycle_statement(StatementClass *self); + +void SC_clear_error(StatementClass *self); +char SC_get_error(StatementClass *self, int *number, char **message); +char *SC_create_errormsg(StatementClass *self); +RETCODE SC_execute(StatementClass *self); +RETCODE SC_fetch(StatementClass *self); +void SC_free_params(StatementClass *self, char option); +void SC_log_error(char *func, char *desc, StatementClass *self); +unsigned long SC_get_bookmark(StatementClass *self); + +#endif diff --git a/src/interfaces/odbc/windev/tuple.c b/src/interfaces/odbc/windev/tuple.c new file mode 100644 index 0000000000..512f36d2b2 --- /dev/null +++ b/src/interfaces/odbc/windev/tuple.c @@ -0,0 +1,66 @@ +/*------- + * Module: tuple.c + * + * Description: This module contains functions for setting the data + * for individual fields (TupleField structure) of a + * manual result set. + * + * Important Note: These functions are ONLY used in building manual + * result sets for info functions (SQLTables, + * SQLColumns, etc.) + * + * Classes: n/a + * + * API functions: none + * + * Comments: See "notice.txt" for copyright and license information. + *------- + */ + +#include "tuple.h" + +#include +#include + + +void +set_tuplefield_null(TupleField *tuple_field) +{ + tuple_field->len = 0; + tuple_field->value = NULL; /* strdup(""); */ +} + + +void +set_tuplefield_string(TupleField *tuple_field, char *string) +{ + tuple_field->len = strlen(string); + tuple_field->value = malloc(strlen(string) + 1); + strcpy(tuple_field->value, string); +} + + +void +set_tuplefield_int2(TupleField *tuple_field, Int2 value) +{ + char buffer[10]; + + sprintf(buffer, "%d", value); + + tuple_field->len = strlen(buffer) + 1; + /* +1 ... is this correct (better be on the save side-...) */ + tuple_field->value = strdup(buffer); +} + + +void +set_tuplefield_int4(TupleField *tuple_field, Int4 value) +{ + char buffer[15]; + + sprintf(buffer, "%ld", value); + + tuple_field->len = strlen(buffer) + 1; + /* +1 ... is this correct (better be on the save side-...) */ + tuple_field->value = strdup(buffer); +} diff --git a/src/interfaces/odbc/windev/tuple.h b/src/interfaces/odbc/windev/tuple.h new file mode 100644 index 0000000000..fdc1a5f9ea --- /dev/null +++ b/src/interfaces/odbc/windev/tuple.h @@ -0,0 +1,46 @@ +/* File: tuple.h + * + * Description: See "tuple.c" + * + * Important NOTE: The TupleField structure is used both to hold backend data and + * manual result set data. The "set_" functions and the TupleNode + * structure are only used for manual result sets by info routines. + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __TUPLE_H__ +#define __TUPLE_H__ + +#include "psqlodbc.h" + +/* Used by backend data AND manual result sets */ +struct TupleField_ +{ + Int4 len; /* length of the current Tuple */ + void *value; /* an array representing the value */ +}; + +/* Used ONLY for manual result sets */ +struct TupleNode_ +{ + struct TupleNode_ *prev, + *next; + TupleField tuple[1]; +}; + +/* These macros are wrappers for the corresponding set_tuplefield functions + but these handle automatic NULL determination and call set_tuplefield_null() + if appropriate for the datatype (used by SQLGetTypeInfo). +*/ +#define set_nullfield_string(FLD, VAL) ((VAL) ? set_tuplefield_string(FLD, (VAL)) : set_tuplefield_null(FLD)) +#define set_nullfield_int2(FLD, VAL) ((VAL) != -1 ? set_tuplefield_int2(FLD, (VAL)) : set_tuplefield_null(FLD)) +#define set_nullfield_int4(FLD, VAL) ((VAL) != -1 ? set_tuplefield_int4(FLD, (VAL)) : set_tuplefield_null(FLD)) + +void set_tuplefield_null(TupleField *tuple_field); +void set_tuplefield_string(TupleField *tuple_field, char *string); +void set_tuplefield_int2(TupleField *tuple_field, Int2 value); +void set_tuplefield_int4(TupleField *tuple_field, Int4 value); + +#endif diff --git a/src/interfaces/odbc/windev/tuplelist.c b/src/interfaces/odbc/windev/tuplelist.c new file mode 100644 index 0000000000..0ae2130bff --- /dev/null +++ b/src/interfaces/odbc/windev/tuplelist.c @@ -0,0 +1,216 @@ +/*-------- + * Module: tuplelist.c + * + * Description: This module contains functions for creating a manual + * result set (the TupleList) and retrieving data from + * it for a specific row/column. + * + * Classes: TupleListClass (Functions prefix: "TL_") + * + * API functions: none + * + * Comments: See "notice.txt" for copyright and license information. + *-------- + */ + +#include "tuplelist.h" + +#include +#include "tuple.h" + + +TupleListClass * +TL_Constructor(UInt4 fieldcnt) +{ + TupleListClass *rv; + + mylog("in TL_Constructor\n"); + + rv = (TupleListClass *) malloc(sizeof(TupleListClass)); + if (rv) + { + rv->num_fields = fieldcnt; + rv->num_tuples = 0; + rv->list_start = NULL; + rv->list_end = NULL; + rv->lastref = NULL; + rv->last_indexed = -1; + } + + mylog("exit TL_Constructor\n"); + + return rv; +} + + +void +TL_Destructor(TupleListClass *self) +{ + int lf; + TupleNode *node, + *tp; + + mylog("TupleList: in DESTRUCTOR\n"); + + node = self->list_start; + while (node != NULL) + { + for (lf = 0; lf < self->num_fields; lf++) + if (node->tuple[lf].value != NULL) + free(node->tuple[lf].value); + tp = node->next; + free(node); + node = tp; + } + + free(self); + + mylog("TupleList: exit DESTRUCTOR\n"); +} + + +void * +TL_get_fieldval(TupleListClass *self, Int4 tupleno, Int2 fieldno) +{ + Int4 lf; + Int4 delta, + from_end; + char end_is_closer, + start_is_closer; + TupleNode *rv; + + if (self->last_indexed == -1) + /* we have an empty tuple list */ + return NULL; + + /* some more sanity checks */ + if ((tupleno >= self->num_tuples) || (tupleno < 0)) + /* illegal tuple number range */ + return NULL; + + if ((fieldno >= self->num_fields) || (fieldno < 0)) + /* illegel field number range */ + return NULL; + + /* + * check if we are accessing the same tuple that was used in the last + * fetch (e.g: for fetching all the fields one after another. Do this + * to speed things up + */ + if (tupleno == self->last_indexed) + return self->lastref->tuple[fieldno].value; + + /* now for the tricky part... */ + + /* + * Since random access is quite inefficient for linked lists we use + * the lastref pointer that points to the last element referenced by a + * get_fieldval() call in conjunction with the its index number that + * is stored in last_indexed. (So we use some locality of reference + * principle to speed things up) + */ + + delta = tupleno - self->last_indexed; + /* if delta is positive, we have to go forward */ + + /* + * now check if we are closer to the start or the end of the list than + * to our last_indexed pointer + */ + from_end = (self->num_tuples - 1) - tupleno; + + start_is_closer = labs(delta) > tupleno; + + /* + * true if we are closer to the start of the list than to the + * last_indexed pointer + */ + + end_is_closer = labs(delta) > from_end; + /* true if we are closer at the end of the list */ + + if (end_is_closer) + { + /* scanning from the end is the shortest way. so we do that... */ + rv = self->list_end; + for (lf = 0; lf < from_end; lf++) + rv = rv->prev; + } + else if (start_is_closer) + { + /* + * the shortest way is to start the search from the head of the + * list + */ + rv = self->list_start; + for (lf = 0; lf < tupleno; lf++) + rv = rv->next; + } + else + { + /* the closest way is starting from our lastref - pointer */ + rv = self->lastref; + + /* + * at first determine whether we have to search forward or + * backwards + */ + if (delta < 0) + { + /* we have to search backwards */ + for (lf = 0; lf < (-1) * delta; lf++) + rv = rv->prev; + } + else + { + /* ok, we have to search forward... */ + for (lf = 0; lf < delta; lf++) + rv = rv->next; + } + } + + /* + * now we have got our return pointer, so update the lastref and the + * last_indexed values + */ + self->lastref = rv; + self->last_indexed = tupleno; + + return rv->tuple[fieldno].value; +} + + +char +TL_add_tuple(TupleListClass *self, TupleNode *new_field) +{ + /* + * we append the tuple at the end of the doubly linked list of the + * tuples we have already read in + */ + + new_field->prev = NULL; + new_field->next = NULL; + + if (self->list_start == NULL) + { + /* the list is empty, we have to add the first tuple */ + self->list_start = new_field; + self->list_end = new_field; + self->lastref = new_field; + self->last_indexed = 0; + } + else + { + /* + * there is already an element in the list, so add the new one at + * the end of the list + */ + self->list_end->next = new_field; + new_field->prev = self->list_end; + self->list_end = new_field; + } + self->num_tuples++; + + /* this method of building a list cannot fail, so we return 1 */ + return 1; +} diff --git a/src/interfaces/odbc/windev/tuplelist.h b/src/interfaces/odbc/windev/tuplelist.h new file mode 100644 index 0000000000..3dc98dd78f --- /dev/null +++ b/src/interfaces/odbc/windev/tuplelist.h @@ -0,0 +1,35 @@ +/* File: tuplelist.h + * + * Description: See "tuplelist.c" + * + * Important Note: This structure and its functions are ONLY used in building manual result + * sets for info functions (SQLTables, SQLColumns, etc.) + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __TUPLELIST_H__ +#define __TUPLELIST_H__ + +#include "psqlodbc.h" + +struct TupleListClass_ +{ + Int4 num_fields; + Int4 num_tuples; + TupleNode *list_start, + *list_end, + *lastref; + Int4 last_indexed; +}; + +#define TL_get_num_tuples(x) (x->num_tuples) + +/* Create a TupleList. Each tuple consits of fieldcnt columns */ +TupleListClass *TL_Constructor(UInt4 fieldcnt); +void TL_Destructor(TupleListClass *self); +void *TL_get_fieldval(TupleListClass *self, Int4 tupleno, Int2 fieldno); +char TL_add_tuple(TupleListClass *self, TupleNode *new_field); + +#endif diff --git a/src/interfaces/odbc/windev/win32.mak b/src/interfaces/odbc/windev/win32.mak new file mode 100644 index 0000000000..1d92eec1bb --- /dev/null +++ b/src/interfaces/odbc/windev/win32.mak @@ -0,0 +1,507 @@ + +# +# File: win32.mak +# +# Description: psqlodbc Makefile for Win32. +# +# Configurations: Debug, Release, MultibyteDebug, MultibyteRelease +# Build Types: ALL, CLEAN +# Usage: NMAKE /f win32.mak CFG=[Release | Debug | MultibyteRelease | MultiByteDebug] [ALL | CLEAN] +# +# Comments: Created by Dave Page, 2001-02-12 +# + +!MESSAGE Building the PostgreSQL ODBC Driver for Win32... +!MESSAGE +!IF "$(CFG)" == "" +CFG=Release +!MESSAGE No configuration specified. Defaulting to Release. +!MESSAGE +!ENDIF + +!IF "$(CFG)" != "Release" && "$(CFG)" != "Debug" && "$(CFG)" != "MultibyteRelease" && "$(CFG)" != "MultibyteDebug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f win32.mak CFG=[Release | Debug | MultibyteRelease | MultiByteDebug] [ALL | CLEAN] +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Release" (Win32 Release DLL) +!MESSAGE "Debug" (Win32 Debug DLL) +!MESSAGE "MultibyteRelease" (Win32 Release DLL with Multibyte support) +!MESSAGE "MultibyteDebug" (Win32 Release DLL with Multibyte support) +!MESSAGE +!ERROR An invalid configuration was specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +!IF "$(CFG)" == "Release" || "$(CFG)" == "MultibyteRelease" + +!IF "$(CFG)" == "MultibyteRelease" +OUTDIR=.\MultibyteRelease +INTDIR=.\MultibyteRelease +!ELSE +OUTDIR=.\Release +INTDIR=.\Release +!ENDIF + +ALL : "$(OUTDIR)\psqlodbc.dll" + + +CLEAN : + -@erase "$(INTDIR)\bind.obj" + -@erase "$(INTDIR)\columninfo.obj" + -@erase "$(INTDIR)\connection.obj" + -@erase "$(INTDIR)\convert.obj" + -@erase "$(INTDIR)\dlg_specific.obj" + -@erase "$(INTDIR)\drvconn.obj" + -@erase "$(INTDIR)\environ.obj" + -@erase "$(INTDIR)\execute.obj" + -@erase "$(INTDIR)\gpps.obj" + -@erase "$(INTDIR)\info.obj" + -@erase "$(INTDIR)\lobj.obj" + -@erase "$(INTDIR)\win_md5.obj" + -@erase "$(INTDIR)\misc.obj" +!IF "$(CFG)" == "MultibyteRelease" + -@erase "$(INTDIR)\multibyte.obj" +!ENDIF + -@erase "$(INTDIR)\options.obj" + -@erase "$(INTDIR)\parse.obj" + -@erase "$(INTDIR)\pgtypes.obj" + -@erase "$(INTDIR)\psqlodbc.obj" + -@erase "$(INTDIR)\psqlodbc.res" + -@erase "$(INTDIR)\qresult.obj" + -@erase "$(INTDIR)\results.obj" + -@erase "$(INTDIR)\setup.obj" + -@erase "$(INTDIR)\socket.obj" + -@erase "$(INTDIR)\statement.obj" + -@erase "$(INTDIR)\tuple.obj" + -@erase "$(INTDIR)\tuplelist.obj" + -@erase "$(INTDIR)\odbcapi.obj" + -@erase "$(INTDIR)\vc60.idb" + -@erase "$(OUTDIR)\psqlodbc.dll" + -@erase "$(OUTDIR)\psqlodbc.exp" + -@erase "$(OUTDIR)\psqlodbc.lib" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +!IF "$(CFG)" == "MultibyteRelease" +CPP_PROJ=/nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PSQLODBC_EXPORTS" /D "MULTIBYTE" /Fp"$(INTDIR)\psqlodbc.pch" /YX /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /c +!ELSE +CPP_PROJ=/nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PSQLODBC_EXPORTS" /Fp"$(INTDIR)\psqlodbc.pch" /YX /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /c +!ENDIF + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x809 /fo"$(INTDIR)\psqlodbc.res" /d "NDEBUG" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\psqlodbc.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib /nologo /dll /incremental:no /pdb:"$(OUTDIR)\psqlodbc.pdb" /machine:I386 /def:"psqlodbc_win32.def" /out:"$(OUTDIR)\psqlodbc.dll" /implib:"$(OUTDIR)\psqlodbc.lib" +DEF_FILE= "psqlodbc_win32.def" +LINK32_OBJS= \ + "$(INTDIR)\bind.obj" \ + "$(INTDIR)\columninfo.obj" \ + "$(INTDIR)\connection.obj" \ + "$(INTDIR)\convert.obj" \ + "$(INTDIR)\dlg_specific.obj" \ + "$(INTDIR)\drvconn.obj" \ + "$(INTDIR)\environ.obj" \ + "$(INTDIR)\execute.obj" \ + "$(INTDIR)\gpps.obj" \ + "$(INTDIR)\info.obj" \ + "$(INTDIR)\lobj.obj" \ + "$(INTDIR)\win_md5.obj" \ + "$(INTDIR)\misc.obj" \ +!IF "$(CFG)" == "MultibyteRelease" + "$(INTDIR)\multibyte.obj" \ +!ENDIF + "$(INTDIR)\options.obj" \ + "$(INTDIR)\parse.obj" \ + "$(INTDIR)\pgtypes.obj" \ + "$(INTDIR)\psqlodbc.obj" \ + "$(INTDIR)\qresult.obj" \ + "$(INTDIR)\results.obj" \ + "$(INTDIR)\setup.obj" \ + "$(INTDIR)\socket.obj" \ + "$(INTDIR)\statement.obj" \ + "$(INTDIR)\tuple.obj" \ + "$(INTDIR)\tuplelist.obj" \ + "$(INTDIR)\odbcapi.obj" \ + "$(INTDIR)\psqlodbc.res" + +"$(OUTDIR)\psqlodbc.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!ELSEIF "$(CFG)" == "Debug" || "$(CFG)" == "MultibyteDebug" + +!IF "$(CFG)" == "MultibyteDebug" +OUTDIR=.\MultibyteDebug +INTDIR=.\MultibyteDebug +!ELSE +OUTDIR=.\Debug +INTDIR=.\Debug +!ENDIF + +ALL : "$(OUTDIR)\psqlodbc.dll" + + +CLEAN : + -@erase "$(INTDIR)\bind.obj" + -@erase "$(INTDIR)\columninfo.obj" + -@erase "$(INTDIR)\connection.obj" + -@erase "$(INTDIR)\convert.obj" + -@erase "$(INTDIR)\dlg_specific.obj" + -@erase "$(INTDIR)\drvconn.obj" + -@erase "$(INTDIR)\environ.obj" + -@erase "$(INTDIR)\execute.obj" + -@erase "$(INTDIR)\gpps.obj" + -@erase "$(INTDIR)\info.obj" + -@erase "$(INTDIR)\lobj.obj" + -@erase "$(INTDIR)\win_md5.obj" + -@erase "$(INTDIR)\misc.obj" +!IF "$(CFG)" == "MultibyteDebug" + -@erase "$(INTDIR)\multibyte.obj" +!ENDIF + -@erase "$(INTDIR)\options.obj" + -@erase "$(INTDIR)\parse.obj" + -@erase "$(INTDIR)\pgtypes.obj" + -@erase "$(INTDIR)\psqlodbc.obj" + -@erase "$(INTDIR)\psqlodbc.res" + -@erase "$(INTDIR)\qresult.obj" + -@erase "$(INTDIR)\results.obj" + -@erase "$(INTDIR)\setup.obj" + -@erase "$(INTDIR)\socket.obj" + -@erase "$(INTDIR)\statement.obj" + -@erase "$(INTDIR)\tuple.obj" + -@erase "$(INTDIR)\tuplelist.obj" + -@erase "$(INTDIR)\odbcapi.obj" + -@erase "$(INTDIR)\vc60.idb" + -@erase "$(INTDIR)\vc60.pdb" + -@erase "$(OUTDIR)\psqlodbc.dll" + -@erase "$(OUTDIR)\psqlodbc.exp" + -@erase "$(OUTDIR)\psqlodbc.ilk" + -@erase "$(OUTDIR)\psqlodbc.lib" + -@erase "$(OUTDIR)\psqlodbc.pdb" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +!IF "$(CFG)" == "MultibyteDebug" +CPP_PROJ=/nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PSQLODBC_EXPORTS" /D "MULTIBYTE" /Fp"$(INTDIR)\psqlodbc.pch" /YX /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /GZ /c +!ELSE +CPP_PROJ=/nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PSQLODBC_EXPORTS" /Fp"$(INTDIR)\psqlodbc.pch" /YX /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /GZ /c +!ENDIF + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x809 /fo"$(INTDIR)\psqlodbc.res" /d "_DEBUG" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\psqlodbc.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib /nologo /dll /incremental:yes /pdb:"$(OUTDIR)\psqlodbc.pdb" /debug /machine:I386 /def:"psqlodbc_win32.def" /out:"$(OUTDIR)\psqlodbc.dll" /implib:"$(OUTDIR)\psqlodbc.lib" /pdbtype:sept +DEF_FILE= "psqlodbc_win32.def" +LINK32_OBJS= \ + "$(INTDIR)\bind.obj" \ + "$(INTDIR)\columninfo.obj" \ + "$(INTDIR)\connection.obj" \ + "$(INTDIR)\convert.obj" \ + "$(INTDIR)\dlg_specific.obj" \ + "$(INTDIR)\drvconn.obj" \ + "$(INTDIR)\environ.obj" \ + "$(INTDIR)\execute.obj" \ + "$(INTDIR)\gpps.obj" \ + "$(INTDIR)\info.obj" \ + "$(INTDIR)\lobj.obj" \ + "$(INTDIR)\win_md5.obj" + "$(INTDIR)\misc.obj" \ +!IF "$(CFG)" == "MultibyteDebug" + "$(INTDIR)\multibyte.obj" \ +!ENDIF + "$(INTDIR)\options.obj" \ + "$(INTDIR)\parse.obj" \ + "$(INTDIR)\pgtypes.obj" \ + "$(INTDIR)\psqlodbc.obj" \ + "$(INTDIR)\qresult.obj" \ + "$(INTDIR)\results.obj" \ + "$(INTDIR)\setup.obj" \ + "$(INTDIR)\socket.obj" \ + "$(INTDIR)\statement.obj" \ + "$(INTDIR)\tuple.obj" \ + "$(INTDIR)\tuplelist.obj" \ + "$(INTDIR)\odbcapi.obj" \ + "$(INTDIR)\psqlodbc.res" + +"$(OUTDIR)\psqlodbc.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!ENDIF + +!IF "$(CFG)" == "Release" || "$(CFG)" == "Debug" || "$(CFG)" == "MultibyteRelease" || "$(CFG)" == "MultibyteDebug" + +SOURCE=bind.c + +"$(INTDIR)\bind.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=columninfo.c + +"$(INTDIR)\columninfo.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=connection.c + +"$(INTDIR)\connection.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=convert.c + +"$(INTDIR)\convert.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=dlg_specific.c + +"$(INTDIR)\dlg_specific.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=drvconn.c + +"$(INTDIR)\drvconn.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=environ.c + +"$(INTDIR)\environ.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=execute.c + +"$(INTDIR)\execute.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=gpps.c + +"$(INTDIR)\gpps.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=info.c + +"$(INTDIR)\info.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=lobj.c + +"$(INTDIR)\lobj.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=misc.c + +"$(INTDIR)\misc.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!IF "$(CFG)" == "MultibyteRelease" || "$(CFG)" == "MultibyteDebug" + +SOURCE=multibyte.c + +"$(INTDIR)\multibyte.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + +!ENDIF + + +SOURCE=options.c + +"$(INTDIR)\options.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=parse.c + +"$(INTDIR)\parse.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=pgtypes.c + +"$(INTDIR)\pgtypes.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=psqlodbc.c + +"$(INTDIR)\psqlodbc.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=psqlodbc.rc + +!IF "$(CFG)" == "Release" +"$(INTDIR)\psqlodbc.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x809 /fo"$(INTDIR)\psqlodbc.res" /d "NDEBUG" $(SOURCE) +!ENDIF + +!IF "$(CFG)" == "MultibyteRelease" +"$(INTDIR)\psqlodbc.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x809 /fo"$(INTDIR)\psqlodbc.res" /d "NDEBUG" /d "MULTIBYTE" $(SOURCE) +!ENDIF + +!IF "$(CFG)" == "Debug" +"$(INTDIR)\psqlodbc.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x809 /fo"$(INTDIR)\psqlodbc.res" /d "_DEBUG" $(SOURCE) +!ENDIF + +!IF "$(CFG)" == "MultibyteDebug" +"$(INTDIR)\psqlodbc.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x809 /fo"$(INTDIR)\psqlodbc.res" /d "_DEBUG" /d "MULTIBYTE" $(SOURCE) +!ENDIF + + +SOURCE=qresult.c + +"$(INTDIR)\qresult.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=results.c + +"$(INTDIR)\results.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=setup.c + +"$(INTDIR)\setup.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=socket.c + +"$(INTDIR)\socket.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=statement.c + +"$(INTDIR)\statement.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=tuple.c + +"$(INTDIR)\tuple.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=tuplelist.c + +"$(INTDIR)\tuplelist.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=win_md5.c + +"$(INTDIR)\win_md5.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +SOURCE=odbcapi.c + +"$(INTDIR)\odbcapi.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + + + +!ENDIF diff --git a/src/interfaces/odbc/windev/win_md5.c b/src/interfaces/odbc/windev/win_md5.c new file mode 100644 index 0000000000..2a7c4014c7 --- /dev/null +++ b/src/interfaces/odbc/windev/win_md5.c @@ -0,0 +1,15 @@ +/* + * win_md5.c + * Under Windows I don't love the following /D in makefiles. - inoue + */ +#define MD5_ODBC +#define FRONTEND + +/* + * md5.c is the exact copy of the src/backend/libpq/md5.c. + * + * psqlodbc driver stuff never refer(link) to other + * stuff directly. + * + */ +#include "md5.c" -- 2.11.0