From 9e3382dfc5affce3b653534714343d5e546e1a3e Mon Sep 17 00:00:00 2001 From: "Marc G. Fournier" Date: Mon, 13 Apr 1998 15:02:05 +0000 Subject: [PATCH] Replace old PostODBC driver with new one... This one is based on an older PostODBC driver, rewritten and maintained by InsightDist(?) --- src/interfaces/odbc/bind.c | 340 +++++ src/interfaces/odbc/bind.h | 45 + src/interfaces/odbc/columninfo.c | 159 +++ src/interfaces/odbc/columninfo.h | 40 + src/interfaces/odbc/connection.c | 979 ++++++++++++++ src/interfaces/odbc/connection.h | 183 +++ src/interfaces/odbc/convert.c | 995 ++++++++++++++ src/interfaces/odbc/convert.h | 47 + src/interfaces/odbc/drvconn.c | 327 +++++ src/interfaces/odbc/environ.c | 416 ++++++ src/interfaces/odbc/environ.h | 32 + src/interfaces/odbc/execute.c | 535 ++++++++ src/interfaces/odbc/info.c | 2180 ++++++++++++++++++++++++++++++ src/interfaces/odbc/license.txt | 962 +++++++++++++ src/interfaces/odbc/misc.c | 224 +++ src/interfaces/odbc/misc.h | 58 + src/interfaces/odbc/notice.txt | 35 + src/interfaces/odbc/options.c | 210 +++ src/interfaces/odbc/pgtypes.c | 441 ++++++ src/interfaces/odbc/pgtypes.h | 81 ++ src/interfaces/odbc/psqlodbc.c | 146 ++ src/interfaces/odbc/psqlodbc.def | 60 + src/interfaces/odbc/psqlodbc.h | 120 ++ src/interfaces/odbc/psqlodbc.rc | 205 +++ src/interfaces/odbc/qresult.c | 472 +++++++ src/interfaces/odbc/qresult.h | 105 ++ src/interfaces/odbc/resource.h | 39 + src/interfaces/odbc/results.c | 694 ++++++++++ src/interfaces/odbc/setup.c | 679 ++++++++++ src/interfaces/odbc/socket.c | 289 ++++ src/interfaces/odbc/socket.h | 69 + src/interfaces/odbc/statement.c | 545 ++++++++ src/interfaces/odbc/statement.h | 119 ++ src/interfaces/odbc/tuple.c | 56 + src/interfaces/odbc/tuple.h | 44 + src/interfaces/odbc/tuplelist.c | 188 +++ src/interfaces/odbc/tuplelist.h | 33 + 37 files changed, 12152 insertions(+) create mode 100644 src/interfaces/odbc/bind.c create mode 100644 src/interfaces/odbc/bind.h create mode 100644 src/interfaces/odbc/columninfo.c create mode 100644 src/interfaces/odbc/columninfo.h create mode 100644 src/interfaces/odbc/connection.c create mode 100644 src/interfaces/odbc/connection.h create mode 100644 src/interfaces/odbc/convert.c create mode 100644 src/interfaces/odbc/convert.h create mode 100644 src/interfaces/odbc/drvconn.c create mode 100644 src/interfaces/odbc/environ.c create mode 100644 src/interfaces/odbc/environ.h create mode 100644 src/interfaces/odbc/execute.c create mode 100644 src/interfaces/odbc/info.c create mode 100644 src/interfaces/odbc/license.txt create mode 100644 src/interfaces/odbc/misc.c create mode 100644 src/interfaces/odbc/misc.h create mode 100644 src/interfaces/odbc/notice.txt create mode 100644 src/interfaces/odbc/options.c create mode 100644 src/interfaces/odbc/pgtypes.c create mode 100644 src/interfaces/odbc/pgtypes.h create mode 100644 src/interfaces/odbc/psqlodbc.c create mode 100644 src/interfaces/odbc/psqlodbc.def create mode 100644 src/interfaces/odbc/psqlodbc.h create mode 100644 src/interfaces/odbc/psqlodbc.rc create mode 100644 src/interfaces/odbc/qresult.c create mode 100644 src/interfaces/odbc/qresult.h create mode 100644 src/interfaces/odbc/resource.h create mode 100644 src/interfaces/odbc/results.c create mode 100644 src/interfaces/odbc/setup.c create mode 100644 src/interfaces/odbc/socket.c create mode 100644 src/interfaces/odbc/socket.h create mode 100644 src/interfaces/odbc/statement.c create mode 100644 src/interfaces/odbc/statement.h create mode 100644 src/interfaces/odbc/tuple.c create mode 100644 src/interfaces/odbc/tuple.h create mode 100644 src/interfaces/odbc/tuplelist.c create mode 100644 src/interfaces/odbc/tuplelist.h diff --git a/src/interfaces/odbc/bind.c b/src/interfaces/odbc/bind.c new file mode 100644 index 0000000000..c01f61ab7f --- /dev/null +++ b/src/interfaces/odbc/bind.c @@ -0,0 +1,340 @@ + +/* 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 +#include + +// Bind parameters on a statement handle + +RETCODE SQL_API SQLBindParameter( + 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; + + if( ! stmt) + return SQL_INVALID_HANDLE; + + 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"; + 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].EXEC_used = NULL; + stmt->parameters[i].EXEC_buffer = NULL; + } + } + + ipar--; /* use zero based column numbers for the below part */ + + // 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) { + free(stmt->parameters[ipar].EXEC_buffer); + stmt->parameters[ipar].EXEC_buffer = NULL; + } + + if (pcbValue && *pcbValue <= SQL_LEN_DATA_AT_EXEC_OFFSET) + stmt->parameters[ipar].data_at_exec = TRUE; + else + stmt->parameters[ipar].data_at_exec = FALSE; + + + return SQL_SUCCESS; +} + +// - - - - - - - - - + +// Associate a user-supplied buffer with a database column. +RETCODE SQL_API SQLBindCol( + HSTMT hstmt, + UWORD icol, + SWORD fCType, + PTR rgbValue, + SDWORD cbValueMax, + SDWORD FAR *pcbValue) +{ +StatementClass *stmt = (StatementClass *) hstmt; +Int2 numcols; + +mylog("**** SQLBindCol: stmt = %u, icol = %d\n", stmt, icol); + + if ( ! stmt) + return SQL_INVALID_HANDLE; + + if (icol < 1) { + /* currently we do not support bookmarks */ + stmt->errormsg = "Bookmarks are not currently supported."; + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + return SQL_ERROR; + } + + icol--; /* use zero based col numbers */ + + SC_clear_error(stmt); + + if( ! stmt->result) { + stmt->errormsg = "Can't bind columns with a NULL query result structure."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + return SQL_ERROR; + } + + if( stmt->status == STMT_EXECUTING) { + stmt->errormsg = "Can't bind columns while statement is still executing."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + return SQL_ERROR; + } + + numcols = QR_NumResultCols(stmt->result); + + mylog("SQLBindCol: numcols = %d\n", numcols); + + if (icol >= numcols) { + stmt->errornumber = STMT_COLNUM_ERROR; + stmt->errormsg = "Column number too big"; + return SQL_ERROR; + } + + if ( ! stmt->bindings) { + stmt->errormsg = "Bindings were not allocated properly."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + return SQL_ERROR; + } + + if ((cbValueMax == 0) || (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; + } + + return SQL_SUCCESS; +} + +// - - - - - - - - - + +// Returns the description of a parameter marker. + +RETCODE SQL_API SQLDescribeParam( + HSTMT hstmt, + UWORD ipar, + SWORD FAR *pfSqlType, + UDWORD FAR *pcbColDef, + SWORD FAR *pibScale, + SWORD FAR *pfNullable) +{ +StatementClass *stmt = (StatementClass *) hstmt; + + if( ! stmt) + return SQL_INVALID_HANDLE; + + if( (ipar < 1) || (ipar > stmt->parameters_allocated) ) { + stmt->errormsg = "Invalid parameter number for SQLDescribeParam."; + stmt->errornumber = STMT_BAD_PARAMETER_NUMBER_ERROR; + return SQL_ERROR; + } + + ipar--; + + 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->parameters[ipar].paramType); + + return SQL_SUCCESS; +} + +// - - - - - - - - - + +// Sets multiple values (arrays) for the set of parameter markers. + +RETCODE SQL_API SQLParamOptions( + HSTMT hstmt, + UDWORD crow, + UDWORD FAR *pirow) +{ + return SQL_ERROR; +} + +// - - - - - - - - - + +// Returns the number of parameter markers. + +RETCODE SQL_API SQLNumParams( + HSTMT hstmt, + SWORD FAR *pcpar) +{ +StatementClass *stmt = (StatementClass *) hstmt; +unsigned int i; + + // I guess this is the number of actual parameter markers + // in the statement, not the number of parameters that are bound. + // why does this have to be driver-specific? + + if(!stmt) + return SQL_INVALID_HANDLE; + + if(!stmt->statement) { + // no statement has been allocated + *pcpar = 0; + stmt->errormsg = "SQLNumParams called with no statement ready."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + return SQL_ERROR; + } else { + *pcpar = 0; + for(i=0; i < strlen(stmt->statement); i++) { + if(stmt->statement[i] == '?') + (*pcpar)++; + } + + 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; + } + + return new_bindings; +} + +void +extend_bindings(StatementClass *stmt, int num_columns) +{ +BindInfoClass *new_bindings; +int i; + + mylog("in extend_bindings\n"); + + /* 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(stmt->bindings) { + for(i=0; ibindings_allocated; i++) + new_bindings[i] = stmt->bindings[i]; + + free(stmt->bindings); + } + + stmt->bindings = new_bindings; // null indicates error + + } else { + /* if we have too many, make sure the extra ones are emptied out */ + /* so we don't accidentally try to use them for anything */ + for(i = num_columns; i < stmt->bindings_allocated; i++) { + stmt->bindings[i].buflen = 0; + stmt->bindings[i].buffer = NULL; + stmt->bindings[i].used = NULL; + } + } + + mylog("exit extend_bindings\n"); +} diff --git a/src/interfaces/odbc/bind.h b/src/interfaces/odbc/bind.h new file mode 100644 index 0000000000..7ce3321a7a --- /dev/null +++ b/src/interfaces/odbc/bind.h @@ -0,0 +1,45 @@ + +/* 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 */ + char *buffer; /* pointer to the buffer */ + Int4 *used; /* used space in the buffer (for strings not counting the '\0') */ + 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; + Int4 *EXEC_used; + char *EXEC_buffer; + 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/columninfo.c b/src/interfaces/odbc/columninfo.c new file mode 100644 index 0000000000..4f029e9c38 --- /dev/null +++ b/src/interfaces/odbc/columninfo.c @@ -0,0 +1,159 @@ + +/* 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 "columninfo.h" +#include "socket.h" +#include +#include + +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; + } + + 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, SocketClass *sock) +{ +Int2 lf; +int new_num_fields; +Oid new_adtid; +Int2 new_adtsize; +char new_field_name[MAX_MESSAGE_LEN+1]; + + + /* 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, MAX_MESSAGE_LEN); + new_adtid = (Oid) SOCK_get_int(sock, 4); + new_adtsize = (Int2) SOCK_get_int(sock, 2); + + mylog("CI_read_fields: fieldname='%s', adtid=%d, adtsize=%d\n", new_field_name, new_adtid, new_adtsize); + + if (self) + CI_set_field_info(self, lf, new_field_name, new_adtid, new_adtsize); + } + + 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]); + } + + /* Safe to call even if null */ + free(self->name); + free(self->adtid); + free(self->adtsize); +} + +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); + self->adtid = (Oid *) malloc (sizeof(Oid) * self->num_fields); + self->adtsize = (Int2 *) malloc (sizeof(Int2) * self->num_fields); +} + +void +CI_set_field_info(ColumnInfoClass *self, int field_num, char *new_name, + Oid new_adtid, Int2 new_adtsize) +{ + + // 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; +} + +char * +CI_get_fieldname(ColumnInfoClass *self, Int2 which) +{ +char *rv = NULL; + + if ( ! self->name) + return NULL; + + if ((which >= 0) && (which < self->num_fields)) + rv = self->name[which]; + + return rv; +} + + +Int2 +CI_get_fieldsize(ColumnInfoClass *self, Int2 which) +{ +Int2 rv = 0; + + if ( ! self->adtsize) + return 0; + + if ((which >= 0) && (which < self->num_fields)) + rv = self->adtsize[which]; + + return rv; +} + diff --git a/src/interfaces/odbc/columninfo.h b/src/interfaces/odbc/columninfo.h new file mode 100644 index 0000000000..05d3dce6f2 --- /dev/null +++ b/src/interfaces/odbc/columninfo.h @@ -0,0 +1,40 @@ + +/* 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 */ +}; + +#define CI_get_num_fields(self) (self->num_fields) +#define CI_get_oid(self, col) (self->adtid[col]) + + +ColumnInfoClass *CI_Constructor(); +void CI_Destructor(ColumnInfoClass *self); +char CI_read_fields(ColumnInfoClass *self, SocketClass *sock); + +/* 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); + +char *CI_get_fieldname(ColumnInfoClass *self, Int2 which); +Int2 CI_get_fieldsize(ColumnInfoClass *self, Int2 which); +void CI_free_memory(ColumnInfoClass *self); + +#endif diff --git a/src/interfaces/odbc/connection.c b/src/interfaces/odbc/connection.c new file mode 100644 index 0000000000..e0ab2a9a60 --- /dev/null +++ b/src/interfaces/odbc/connection.c @@ -0,0 +1,979 @@ + +/* 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. + * + */ + +#include "environ.h" +#include "connection.h" +#include "socket.h" +#include "statement.h" +#include "qresult.h" +#include +#include + +#define STMT_INCREMENT 16 /* how many statement holders to allocate at a time */ + +extern GLOBAL_VALUES globals; + + +RETCODE SQL_API SQLAllocConnect( + HENV henv, + HDBC FAR *phdbc) +{ +EnvironmentClass *env = (EnvironmentClass *)henv; +ConnectionClass *conn; + + + conn = CC_Constructor(); + mylog("**** SQLAllocConnect: henv = %u, conn = %u\n", henv, conn); + + if( ! conn) { + env->errormsg = "Couldn't allocate memory for Connection object."; + env->errornumber = ENV_ALLOC_ERROR; + *phdbc = SQL_NULL_HDBC; + 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; + return SQL_ERROR; + } + + *phdbc = (HDBC) conn; + + return SQL_SUCCESS; +} + + +// - - - - - - - - - + +RETCODE SQL_API SQLConnect( + HDBC hdbc, + UCHAR FAR *szDSN, + SWORD cbDSN, + UCHAR FAR *szUID, + SWORD cbUID, + UCHAR FAR *szAuthStr, + SWORD cbAuthStr) +{ +ConnectionClass *conn = (ConnectionClass *) hdbc; + + if ( ! conn) + return SQL_INVALID_HANDLE; + + make_string(szDSN, cbDSN, conn->connInfo.dsn); + + /* get the values for the DSN from the registry */ + CC_DSN_info(conn, CONN_OVERWRITE); + + /* override values from DSN info with UID and authStr(pwd) + This only occurs if the values are actually there. + */ + make_string(szUID, cbUID, conn->connInfo.username); + make_string(szAuthStr, cbAuthStr, conn->connInfo.password); + + /* fill in any defaults */ + CC_set_defaults(conn); + + qlog("conn = %u, SQLConnect(DSN='%s', UID='%s', PWD='%s')\n", conn->connInfo.dsn, conn->connInfo.username, conn->connInfo.password); + + if ( CC_connect(conn, FALSE) <= 0) + // Error messages are filled in + return SQL_ERROR; + + return SQL_SUCCESS; +} + +// - - - - - - - - - + +RETCODE SQL_API SQLBrowseConnect( + HDBC hdbc, + UCHAR FAR *szConnStrIn, + SWORD cbConnStrIn, + UCHAR FAR *szConnStrOut, + SWORD cbConnStrOutMax, + SWORD FAR *pcbConnStrOut) +{ + return SQL_SUCCESS; +} + +// - - - - - - - - - + +/* Drop any hstmts open on hdbc and disconnect from database */ +RETCODE SQL_API SQLDisconnect( + HDBC hdbc) +{ +ConnectionClass *conn = (ConnectionClass *) hdbc; + + mylog("**** in SQLDisconnect\n"); + + if ( ! conn) + return SQL_INVALID_HANDLE; + + qlog("conn=%u, SQLDisconnect\n", conn); + + if (conn->status == CONN_EXECUTING) { + conn->errornumber = CONN_IN_USE; + conn->errormsg = "A transaction is currently being executed"; + return SQL_ERROR; + } + + mylog("SQLDisconnect: about to CC_cleanup\n"); + + /* Close the connection and free statements */ + CC_cleanup(conn); + + mylog("SQLDisconnect: done CC_cleanup\n"); + mylog("exit SQLDisconnect\n"); + + return SQL_SUCCESS; +} + + +// - - - - - - - - - + +RETCODE SQL_API SQLFreeConnect( + HDBC hdbc) +{ +ConnectionClass *conn = (ConnectionClass *) hdbc; + + mylog("**** in SQLFreeConnect: hdbc=%u\n", hdbc); + + if ( ! conn) + 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"; + return SQL_ERROR; + } + + CC_Destructor(conn); + + mylog("exit SQLFreeConnect\n"); + + 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)); + + rv->sock = SOCK_Constructor(); + 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; + + } + 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"); + + /* Free up statement holders */ + if (self->stmts) { + free(self->stmts); + self->stmts = NULL; + } + mylog("after free statement holders\n"); + + free(self); + + mylog("exit CC_Destructor\n"); + + return 1; +} + +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, 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; + } + } + mylog("exit CC_Cleanup\n"); + return TRUE; +} + +void +CC_set_defaults(ConnectionClass *self) +{ +ConnInfo *ci = &(self->connInfo); + + if (ci->port[0] == '\0') + strcpy(ci->port, DEFAULT_PORT); + + if (ci->readonly[0] == '\0') + strcpy(ci->readonly, DEFAULT_READONLY); +} + +void +CC_DSN_info(ConnectionClass *self, char overwrite) +{ +ConnInfo *ci = &(self->connInfo); +char *DSN = ci->dsn; + + // 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. + if ( DSN[0] == '\0') { + if ( ci->driver[0] != '\0') + return; + else + strcpy(DSN, "DEFAULT"); + } + + // Proceed with getting info for the given DSN. + 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->readonly[0] == '\0' || overwrite) + SQLGetPrivateProfileString(DSN, INI_READONLY, "", ci->readonly, sizeof(ci->readonly), 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, "", ci->conn_settings, sizeof(ci->conn_settings), ODBC_INI); + + qlog("conn=%u, DSN info(DSN='%s',server='%s',dbase='%s',user='%s',passwd='%s',port='%s',readonly='%s',protocol='%s',conn_settings='%s')\n", + self, DSN, + ci->server, + ci->database, + ci->username, + ci->password, + ci->port, + ci->readonly, + ci->protocol, + ci->conn_settings); +} + + +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[2]; + + if ( do_password) + + sock = self->sock; /* already connected, just authenticate */ + + else { + + 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); + + /* 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(); + 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); + + 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)) do { + + if (do_password) + beresp = 'R'; + else + beresp = SOCK_get_char(sock); + + switch(beresp) { + case 'E': + mylog("auth got 'E'\n"); + + 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); + return 0; + case 'R': + + if (do_password) { + mylog("in 'R' do_password\n"); + areq = AUTH_REQ_PASSWORD; + do_password = FALSE; + } + else { + mylog("auth got 'R'\n"); + + areq = SOCK_get_int(sock, 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; + + default: + self->errormsg = "Unknown authentication type"; + self->errornumber = CONN_AUTH_TYPE_UNSUPPORTED; + return 0; + } + break; + default: + self->errormsg = "Unexpected protocol character during authentication"; + self->errornumber = CONN_INVALID_AUTHENTICATION; + return 0; + } + + } while (areq != AUTH_REQ_OK); + + + 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, 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"); + + + /**********************************************/ + /******* Send any initial settings *********/ + /**********************************************/ + + CC_send_settings(self); + + + CC_clear_error(self); /* clear any initial command errors */ + self->status = CONN_CONNECTED; + + 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, QResultClass *result_in, char *cursor) +{ +QResultClass *res = NULL; +char id, swallow; +SocketClass *sock = self->sock; +static char msgbuffer[MAX_MESSAGE_LEN+1]; +char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont need static + + + 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 + if(strlen(query) > MAX_MESSAGE_LEN-2) { + 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"); + + while(1) { + /* what type of message is comming 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"; + if (res) + QR_Destructor(res); + + mylog("send_query: 'id' - %s\n", self->errormsg); + CC_set_no_trans(self); + return NULL; + } + + 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, MAX_MESSAGE_LEN); + /* 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, MAX_MESSAGE_LEN); + 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); + return NULL; + } else { + + char clear = 0; + + 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 */ + QR_set_status(res, PGRES_COMMAND_OK); + QR_set_command(res, cmdbuffer); + + /* (Quotation from the original comments) + since backend may produze 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 + */ + + + SOCK_put_string(sock, "Q "); + SOCK_flush_output(sock); + while(!clear) { + SOCK_get_string(sock, cmdbuffer, ERROR_MSG_LENGTH); + mylog("send_query: read command '%s'\n", cmdbuffer); + clear = (cmdbuffer[0] == 'I'); + } + + mylog("send_query: returning res = %u\n", res); + return res; + } + case 'N' : /* NOTICE: */ + SOCK_get_string(sock, cmdbuffer, ERROR_MSG_LENGTH); + + res = QR_Constructor(); + 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); + + 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 ((swallow != '\0') || SOCK_get_errcode(sock) != 0) { + self->errornumber = CONNECTION_BACKEND_CRAZY; + self->errormsg = "Unexpected protocol character from backend"; + res = QR_Constructor(); + QR_set_status(res, PGRES_FATAL_ERROR); + return res; + } else { + /* We return the empty query */ + res = QR_Constructor(); + QR_set_status(res, PGRES_EMPTY_QUERY); + return res; + } + break; + case 'E' : + 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); + + if ( ! strncmp(self->errormsg, "FATAL", 5)) { + self->errornumber = CONNECTION_SERVER_REPORTED_ERROR; + CC_set_no_trans(self); + } + else + self->errornumber = CONNECTION_SERVER_REPORTED_WARNING; + + return NULL; + + case 'P' : /* get the Portal name */ + SOCK_get_string(sock, msgbuffer, MAX_MESSAGE_LEN); + break; + case 'T': /* Tuple results start here */ + 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."; + return NULL; + } + + if ( ! QR_fetch_tuples(result_in, self, cursor)) { + self->errornumber = CONNECTION_COULD_NOT_RECEIVE; + self->errormsg = QR_get_message(result_in); + return NULL; + } + } + else { // next fetch, so reuse an existing result + if ( ! QR_fetch_tuples(result_in, NULL, NULL)) { + self->errornumber = CONNECTION_COULD_NOT_RECEIVE; + self->errormsg = QR_get_message(result_in); + return NULL; + } + } + + return result_in; + case 'D': /* Copy in command began successfully */ + res = QR_Constructor(); + QR_set_status(res, PGRES_COPY_IN); + return res; + case 'B': /* Copy out command began successfully */ + res = QR_Constructor(); + QR_set_status(res, PGRES_COPY_OUT); + return res; + default: + self->errornumber = CONNECTION_BACKEND_CRAZY; + self->errormsg = "Unexpected protocol character from backend"; + CC_set_no_trans(self); + + mylog("send_query: error - %s\n", self->errormsg); + return NULL; + } + } +} + +char +CC_send_settings(ConnectionClass *self) +{ +char ini_query[MAX_MESSAGE_LEN]; +ConnInfo *ci = &(self->connInfo); +QResultClass *res; + + ini_query[0] = '\0'; + + /* Turn on/off genetic optimizer based on global flag */ + if (globals.optimizer[0] != '\0') + sprintf(ini_query, "set geqo to '%s'", globals.optimizer); + + /* Global settings */ + if (globals.conn_settings[0] != '\0') + sprintf(&ini_query[strlen(ini_query)], "%s%s", + ini_query[0] != '\0' ? "; " : "", + globals.conn_settings); + + /* Per Datasource settings */ + if (ci->conn_settings[0] != '\0') + sprintf(&ini_query[strlen(ini_query)], "%s%s", + ini_query[0] != '\0' ? "; " : "", + ci->conn_settings); + + if (ini_query[0] != '\0') { + mylog("Sending Initial Connection query: '%s'\n", ini_query); + + res = CC_send_query(self, ini_query, NULL, NULL); + if (res && QR_get_status(res) != PGRES_FATAL_ERROR) { + mylog("Initial Query response: '%s'\n", QR_get_notice(res)); + } + + if ( res == NULL || + QR_get_status(res) == PGRES_BAD_RESPONSE || + QR_get_status(res) == PGRES_FATAL_ERROR || + QR_get_status(res) == PGRES_INTERNAL_ERROR) { + + self->errornumber = CONNECTION_COULD_NOT_SEND; + self->errormsg = "Error sending ConnSettings"; + if (res) + QR_Destructor(res); + return 0; + } + + if (res) + QR_Destructor(res); + } + return TRUE; +} + diff --git a/src/interfaces/odbc/connection.h b/src/interfaces/odbc/connection.h new file mode 100644 index 0000000000..30d567212c --- /dev/null +++ b/src/interfaces/odbc/connection.h @@ -0,0 +1,183 @@ + +/* File: connection.h + * + * Description: See "connection.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __CONNECTION_H__ +#define __CONNECTION_H__ + +#include +#include +#include "psqlodbc.h" + +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 1 +#define CONNECTION_MSG_TOO_LONG 3 +#define CONNECTION_COULD_NOT_SEND 4 +#define CONNECTION_NO_SUCH_DATABASE 5 +#define CONNECTION_BACKEND_CRAZY 6 +#define CONNECTION_NO_RESPONSE 7 +#define CONNECTION_SERVER_REPORTED_ERROR 8 +#define CONNECTION_COULD_NOT_RECEIVE 9 +#define CONNECTION_SERVER_REPORTED_WARNING 10 +#define CONNECTION_NEED_PASSWORD 12 + +/* These errors correspond to specific SQL states */ +#define CONN_INIREAD_ERROR 1 +#define CONN_OPENDB_ERROR 2 +#define CONN_STMT_ALLOC_ERROR 3 +#define CONN_IN_USE 4 +#define CONN_UNSUPPORTED_OPTION 5 +/* Used by SetConnectoption to indicate unsupported options */ +#define CONN_INVALID_ARGUMENT_NO 6 +/* SetConnectOption: corresponds to ODBC--"S1009" */ +#define CONN_TRANSACT_IN_PROGRES 7 +#define CONN_NO_MEMORY_ERROR 8 +#define CONN_NOT_IMPLEMENTED_ERROR 9 +#define CONN_INVALID_AUTHENTICATION 10 +#define CONN_AUTH_TYPE_UNSUPPORTED 11 + + +/* 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 + +/* 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(1, 0) +#define PG_PROTOCOL_EARLIEST PG_PROTOCOL(0, 0) + +/* This startup packet is to support latest Postgres protocol (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 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 readonly[SMALL_REGISTRY_LEN]; + char focus_password; +} ConnInfo; + +/* Macro to determine is the connection using 6.2 protocol? */ +#define PROTOCOL_62(conninfo_) (strncmp((conninfo_)->protocol, PG62, strlen(PG62)) == 0) + + +/******* The Connection handle ************/ +struct ConnectionClass_ { + HENV henv; /* environment this connection was created on */ + char *errormsg; + int errornumber; + CONN_Status status; + ConnInfo connInfo; + StatementClass **stmts; + int num_stmts; + SocketClass *sock; + char transact_status; /* Is a transaction is currently in progress */ + char errormsg_created; /* has an informative error msg been created? */ +}; + + +/* 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_readonly(x) (x->connInfo.readonly[0] == '1') + + +/* for CC_DSN_info */ +#define CONN_DONT_OVERWRITE 0 +#define CONN_OVERWRITE 1 + + +/* prototypes */ +ConnectionClass *CC_Constructor(); +char CC_Destructor(ConnectionClass *self); +char CC_cleanup(ConnectionClass *self); +char CC_abort(ConnectionClass *self); +void CC_DSN_info(ConnectionClass *self, char overwrite); +void CC_set_defaults(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, QResultClass *result_in, char *cursor); +void CC_clear_error(ConnectionClass *self); +char *CC_create_errormsg(ConnectionClass *self); +char CC_send_settings(ConnectionClass *self); + +#endif diff --git a/src/interfaces/odbc/convert.c b/src/interfaces/odbc/convert.c new file mode 100644 index 0000000000..e340c83ec0 --- /dev/null +++ b/src/interfaces/odbc/convert.c @@ -0,0 +1,995 @@ + +/* 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "convert.h" +#include "statement.h" +#include "bind.h" +#include "pgtypes.h" + +/******** 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 +******************************************************************************/ + + +/* This is called by SQLFetch() */ +int +copy_and_convert_field_bindinfo(Int4 field_type, void *value, BindInfoClass *bic) +{ + return copy_and_convert_field(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(Int4 field_type, void *value, Int2 fCType, PTR rgbValue, SDWORD cbValueMax, SDWORD *pcbValue) +{ +Int4 len = 0, nf; +char day[4], mon[4], tz[4]; +SIMPLE_TIME st; +time_t t = time(NULL); +struct tm *tim; +int bool; + + + 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; + + bool = 0; + + mylog("copy_and_convert: field_type = %d, fctype = %d, value = '%s', cbValueMax=%d\n", field_type, fCType, value, cbValueMax); + if(value) { + + /******************************************************************** + 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, "%2d-%2d-%4d", &st.m, &st.d, &st.y); + 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: + if (strnicmp(value, "invalid", 7) != 0) { + nf = sscanf(value, "%3s %3s %2d %2d:%2d:%2d %4d %3s", &day, &mon, &st.d, &st.hh, &st.mm, &st.ss, &st.y, &tz); + + if (nf == 7 || nf == 8) { + /* convert month name to month number */ + st.m = monthToNumber(mon); + } + } 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 = (char *) value; + if (s[0] == 'T' || s[0] == 't' || s[0] == '1') + bool = 1; + else + bool = 0; + } + break; + + /* This is for internal use by SQLStatistics() */ + case PG_TYPE_INT28: { + // this is an array of eight integers + short *short_array = (short *)rgbValue; + + len = 16; + + 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]); + + /* There is no corresponding fCType for this. */ + if(pcbValue) + *pcbValue = len; + + return COPY_OK; /* dont go any further or the data will be trashed */ + } + + } + + /* Change default into something useable */ + if (fCType == SQL_C_DEFAULT) { + fCType = pgtype_to_ctype(field_type); + + mylog("copy_and_convert, SQL_C_DEFAULT: fCType = %d\n", fCType); + } + + + if(fCType == SQL_C_CHAR) { + + /* Special character formatting as required */ + switch(field_type) { + case PG_TYPE_DATE: + len = 11; + if (cbValueMax > len) + sprintf((char *)rgbValue, "%.4d-%.2d-%.2d", st.y, st.m, st.d); + break; + + case PG_TYPE_TIME: + len = 9; + if (cbValueMax > len) + sprintf((char *)rgbValue, "%.2d:%.2d:%.2d", st.hh, st.mm, st.ss); + break; + + case PG_TYPE_ABSTIME: + case PG_TYPE_DATETIME: + len = 19; + if (cbValueMax > len) + sprintf((char *) rgbValue, "%.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((char *) rgbValue, bool ? "1" : "0"); + mylog("PG_TYPE_BOOL: rgbValue = '%s'\n", rgbValue); + } + break; + + case PG_TYPE_BYTEA: // convert binary data to hex strings (i.e, 255 = "FF") + len = convert_pgbinary_to_char(value, rgbValue, cbValueMax); + break; + + default: + /* convert linefeeds to carriage-return/linefeed */ + convert_linefeeds( (char *) value, rgbValue, cbValueMax); + len = strlen(rgbValue); + + mylog(" SQL_C_CHAR, default: len = %d, cbValueMax = %d, rgbValue = '%s'\n", len, cbValueMax, rgbValue); + break; + } + + } else { + + /* for SQL_C_CHAR, its 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) + convert_money(value); + + switch(fCType) { + case SQL_C_DATE: + len = 6; + if (cbValueMax >= len) { + DATE_STRUCT *ds = (DATE_STRUCT *) rgbValue; + ds->year = st.y; + ds->month = st.m; + ds->day = st.d; + } + break; + + case SQL_C_TIME: + len = 6; + if (cbValueMax >= len) { + TIME_STRUCT *ts = (TIME_STRUCT *) rgbValue; + ts->hour = st.hh; + ts->minute = st.mm; + ts->second = st.ss; + } + break; + + case SQL_C_TIMESTAMP: + len = 16; + if (cbValueMax >= len) { + TIMESTAMP_STRUCT *ts = (TIMESTAMP_STRUCT *) rgbValue; + 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 = 0; + } + break; + + case SQL_C_BIT: + len = 1; + if (cbValueMax >= len || field_type == PG_TYPE_BOOL) { + *((UCHAR *)rgbValue) = (UCHAR) bool; + mylog("SQL_C_BIT: val = %d, cb = %d, rgb=%d\n", atoi(value), cbValueMax, *((UCHAR *)rgbValue)); + } + break; + + case SQL_C_STINYINT: + case SQL_C_TINYINT: + len = 1; + if (cbValueMax >= len) + *((SCHAR *) rgbValue) = atoi(value); + break; + + case SQL_C_UTINYINT: + len = 1; + if (cbValueMax >= len) + *((UCHAR *) rgbValue) = atoi(value); + break; + + case SQL_C_FLOAT: + len = 4; + if(cbValueMax >= len) + *((SFLOAT *)rgbValue) = (float) atof(value); + break; + + case SQL_C_DOUBLE: + len = 8; + if(cbValueMax >= len) + *((SDOUBLE *)rgbValue) = atof(value); + break; + + case SQL_C_SSHORT: + case SQL_C_SHORT: + len = 2; + if(cbValueMax >= len) + *((SWORD *)rgbValue) = atoi(value); + break; + + case SQL_C_USHORT: + len = 2; + if(cbValueMax >= len) + *((UWORD *)rgbValue) = atoi(value); + break; + + case SQL_C_SLONG: + case SQL_C_LONG: + len = 4; + if(cbValueMax >= len) + *((SDWORD *)rgbValue) = atol(value); + break; + + case SQL_C_ULONG: + len = 4; + if(cbValueMax >= len) + *((UDWORD *)rgbValue) = atol(value); + break; + + case SQL_C_BINARY: + // truncate if necessary + // convert octal escapes to bytes + len = convert_from_pgbinary(value, rgbValue, cbValueMax); + mylog("SQL_C_BINARY: len = %d\n", len); + break; + + default: + return COPY_UNSUPPORTED_TYPE; + } + } + } else { + /* handle a null just by returning SQL_NULL_DATA in pcbValue, */ + /* and doing nothing to the buffer. */ + if(pcbValue) { + *pcbValue = SQL_NULL_DATA; + } + } + + // store the length of what was copied, if there's a place for it + // unless it was a NULL (in which case it was already set above) + if(pcbValue && value) + *pcbValue = len; + + if(len > cbValueMax) { + mylog("!!! COPY_RESULT_TRUNCATED !!!\n"); + + // Don't return truncated because an application + // (like Access) will try to call GetData again + // to retrieve the rest of the data. Since we + // are not currently ready for this, and the result + // is an endless loop, we better just silently + // truncate the data. + // return COPY_RESULT_TRUNCATED; + + *pcbValue = cbValueMax -1; + return COPY_OK; + + } else { + + return COPY_OK; + } +} + +/* This function inserts parameters into an SQL statements. + It will also modify a SELECT statement for use with declare/fetch cursors. + This function no longer does any dynamic memory allocation! +*/ +int +copy_statement_with_parameters(StatementClass *stmt) +{ +unsigned int opos, npos; +char param_string[128], tmp[256], cbuf[TEXT_FIELD_SIZE+5]; +int param_number; +Int2 param_ctype, param_sqltype; +char *old_statement = stmt->statement; +char *new_statement = stmt->stmt_with_params; +SIMPLE_TIME st; +time_t t = time(NULL); +struct tm *tim; +SDWORD FAR *used; +char *buffer, *buf; + + + if ( ! old_statement) + 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; + + + + // For selects, prepend a declare cursor to the statement + if (stmt->statement_type == STMT_TYPE_SELECT) { + sprintf(new_statement, "declare C%u cursor for ", stmt); + npos = strlen(new_statement); + } + else { + new_statement[0] = '0'; + npos = 0; + } + + param_number = -1; + + for (opos = 0; opos < strlen(old_statement); opos++) { + + // Squeeze carriage-returns/linfeed pairs to linefeed only + if (old_statement[opos] == '\r' && opos+1= stmt->parameters_allocated) + break; + + /* 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; + buffer = stmt->parameters[param_number].EXEC_buffer; + } + else { + used = stmt->parameters[param_number].used; + buffer = stmt->parameters[param_number].buffer; + } + + /* Handle NULL parameter data */ + if (used && *used == SQL_NULL_DATA) { + strcpy(&new_statement[npos], "NULL"); + npos += 4; + continue; + } + + /* If no buffer, and its not null, then what the hell is it? + Just leave it alone then. + */ + if ( ! buffer) { + new_statement[npos++] = '?'; + 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, "%f", + *((SDOUBLE *) buffer)); + break; + + case SQL_C_FLOAT: + sprintf(param_string, "%f", + *((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, "'%s'", i ? "t" : "f"); + break; + } + + case SQL_C_DATE: { + DATE_STRUCT *ds = (DATE_STRUCT *) buffer; + st.m = ds->month; + st.d = ds->day; + st.y = ds->year; + + break; + } + + case SQL_C_TIME: { + TIME_STRUCT *ts = (TIME_STRUCT *) buffer; + st.hh = ts->hour; + st.mm = ts->minute; + st.ss = ts->second; + + break; + } + + case SQL_C_TIMESTAMP: { + 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; + + 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; + new_statement[npos] = '\0'; // just in case + 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: + + new_statement[npos++] = '\''; /* Open Quote */ + + /* it was a SQL_C_CHAR */ + if (buf) { + convert_returns(buf, &new_statement[npos], used ? *used : SQL_NTS); + npos += strlen(&new_statement[npos]); + } + + /* it was a numeric type */ + else if (param_string[0] != '\0') { + strcpy(&new_statement[npos], param_string); + npos += strlen(param_string); + } + + /* it was date,time,timestamp -- use m,d,y,hh,mm,ss */ + else { + char *buf = convert_time(&st); + strcpy(&new_statement[npos], buf); + npos += strlen(buf); + } + + new_statement[npos++] = '\''; /* Close Quote */ + + break; + + case SQL_DATE: + if (buf && used) { /* copy char data to time */ + my_strcpy(cbuf, sizeof(cbuf), buf, *used); + parse_datetime(cbuf, &st); + } + + sprintf(tmp, "'%.2d-%.2d-%.4d'", st.m, st.d, st.y); + + strcpy(&new_statement[npos], tmp); + npos += strlen(tmp); + break; + + case SQL_TIME: + if (buf && used) { /* 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); + + strcpy(&new_statement[npos], tmp); + npos += strlen(tmp); + break; + + case SQL_TIMESTAMP: { + char *tbuf; + + if (buf && used) { + my_strcpy(cbuf, sizeof(cbuf), buf, *used); + parse_datetime(cbuf, &st); + } + + tbuf = convert_time(&st); + + sprintf(&new_statement[npos], "'%s'", tbuf); + npos += strlen(tbuf) + 2; + + break; + } + + case SQL_BINARY: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: /* non-ascii characters should be converted to octal */ + + new_statement[npos++] = '\''; /* Open Quote */ + + mylog("SQL_LONGVARBINARY: about to call convert_to_pgbinary, *used = %d\n", *used); + + npos += convert_to_pgbinary(buf, &new_statement[npos], *used); + + new_statement[npos++] = '\''; /* Close Quote */ + + break; + + default: /* a numeric type */ + strcpy(&new_statement[npos], param_string); + npos += strlen(param_string); + break; + + } + + } /* end, for */ + + // make sure new_statement is always null-terminated + new_statement[npos] = '\0'; + + return SQL_SUCCESS; +} + + +// This function returns a pointer to static memory! +char * +convert_escape(char *value) +{ +char key[32], val[256]; +static char escape[256]; +SIMPLE_TIME st; + + sscanf(value, "%[^'] '%[^']'", key, val); + + mylog("convert_escape: key='%s', val='%s'\n", key, val); + + if ( ! strncmp(key, "d", 1)) { + sscanf(val, "%4d-%2d-%2d", &st.y, &st.m, &st.d); + sprintf(escape, "'%.2d-%.2d-%.4d'", st.m, st.d, st.y); + + } else if (! strncmp(key, "t", 1)) { + sprintf(escape, "'%s'", val); + + } else if (! strncmp(key, "ts", 2)) { + sscanf(val, "%4d-%2d-%2d %2d:%2d:%2d", &st.y, &st.m, &st.d, &st.hh, &st.mm, &st.ss); + strcpy(escape, convert_time(&st)); + } + else { + return NULL; + } + + return escape; + +} + + +int +monthToNumber(char *mon) +{ +int m = 0; + + if ( ! stricmp(mon, "Jan")) + m = 1; + else if ( ! stricmp(mon, "Feb")) + m = 2; + else if ( ! stricmp(mon, "Mar")) + m = 3; + else if ( ! stricmp(mon, "Apr")) + m = 4; + else if ( ! stricmp(mon, "May")) + m = 5; + else if ( ! stricmp(mon, "Jun")) + m = 6; + else if ( ! stricmp(mon, "Jul")) + m = 7; + else if ( ! stricmp(mon, "Aug")) + m = 8; + else if ( ! stricmp(mon, "Sep")) + m = 9; + else if ( ! stricmp(mon, "Oct")) + m = 10; + else if ( ! stricmp(mon, "Nov")) + m = 11; + else if ( ! stricmp(mon, "Dec")) + m = 12; + + return m; +} + + +char * +convert_money(char *s) +{ +size_t i = 0, out = 0; + + for (i = 0; i < strlen(s); i++) { + if (s[i] == '$' || s[i] == ',' || s[i] == ')') + ; // skip these characters + else if (s[i] == '(') + s[out++] = '-'; + else + s[out++] = s[i]; + } + s[out] = '\0'; + return s; +} + +/* Convert a discrete time into a localized string */ +char * +convert_time(SIMPLE_TIME *st) +{ +struct tm tim; +static char buf[1024]; + +mylog("convert_time: 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); + + memset(&tim, 0, sizeof(tim)); + + tim.tm_mon = st->m - 1; + tim.tm_mday = st->d; + tim.tm_year = st->y - 1900; + tim.tm_hour = st->hh; + tim.tm_min = st->mm; + tim.tm_sec = st->ss; + + /* Dont bother trying to figure out the day of week because + postgres will determine it correctly. However, the timezone + should be taken into account. $$$$ + */ + + // tim.tm_isdst = _daylight; + + strftime(buf, sizeof(buf), "%b %d %H:%M:%S %Y", + &tim); + +mylog("convert_time: buf = '%s'\n", buf); + + return buf; +} + +/* 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; + + 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 */ +char * +convert_linefeeds(char *si, char *dst, size_t max) +{ +size_t i = 0, out = 0; +static char sout[TEXT_FIELD_SIZE+5]; +char *p; + + if (dst) + p = dst; + else { + p = sout; + max = sizeof(sout); + } + + p[0] = '\0'; + + for (i = 0; i < strlen(si) && out < max-2; i++) { + if (si[i] == '\n') { + p[out++] = '\r'; + p[out++] = '\n'; + } + else + p[out++] = si[i]; + } + p[out] = '\0'; + return p; +} + +/* Change carriage-return/linefeed to just linefeed */ +char * +convert_returns(char *si, char *dst, int used) +{ +size_t i = 0, out = 0, max; +static char sout[TEXT_FIELD_SIZE+5]; +char *p; + + if (dst) + p = dst; + else + p = sout; + + p[0] = '\0'; + + if (used == SQL_NTS) + max = strlen(si); + else + max = used; + + for (i = 0; i < max; i++) { + if (si[i] == '\r' && i+1 < strlen(si) && si[i+1] == '\n') + continue; + else + p[out++] = si[i]; + } + p[out] = '\0'; + return p; +} + +int +convert_pgbinary_to_char(char *value, char *rgbValue, int cbValueMax) +{ + return 0; +} + +unsigned int +conv_from_octal(unsigned char *s) +{ +int i, y=0; + + for (i = 1; i <= 3; i++) { + y += (s[i] - 48) * (int) pow(8, 3-i); + } + + return y; + +} + +// convert octal escapes to bytes +int +convert_from_pgbinary(unsigned char *value, unsigned char *rgbValue, int cbValueMax) +{ +size_t i; +int o=0; + + for (i = 0; i < strlen(value); ) { + if (value[i] == '\\') { + 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++; + } + return o; +} + + +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(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 (in[i] < 32 || in[i] > 126) { + strcpy(&out[o], conv_to_octal(in[i])); + o += 5; + } + else + out[o++] = in[i]; + + } + + mylog("convert_to_pgbinary: returning %d, out='%.*s'\n", o, o, out); + + return o; +} + diff --git a/src/interfaces/odbc/convert.h b/src/interfaces/odbc/convert.h new file mode 100644 index 0000000000..2826589a6a --- /dev/null +++ b/src/interfaces/odbc/convert.h @@ -0,0 +1,47 @@ + +/* 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 + +typedef struct { + int m; + int d; + int y; + int hh; + int mm; + int ss; +} SIMPLE_TIME; + +int copy_and_convert_field_bindinfo(Int4 field_type, void *value, BindInfoClass *bic); +int copy_and_convert_field(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); +char *convert_money(char *s); +int monthToNumber(char *mon); +char *convert_time(SIMPLE_TIME *st); +char parse_datetime(char *buf, SIMPLE_TIME *st); +char *convert_linefeeds(char *s, char *dst, size_t max); +char *convert_returns(char *si, char *dst, int used); + +int convert_pgbinary_to_char(char *value, char *rgbValue, int cbValueMax); +int convert_from_pgbinary(unsigned char *value, unsigned char *rgbValue, int cbValueMax); +int convert_to_pgbinary(unsigned char *in, char *out, int len); + +#endif diff --git a/src/interfaces/odbc/drvconn.c b/src/interfaces/odbc/drvconn.c new file mode 100644 index 0000000000..ee0d8252d7 --- /dev/null +++ b/src/interfaces/odbc/drvconn.c @@ -0,0 +1,327 @@ + +/* 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 +#include + +#include "psqlodbc.h" +#include "connection.h" + +#include +#include +#include +#include +#include +#include +#include "resource.h" + +/* prototypes */ +BOOL FAR PASCAL dconn_FDriverConnectProc(HWND hdlg, UINT wMsg, WPARAM wParam, LPARAM lParam); +RETCODE dconn_DoDialog(HWND hwnd, ConnInfo *ci); +void dconn_get_connect_attributes(UCHAR FAR *connect_string, ConnInfo *ci); + + +extern HINSTANCE NEAR s_hModule; /* Saved module handle. */ +extern GLOBAL_VALUES globals; + +RETCODE SQL_API SQLDriverConnect( + HDBC hdbc, + HWND hwnd, + UCHAR FAR *szConnStrIn, + SWORD cbConnStrIn, + UCHAR FAR *szConnStrOut, + SWORD cbConnStrOutMax, + SWORD FAR *pcbConnStrOut, + UWORD fDriverCompletion) +{ +ConnectionClass *conn = (ConnectionClass *) hdbc; +ConnInfo *ci; +RETCODE dialog_result; +char connect_string[MAX_CONNECT_STRING]; +int retval; +char password_required = FALSE; + + mylog("**** SQLDriverConnect: fDriverCompletion=%d, connStrIn='%s'\n", fDriverCompletion, szConnStrIn); + + if ( ! conn) + return SQL_INVALID_HANDLE; + + qlog("conn=%u, SQLDriverConnect( in)='%s'\n", conn, szConnStrIn); + + ci = &(conn->connInfo); + + // Parse the connect string and fill in conninfo for this hdbc. + dconn_get_connect_attributes(szConnStrIn, 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!) + CC_DSN_info(conn, CONN_DONT_OVERWRITE); + + // Fill in any default parameters if they are not there. + CC_set_defaults(conn); + +dialog: + ci->focus_password = password_required; + + switch(fDriverCompletion) { + case SQL_DRIVER_PROMPT: + dialog_result = dconn_DoDialog(hwnd, ci); + if(dialog_result != SQL_SUCCESS) { + return dialog_result; + } + break; + + case SQL_DRIVER_COMPLETE: + case SQL_DRIVER_COMPLETE_REQUIRED: + /* 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; + case SQL_DRIVER_NOPROMPT: + break; + } + + /* Password is not a required parameter unless authentication asks for it. + For now, I think its 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; + } + + if(szConnStrOut) { + + // return the completed string to the caller. + + char got_dsn = (ci->dsn[0] != '\0'); + + sprintf(connect_string, "%s=%s;DATABASE=%s;SERVER=%s;PORT=%s;UID=%s;READONLY=%s;PWD=%s;PROTOCOL=%s;CONNSETTINGS=%s", + got_dsn ? "DSN" : "DRIVER", + got_dsn ? ci->dsn : ci->driver, + ci->database, + ci->server, + ci->port, + ci->username, + ci->readonly, + ci->password, + ci->protocol, + ci->conn_settings); + + if(pcbConnStrOut) { + *pcbConnStrOut = strlen(connect_string); + } + strncpy_null(szConnStrOut, connect_string, cbConnStrOutMax); + } + + mylog("szConnStrOut = '%s'\n", szConnStrOut); + qlog("conn=%u, SQLDriverConnect(out)='%s'\n", conn, szConnStrOut); + + // do the actual connect + retval = CC_connect(conn, password_required); + if (retval < 0) { /* need a password */ + if (fDriverCompletion == SQL_DRIVER_NOPROMPT) + return SQL_ERROR; /* need a password but not allowed to prompt so error */ + else { + password_required = TRUE; + goto dialog; + } + } + else if (retval == 0) { + // error msg filled in above + return SQL_ERROR; + } + + mylog("SQLDRiverConnect: returning success\n"); + return SQL_SUCCESS; +} + + +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(DRIVERCONNDIALOG), + 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) +{ +static ConnInfo *ci; + + switch (wMsg) { + case WM_INITDIALOG: + ci = (ConnInfo *) lParam; // Save the ConnInfo for the "OK" + + SetDlgItemText(hdlg, SERVER_EDIT, ci->server); + SetDlgItemText(hdlg, DATABASE_EDIT, ci->database); + SetDlgItemText(hdlg, USERNAME_EDIT, ci->username); + SetDlgItemText(hdlg, PASSWORD_EDIT, ci->password); + SetDlgItemText(hdlg, PORT_EDIT, ci->port); + CheckDlgButton(hdlg, READONLY_EDIT, atoi(ci->readonly)); + + CheckDlgButton(hdlg, PG62_EDIT, PROTOCOL_62(ci)); + + /* The driver connect dialog box allows manipulating this global variable */ + CheckDlgButton(hdlg, COMMLOG_EDIT, globals.commlog); + + if (ci->database[0] == '\0') + ; /* default focus */ + else if (ci->server[0] == '\0') + SetFocus(GetDlgItem(hdlg, SERVER_EDIT)); + else if (ci->port[0] == '\0') + SetFocus(GetDlgItem(hdlg, PORT_EDIT)); + else if (ci->username[0] == '\0') + SetFocus(GetDlgItem(hdlg, USERNAME_EDIT)); + else if (ci->focus_password) + SetFocus(GetDlgItem(hdlg, PASSWORD_EDIT)); + + break; + + case WM_COMMAND: + switch (GET_WM_COMMAND_ID(wParam, lParam)) { + case IDOK: + + GetDlgItemText(hdlg, SERVER_EDIT, ci->server, sizeof(ci->server)); + GetDlgItemText(hdlg, DATABASE_EDIT, ci->database, sizeof(ci->database)); + GetDlgItemText(hdlg, USERNAME_EDIT, ci->username, sizeof(ci->username)); + GetDlgItemText(hdlg, PASSWORD_EDIT, ci->password, sizeof(ci->password)); + GetDlgItemText(hdlg, PORT_EDIT, ci->port, sizeof(ci->port)); + + sprintf(ci->readonly, "%d", IsDlgButtonChecked(hdlg, READONLY_EDIT)); + + if (IsDlgButtonChecked(hdlg, PG62_EDIT)) + strcpy(ci->protocol, PG62); + else + ci->protocol[0] = '\0'; + + /* The driver connect dialog box allows manipulating this global variable */ + globals.commlog = IsDlgButtonChecked(hdlg, COMMLOG_EDIT); + updateGlobals(); + + case IDCANCEL: + EndDialog(hdlg, GET_WM_COMMAND_ID(wParam, lParam) == IDOK); + return TRUE; + } + } + + return FALSE; +} + + + +void dconn_get_connect_attributes(UCHAR FAR *connect_string, ConnInfo *ci) +{ +char *our_connect_string; +char *pair, *attribute, *value, *equals; +char *strtok_arg; + + memset(ci, 0, sizeof(ConnInfo)); + + 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; + + /*********************************************************/ + /* PARSE ATTRIBUTES */ + /*********************************************************/ + if(stricmp(attribute, "DSN") == 0) + strcpy(ci->dsn, value); + + else if(stricmp(attribute, "driver") == 0) + strcpy(ci->driver, value); + + else if(stricmp(attribute, "uid") == 0) + strcpy(ci->username, value); + + else if(stricmp(attribute, "pwd") == 0) + strcpy(ci->password, value); + + else if ((stricmp(attribute, "server") == 0) || + (stricmp(attribute, "servername") == 0)) + strcpy(ci->server, value); + + else if(stricmp(attribute, "port") == 0) + strcpy(ci->port, value); + + else if(stricmp(attribute, "database") == 0) + strcpy(ci->database, value); + + else if (stricmp(attribute, "readonly") == 0) + strcpy(ci->readonly, value); + + else if (stricmp(attribute, "protocol") == 0) + strcpy(ci->protocol, value); + + else if (stricmp(attribute, "connsettings") == 0) + strcpy(ci->conn_settings, value); + + } + + + free(our_connect_string); +} diff --git a/src/interfaces/odbc/environ.c b/src/interfaces/odbc/environ.c new file mode 100644 index 0000000000..b7d808476f --- /dev/null +++ b/src/interfaces/odbc/environ.c @@ -0,0 +1,416 @@ + +/* 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 "statement.h" +#include +#include + +/* The one instance of the handles */ +ConnectionClass *conns[MAX_CONNECTIONS]; + + +RETCODE SQL_API SQLAllocEnv(HENV FAR *phenv) +{ +mylog("**** in SQLAllocEnv ** \n"); + + *phenv = (HENV) EN_Constructor(); + if ( ! *phenv) { + *phenv = SQL_NULL_HENV; + return SQL_ERROR; + } + + mylog("** exit SQLAllocEnv: phenv = %u **\n", *phenv); + return SQL_SUCCESS; +} + +RETCODE SQL_API SQLFreeEnv(HENV henv) +{ +EnvironmentClass *env = (EnvironmentClass *) henv; + +mylog("**** in SQLFreeEnv: env = %u ** \n", env); + + if (env && EN_Destructor(env)) { + mylog(" ok\n"); + return SQL_SUCCESS; + } + + mylog(" error\n"); + return SQL_ERROR; +} + +// Returns the next SQL error information. + +RETCODE SQL_API SQLError( + HENV henv, + HDBC hdbc, + HSTMT hstmt, + UCHAR FAR *szSqlState, + SDWORD FAR *pfNativeError, + UCHAR FAR *szErrorMsg, + SWORD cbErrorMsgMax, + SWORD FAR *pcbErrorMsg) +{ +char *msg; +int status; + + mylog("**** SQLError: henv=%u, hdbc=%u, hstmt=%u\n", henv, hdbc, hstmt); + + if (SQL_NULL_HSTMT != hstmt) { + // CC: return an error of a hstmt + StatementClass *stmt = (StatementClass *) hstmt; + + if (NULL == stmt) + return SQL_INVALID_HANDLE; + + 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; + } + if (NULL != pcbErrorMsg) + *pcbErrorMsg = (SWORD)strlen(msg); + + 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_TRUNCATED: + strcpy(szSqlState, "01004"); + // data truncated + break; + case STMT_INFO_ONLY: + strcpy(szSqlState, "00000"); + // just information that is returned, no error + break; + case STMT_EXEC_ERROR: + strcpy(szSqlState, "08S01"); + // communication link failure + 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_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; + 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; + } + 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; + } + + if (NULL != pcbErrorMsg) + *pcbErrorMsg = (SWORD)strlen(msg); + if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0)) + strncpy_null(szErrorMsg, msg, cbErrorMsgMax); + if (NULL != pfNativeError) + *pfNativeError = status; + + if (NULL != szSqlState) + switch(status) { + 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: + strcpy(szSqlState, "S1C00"); + 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; + } + 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]); + } + + mylog("exit EN_Destructor: rv = %d\n", rv); + 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; +} diff --git a/src/interfaces/odbc/environ.h b/src/interfaces/odbc/environ.h new file mode 100644 index 0000000000..0989163506 --- /dev/null +++ b/src/interfaces/odbc/environ.h @@ -0,0 +1,32 @@ + +/* 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" +#include +#include + +#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); + +#endif diff --git a/src/interfaces/odbc/execute.c b/src/interfaces/odbc/execute.c new file mode 100644 index 0000000000..6afbe308c9 --- /dev/null +++ b/src/interfaces/odbc/execute.c @@ -0,0 +1,535 @@ + +/* 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 +#include + +#include "connection.h" +#include "statement.h" +#include "qresult.h" +#include "convert.h" +#include "bind.h" + + +// Perform a Prepare on the SQL statement +RETCODE SQL_API SQLPrepare(HSTMT hstmt, + UCHAR FAR *szSqlStr, + SDWORD cbSqlStr) +{ +StatementClass *self = (StatementClass *) hstmt; + + if ( ! self) + return SQL_INVALID_HANDLE; + + /* CC: 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("**** SQLPrepare: STMT_PREMATURE, recycle\n"); + + SC_recycle_statement(self); /* recycle the statement, but do not remove parameter bindings */ + + /* NO Break! -- Contiue the same way as with a newly allocated statement ! */ + + case STMT_ALLOCATED: + // it is not really necessary to do any conversion of the statement + // here--just copy it, and deal with it when it's ready to be + // executed. + mylog("**** SQLPrepare: STMT_ALLOCATED, copy\n"); + + self->statement = make_string(szSqlStr, cbSqlStr, NULL); + if ( ! self->statement) { + self->errornumber = STMT_NO_MEMORY_ERROR; + self->errormsg = "No memory available to store statement"; + return SQL_ERROR; + } + + self->statement_type = statement_type(self->statement); + + // Check if connection is readonly (only selects are allowed) + if ( CC_is_readonly(self->hdbc) && self->statement_type != STMT_TYPE_SELECT ) { + self->errornumber = STMT_EXEC_ERROR; + self->errormsg = "Connection is readonly, only select statements are allowed."; + return SQL_ERROR; + } + + self->prepare = TRUE; + self->status = STMT_READY; + + return SQL_SUCCESS; + + case STMT_READY: /* SQLPrepare has already been called -- Just changed the SQL statement that is assigned to the handle */ + mylog("**** SQLPrepare: STMT_READY, change SQL\n"); + + 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"; + return SQL_ERROR; + } + + self->prepare = TRUE; + self->statement_type = statement_type(self->statement); + + // Check if connection is readonly (only selects are allowed) + if ( CC_is_readonly(self->hdbc) && self->statement_type != STMT_TYPE_SELECT ) { + self->errornumber = STMT_EXEC_ERROR; + self->errormsg = "Connection is readonly, only select statements are allowed."; + return SQL_ERROR; + } + + return SQL_SUCCESS; + + case STMT_FINISHED: + mylog("**** SQLPrepare: STMT_FINISHED\n"); + /* No BREAK: continue as with STMT_EXECUTING */ + + case STMT_EXECUTING: + mylog("**** SQLPrepare: STMT_EXECUTING, error!\n"); + + self->errornumber = STMT_SEQUENCE_ERROR; + self->errormsg = "SQLPrepare(): The handle does not point to a statement that is ready to be executed"; + + return SQL_ERROR; + + default: + self->errornumber = STMT_INTERNAL_ERROR; + self->errormsg = "An Internal Error has occured -- Unknown statement status."; + return SQL_ERROR; + } +} + +// - - - - - - - - - + +// Performs the equivalent of SQLPrepare, followed by SQLExecute. + +RETCODE SQL_API SQLExecDirect( + HSTMT hstmt, + UCHAR FAR *szSqlStr, + SDWORD cbSqlStr) +{ +StatementClass *stmt = (StatementClass *) hstmt; + + if ( ! stmt) + 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"; + return SQL_ERROR; + } + + mylog("**** SQLExecDirect: hstmt=%u, statement='%s'\n", hstmt, stmt->statement); + + stmt->prepare = FALSE; + stmt->statement_type = statement_type(stmt->statement); + + // Check if connection is readonly (only selects are allowed) + if ( CC_is_readonly(stmt->hdbc) && stmt->statement_type != STMT_TYPE_SELECT ) { + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "Connection is readonly, only select statements are allowed."; + return SQL_ERROR; + } + + mylog("SQLExecDirect: calling SQLExecute\n"); + + return SQLExecute(hstmt); +} + +// Execute a prepared SQL statement +RETCODE SQL_API SQLExecute( + HSTMT hstmt) +{ +StatementClass *stmt = (StatementClass *) hstmt; +ConnectionClass *conn; +int i, retval; + + + if ( ! stmt) + 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) { + stmt->status = STMT_FINISHED; + return stmt->errormsg == NULL ? SQL_SUCCESS : SQL_ERROR; + } + + 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; + return SQL_ERROR; + } + + if ( ! stmt->statement) { + stmt->errornumber = STMT_NO_STMTSTRING; + stmt->errormsg = "This handle does not have a SQL statement stored in it"; + 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) { + 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"; + return SQL_ERROR; + } + + + /* 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++) { + /* 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("SQLExecute: copying statement params: trans_status=%d, len=%d, stmt='%s'\n", 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); + + + return SC_execute(stmt); + +} + + + + +// - - - - - - - - - +RETCODE SQL_API SQLTransact( + HENV henv, + HDBC hdbc, + UWORD fType) +{ +extern ConnectionClass *conns[]; +ConnectionClass *conn; +QResultClass *res; +char ok, *stmt_string; +int lf; + +mylog("**** SQLTransact: hdbc=%u, henv=%u\n", hdbc, henv); + + if (hdbc == SQL_NULL_HDBC && henv == SQL_NULL_HENV) + 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 henv == henv) + if ( SQLTransact(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 ="SQLTransact can only be called with SQL_COMMIT or SQL_ROLLBACK as parameter"; + return SQL_ERROR; + } + + /* If manual commit and in transaction, then proceed. */ + if ( ! CC_is_in_autocommit(conn) && CC_is_in_trans(conn)) { + + mylog("SQLTransact: sending on conn %d '%s'\n", conn, stmt_string); + + res = CC_send_query(conn, stmt_string, NULL, NULL); + CC_set_no_trans(conn); + + if ( ! res) + // error msg will be in the connection + return SQL_ERROR; + + ok = QR_command_successful(res); + QR_Destructor(res); + + if (!ok) + return SQL_ERROR; + } + return SQL_SUCCESS; +} + +// - - - - - - - - - + + +RETCODE SQL_API SQLCancel( + HSTMT hstmt) // Statement to cancel. +{ +StatementClass *stmt = (StatementClass *) hstmt; + + // Check if this can handle canceling in the middle of a SQLPutData? + if ( ! stmt) + return SQL_INVALID_HANDLE; + + // Not in the middle of SQLParamData/SQLPutData so cancel like a close. + if (stmt->data_at_exec < 0) + return SQLFreeStmt(hstmt, SQL_CLOSE); + + // 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; + +} + +// - - - - - - - - - + +// Returns the SQL string as modified by the driver. + +RETCODE SQL_API SQLNativeSql( + HDBC hdbc, + UCHAR FAR *szSqlStrIn, + SDWORD cbSqlStrIn, + UCHAR FAR *szSqlStr, + SDWORD cbSqlStrMax, + SDWORD FAR *pcbSqlStr) +{ + + strncpy_null(szSqlStr, szSqlStrIn, cbSqlStrMax); + + return SQL_SUCCESS; +} + +// - - - - - - - - - + +// Supplies parameter data at execution time. Used in conjuction with +// SQLPutData. + +RETCODE SQL_API SQLParamData( + HSTMT hstmt, + PTR FAR *prgbValue) +{ +StatementClass *stmt = (StatementClass *) hstmt; +int i, retval; + + if ( ! stmt) + return SQL_INVALID_HANDLE; + + if (stmt->data_at_exec < 0) { + stmt->errornumber = STMT_SEQUENCE_ERROR; + stmt->errormsg = "No execution-time parameters for this statement"; + 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"; + return SQL_ERROR; + } + + /* 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; + + return SC_execute(stmt); + } + + /* At least 1 data at execution parameter, so Fill in the token value */ + for (i = 0; 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 */ + } + } + + return SQL_NEED_DATA; +} + +// - - - - - - - - - + +// Supplies parameter data at execution time. Used in conjunction with +// SQLParamData. + +RETCODE SQL_API SQLPutData( + HSTMT hstmt, + PTR rgbValue, + SDWORD cbValue) +{ +StatementClass *stmt = (StatementClass *) hstmt; +char *buffer; +SDWORD *used; +int old_pos; + + + if ( ! stmt) + return SQL_INVALID_HANDLE; + + + if (stmt->current_exec_param < 0) { + stmt->errornumber = STMT_SEQUENCE_ERROR; + stmt->errormsg = "Previous call was not SQLPutData or SQLParamData"; + return SQL_ERROR; + } + + if ( ! stmt->put_data) { /* first call */ + + mylog("SQLPutData: (1) cbValue = %d\n", cbValue); + + stmt->put_data = TRUE; + + used = (SDWORD *) malloc(sizeof(SDWORD)); + if ( ! used) { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Out of memory in SQLPutData (1)"; + return SQL_ERROR; + } + + *used = cbValue; + stmt->parameters[stmt->current_exec_param].EXEC_used = used; + + if (cbValue == SQL_NULL_DATA) + return SQL_SUCCESS; + + if (cbValue == SQL_NTS) { + buffer = strdup(rgbValue); + if ( ! buffer) { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Out of memory in SQLPutData (2)"; + return SQL_ERROR; + } + } + else { + buffer = malloc(cbValue + 1); + if ( ! buffer) { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Out of memory in SQLPutData (2)"; + return SQL_ERROR; + } + memcpy(buffer, rgbValue, cbValue); + buffer[cbValue] = '\0'; + } + + stmt->parameters[stmt->current_exec_param].EXEC_buffer = buffer; + } + + else { /* calling SQLPutData more than once */ + + mylog("SQLPutData: (>1) cbValue = %d\n", cbValue); + + used = stmt->parameters[stmt->current_exec_param].EXEC_used; + buffer = stmt->parameters[stmt->current_exec_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 SQLPutData (3)"; + return SQL_ERROR; + } + strcat(buffer, rgbValue); + + mylog(" cbValue = SQL_NTS: strlen(buffer) = %d\n", strlen(buffer)); + + *used = cbValue; + + } + else if (cbValue > 0) { + + old_pos = *used; + + *used += cbValue; + + mylog(" cbValue = %d, old_pos = %d, *used = %d\n", cbValue, old_pos, *used); + + buffer = realloc(buffer, *used + 1); + if ( ! buffer) { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Out of memory in SQLPutData (3)"; + return SQL_ERROR; + } + + memcpy(&buffer[old_pos], rgbValue, cbValue); + buffer[*used] = '\0'; + + } + else + return SQL_ERROR; + + + /* reassign buffer incase realloc moved it */ + stmt->parameters[stmt->current_exec_param].EXEC_buffer = buffer; + + } + + + return SQL_SUCCESS; +} + + diff --git a/src/interfaces/odbc/info.c b/src/interfaces/odbc/info.c new file mode 100644 index 0000000000..b87efbd33d --- /dev/null +++ b/src/interfaces/odbc/info.c @@ -0,0 +1,2180 @@ + +/* 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 +#include +#include "psqlodbc.h" +#include +#include +#include +#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" + +// - - - - - - - - - + +RETCODE SQL_API SQLGetInfo( + HDBC hdbc, + UWORD fInfoType, + PTR rgbInfoValue, + SWORD cbInfoValueMax, + SWORD FAR *pcbInfoValue) +{ +ConnectionClass *conn = (ConnectionClass *) hdbc; +char *p; + + if ( ! conn) + return SQL_INVALID_HANDLE; + + /* CC: Some sanity checks */ + if ((NULL == (char *)rgbInfoValue) || + (cbInfoValueMax == 0)) + + /* removed: */ + /* || (NULL == pcbInfoValue) */ + + /* pcbInfoValue is ignored for non-character output. */ + /* some programs (at least Microsoft Query) seem to just send a NULL, */ + /* so let them get away with it... */ + + return SQL_INVALID_HANDLE; + + + switch (fInfoType) { + case SQL_ACCESSIBLE_PROCEDURES: /* ODBC 1.0 */ + // can the user call all functions returned by SQLProcedures? + // I assume access permissions could prevent this in some cases(?) + // anyway, SQLProcedures doesn't exist yet. + *pcbInfoValue = 1; + strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax); + break; + + case SQL_ACCESSIBLE_TABLES: /* ODBC 1.0 */ + // is the user guaranteed "SELECT" on every table? + *pcbInfoValue = 1; + strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax); + break; + + case SQL_ACTIVE_CONNECTIONS: /* ODBC 1.0 */ + // how many simultaneous connections do we support? + *((WORD *)rgbInfoValue) = MAX_CONNECTIONS; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_ACTIVE_STATEMENTS: /* ODBC 1.0 */ + // no limit on the number of active statements. + *((WORD *)rgbInfoValue) = (WORD)0; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_ALTER_TABLE: /* ODBC 2.0 */ + // what does 'alter table' support? (bitmask) + // postgres doesn't seem to let you drop columns. + *((DWORD *)rgbInfoValue) = SQL_AT_ADD_COLUMN; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_BOOKMARK_PERSISTENCE: /* ODBC 2.0 */ + // through what operations do bookmarks persist? (bitmask) + // bookmarks don't exist yet, so they're not very persistent. + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_COLUMN_ALIAS: /* ODBC 2.0 */ + // do we support column aliases? guess not. + *pcbInfoValue = 1; + strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax); + break; + + case SQL_CONCAT_NULL_BEHAVIOR: /* ODBC 1.0 */ + // how does concatenation work with NULL columns? + // not sure how you do concatentation, but this way seems + // more reasonable + *((WORD *)rgbInfoValue) = SQL_CB_NON_NULL; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + // which types of data-conversion do we support? + // currently we don't support any, except converting a type + // to itself. + 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 */ + // only return the type we were called with (bitmask) + *((DWORD *)rgbInfoValue) = fInfoType; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_CONVERT_FUNCTIONS: /* ODBC 1.0 */ + // which conversion functions do we support? (bitmask) + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_CORRELATION_NAME: /* ODBC 1.0 */ + // I don't know what a correlation name is, so I guess we don't + // support them. + + // *((WORD *)rgbInfoValue) = (WORD)SQL_CN_NONE; + + // well, let's just say we do--otherwise Query won't work. + *((WORD *)rgbInfoValue) = (WORD)SQL_CN_ANY; + if(pcbInfoValue) { *pcbInfoValue = 2; } + + break; + + case SQL_CURSOR_COMMIT_BEHAVIOR: /* ODBC 1.0 */ + // postgres definitely closes cursors when a transaction ends, + // but you shouldn't have to re-prepare a statement after + // commiting a transaction (I don't think) + *((WORD *)rgbInfoValue) = (WORD)SQL_CB_CLOSE; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_CURSOR_ROLLBACK_BEHAVIOR: /* ODBC 1.0 */ + // see above + *((WORD *)rgbInfoValue) = (WORD)SQL_CB_CLOSE; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_DATA_SOURCE_NAME: /* ODBC 1.0 */ + p = CC_get_DSN(conn); + if (pcbInfoValue) *pcbInfoValue = strlen(p); + strncpy_null((char *)rgbInfoValue, p, (size_t)cbInfoValueMax); + break; + + case SQL_DATA_SOURCE_READ_ONLY: /* ODBC 1.0 */ + if (pcbInfoValue) *pcbInfoValue = 1; + sprintf((char *)rgbInfoValue, "%c", CC_is_readonly(conn) ? 'Y' : 'N'); + break; + + case SQL_DATABASE_NAME: /* Support for old ODBC 1.0 Apps */ + // case SQL_CURRENT_QUALIFIER: + // this tag doesn't seem to be in ODBC 2.0, and it conflicts + // with a valid tag (SQL_TIMEDATE_ADD_INTERVALS). + + p = CC_get_database(conn); + if (pcbInfoValue) *pcbInfoValue = strlen(p); + strncpy_null((char *)rgbInfoValue, p, (size_t)cbInfoValueMax); + break; + + case SQL_DBMS_NAME: /* ODBC 1.0 */ + if (pcbInfoValue) *pcbInfoValue = 10; + strncpy_null((char *)rgbInfoValue, DBMS_NAME, (size_t)cbInfoValueMax); + break; + + case SQL_DBMS_VER: /* ODBC 1.0 */ + if (pcbInfoValue) *pcbInfoValue = 25; + strncpy_null((char *)rgbInfoValue, DBMS_VERSION, (size_t)cbInfoValueMax); + break; + + case SQL_DEFAULT_TXN_ISOLATION: /* ODBC 1.0 */ + // are dirty reads, non-repeatable reads, and phantoms possible? (bitmask) + // by direct experimentation they are not. postgres forces + // the newer transaction to wait before doing something that + // would cause one of these problems. + *((DWORD *)rgbInfoValue) = SQL_TXN_SERIALIZABLE; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_DRIVER_NAME: /* ODBC 1.0 */ + // this should be the actual filename of the driver + p = DRIVER_FILE_NAME; + if (pcbInfoValue) *pcbInfoValue = strlen(p); + strncpy_null((char *)rgbInfoValue, p, (size_t)cbInfoValueMax); + break; + + case SQL_DRIVER_ODBC_VER: + /* I think we should return 02.00--at least, that is the version of the */ + /* spec I'm currently referring to. */ + if (pcbInfoValue) *pcbInfoValue = 5; + strncpy_null((char *)rgbInfoValue, "02.00", (size_t)cbInfoValueMax); + break; + + case SQL_DRIVER_VER: /* ODBC 1.0 */ + p = POSTGRESDRIVERVERSION; + if (pcbInfoValue) *pcbInfoValue = strlen(p); + strncpy_null((char *)rgbInfoValue, p, (size_t)cbInfoValueMax); + break; + + case SQL_EXPRESSIONS_IN_ORDERBY: /* ODBC 1.0 */ + // can you have expressions in an 'order by' clause? + // not sure about this. say no for now. + if (pcbInfoValue) *pcbInfoValue = 1; + strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax); + break; + + case SQL_FETCH_DIRECTION: /* ODBC 1.0 */ + // which fetch directions are supported? (bitmask) + // I guess these apply to SQLExtendedFetch? + *((DWORD *)rgbInfoValue) = SQL_FETCH_NEXT || + SQL_FETCH_FIRST || + SQL_FETCH_LAST || + SQL_FETCH_PRIOR || + SQL_FETCH_ABSOLUTE; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_FILE_USAGE: /* ODBC 2.0 */ + // we are a two-tier driver, not a file-based one. + *((WORD *)rgbInfoValue) = (WORD)SQL_FILE_NOT_SUPPORTED; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_GETDATA_EXTENSIONS: /* ODBC 2.0 */ + // (bitmask) + *((DWORD *)rgbInfoValue) = (SQL_GD_ANY_COLUMN | SQL_GD_ANY_ORDER | SQL_GD_BOUND); + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_GROUP_BY: /* ODBC 2.0 */ + // how do the columns selected affect the columns you can group by? + *((WORD *)rgbInfoValue) = SQL_GB_GROUP_BY_EQUALS_SELECT; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_IDENTIFIER_CASE: /* ODBC 1.0 */ + // are identifiers case-sensitive (yes) + *((WORD *)rgbInfoValue) = SQL_IC_SENSITIVE; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_IDENTIFIER_QUOTE_CHAR: /* ODBC 1.0 */ + // the character used to quote "identifiers" (what are they?) + // the manual index lists 'owner names' and 'qualifiers' as + // examples of identifiers. it says return a blank for no + // quote character, we'll try that... + if (pcbInfoValue) *pcbInfoValue = 1; + strncpy_null((char *)rgbInfoValue, " ", (size_t)cbInfoValueMax); + break; + + case SQL_KEYWORDS: /* ODBC 2.0 */ + // do this later + conn->errormsg = "SQL_KEYWORDS parameter to SQLGetInfo not implemented."; + conn->errornumber = CONN_NOT_IMPLEMENTED_ERROR; + return SQL_ERROR; + 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 + if (pcbInfoValue) *pcbInfoValue = 1; + strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax); + break; + + case SQL_LOCK_TYPES: /* ODBC 2.0 */ + // which lock types does SQLSetPos support? (bitmask) + // SQLSetPos doesn't exist yet + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_MAX_BINARY_LITERAL_LEN: /* ODBC 2.0 */ + // the maximum length of a query is 2k, so maybe we should + // set the maximum length of all these literals to that value? + // for now just use zero for 'unknown or no limit' + + // maximum length of a binary literal + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_MAX_CHAR_LITERAL_LEN: /* ODBC 2.0 */ + // maximum length of a character literal + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_MAX_COLUMN_NAME_LEN: /* ODBC 1.0 */ + // maximum length of a column name + *((WORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_MAX_COLUMNS_IN_GROUP_BY: /* ODBC 2.0 */ + // maximum number of columns in a 'group by' clause + *((WORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_MAX_COLUMNS_IN_INDEX: /* ODBC 2.0 */ + // maximum number of columns in an index + *((WORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_MAX_COLUMNS_IN_ORDER_BY: /* ODBC 2.0 */ + // maximum number of columns in an ORDER BY statement + *((WORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_MAX_COLUMNS_IN_SELECT: /* ODBC 2.0 */ + // I think you get the idea by now + *((WORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_MAX_COLUMNS_IN_TABLE: /* ODBC 2.0 */ + *((WORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_MAX_CURSOR_NAME_LEN: /* ODBC 1.0 */ + *((WORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_MAX_INDEX_SIZE: /* ODBC 2.0 */ + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_MAX_OWNER_NAME_LEN: /* ODBC 1.0 */ + // the maximum length of a table owner's name. (0 == none) + // (maybe this should be 8) + *((WORD *)rgbInfoValue) = (WORD)0; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_MAX_PROCEDURE_NAME_LEN: /* ODBC 1.0 */ + *((WORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_MAX_QUALIFIER_NAME_LEN: /* ODBC 1.0 */ + *((WORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_MAX_ROW_SIZE: /* ODBC 2.0 */ + // the maximum size of one row + // here I do know a definite value + *((DWORD *)rgbInfoValue) = 8192; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_MAX_ROW_SIZE_INCLUDES_LONG: /* ODBC 2.0 */ + // does the preceding value include LONGVARCHAR and LONGVARBINARY + // fields? + *pcbInfoValue = 1; + strncpy_null((char *)rgbInfoValue, "Y", (size_t)cbInfoValueMax); + break; + + case SQL_MAX_STATEMENT_LEN: /* ODBC 2.0 */ + // there should be a definite value here (2k?) + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_MAX_TABLE_NAME_LEN: /* ODBC 1.0 */ + *((WORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_MAX_TABLES_IN_SELECT: /* ODBC 2.0 */ + *((WORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_MAX_USER_NAME_LEN: + *(SWORD FAR *)rgbInfoValue = 0; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_MULT_RESULT_SETS: /* ODBC 1.0 */ + // do we support multiple result sets? + if (pcbInfoValue) *pcbInfoValue = 1; + strncpy_null((char *)rgbInfoValue, "Y", (size_t)cbInfoValueMax); + break; + + case SQL_MULTIPLE_ACTIVE_TXN: /* ODBC 1.0 */ + // do we support multiple simultaneous transactions? + if (pcbInfoValue) *pcbInfoValue = 1; + strncpy_null((char *)rgbInfoValue, "Y", (size_t)cbInfoValueMax); + break; + + case SQL_NEED_LONG_DATA_LEN: /* ODBC 2.0 */ + if (pcbInfoValue) *pcbInfoValue = 1; + /* Dont need the length, SQLPutData can handle any size and multiple calls */ + strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax); + break; + + case SQL_NON_NULLABLE_COLUMNS: /* ODBC 1.0 */ + // I think you can have NOT NULL columns with one of dal Zotto's + // patches, but for now we'll say no. + *((WORD *)rgbInfoValue) = (WORD)SQL_NNC_NULL; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_NULL_COLLATION: /* ODBC 2.0 */ + // where are nulls sorted? + *((WORD *)rgbInfoValue) = (WORD)SQL_NC_END; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_NUMERIC_FUNCTIONS: /* ODBC 1.0 */ + // what numeric functions are supported? (bitmask) + // I'm not sure if any of these are actually supported + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_ODBC_API_CONFORMANCE: /* ODBC 1.0 */ + *((WORD *)rgbInfoValue) = SQL_OAC_LEVEL1; /* well, almost */ + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_ODBC_SAG_CLI_CONFORMANCE: /* ODBC 1.0 */ + // can't find any reference to SAG in the ODBC reference manual + // (although it's in the index, it doesn't actually appear on + // the pages referenced) + *((WORD *)rgbInfoValue) = SQL_OSCC_NOT_COMPLIANT; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_ODBC_SQL_CONFORMANCE: /* ODBC 1.0 */ + *((WORD *)rgbInfoValue) = SQL_OSC_CORE; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_ODBC_SQL_OPT_IEF: /* ODBC 1.0 */ + // do we support the "Integrity Enhancement Facility" (?) + // (something to do with referential integrity?) + if (pcbInfoValue) *pcbInfoValue = 1; + strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax); + break; + + case SQL_ORDER_BY_COLUMNS_IN_SELECT: /* ODBC 2.0 */ + // do the columns sorted by have to be in the list of + // columns selected? + if (pcbInfoValue) *pcbInfoValue = 1; + strncpy_null((char *)rgbInfoValue, "Y", (size_t)cbInfoValueMax); + break; + + case SQL_OUTER_JOINS: /* ODBC 1.0 */ + // do we support outer joins? + if (pcbInfoValue) *pcbInfoValue = 1; + strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax); + break; + + case SQL_OWNER_TERM: /* ODBC 1.0 */ + // what we call an owner + if (pcbInfoValue) *pcbInfoValue = 5; + strncpy_null((char *)rgbInfoValue, "owner", (size_t)cbInfoValueMax); + break; + + case SQL_OWNER_USAGE: /* ODBC 2.0 */ + // in which statements can "owners be used"? (what does that mean? + // specifying 'owner.table' instead of just 'table' or something?) + // (bitmask) + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_POS_OPERATIONS: /* ODBC 2.0 */ + // what functions does SQLSetPos support? (bitmask) + // SQLSetPos does not exist yet + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_POSITIONED_STATEMENTS: /* ODBC 2.0 */ + // what 'positioned' functions are supported? (bitmask) + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_PROCEDURE_TERM: /* ODBC 1.0 */ + // what do we call a procedure? + if (pcbInfoValue) *pcbInfoValue = 9; + strncpy_null((char *)rgbInfoValue, "procedure", (size_t)cbInfoValueMax); + break; + + case SQL_PROCEDURES: /* ODBC 1.0 */ + // do we support procedures? + if (pcbInfoValue) *pcbInfoValue = 1; + strncpy_null((char *)rgbInfoValue, "Y", (size_t)cbInfoValueMax); + break; + + case SQL_QUALIFIER_LOCATION: /* ODBC 2.0 */ + // where does the qualifier go (before or after the table name?) + // we don't really use qualifiers, so... + *((WORD *)rgbInfoValue) = SQL_QL_START; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_QUALIFIER_NAME_SEPARATOR: /* ODBC 1.0 */ + // not really too sure what a qualifier is supposed to do either + // (specify the name of a database in certain cases?), so nix + // on that, too. + if (pcbInfoValue) *pcbInfoValue = 0; + strncpy_null((char *)rgbInfoValue, "", (size_t)cbInfoValueMax); + break; + + case SQL_QUALIFIER_TERM: /* ODBC 1.0 */ + // what we call a qualifier + if (pcbInfoValue) *pcbInfoValue = 0; + strncpy_null((char *)rgbInfoValue, "", (size_t)cbInfoValueMax); + break; + + case SQL_QUALIFIER_USAGE: /* ODBC 2.0 */ + // where can qualifiers be used? (bitmask) + // nowhere + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_QUOTED_IDENTIFIER_CASE: /* ODBC 2.0 */ + // are "quoted" identifiers case-sensitive? + // well, we don't really let you quote identifiers, so... + *((WORD *)rgbInfoValue) = SQL_IC_SENSITIVE; + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_ROW_UPDATES: /* ODBC 1.0 */ + // not quite sure what this means + if (pcbInfoValue) *pcbInfoValue = 1; + strncpy_null((char *)rgbInfoValue, "Y", (size_t)cbInfoValueMax); + break; + + case SQL_SCROLL_CONCURRENCY: /* ODBC 1.0 */ + // what concurrency options are supported? (bitmask) + // taking a guess here + *((DWORD *)rgbInfoValue) = SQL_SCCO_OPT_ROWVER; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_SCROLL_OPTIONS: /* ODBC 1.0 */ + // what options are supported for scrollable cursors? (bitmask) + // not too sure about this one, either... + *((DWORD *)rgbInfoValue) = SQL_SO_KEYSET_DRIVEN; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_SEARCH_PATTERN_ESCAPE: /* ODBC 1.0 */ + // this is supposed to be the character that escapes '_' or '%' + // in LIKE clauses. as far as I can tell postgres doesn't have one + // (backslash generates an error). returning an empty string means + // no escape character is supported. + if (pcbInfoValue) *pcbInfoValue = 0; + strncpy_null((char *)rgbInfoValue, "", (size_t)cbInfoValueMax); + break; + + case SQL_SERVER_NAME: /* ODBC 1.0 */ + p = CC_get_server(conn); + if (pcbInfoValue) *pcbInfoValue = strlen(p); + strncpy_null((char *)rgbInfoValue, p, (size_t)cbInfoValueMax); + break; + + case SQL_SPECIAL_CHARACTERS: /* ODBC 2.0 */ + // what special characters can be used in table and column names, etc.? + // probably more than just this... + if (pcbInfoValue) *pcbInfoValue = 1; + strncpy_null((char *)rgbInfoValue, "_", (size_t)cbInfoValueMax); + break; + + case SQL_STATIC_SENSITIVITY: /* ODBC 2.0 */ + // can changes made inside a cursor be detected? (or something like that) + // (bitmask) + // only applies to SQLSetPos, which doesn't exist yet. + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_STRING_FUNCTIONS: /* ODBC 1.0 */ + // what string functions exist? (bitmask) + // not sure if any of these exist, either + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_SUBQUERIES: /* ODBC 2.0 */ + /* postgres 6.3 supports subqueries */ + *((DWORD *)rgbInfoValue) = (SQL_SQ_QUANTIFIED | + SQL_SQ_IN | + SQL_SQ_EXISTS | + SQL_SQ_COMPARISON); + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_SYSTEM_FUNCTIONS: /* ODBC 1.0 */ + // what system functions are supported? (bitmask) + // none of these seem to be supported, either + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_TABLE_TERM: /* ODBC 1.0 */ + // what we call a table + if (pcbInfoValue) *pcbInfoValue = 5; + strncpy_null((char *)rgbInfoValue, "table", (size_t)cbInfoValueMax); + break; + + case SQL_TIMEDATE_ADD_INTERVALS: /* ODBC 2.0 */ + // what resolutions are supported by the "TIMESTAMPADD scalar + // function" (whatever that is)? (bitmask) + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_TIMEDATE_DIFF_INTERVALS: /* ODBC 2.0 */ + // what resolutions are supported by the "TIMESTAMPDIFF scalar + // function" (whatever that is)? (bitmask) + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_TIMEDATE_FUNCTIONS: /* ODBC 1.0 */ + // what time and date functions are supported? (bitmask) + *((DWORD *)rgbInfoValue) = 0; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_TXN_CAPABLE: /* ODBC 1.0 */ + *((WORD *)rgbInfoValue) = (WORD)SQL_TC_ALL; + // Postgres can deal with create or drop table statements in a transaction + if(pcbInfoValue) { *pcbInfoValue = 2; } + break; + + case SQL_TXN_ISOLATION_OPTION: /* ODBC 1.0 */ + // what transaction isolation options are available? (bitmask) + // only the default--serializable transactions. + *((DWORD *)rgbInfoValue) = SQL_TXN_SERIALIZABLE; + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_UNION: /* ODBC 2.0 */ + /* unions with all supported in postgres 6.3 */ + *((DWORD *)rgbInfoValue) = (SQL_U_UNION | SQL_U_UNION_ALL); + if(pcbInfoValue) { *pcbInfoValue = 4; } + break; + + case SQL_USER_NAME: /* ODBC 1.0 */ + p = CC_get_username(conn); + if (pcbInfoValue) *pcbInfoValue = strlen(p); + strncpy_null((char *)rgbInfoValue, p, (size_t)cbInfoValueMax); + break; + + default: + /* unrecognized key */ + conn->errormsg = "Unrecognized key passed to SQLGetInfo."; + conn->errornumber = CONN_NOT_IMPLEMENTED_ERROR; + return SQL_ERROR; + } + + return SQL_SUCCESS; +} + +// - - - - - - - - - + + +RETCODE SQL_API SQLGetTypeInfo( + HSTMT hstmt, + SWORD fSqlType) +{ +StatementClass *stmt = (StatementClass *) hstmt; +TupleNode *row; +int i; +Int4 type; + + mylog("**** in SQLGetTypeInfo: fSqlType = %d\n", fSqlType); + + if( ! stmt) { + return SQL_INVALID_HANDLE; + } + + stmt->manual_result = TRUE; + stmt->result = QR_Constructor(); + if( ! stmt->result) { + 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); + + // cycle through the types + for(i=0, type = pgtypes_defined[0]; type; type = pgtypes_defined[++i]) { + + if(fSqlType == SQL_ALL_TYPES || fSqlType == pgtype_to_sqltype(type)) { + + row = (TupleNode *)malloc(sizeof(TupleNode) + (15 - 1)*sizeof(TupleField)); + + /* These values can't be NULL */ + set_tuplefield_string(&row->tuple[0], pgtype_to_name(type)); + set_tuplefield_int2(&row->tuple[1], pgtype_to_sqltype(type)); + set_tuplefield_int2(&row->tuple[6], pgtype_nullable(type)); + set_tuplefield_int2(&row->tuple[7], pgtype_case_sensitive(type)); + set_tuplefield_int2(&row->tuple[8], pgtype_searchable(type)); + set_tuplefield_int2(&row->tuple[10], pgtype_money(type)); + + /* 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(type)); + set_nullfield_string(&row->tuple[3], pgtype_literal_prefix(type)); + set_nullfield_string(&row->tuple[4], pgtype_literal_suffix(type)); + set_nullfield_string(&row->tuple[5], pgtype_create_params(type)); + set_nullfield_int2(&row->tuple[9], pgtype_unsigned(type)); + set_nullfield_int2(&row->tuple[11], pgtype_auto_increment(type)); + set_nullfield_int2(&row->tuple[13], pgtype_scale(type)); + set_nullfield_int2(&row->tuple[14], pgtype_scale(type)); + + QR_add_tuple(stmt->result, row); + } + } + + stmt->status = STMT_FINISHED; + stmt->currTuple = -1; + + return SQL_SUCCESS; +} + +// - - - - - - - - - + +RETCODE SQL_API SQLGetFunctions( + HDBC hdbc, + UWORD fFunction, + UWORD FAR *pfExists) +{ + if (fFunction == SQL_API_ALL_FUNCTIONS) { + + +#ifdef GETINFO_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 + 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] = FALSE; + pfExists[SQL_API_SQLNUMRESULTCOLS] = TRUE; + pfExists[SQL_API_SQLPREPARE] = TRUE; // complete? + pfExists[SQL_API_SQLROWCOUNT] = TRUE; + pfExists[SQL_API_SQLSETCURSORNAME] = FALSE; + pfExists[SQL_API_SQLSETPARAM] = FALSE; + 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; // sadly, I still + // had to think about + // this one + pfExists[SQL_API_SQLGETINFO] = TRUE; + pfExists[SQL_API_SQLGETSTMTOPTION] = TRUE; // very 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; + pfExists[SQL_API_SQLDRIVERS] = FALSE; + pfExists[SQL_API_SQLEXTENDEDFETCH] = TRUE; // partial? + 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; + pfExists[SQL_API_SQLPROCEDURES] = FALSE; + pfExists[SQL_API_SQLSETPOS] = FALSE; + pfExists[SQL_API_SQLSETSCROLLOPTIONS] = FALSE; + pfExists[SQL_API_SQLTABLEPRIVILEGES] = FALSE; +#endif + } else { +#ifdef GETINFO_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 = FALSE; 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 = FALSE; break; + case SQL_API_SQLSETPARAM: *pfExists = FALSE; break; + 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; // very 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; + case SQL_API_SQLDRIVERS: *pfExists = FALSE; break; + case SQL_API_SQLEXTENDEDFETCH: *pfExists = TRUE; break; // partial? + 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: *pfExists = FALSE; break; + case SQL_API_SQLSETPOS: *pfExists = FALSE; break; + case SQL_API_SQLSETSCROLLOPTIONS: *pfExists = FALSE; break; + case SQL_API_SQLTABLEPRIVILEGES: *pfExists = FALSE; break; + } +#endif + } + + return SQL_SUCCESS; +} + + + +RETCODE SQL_API SQLTables( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName, + UCHAR FAR * szTableType, + SWORD cbTableType) +{ +StatementClass *stmt = (StatementClass *) hstmt; +StatementClass *tbl_stmt; +TupleNode *row; +HSTMT htbl_stmt; +RETCODE result; +char *tableType; +char tables_query[MAX_STATEMENT_LEN]; +char table_name[MAX_INFO_STRING], table_owner[MAX_INFO_STRING]; +SDWORD table_name_len, table_owner_len; + +mylog("**** SQLTables(): ENTER, stmt=%u\n", stmt); + + if( ! stmt) + return SQL_INVALID_HANDLE; + + stmt->manual_result = TRUE; + stmt->errormsg_created = TRUE; + + result = SQLAllocStmt( 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 SQLTables result."; + return SQL_ERROR; + } + tbl_stmt = (StatementClass *) htbl_stmt; + + // ********************************************************************** + // Create the query to find out the tables + // ********************************************************************** + + strcpy(tables_query, "select relname, usename from pg_class, pg_user where relkind = 'r' "); + + my_strcat(tables_query, " and usename like '%.*s'", szTableOwner, cbTableOwner); + my_strcat(tables_query, " and relname like '%.*s'", szTableName, cbTableName); + + // make_string mallocs memory + tableType = make_string(szTableType, cbTableType, NULL); + if (tableType && ! strstr(tableType, "SYSTEM TABLE")) // is SYSTEM TABLE not present? + strcat(tables_query, " and relname not like '" POSTGRES_SYS_PREFIX "%' and relname not like '" INSIGHT_SYS_PREFIX "%'"); + + if (tableType) + free(tableType); + + strcat(tables_query, " and relname !~ '^Inv[0-9]+' and int4out(usesysid) = int4out(relowner) order by relname"); + + // ********************************************************************** + + result = SQLExecDirect(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; + SQLFreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLBindCol(htbl_stmt, 1, SQL_C_CHAR, + table_name, MAX_INFO_STRING, &table_name_len); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SQLFreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLBindCol(htbl_stmt, 2, SQL_C_CHAR, + table_owner, MAX_INFO_STRING, &table_owner_len); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SQLFreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + stmt->result = QR_Constructor(); + if(!stmt->result) { + stmt->errormsg = "Couldn't allocate memory for SQLTables result."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + SQLFreeStmt(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 = SQLFetch(htbl_stmt); + while((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) { + 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); + + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], table_name); + + mylog("SQLTables: table_name = '%s'\n", table_name); + + // careful: this is case-sensitive + if(strncmp(table_name, POSTGRES_SYS_PREFIX, strlen(POSTGRES_SYS_PREFIX)) == 0 || + strncmp(table_name, INSIGHT_SYS_PREFIX, strlen(INSIGHT_SYS_PREFIX)) == 0) { + set_tuplefield_string(&row->tuple[3], "SYSTEM TABLE"); + } else { + set_tuplefield_string(&row->tuple[3], "TABLE"); + } + + set_tuplefield_string(&row->tuple[4], ""); + + QR_add_tuple(stmt->result, row); + + result = SQLFetch(htbl_stmt); + } + if(result != SQL_NO_DATA_FOUND) { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SQLFreeStmt(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; + + SQLFreeStmt(htbl_stmt, SQL_DROP); + mylog("SQLTables(): EXIT, stmt=%u\n", stmt); + return SQL_SUCCESS; +} + +RETCODE SQL_API SQLColumns( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName, + UCHAR FAR * szColumnName, + SWORD cbColumnName) +{ +StatementClass *stmt = (StatementClass *) hstmt; +TupleNode *row; +HSTMT hcol_stmt; +StatementClass *col_stmt; +char columns_query[MAX_STATEMENT_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, field_length, mod_length; +Int4 field_type; +SDWORD table_owner_len, table_name_len, field_name_len, + field_type_len, field_type_name_len, field_number_len, + field_length_len, mod_length_len; + +mylog("**** SQLColumns(): ENTER, stmt=%u\n", stmt); + + if( ! stmt) + return SQL_INVALID_HANDLE; + + stmt->manual_result = TRUE; + stmt->errormsg_created = TRUE; + + // ********************************************************************** + // 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 from pg_user u, pg_class c, pg_attribute a, pg_type t where " + "int4out(u.usesysid) = int4out(c.relowner) and c.oid= a.attrelid and a.atttypid = t.oid and (a.attnum > 0)", + PROTOCOL_62(&(stmt->hdbc->connInfo)) ? "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 = SQLAllocStmt( 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 SQLColumns result."; + return SQL_ERROR; + } + col_stmt = (StatementClass *) hcol_stmt; + + result = SQLExecDirect(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; + SQLFreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLBindCol(hcol_stmt, 1, SQL_C_CHAR, + table_owner, MAX_INFO_STRING, &table_owner_len); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SQLFreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLBindCol(hcol_stmt, 2, SQL_C_CHAR, + table_name, MAX_INFO_STRING, &table_name_len); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SQLFreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLBindCol(hcol_stmt, 3, SQL_C_CHAR, + field_name, MAX_INFO_STRING, &field_name_len); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SQLFreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLBindCol(hcol_stmt, 4, SQL_C_DEFAULT, + &field_type, 4, &field_type_len); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SQLFreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLBindCol(hcol_stmt, 5, SQL_C_CHAR, + field_type_name, MAX_INFO_STRING, &field_type_name_len); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SQLFreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLBindCol(hcol_stmt, 6, SQL_C_DEFAULT, + &field_number, MAX_INFO_STRING, &field_number_len); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SQLFreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLBindCol(hcol_stmt, 7, SQL_C_DEFAULT, + &field_length, MAX_INFO_STRING, &field_length_len); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SQLFreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLBindCol(hcol_stmt, 8, SQL_C_DEFAULT, + &mod_length, MAX_INFO_STRING, &mod_length_len); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SQLFreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + stmt->result = QR_Constructor(); + if(!stmt->result) { + stmt->errormsg = "Couldn't allocate memory for SQLColumns result."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + SQLFreeStmt(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. + extend_bindings(stmt, 12); + + // set the field names + QR_set_num_fields(stmt->result, 12); + 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); + + result = SQLFetch(hcol_stmt); + while((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) { + row = (TupleNode *)malloc(sizeof(TupleNode) + + (12 - 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); + + /* Replace an unknown postgres type with SQL_CHAR type */ + /* Leave the field_type_name with "unknown" */ + if (pgtype_to_sqltype(field_type) == PG_UNKNOWN) + set_tuplefield_int2(&row->tuple[4], SQL_CHAR); + else + set_tuplefield_int2(&row->tuple[4], pgtype_to_sqltype(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 + NAME - the length is fixed and stored in pg_attribute.attlen field (32 on my system) + + */ + if((field_type == PG_TYPE_VARCHAR) || + (field_type == PG_TYPE_NAME) || + (field_type == PG_TYPE_BPCHAR)) { + + if (field_type == PG_TYPE_NAME) + mod_length = field_length; // the length is in attlen + else if (mod_length >= 4) + mod_length -= 4; // the length is in atttypmod - 4 + + if (mod_length > MAX_VARCHAR_SIZE || mod_length <= 0) + mod_length = MAX_VARCHAR_SIZE; + + mylog("SQLColumns: field type is VARCHAR,NAME: field_type = %d, mod_length = %d\n", field_type, mod_length); + + set_tuplefield_int4(&row->tuple[7], mod_length); + set_tuplefield_int4(&row->tuple[6], mod_length); + } else { + mylog("SQLColumns: field type is OTHER: field_type = %d, pgtype_length = %d\n", field_type, pgtype_length(field_type)); + + set_tuplefield_int4(&row->tuple[7], pgtype_length(field_type)); + set_tuplefield_int4(&row->tuple[6], pgtype_precision(field_type)); + + } + + set_nullfield_int2(&row->tuple[8], pgtype_scale(field_type)); + set_nullfield_int2(&row->tuple[9], pgtype_radix(field_type)); + set_tuplefield_int2(&row->tuple[10], pgtype_nullable(field_type)); + set_tuplefield_string(&row->tuple[11], ""); + + QR_add_tuple(stmt->result, row); + + result = SQLFetch(hcol_stmt); + } + if(result != SQL_NO_DATA_FOUND) { + stmt->errormsg = SC_create_errormsg(hcol_stmt); + stmt->errornumber = col_stmt->errornumber; + SQLFreeStmt(hcol_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; + + SQLFreeStmt(hcol_stmt, SQL_DROP); + mylog("SQLColumns(): EXIT, stmt=%u\n", stmt); + return SQL_SUCCESS; +} + +RETCODE SQL_API SQLSpecialColumns( + HSTMT hstmt, + UWORD fColType, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName, + UWORD fScope, + UWORD fNullable) +{ +TupleNode *row; +StatementClass *stmt = (StatementClass *) hstmt; + +mylog("**** SQLSpecialColumns(): ENTER, stmt=%u\n", stmt); + + if( ! stmt) { + return SQL_INVALID_HANDLE; + } + stmt->manual_result = TRUE; + 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); + + /* 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(PG_TYPE_OID)); + set_tuplefield_string(&row->tuple[3], "OID"); + set_tuplefield_int4(&row->tuple[4], pgtype_precision(PG_TYPE_OID)); + set_tuplefield_int4(&row->tuple[5], pgtype_length(PG_TYPE_OID)); + set_tuplefield_int2(&row->tuple[6], pgtype_scale(PG_TYPE_OID)); + set_tuplefield_int2(&row->tuple[7], SQL_PC_PSEUDO); + + QR_add_tuple(stmt->result, row); + + } else if(fColType == SQL_ROWVER) { + /* can columns automatically update? */ + /* for now assume no. */ + /* return an empty result. */ + } + + stmt->status = STMT_FINISHED; + stmt->currTuple = -1; + + mylog("SQLSpecialColumns(): EXIT, stmt=%u\n", stmt); + return SQL_SUCCESS; +} + +RETCODE SQL_API SQLStatistics( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName, + UWORD fUnique, + UWORD fAccuracy) +{ +StatementClass *stmt = (StatementClass *) hstmt; +char index_query[MAX_STATEMENT_LEN]; +HSTMT hindx_stmt; +RETCODE result; +char *table_name; +char index_name[MAX_INFO_STRING]; +short fields_vector[8]; +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]; +char **column_names = 0; +Int4 column_name_len; +int total_columns = 0; +char error = TRUE; + +mylog("**** SQLStatistics(): ENTER, stmt=%u\n", stmt); + + if( ! stmt) { + 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 SQLStatistics result."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + 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); + + // there are no unique indexes in postgres, so return nothing + // if those are requested + if(fUnique != SQL_INDEX_UNIQUE) { + // 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 SQLStatistics."; + stmt->errornumber = STMT_INTERNAL_ERROR; + return SQL_ERROR; + } + + // we need to get a list of the field names first, + // so we can return them later. + result = SQLAllocStmt( stmt->hdbc, &hcol_stmt); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = "SQLAllocStmt failed in SQLStatistics for columns."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + goto SEEYA; + } + + col_stmt = (StatementClass *) hcol_stmt; + + result = SQLColumns(hcol_stmt, "", 0, "", 0, + table_name, (SWORD) strlen(table_name), "", 0); + 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; + SQLFreeStmt(hcol_stmt, SQL_DROP); + goto SEEYA; + } + result = SQLBindCol(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; + SQLFreeStmt(hcol_stmt, SQL_DROP); + goto SEEYA; + + } + + result = SQLFetch(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); + + result = SQLFetch(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; + SQLFreeStmt(hcol_stmt, SQL_DROP); + goto SEEYA; + + } + + SQLFreeStmt(hcol_stmt, SQL_DROP); + + // get a list of indexes on this table + result = SQLAllocStmt( stmt->hdbc, &hindx_stmt); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = "SQLAllocStmt 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 from pg_index i, pg_class c, pg_class d where c.oid = i.indexrelid and d.relname = '%s' and d.oid = i.indrelid", + table_name); + + result = SQLExecDirect(hindx_stmt, index_query, strlen(index_query)); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = SC_create_errormsg(hindx_stmt); // "Couldn't execute index query (w/SQLExecDirect) in SQLStatistics."; + stmt->errornumber = indx_stmt->errornumber; + SQLFreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + + } + + result = SQLBindCol(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; + SQLFreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + + } + // bind the vector column + result = SQLBindCol(hindx_stmt, 2, SQL_C_DEFAULT, + fields_vector, 16, &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; + SQLFreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + + } + + result = SQLFetch(hindx_stmt); + while((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) { + i = 0; + // add a row in this table for each field in the index + while(i < 8 && 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); + + // Postgres95 indices always allow non-unique values. + set_tuplefield_int2(&row->tuple[3], TRUE); + + // no index qualifier + set_tuplefield_string(&row->tuple[4], ""); + set_tuplefield_string(&row->tuple[5], index_name); + + // check this--what does it mean for an index + // to be clustered? (none of mine seem to be-- + // we can and probably should find this out from + // the pg_index table) + set_tuplefield_int2(&row->tuple[6], SQL_INDEX_HASHED); + set_tuplefield_int2(&row->tuple[7], (Int2) (i+1)); + + if(fields_vector[i] < 0 || fields_vector[i] > total_columns) + set_tuplefield_string(&row->tuple[8], "UNKNOWN"); + else + set_tuplefield_string(&row->tuple[8], 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 = SQLFetch(hindx_stmt); + } + if(result != SQL_NO_DATA_FOUND) { + stmt->errormsg = SC_create_errormsg(hindx_stmt); // "SQLFetch failed in SQLStatistics."; + stmt->errornumber = indx_stmt->errornumber; + SQLFreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + } + + SQLFreeStmt(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; + + 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("SQLStatistics(): EXIT, %s, stmt=%u\n", error ? "error" : "success", stmt); + + if (error) + return SQL_ERROR; + else + return SQL_SUCCESS; +} + +RETCODE SQL_API SQLColumnPrivileges( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName, + UCHAR FAR * szColumnName, + SWORD cbColumnName) +{ + return SQL_ERROR; +} + +RETCODE +getPrimaryKeyString(StatementClass *stmt, char *szTableName, SWORD cbTableName, char *svKey, int *nKey) +{ +HSTMT htbl_stmt; +StatementClass *tbl_stmt; +RETCODE result; +char tables_query[MAX_STATEMENT_LEN]; +char attname[MAX_INFO_STRING]; +SDWORD attname_len; +int nk = 0; + + if (nKey != NULL) + *nKey = 0; + + svKey[0] = '\0'; + + stmt->errormsg_created = TRUE; + + result = SQLAllocStmt( 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."; + return SQL_ERROR; + } + tbl_stmt = (StatementClass *) htbl_stmt; + + tables_query[0] = '\0'; + if ( ! my_strcat(tables_query, "select distinct on attnum a2.attname, a2.attnum from pg_attribute a1, pg_attribute a2, pg_class c, pg_index i where c.relname = '%.*s_key' AND c.oid = i.indexrelid AND a1.attrelid = c.oid AND a2.attrelid = c.oid AND (i.indkey[0] = a1.attnum OR i.indkey[1] = a1.attnum OR i.indkey[2] = a1.attnum OR i.indkey[3] = a1.attnum OR i.indkey[4] = a1.attnum OR i.indkey[5] = a1.attnum OR i.indkey[6] = a1.attnum OR i.indkey[7] = a1.attnum) order by a2.attnum", + szTableName, cbTableName)) { + + stmt->errormsg = "No Table specified to getPrimaryKeyString."; + stmt->errornumber = STMT_INTERNAL_ERROR; + SQLFreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + mylog("getPrimaryKeyString: tables_query='%s'\n", tables_query); + + result = SQLExecDirect(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; + SQLFreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLBindCol(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; + SQLFreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLFetch(htbl_stmt); + while((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) { + + if (strlen(svKey) > 0) + strcat(svKey, "+"); + strcat(svKey, attname); + + result = SQLFetch(htbl_stmt); + nk++; + } + + if(result != SQL_NO_DATA_FOUND) { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SQLFreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + SQLFreeStmt(htbl_stmt, SQL_DROP); + + if (nKey != NULL) + *nKey = nk; + + mylog(">> getPrimaryKeyString: returning nKey=%d, svKey='%s'\n", nk, svKey); + return result; +} + +RETCODE +getPrimaryKeyArray(StatementClass *stmt, char *szTableName, SWORD cbTableName, char keyArray[][MAX_INFO_STRING], int *nKey) +{ +RETCODE result; +char svKey[MAX_KEYLEN], *svKeyPtr; +int i = 0; + + result = getPrimaryKeyString(stmt, szTableName, cbTableName, svKey, nKey); + if (result != SQL_SUCCESS && result != SQL_NO_DATA_FOUND) + // error passed from above + return result; + + // If no keys, return NO_DATA_FOUND + if (svKey[0] == '\0') { + mylog("!!!!!! getPrimaryKeyArray: svKey was null\n"); + return SQL_NO_DATA_FOUND; + } + + // mylog(">> primarykeyArray: nKey=%d, svKey='%s'\n", *nKey, svKey); + + svKeyPtr = strtok(svKey, "+"); + while (svKeyPtr != NULL && i < MAX_KEYPARTS) { + strcpy(keyArray[i++], svKeyPtr); + svKeyPtr = strtok(NULL, "+"); + } + + /* + for (i = 0; i < *nKey; i++) + mylog(">> keyArray[%d] = '%s'\n", i, keyArray[i]); + */ + + return result; +} + + +RETCODE SQL_API SQLPrimaryKeys( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName) +{ +StatementClass *stmt = (StatementClass *) hstmt; +TupleNode *row; +RETCODE result; +char svKey[MAX_KEYLEN], *ptr; +int seq = 1, nkeys = 0; + +mylog("**** SQLPrimaryKeys(): ENTER, stmt=%u\n", stmt); + + if( ! stmt) { + return SQL_INVALID_HANDLE; + } + stmt->manual_result = TRUE; + + result = getPrimaryKeyString(stmt, szTableName, cbTableName, svKey, &nkeys); + + mylog(">> PrimaryKeys: getPrimaryKeyString() returned %d, nkeys=%d, svKey = '%s'\n", result, nkeys, svKey); + + if (result != SQL_SUCCESS && result != SQL_NO_DATA_FOUND) { + // error msg passed from above + return result; + } + + // I'm not sure if this is correct to return when there are no keys or + // if an empty result set would be better. + if (nkeys == 0) { + stmt->errornumber = STMT_INFO_ONLY; + stmt->errormsg = "No primary keys for this table."; + return SQL_SUCCESS_WITH_INFO; + } + + stmt->result = QR_Constructor(); + if(!stmt->result) { + stmt->errormsg = "Couldn't allocate memory for SQLPrimaryKeys result."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + 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, 6); + + // set the field names + QR_set_num_fields(stmt->result, 6); + 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); + + // add the tuples + ptr = strtok(svKey, "+"); + while( ptr != NULL) { + row = (TupleNode *)malloc(sizeof(TupleNode) + (6 - 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.) + + mylog(">> primaryKeys: ptab = '%s', seq = %d\n", ptr, seq); + + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], szTableName); + set_tuplefield_string(&row->tuple[3], ptr); + set_tuplefield_int2(&row->tuple[4], (Int2) (seq++)); + set_tuplefield_null(&row->tuple[5]); + + QR_add_tuple(stmt->result, row); + + ptr = strtok(NULL, "+"); + } + + // 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; + + mylog("SQLPrimaryKeys(): EXIT, stmt=%u\n", stmt); + return SQL_SUCCESS; +} + +RETCODE SQL_API SQLForeignKeys( + 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) +{ +StatementClass *stmt = (StatementClass *) hstmt; +TupleNode *row; +HSTMT htbl_stmt; +StatementClass *tbl_stmt; +RETCODE result; +char tables_query[MAX_STATEMENT_LEN]; +char relname[MAX_INFO_STRING], attnames[MAX_INFO_STRING], frelname[MAX_INFO_STRING]; +SDWORD relname_len, attnames_len, frelname_len; +char *pktab, *fktab; +char fkey = FALSE; +char primaryKey[MAX_KEYPARTS][MAX_INFO_STRING]; +char *attnamePtr; +int pkeys, seq; + +mylog("**** SQLForeignKeys(): ENTER, stmt=%u\n", stmt); + + memset(primaryKey, 0, sizeof(primaryKey)); + + if( ! stmt) { + return SQL_INVALID_HANDLE; + } + stmt->manual_result = TRUE; + stmt->errormsg_created = TRUE; + + result = SQLAllocStmt( 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 SQLForeignKeys result."; + return SQL_ERROR; + } + + tbl_stmt = (StatementClass *) htbl_stmt; + + pktab = make_string(szPkTableName, cbPkTableName, NULL); + fktab = make_string(szFkTableName, cbFkTableName, NULL); + + if (pktab && fktab) { + // Get the primary key of the table listed in szPkTable + result = getPrimaryKeyArray(stmt, pktab, (SWORD) strlen(pktab), primaryKey, &pkeys); + if (result != SQL_SUCCESS && result != SQL_NO_DATA_FOUND) { + // error msg passed from above + SQLFreeStmt(htbl_stmt, SQL_DROP); + free(pktab); free(fktab); + return result; + } + if (pkeys == 0) { + stmt->errornumber = STMT_INFO_ONLY; + stmt->errormsg = "No primary keys for this table."; + SQLFreeStmt(htbl_stmt, SQL_DROP); + free(pktab); free(fktab); + return SQL_SUCCESS_WITH_INFO; + } + + sprintf(tables_query, "select relname, attnames, frelname from %s where relname='%s' AND frelname='%s'", KEYS_TABLE, fktab, pktab); + free(pktab); free(fktab); + } + else if (pktab) { + // Get the primary key of the table listed in szPkTable + result = getPrimaryKeyArray(stmt, pktab, (SWORD) strlen(pktab), primaryKey, &pkeys); + if (result != SQL_SUCCESS && result != SQL_NO_DATA_FOUND) { + // error msg passed from above + SQLFreeStmt(htbl_stmt, SQL_DROP); + free(pktab); + return result; + } + if (pkeys == 0) { + stmt->errornumber = STMT_INFO_ONLY; + stmt->errormsg = "No primary keys for this table."; + SQLFreeStmt(htbl_stmt, SQL_DROP); + free(pktab); + return SQL_SUCCESS_WITH_INFO; + } + + sprintf(tables_query, "select relname, attnames, frelname from %s where frelname='%s'", KEYS_TABLE, pktab); + free(pktab); + } + else if (fktab) { + // This query could involve multiple calls to getPrimaryKey() + // so put that off till we know what pktables we need. + fkey = TRUE; + + sprintf(tables_query, "select relname, attnames, frelname from %s where relname='%s'", KEYS_TABLE, fktab); + free(fktab); + } + else { + stmt->errormsg = "No tables specified to SQLForeignKeys."; + stmt->errornumber = STMT_INTERNAL_ERROR; + SQLFreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLExecDirect(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; + SQLFreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLBindCol(htbl_stmt, 1, SQL_C_CHAR, + relname, MAX_INFO_STRING, &relname_len); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SQLFreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + result = SQLBindCol(htbl_stmt, 2, SQL_C_CHAR, + attnames, MAX_INFO_STRING, &attnames_len); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SQLFreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = SQLBindCol(htbl_stmt, 3, SQL_C_CHAR, + frelname, MAX_INFO_STRING, &frelname_len); + if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SQLFreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + stmt->result = QR_Constructor(); + if(!stmt->result) { + stmt->errormsg = "Couldn't allocate memory for SQLForeignKeys result."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + SQLFreeStmt(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, 13); + + // set the field names + QR_set_num_fields(stmt->result, 13); + 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); + + // add the tuples + result = SQLFetch(htbl_stmt); + + while((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) { + + if (fkey == TRUE) { + result = getPrimaryKeyArray(stmt, frelname, (SWORD) strlen(frelname), primaryKey, &pkeys); + + // mylog(">> getPrimaryKeyArray: frelname = '%s', pkeys = %d, result = %d\n", frelname, pkeys, result); + + // If an error occurs or for some reason there is no primary key for a + // table that is a foreign key, then skip that one. + if ((result != SQL_SUCCESS && result != SQL_NO_DATA_FOUND) || pkeys == 0) { + result = SQLFetch(htbl_stmt); + continue; + } + + /* + for (i = 0; i< pkeys; i++) + mylog(">> fkey: pkeys=%d, primaryKey[%d] = '%s'\n", pkeys, i, primaryKey[i]); + mylog(">> !!!!!!!!! pkeys = %d\n", pkeys); + */ + } + + // mylog(">> attnames='%s'\n", attnames); + + attnamePtr = strtok(attnames, "+"); + seq = 0; + + while (attnamePtr != NULL && seq < pkeys) { + + row = (TupleNode *)malloc(sizeof(TupleNode) + (13 - 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.) + + mylog(">> foreign keys: pktab='%s' patt='%s' fktab='%s' fatt='%s' seq=%d\n", + frelname, primaryKey[seq], relname, attnamePtr, (seq+1)); + + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], frelname); + set_tuplefield_string(&row->tuple[3], primaryKey[seq]); + set_tuplefield_null(&row->tuple[4]); + set_tuplefield_string(&row->tuple[5], ""); + set_tuplefield_string(&row->tuple[6], relname); + set_tuplefield_string(&row->tuple[7], attnamePtr); + set_tuplefield_int2(&row->tuple[8], (Int2) (++seq)); + set_tuplefield_null(&row->tuple[9]); + set_tuplefield_null(&row->tuple[10]); + set_tuplefield_null(&row->tuple[11]); + set_tuplefield_null(&row->tuple[12]); + + QR_add_tuple(stmt->result, row); + + attnamePtr = strtok(NULL, "+"); + } + result = SQLFetch(htbl_stmt); + } + + if(result != SQL_NO_DATA_FOUND) { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SQLFreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + SQLFreeStmt(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; + + mylog("SQLForeignKeys(): EXIT, stmt=%u\n", stmt); + return SQL_SUCCESS; +} + + + +RETCODE SQL_API SQLProcedureColumns( + HSTMT hstmt, + UCHAR FAR * szProcQualifier, + SWORD cbProcQualifier, + UCHAR FAR * szProcOwner, + SWORD cbProcOwner, + UCHAR FAR * szProcName, + SWORD cbProcName, + UCHAR FAR * szColumnName, + SWORD cbColumnName) +{ + return SQL_ERROR; +} + +RETCODE SQL_API SQLProcedures( + HSTMT hstmt, + UCHAR FAR * szProcQualifier, + SWORD cbProcQualifier, + UCHAR FAR * szProcOwner, + SWORD cbProcOwner, + UCHAR FAR * szProcName, + SWORD cbProcName) +{ + return SQL_ERROR; +} + +RETCODE SQL_API SQLTablePrivileges( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName) +{ + return SQL_ERROR; +} diff --git a/src/interfaces/odbc/license.txt b/src/interfaces/odbc/license.txt new file mode 100644 index 0000000000..c5e8acfc57 --- /dev/null +++ b/src/interfaces/odbc/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/misc.c b/src/interfaces/odbc/misc.c new file mode 100644 index 0000000000..7ce83484d8 --- /dev/null +++ b/src/interfaces/odbc/misc.c @@ -0,0 +1,224 @@ + +/* 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 +#include +#include + +#include "psqlodbc.h" + +extern GLOBAL_VALUES globals; + + +#ifdef MY_LOG +#include + +void +mylog(va_alist) +va_dcl +{ +char *fmt; +char *args; + +static FILE *LOGFP = 0; + + if ( globals.debug) { + va_start(args); + fmt = va_arg(args, char *); + + if (! LOGFP) { + LOGFP = fopen("c:\\mylog.log", "w"); + setbuf(LOGFP, NULL); + } + + if (LOGFP) + vfprintf(LOGFP, fmt, args); + + va_end(args); + } +} +#endif + + +#ifdef Q_LOG +#include + +void qlog(va_alist) +va_dcl +{ +char *fmt; +char *args; +static FILE *LOGFP = 0; + + if ( globals.commlog) { + va_start(args); + fmt = va_arg(args, char *); + + if (! LOGFP) { + LOGFP = fopen("c:\\psqlodbc.log", "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, size_t dst_len, char *src, size_t 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) { + if (src_len < dst_len) + strcpy(dst, src); + else { + memcpy(dst, src, dst_len-1); + dst[dst_len-1] = '\0'; /* truncated */ + return STRCPY_TRUNCATED; + } + } + + else 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, size_t len) +{ +unsigned 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(char *s, int len, char *buf) +{ +int length; +char *str; + + if(s && (len > 0 || len == SQL_NTS)) { + 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, char *fmt, 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/misc.h b/src/interfaces/odbc/misc.h new file mode 100644 index 0000000000..b9047f16ff --- /dev/null +++ b/src/interfaces/odbc/misc.h @@ -0,0 +1,58 @@ + +/* File: misc.h + * + * Description: See "misc.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __MISC_H__ +#define __MISC_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 +void mylog(); /* prototype */ +#else +#define mylog // mylog +#endif + +#ifdef Q_LOG +void qlog(); /* prototype */ +#else +#define qlog // qlog +#endif + +void remove_newlines(char *string); +char *strncpy_null(char *dst, const char *src, size_t len); +char *trim(char *string); +char *make_string(char *s, int len, char *buf); +char *my_strcat(char *buf, char *fmt, 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, size_t dst_len, char *src, size_t src_len); + +#endif diff --git a/src/interfaces/odbc/notice.txt b/src/interfaces/odbc/notice.txt new file mode 100644 index 0000000000..695ba6b483 --- /dev/null +++ b/src/interfaces/odbc/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/options.c b/src/interfaces/odbc/options.c new file mode 100644 index 0000000000..1649eedfa4 --- /dev/null +++ b/src/interfaces/odbc/options.c @@ -0,0 +1,210 @@ + +/* 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 +#include "environ.h" +#include "connection.h" +#include "statement.h" + +/* Implements only SQL_AUTOCOMMIT */ +RETCODE SQL_API SQLSetConnectOption( + HDBC hdbc, + UWORD fOption, + UDWORD vParam) +{ +ConnectionClass *conn = (ConnectionClass *) hdbc; + + if ( ! conn) + return SQL_INVALID_HANDLE; + + switch (fOption) { + case SQL_AUTOCOMMIT: + + /* Since we are almost always in a transaction, this is now ok. + Even if we were, the logic will handle it by sending a commit + after the statement. + + if (CC_is_in_trans(conn)) { + conn->errormsg = "Cannot switch commit mode while a transaction is in progres"; + conn->errornumber = CONN_TRANSACT_IN_PROGRES; + return SQL_ERROR; + } + */ + + mylog("SQLSetConnectOption: 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; + return SQL_ERROR; + } + + break; + + case SQL_LOGIN_TIMEOUT: + break; + + case SQL_ACCESS_MODE: + break; + + default: + conn->errormsg = "This option is currently unsupported by the driver"; + conn->errornumber = CONN_UNSUPPORTED_OPTION; + return SQL_ERROR; + + } + return SQL_SUCCESS; +} + +// - - - - - - - - - + +RETCODE SQL_API SQLSetStmtOption( + HSTMT hstmt, + UWORD fOption, + UDWORD vParam) +{ +StatementClass *stmt = (StatementClass *) hstmt; + + // 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) { + return SQL_INVALID_HANDLE; + } + + switch(fOption) { + case SQL_QUERY_TIMEOUT: + mylog("SetStmtOption: vParam = %d\n", vParam); + /* + stmt->errornumber = STMT_OPTION_VALUE_CHANGED; + stmt->errormsg = "Query Timeout: value changed to 0"; + return SQL_SUCCESS_WITH_INFO; + */ + return SQL_SUCCESS; + break; + case SQL_MAX_LENGTH: +/* CC: Some apps consider returning SQL_SUCCESS_WITH_INFO to be an error */ +/* so if we're going to return SQL_SUCCESS, we better not set an */ +/* error message. (otherwise, if a subsequent function call returns */ +/* SQL_ERROR without setting a message, things can get confused.) */ + + /* + stmt->errormsg = "Requested value changed."; + stmt->errornumber = STMT_OPTION_VALUE_CHANGED; + */ + + return SQL_SUCCESS; + break; + case SQL_MAX_ROWS: + mylog("SetStmtOption(): SQL_MAX_ROWS = %d, returning success\n", vParam); + stmt->maxRows = vParam; + return SQL_SUCCESS; + break; + default: + return SQL_ERROR; + } + + return SQL_SUCCESS; +} + +// - - - - - - - - - + +/* This function just can tell you whether you are in Autcommit mode or not */ +RETCODE SQL_API SQLGetConnectOption( + HDBC hdbc, + UWORD fOption, + PTR pvParam) +{ +ConnectionClass *conn = (ConnectionClass *) hdbc; + + if (! conn) + return SQL_INVALID_HANDLE; + + switch (fOption) { + case SQL_AUTOCOMMIT: + /* CC 28.05.96: Do not set fOption, but pvParam */ + *((UDWORD *)pvParam) = (UDWORD)( CC_is_in_autocommit(conn) ? + SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF); + break; + /* we don't use qualifiers */ + case SQL_CURRENT_QUALIFIER: + if(pvParam) { + strcpy(pvParam, ""); + } + break; + default: + conn->errormsg = "This option is currently unsupported by the driver"; + conn->errornumber = CONN_UNSUPPORTED_OPTION; + return SQL_ERROR; + break; + + } + + return SQL_SUCCESS; +} + +// - - - - - - - - - + +RETCODE SQL_API SQLGetStmtOption( + HSTMT hstmt, + UWORD fOption, + PTR pvParam) +{ +StatementClass *stmt = (StatementClass *) hstmt; + + // 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) { + return SQL_INVALID_HANDLE; + } + + switch(fOption) { + case SQL_QUERY_TIMEOUT: + // how long we wait on a query before returning to the + // application (0 == forever) + *((SDWORD *)pvParam) = 0; + break; + case SQL_MAX_LENGTH: + // what is the maximum length that will be returned in + // a single column + *((SDWORD *)pvParam) = 4096; + break; + case SQL_MAX_ROWS: + *((SDWORD *)pvParam) = stmt->maxRows; + mylog("GetSmtOption: MAX_ROWS, returning %d\n", stmt->maxRows); + + break; + default: + return SQL_ERROR; + } + + return SQL_SUCCESS; +} + +// - - - - - - - - - diff --git a/src/interfaces/odbc/pgtypes.c b/src/interfaces/odbc/pgtypes.c new file mode 100644 index 0000000000..e1762b3fec --- /dev/null +++ b/src/interfaces/odbc/pgtypes.c @@ -0,0 +1,441 @@ + +/* 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 "psqlodbc.h" +#include "pgtypes.h" +#include +#include +#include + +/* these are the types we support. all of the pgtype_ functions should */ +/* return values for each one of these. */ + +/* NOTE: Even types not directly supported are handled as character types + so all types should work (points, etc.) */ + +Int4 pgtypes_defined[] = { + PG_TYPE_CHAR, + PG_TYPE_CHAR2, + PG_TYPE_CHAR4, + PG_TYPE_CHAR8, + PG_TYPE_BPCHAR, + PG_TYPE_VARCHAR, + PG_TYPE_DATE, + PG_TYPE_TIME, + PG_TYPE_ABSTIME, /* a timestamp, sort of */ + PG_TYPE_TEXT, + PG_TYPE_NAME, + PG_TYPE_INT2, + PG_TYPE_INT4, + PG_TYPE_FLOAT4, + PG_TYPE_FLOAT8, + PG_TYPE_OID, + PG_TYPE_MONEY, + PG_TYPE_BOOL, + PG_TYPE_CHAR16, + PG_TYPE_DATETIME, + PG_TYPE_BYTEA, + 0 }; + + +/* 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 PG_UNKNOWN when it does not know. The calling + routine checks for this and changes it to a char type. This allows for supporting + types that are unknown. All other pg routines in here return a suitable default. +*/ +Int2 pgtype_to_sqltype(Int4 type) +{ + switch(type) { + case PG_TYPE_CHAR: + case PG_TYPE_CHAR2: + case PG_TYPE_CHAR4: + case PG_TYPE_CHAR8: + case PG_TYPE_CHAR16: return SQL_CHAR; + + case PG_TYPE_BPCHAR: + case PG_TYPE_NAME: + case PG_TYPE_VARCHAR: return SQL_VARCHAR; + + case PG_TYPE_TEXT: return SQL_LONGVARCHAR; + case PG_TYPE_BYTEA: return SQL_LONGVARBINARY; + + case PG_TYPE_INT2: return SQL_SMALLINT; + case PG_TYPE_OID: + case PG_TYPE_INT4: return SQL_INTEGER; + 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: return SQL_TIMESTAMP; + case PG_TYPE_MONEY: return SQL_FLOAT; + case PG_TYPE_BOOL: return SQL_CHAR; + + default: return PG_UNKNOWN; /* check return for this */ + } +} + +Int2 pgtype_to_ctype(Int4 type) +{ + switch(type) { + case PG_TYPE_INT2: return SQL_C_SSHORT; + case PG_TYPE_OID: + 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: return SQL_C_TIMESTAMP; + case PG_TYPE_MONEY: return SQL_C_FLOAT; + case PG_TYPE_BOOL: return SQL_C_CHAR; + + case PG_TYPE_BYTEA: return SQL_C_BINARY; + + default: return SQL_C_CHAR; + } +} + +char *pgtype_to_name(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_CHAR16: return "char16"; + case PG_TYPE_VARCHAR: return "varchar"; + case PG_TYPE_BPCHAR: return "bpchar"; + 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_MONEY: return "money"; + case PG_TYPE_BOOL: return "bool"; + case PG_TYPE_BYTEA: return "bytea"; + + /* "unknown" can actually be used in alter table because it is a real PG type! */ + default: return "unknown"; + } +} + +/* For PG_TYPE_VARCHAR, PG_TYPE_BPCHAR, SQLColumns will + override this length with the atttypmod length from pg_attribute +*/ +Int4 pgtype_precision(Int4 type) +{ + 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_CHAR16: return 16; + + case PG_TYPE_NAME: return 32; + + case PG_TYPE_VARCHAR: + case PG_TYPE_BPCHAR: return MAX_VARCHAR_SIZE; + + case PG_TYPE_INT2: return 5; + + case PG_TYPE_OID: + case PG_TYPE_INT4: return 10; + + 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_DATETIME: return 19; + + case PG_TYPE_BOOL: return 1; + + default: + return TEXT_FIELD_SIZE; /* text field types and unknown types */ + } +} + +/* For PG_TYPE_VARCHAR, PG_TYPE_BPCHAR, SQLColumns will + override this length with the atttypmod length from pg_attribute +*/ +Int4 pgtype_length(Int4 type) +{ + 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_CHAR16: return 16; + + case PG_TYPE_NAME: return 32; + + case PG_TYPE_VARCHAR: + case PG_TYPE_BPCHAR: return MAX_VARCHAR_SIZE; + + case PG_TYPE_INT2: return 2; + + case PG_TYPE_OID: + case PG_TYPE_INT4: return 4; + + 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; + + case PG_TYPE_ABSTIME: + case PG_TYPE_DATETIME: return 16; + + case PG_TYPE_BOOL: return 1; + + default: + return TEXT_FIELD_SIZE; /* text field types and unknown types */ + } +} + +Int2 pgtype_scale(Int4 type) +{ + switch(type) { + + case PG_TYPE_INT2: + case PG_TYPE_OID: + case PG_TYPE_INT4: + 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_DATETIME: return 0; + + default: return -1; + } +} + + +Int2 pgtype_radix(Int4 type) +{ + switch(type) { + case PG_TYPE_INT2: + case PG_TYPE_OID: + case PG_TYPE_INT4: + case PG_TYPE_FLOAT4: + case PG_TYPE_MONEY: + case PG_TYPE_FLOAT8: return 10; + + default: return -1; + } +} + +Int2 pgtype_nullable(Int4 type) +{ + return SQL_NULLABLE; /* everything should be nullable */ +} + +Int2 pgtype_auto_increment(Int4 type) +{ + switch(type) { + + case PG_TYPE_INT2: + case PG_TYPE_OID: + case PG_TYPE_INT4: + case PG_TYPE_FLOAT4: + case PG_TYPE_MONEY: + case PG_TYPE_BOOL: + case PG_TYPE_FLOAT8: + + case PG_TYPE_DATE: + case PG_TYPE_TIME: + case PG_TYPE_ABSTIME: + case PG_TYPE_DATETIME: return FALSE; + + default: return -1; + } +} + +Int2 pgtype_case_sensitive(Int4 type) +{ + switch(type) { + case PG_TYPE_CHAR: + + case PG_TYPE_CHAR2: + case PG_TYPE_CHAR4: + case PG_TYPE_CHAR8: + case PG_TYPE_CHAR16: + + case PG_TYPE_VARCHAR: + case PG_TYPE_BPCHAR: + case PG_TYPE_TEXT: + case PG_TYPE_NAME: return TRUE; + + default: return FALSE; + } +} + +Int2 pgtype_money(Int4 type) +{ + switch(type) { + case PG_TYPE_MONEY: return TRUE; + default: return FALSE; + } +} + +Int2 pgtype_searchable(Int4 type) +{ + switch(type) { + case PG_TYPE_CHAR: + case PG_TYPE_CHAR2: + case PG_TYPE_CHAR4: + case PG_TYPE_CHAR8: + case PG_TYPE_CHAR16: + + 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(Int4 type) +{ + switch(type) { + case PG_TYPE_OID: return TRUE; + + case PG_TYPE_INT2: + case PG_TYPE_INT4: + case PG_TYPE_FLOAT4: + case PG_TYPE_FLOAT8: + case PG_TYPE_MONEY: return FALSE; + + default: return -1; + } +} + +char *pgtype_literal_prefix(Int4 type) +{ + switch(type) { + + case PG_TYPE_INT2: + case PG_TYPE_OID: + case PG_TYPE_INT4: + case PG_TYPE_FLOAT4: + case PG_TYPE_FLOAT8: + case PG_TYPE_MONEY: return NULL; + + default: return "'"; + } +} + +char *pgtype_literal_suffix(Int4 type) +{ + switch(type) { + + case PG_TYPE_INT2: + case PG_TYPE_OID: + case PG_TYPE_INT4: + case PG_TYPE_FLOAT4: + case PG_TYPE_FLOAT8: + case PG_TYPE_MONEY: return NULL; + + default: return "'"; + } +} + +char *pgtype_create_params(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; + } +} + diff --git a/src/interfaces/odbc/pgtypes.h b/src/interfaces/odbc/pgtypes.h new file mode 100644 index 0000000000..0b4be5fe06 --- /dev/null +++ b/src/interfaces/odbc/pgtypes.h @@ -0,0 +1,81 @@ + +/* File: pgtypes.h + * + * Description: See "pgtypes.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __PGTYPES_H__ +#define __PGTYPES_H__ + +/* the type numbers are defined by the OID's of the types' rows */ +/* in table pg_type */ + +#define PG_UNKNOWN -666 /* returned only from pgtype_to_sqltype() */ + +#define PG_TYPE_BOOL 16 +#define PG_TYPE_BYTEA 17 +#define PG_TYPE_CHAR 18 +#define PG_TYPE_NAME 19 +#define PG_TYPE_CHAR16 20 +#define PG_TYPE_INT2 21 +#define PG_TYPE_INT28 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_OID8 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_DATETIME 1184 + +extern Int4 pgtypes_defined[]; + +Int2 pgtype_to_sqltype(Int4 type); +Int2 pgtype_to_ctype(Int4 type); +char *pgtype_to_name(Int4 type); +Int4 pgtype_precision(Int4 type); +Int4 pgtype_length(Int4 type); +Int2 pgtype_scale(Int4 type); +Int2 pgtype_radix(Int4 type); +Int2 pgtype_nullable(Int4 type); +Int2 pgtype_auto_increment(Int4 type); +Int2 pgtype_case_sensitive(Int4 type); +Int2 pgtype_money(Int4 type); +Int2 pgtype_searchable(Int4 type); +Int2 pgtype_unsigned(Int4 type); +char *pgtype_literal_prefix(Int4 type); +char *pgtype_literal_suffix(Int4 type); +char *pgtype_create_params(Int4 type); + +Int2 sqltype_to_default_ctype(Int2 sqltype); + +#endif + diff --git a/src/interfaces/odbc/psqlodbc.c b/src/interfaces/odbc/psqlodbc.c new file mode 100644 index 0000000000..08a22a249d --- /dev/null +++ b/src/interfaces/odbc/psqlodbc.c @@ -0,0 +1,146 @@ + +/* 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 +#include +#include +#include + +HINSTANCE NEAR s_hModule; /* Saved module handle. */ +GLOBAL_VALUES globals; + + +/* This function reads the ODBCINST.INI portion of + the registry and gets any driver defaults. +*/ +void getGlobalDefaults(void) +{ +char temp[128]; + + + // Fetch Count is stored in driver section + SQLGetPrivateProfileString(DBMS_NAME, INI_FETCH, "", + temp, sizeof(temp), ODBCINST_INI); + if ( temp[0] ) + globals.fetch_max = atoi(temp); + else + globals.fetch_max = FETCH_MAX; + + + // Socket Buffersize is stored in driver section + SQLGetPrivateProfileString(DBMS_NAME, INI_SOCKET, "", + temp, sizeof(temp), ODBCINST_INI); + if ( temp[0] ) + globals.socket_buffersize = atoi(temp); + else + globals.socket_buffersize = SOCK_BUFFER_SIZE; + + + // Debug is stored in the driver section + SQLGetPrivateProfileString(DBMS_NAME, INI_DEBUG, "0", + temp, sizeof(temp), ODBCINST_INI); + globals.debug = atoi(temp); + + + // CommLog is stored in the driver section + SQLGetPrivateProfileString(DBMS_NAME, INI_COMMLOG, "0", + temp, sizeof(temp), ODBCINST_INI); + globals.commlog = atoi(temp); + + + // Optimizer is stored in the driver section only (OFF, ON, or ON=x) + SQLGetPrivateProfileString(DBMS_NAME, INI_OPTIMIZER, "", + globals.optimizer, sizeof(globals.optimizer), ODBCINST_INI); + + + // ConnSettings is stored in the driver section and per datasource for override + SQLGetPrivateProfileString(DBMS_NAME, INI_CONNSETTINGS, "", + globals.conn_settings, sizeof(globals.conn_settings), ODBCINST_INI); +} + + +/* This function writes any global parameters (that can be manipulated) + to the ODBCINST.INI portion of the registry +*/ +void updateGlobals(void) +{ +char tmp[128]; + + sprintf(tmp, "%d", globals.commlog); + SQLWritePrivateProfileString(DBMS_NAME, + INI_COMMLOG, tmp, ODBCINST_INI); +} + +/* 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; + } + + getGlobalDefaults(); + 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); +} + +/* 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/psqlodbc.def b/src/interfaces/odbc/psqlodbc.def new file mode 100644 index 0000000000..23a5a82b39 --- /dev/null +++ b/src/interfaces/odbc/psqlodbc.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/psqlodbc.h b/src/interfaces/odbc/psqlodbc.h new file mode 100644 index 0000000000..a08acf6410 --- /dev/null +++ b/src/interfaces/odbc/psqlodbc.h @@ -0,0 +1,120 @@ + +/* 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. + * + */ + +#ifndef __PSQLODBC_H__ +#define __PSQLODBC_H__ + +#define Int4 int +#define UInt4 unsigned int +#define Int2 short +#define UInt2 unsigned short + +typedef UInt4 Oid; + + +/* Limits */ +#define MAX_MESSAGE_LEN 8192 +#define MAX_CONNECT_STRING 4096 +#define ERROR_MSG_LENGTH 4096 +#define FETCH_MAX 100 /* default number of rows to cache for declare/fetch */ +#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) + +/* Registry length limits */ +#define LARGE_REGISTRY_LEN 4096 /* used for special cases */ +#define MEDIUM_REGISTRY_LEN 128 /* normal size for user,database,etc. */ +#define SMALL_REGISTRY_LEN 10 /* for 1/0 settings */ + + +/* Connection Defaults */ +#define DEFAULT_PORT "5432" +#define DEFAULT_READONLY "1" + +/* These prefixes denote system tables */ +#define INSIGHT_SYS_PREFIX "dd_" +#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_STATEMENT_LEN MAX_MESSAGE_LEN + +/* Driver stuff */ +#define DRIVERNAME "PostgreSQL ODBC" +#define DBMS_NAME "PostgreSQL" +#define DBMS_VERSION "06.30.0000 PostgreSQL 6.3" +#define POSTGRESDRIVERVERSION "06.30.0000" +#define DRIVER_FILE_NAME "PSQLODBC.DLL" + + +#define PG62 "6.2" /* "Protocol" key setting to force Postgres 6.2 */ + +/* INI File Stuff */ +#define ODBC_INI "ODBC.INI" /* ODBC initialization file */ +#define ODBCINST_INI "ODBCINST.INI" /* ODBC Installation file */ + +#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_CONNSETTINGS "ConnSettings" /* Anything to send to backend on successful connection */ + + +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 GlobalValues_ +{ + int fetch_max; + int socket_buffersize; + int debug; + int commlog; + char optimizer[MEDIUM_REGISTRY_LEN]; + char conn_settings[LARGE_REGISTRY_LEN]; +} GLOBAL_VALUES; + + +/* sizes */ +#define TEXT_FIELD_SIZE 4094 /* size of text fields (not including null term) */ +#define MAX_VARCHAR_SIZE 254 /* maximum size of a varchar (not including null term) */ + + +/* global prototypes */ +void updateGlobals(void); + + +#include "misc.h" + +#endif diff --git a/src/interfaces/odbc/psqlodbc.rc b/src/interfaces/odbc/psqlodbc.rc new file mode 100644 index 0000000000..b84bb86462 --- /dev/null +++ b/src/interfaces/odbc/psqlodbc.rc @@ -0,0 +1,205 @@ +//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 +// + +DRIVERCONNDIALOG DIALOG DISCARDABLE 0, 0, 269, 133 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PostgreSQL Connection" +FONT 8, "MS Sans Serif" +BEGIN + RTEXT "&Database:",IDC_STATIC,16,25,37,8 + EDITTEXT DATABASE_EDIT,55,25,72,12,ES_AUTOHSCROLL + RTEXT "&Server:",IDC_STATIC,26,40,27,8 + EDITTEXT SERVER_EDIT,55,40,72,12,ES_AUTOHSCROLL + RTEXT "&Port:",IDC_STATIC,150,40,20,8 + EDITTEXT PORT_EDIT,172,40,72,12,ES_AUTOHSCROLL + RTEXT "&User Name:",IDC_STATIC,16,56,37,8 + EDITTEXT USERNAME_EDIT,55,56,72,12,ES_AUTOHSCROLL + RTEXT "Pass&word:",IDC_STATIC,137,56,33,8 + EDITTEXT PASSWORD_EDIT,172,56,72,12,ES_PASSWORD | ES_AUTOHSCROLL + GROUPBOX "Options:",IDC_STATIC,25,71,200,25 + CONTROL "&ReadOnly:",READONLY_EDIT,"Button",BS_AUTOCHECKBOX | + BS_LEFTTEXT | BS_RIGHT | WS_GROUP | WS_TABSTOP,45,80,45, + 14 + CONTROL "&CommLog (Global):",COMMLOG_EDIT,"Button", + BS_AUTOCHECKBOX | BS_LEFTTEXT | BS_RIGHT | WS_TABSTOP, + 100,80,75,14 + CONTROL "6.2",PG62_EDIT,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | + BS_RIGHT | WS_TABSTOP,185,80,25,14 + DEFPUSHBUTTON "OK",IDOK,84,108,40,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,146,108,40,14 + CTEXT "Please supply any missing information needed to connect.", + IDC_STATIC,40,7,188,11 +END + +CONFIGDSN DIALOG DISCARDABLE 65, 43, 292, 151 +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,30,50,12,NOT WS_GROUP + EDITTEXT IDC_DSNAME,57,30,72,12,ES_AUTOHSCROLL | WS_GROUP + RTEXT "Des&cription:",IDC_STATIC,135,30,39,12,NOT WS_GROUP + EDITTEXT IDC_DESC,175,30,108,12,ES_AUTOHSCROLL + RTEXT "Data&base:",IDC_STATIC,17,45,38,12,NOT WS_GROUP + EDITTEXT IDC_DATABASE,57,45,72,12,ES_AUTOHSCROLL + RTEXT "&Server:",IDC_STATIC,27,60,29,12,NOT WS_GROUP + EDITTEXT IDC_SERVER,57,60,72,12,ES_AUTOHSCROLL + RTEXT "&Port:",IDC_STATIC,153,60,22,12 + EDITTEXT IDC_PORT,175,60,37,12,ES_AUTOHSCROLL + RTEXT "&User Name:",IDC_STATIC,17,75,39,12 + EDITTEXT IDC_USER,57,75,72,12,ES_AUTOHSCROLL + RTEXT "Pass&word:",IDC_STATIC,141,75,34,12 + EDITTEXT IDC_PASSWORD,175,75,72,12,ES_PASSWORD | ES_AUTOHSCROLL + GROUPBOX "Options:",IDC_STATIC,35,92,205,25 + CONTROL "&ReadOnly:",IDC_READONLY,"Button",BS_AUTOCHECKBOX | + BS_LEFTTEXT | BS_RIGHT | WS_GROUP | WS_TABSTOP,50,100,45, + 14 + CONTROL "&CommLog (Global):",IDC_COMMLOG,"Button", + BS_AUTOCHECKBOX | BS_LEFTTEXT | BS_RIGHT | WS_TABSTOP, + 105,100,75,14 + CONTROL "6.2",IDC_PG62,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | + BS_RIGHT | WS_TABSTOP,195,100,25,14 + DEFPUSHBUTTON "OK",IDOK,85,129,40,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,145,129,40,14 + CTEXT "Change data source name, description, or options. Then choose OK.", + IDC_STATIC,44,5,180,17 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + DRIVERCONNDIALOG, DIALOG + BEGIN + RIGHTMARGIN, 268 + END +END +#endif // APSTUDIO_INVOKED + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 6,30,0,0 + PRODUCTVERSION 6,30,0,0 + 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 for Windows 95\0" + VALUE "CompanyName", "Insight Distribution Systems\0" + VALUE "FileDescription", "PostgreSQL Driver\0" + VALUE "FileVersion", " 6.30.0000\0" + VALUE "InternalName", "psqlodbc\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 "ProductName", "Microsoft Open Database Connectivity\0" + VALUE "ProductVersion", " 6.30.0000\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/qresult.c b/src/interfaces/odbc/qresult.c new file mode 100644 index 0000000000..148e469cef --- /dev/null +++ b/src/interfaces/odbc/qresult.c @@ -0,0 +1,472 @@ + +/* 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 + +extern GLOBAL_VALUES globals; + +/* 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"); +} + +/************************************/ +/* 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->num_fields = 0; + rv->tupleField = NULL; + rv->cursor = NULL; + } + + 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) +{ + // 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) { + self->conn = conn; + + mylog("QR_fetch_tuples: cursor = '%s', self->cursor=%u\n", cursor, self->cursor); + + if (self->cursor) + free(self->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, CC_get_socket(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); + + /* allocate memory for the tuple cache */ + self->backend_tuples = (TupleField *) malloc(self->num_fields * sizeof(TupleField) * globals.fetch_max); + if ( ! self->backend_tuples) { + self->status = PGRES_FATAL_ERROR; + QR_set_message(self, "Could not get memory for tuple cache."); + return FALSE; + } + + self->inTuples = TRUE; + + /* Force a read to occur in next_tuple */ + self->fcount = globals.fetch_max+1; + self->fetch_count = globals.fetch_max+1; + + 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, CC_get_socket(self->conn))) { + self->status = PGRES_BAD_RESPONSE; + QR_set_message(self, "Error reading field information"); + return FALSE; + } + } +} + +// Close the cursor and end the transaction +// We only close cursor/end the transaction if a cursor was used. +int +QR_close(QResultClass *self) +{ +QResultClass *res; + + if (self->conn && self->cursor) { + char buf[64]; + + sprintf(buf, "close %s; END", self->cursor); + + mylog("QResult: closing cursor: '%s'\n", buf); + + res = CC_send_query(self->conn, buf, NULL, NULL); + CC_set_no_trans(self->conn); + + self->inTuples = FALSE; + free(self->cursor); + self->cursor = NULL; + + if (res == NULL) { + self->status = PGRES_FATAL_ERROR; + QR_set_message(self, "Error closing cursor."); + return FALSE; + } + + } + + 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; +TupleField *the_tuples = self->backend_tuples; +static char msgbuffer[MAX_MESSAGE_LEN+1]; +char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont need static + + 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 < globals.fetch_max) { /* last row from cache */ + // We are done because we didn't even get FETCH_MAX tuples + mylog("next_tuple: fcount < FETCH_MAX: fcount = %d, fetch_count = %d\n", fcount, fetch_count); + self->tupleField = NULL; + self->status = PGRES_END_TUPLES; + return -1; /* end of tuples */ + } + 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) { + char fetch[128]; + + sprintf(fetch, "fetch %d in %s", globals.fetch_max, self->cursor); + + mylog("next_tuple: sending actual fetch (%d) query '%s'\n", globals.fetch_max, fetch); + + // don't read ahead for the next tuple (self) ! + res = CC_send_query(self->conn, fetch, self, NULL); + if (res == NULL) { + self->status = PGRES_FATAL_ERROR; + QR_set_message(self, "Error fetching next group."); + return FALSE; + } + self->inTuples = TRUE; + /* This is a true fetch, like SQLFetch() */ + self->fetch_count = 1; + } + 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; + } + // fall through and read the next group + } + + + self->fcount = 0; + sock = CC_get_socket(self->conn); + self->tupleField = NULL; + + 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 ( ! 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, MAX_MESSAGE_LEN); + 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 = the_tuples; + 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; + 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 */ + 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); + + + /* 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; + } + /* + 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; + } + return TRUE; +} diff --git a/src/interfaces/odbc/qresult.h b/src/interfaces/odbc/qresult.h new file mode 100644 index 0000000000..b425a34215 --- /dev/null +++ b/src/interfaces/odbc/qresult.h @@ -0,0 +1,105 @@ + +/* File: qresult.h + * + * Description: See "qresult.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __QRESULT_H__ +#define __QRESULT_H__ + +#include "connection.h" +#include "socket.h" +#include "columninfo.h" +#include "tuplelist.h" +#include "psqlodbc.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 fetch_count; // logical rows read so far + int fcount; // actual rows read in the fetch + + int num_fields; // number of fields in the result + 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? +}; + +#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) + +/* 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_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) : 0) +#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)) + +/* 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_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) + +// Core Functions +QResultClass *QR_Constructor(); +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 */ + +#endif diff --git a/src/interfaces/odbc/resource.h b/src/interfaces/odbc/resource.h new file mode 100644 index 0000000000..c58a0b72a0 --- /dev/null +++ b/src/interfaces/odbc/resource.h @@ -0,0 +1,39 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by psqlodbc.rc +// +#define IDS_BADDSN 1 +#define IDS_MSGTITLE 2 +#define DRIVERCONNDIALOG 101 +#define IDC_DSNAME 400 +#define IDC_DSNAMETEXT 401 +#define IDC_DESC 404 +#define IDC_SERVER 407 +#define IDC_DATABASE 408 +#define CONFIGDSN 1001 +#define IDC_PORT 1002 +#define IDC_USER 1006 +#define IDC_PASSWORD 1009 +#define IDC_READONLY 1011 +#define READONLY_EDIT 1012 +#define SAVEPASSWORD_EDIT 1013 +#define IDC_COMMLOG 1014 +#define COMMLOG_EDIT 1015 +#define IDC_PG62 1016 +#define PG62_EDIT 1017 +#define SERVER_EDIT 1501 +#define PORT_EDIT 1502 +#define DATABASE_EDIT 1503 +#define USERNAME_EDIT 1504 +#define PASSWORD_EDIT 1505 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1018 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/interfaces/odbc/results.c b/src/interfaces/odbc/results.c new file mode 100644 index 0000000000..1396083046 --- /dev/null +++ b/src/interfaces/odbc/results.c @@ -0,0 +1,694 @@ + +/* 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(NI), SQLSetScrollOptions(NI), + * SQLSetCursorName(NI), SQLGetCursorName(NI) + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#include +#include "psqlodbc.h" +#include "environ.h" +#include "connection.h" +#include "statement.h" +#include "bind.h" +#include "qresult.h" +#include "convert.h" +#include "pgtypes.h" + +#include +#include +#include + + +RETCODE SQL_API SQLRowCount( + HSTMT hstmt, + SDWORD FAR *pcrow) +{ +StatementClass *stmt = (StatementClass *) hstmt; +QResultClass *res; +char *msg, *ptr; + + if ( ! stmt) + return SQL_ERROR; + + if(stmt->statement_type == STMT_TYPE_SELECT) { + if (stmt->status == STMT_FINISHED) { + res = SC_get_Result(stmt); + + if(res && pcrow) { + *pcrow = 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("**** SQLRowCount(): THE ROWS: *pcrow = %d\n", *pcrow); + } + else { + *pcrow = -1; + + mylog("**** SQLRowCount(): NO ROWS: *pcrow = %d\n", *pcrow); + } + + return SQL_SUCCESS; + } + } + + return SQL_ERROR; +} + + +// This returns the number of columns associated with the database +// attached to "hstmt". + + +RETCODE SQL_API SQLNumResultCols( + HSTMT hstmt, + SWORD FAR *pccol) +{ +StatementClass *stmt = (StatementClass *) hstmt; +QResultClass *result; + + if ( ! stmt) + return SQL_INVALID_HANDLE; + + SC_clear_error(stmt); + + /* CC: Now check for the "prepared, but not executed" situation, that enables us to + deal with "SQLPrepare -- SQLDescribeCol -- ... -- SQLExecute" situations. + (AutoCAD 13 ASE/ASI just _loves_ that ;-) ) + */ + mylog("**** SQLNumResultCols: calling SC_pre_execute\n"); + + SC_pre_execute(stmt); + + result = SC_get_Result(stmt); + + mylog("SQLNumResultCols: 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"; + return SQL_ERROR; + } + + *pccol = QR_NumResultCols(result); + + return SQL_SUCCESS; +} + +// - - - - - - - - - + +// Return information about the database column the user wants +// information about. +/* CC: preliminary implementation */ +RETCODE SQL_API SQLDescribeCol( + HSTMT hstmt, + UWORD icol, + UCHAR FAR *szColName, + SWORD cbColNameMax, + SWORD FAR *pcbColName, + SWORD FAR *pfSqlType, + UDWORD FAR *pcbColDef, + SWORD FAR *pibScale, + SWORD FAR *pfNullable) +{ + /* gets all the information about a specific column */ +StatementClass *stmt = (StatementClass *) hstmt; +QResultClass *result; +char *name; +Int4 fieldtype; + + if ( ! stmt) + return SQL_INVALID_HANDLE; + + SC_clear_error(stmt); + + /* CC: Now check for the "prepared, but not executed" situation, that enables us to + deal with "SQLPrepare -- SQLDescribeCol -- ... -- SQLExecute" situations. + (AutoCAD 13 ASE/ASI just _loves_ that ;-) ) + */ + + SC_pre_execute(stmt); + + + result = SC_get_Result(stmt); + mylog("**** SQLDescribeCol: result = %u, stmt->status = %d, !finished=%d, !premature=%d\n", result, stmt->status, stmt->status != STMT_FINISHED, stmt->status != STMT_PREMATURE); + if ( (NULL == 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 assigned to this statement."; + return SQL_ERROR; + } + + if (cbColNameMax >= 1) { + name = QR_get_fieldname(result, (Int2) (icol-1)); + mylog("describeCol: col %d fieldname = '%s'\n", icol - 1, name); + /* our indices start from 0 whereas ODBC defines indices starting from 1 */ + if (NULL != pcbColName) { + // we want to get the total number of bytes in the column name + if (NULL == name) + *pcbColName = 0; + else + *pcbColName = strlen(name); + } + if (NULL != szColName) { + // get the column name into the buffer if there is one + if (NULL == name) + szColName[0] = '\0'; + else + strncpy_null(szColName, name, cbColNameMax); + } + } + + fieldtype = QR_get_field_type(result, (Int2) (icol-1)); + mylog("describeCol: col %d fieldtype = %d\n", icol - 1, fieldtype); + + if (NULL != pfSqlType) { + *pfSqlType = pgtype_to_sqltype(fieldtype); + if (*pfSqlType == PG_UNKNOWN) + *pfSqlType = SQL_CHAR; + } + + if (NULL != pcbColDef) + *pcbColDef = pgtype_precision(fieldtype); + + if (NULL != pibScale) { + Int2 scale; + scale = pgtype_scale(fieldtype); + if(scale == -1) { scale = 0; } + + *pibScale = scale; + } + if (NULL != pfNullable) { + *pfNullable = pgtype_nullable(fieldtype); + } + + return SQL_SUCCESS; +} + +// Returns result column descriptor information for a result set. + +RETCODE SQL_API SQLColAttributes( + HSTMT hstmt, + UWORD icol, + UWORD fDescType, + PTR rgbDesc, + SWORD cbDescMax, + SWORD FAR *pcbDesc, + SDWORD FAR *pfDesc) +{ +StatementClass *stmt = (StatementClass *) hstmt; +char *value; +Int4 field_type; + + if( ! stmt) { + return SQL_INVALID_HANDLE; + } + + /* CC: Now check for the "prepared, but not executed" situation, that enables us to + deal with "SQLPrepare -- SQLDescribeCol -- ... -- SQLExecute" situations. + (AutoCAD 13 ASE/ASI just _loves_ that ;-) ) + */ + SC_pre_execute(stmt); + + mylog("**** SQLColAtt: 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; + return SQL_ERROR; + } + + if(icol < 1) { + // we do not support bookmarks + stmt->errormsg = "Bookmarks are not currently supported."; + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + return SQL_ERROR; + } + + icol -= 1; + 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: + if (NULL != pfDesc) { + *pfDesc = pgtype_auto_increment(field_type); + + if(*pfDesc == -1) { /* "not applicable" becomes false */ + *pfDesc = FALSE; + } + } + break; + case SQL_COLUMN_CASE_SENSITIVE: + if (NULL != pfDesc) + *pfDesc = pgtype_case_sensitive(field_type); + break; + case SQL_COLUMN_COUNT: + if (NULL != pfDesc) + *pfDesc = QR_NumResultCols(stmt->result); + break; + case SQL_COLUMN_DISPLAY_SIZE: + if (NULL != pfDesc) + *pfDesc = pgtype_precision(field_type); + + mylog("colAttr: col %d fieldsize = %d\n", icol, *pfDesc); + + break; + case SQL_COLUMN_LABEL: + case SQL_COLUMN_NAME: + value = QR_get_fieldname(stmt->result, icol); + strncpy_null((char *)rgbDesc, value, cbDescMax); + /* CC: Check for Nullpointesr */ + if (NULL != pcbDesc) + *pcbDesc = strlen(value); + break; + case SQL_COLUMN_LENGTH: + if (NULL != pfDesc) + *pfDesc = pgtype_precision(field_type); + return SQL_SUCCESS; + break; + case SQL_COLUMN_MONEY: + if (NULL != pfDesc) + *pfDesc = pgtype_money(field_type); + break; + case SQL_COLUMN_NULLABLE: + if (NULL != pfDesc) + *pfDesc = pgtype_nullable(field_type); + break; + case SQL_COLUMN_OWNER_NAME: + return SQL_ERROR; + break; + case SQL_COLUMN_PRECISION: + if (NULL != pfDesc) + *pfDesc = pgtype_precision(field_type); + break; + case SQL_COLUMN_QUALIFIER_NAME: + strncpy_null((char *)rgbDesc, "", cbDescMax); + if (NULL != pfDesc) + *pcbDesc = 1; + break; + case SQL_COLUMN_SCALE: + if (NULL != pfDesc) + *pfDesc = pgtype_scale(field_type); + break; + case SQL_COLUMN_SEARCHABLE: + if (NULL != pfDesc) + *pfDesc = pgtype_searchable(field_type); + break; + case SQL_COLUMN_TABLE_NAME: + return SQL_ERROR; + break; + case SQL_COLUMN_TYPE: + if (NULL != pfDesc) { + *pfDesc = pgtype_to_sqltype(field_type); + if (*pfDesc == PG_UNKNOWN) + *pfDesc = SQL_CHAR; + } + break; + case SQL_COLUMN_TYPE_NAME: + value = pgtype_to_name(field_type); + strncpy_null((char *)rgbDesc, value, cbDescMax); + if (NULL != pcbDesc) + *pcbDesc = strlen(value); + break; + case SQL_COLUMN_UNSIGNED: + if (NULL != pfDesc) { + *pfDesc = pgtype_unsigned(field_type); + if(*pfDesc == -1) { + *pfDesc = FALSE; + } + } + break; + case SQL_COLUMN_UPDATABLE: + // everything should be updatable, I guess, unless access permissions + // prevent it--are we supposed to check for that here? seems kind + // of complicated. hmm... + if (NULL != pfDesc) + *pfDesc = SQL_ATTR_WRITE; + break; + } + + return SQL_SUCCESS; +} + +// Returns result data for a single column in the current row. + +RETCODE SQL_API SQLGetData( + HSTMT hstmt, + UWORD icol, + SWORD fCType, + PTR rgbValue, + SDWORD cbValueMax, + SDWORD FAR *pcbValue) +{ +QResultClass *res; +StatementClass *stmt = (StatementClass *) hstmt; +int num_cols, num_rows; +Int4 field_type; +void *value; +int result; + + if( ! stmt) { + return SQL_INVALID_HANDLE; + } + res = stmt->result; + + if (STMT_EXECUTING == stmt->status) { + stmt->errormsg = "Can't get data while statement is still executing."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + return 0; + } + + 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"; + return 0; + } + + if (icol == 0) { + stmt->errormsg = "Bookmarks are not currently supported."; + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + return SQL_ERROR; + } + + // 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; + return SQL_ERROR; + } + + if ( stmt->manual_result) { + // 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; + return SQL_ERROR; + } + value = QR_get_value_manual(res, stmt->currTuple, icol); + } + else { /* its a SOCKET result (backend data) */ + if (stmt->currTuple == -1 || ! res || QR_end_tuples(res)) { + stmt->errormsg = "Not positioned on a valid row for GetData."; + stmt->errornumber = STMT_INVALID_CURSOR_STATE_ERROR; + return SQL_ERROR; + } + + value = QR_get_value_backend(res, icol); + + } + + field_type = QR_get_field_type(res, icol); + + mylog("**** SQLGetData: icol = %d, fCType = %d, field_type = %d, value = '%s'\n", icol, fCType, field_type, value); + + result = copy_and_convert_field(field_type, value, + fCType, rgbValue, cbValueMax, pcbValue); + + + if(result == COPY_UNSUPPORTED_TYPE) { + stmt->errormsg = "Received an unsupported type from Postgres."; + stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR; + return SQL_ERROR; + } else if(result == COPY_UNSUPPORTED_CONVERSION) { + stmt->errormsg = "Couldn't handle the necessary data type conversion."; + stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR; + return SQL_ERROR; + } else if(result == COPY_RESULT_TRUNCATED) { + stmt->errornumber = STMT_TRUNCATED; + stmt->errormsg = "The buffer was too small for the result."; + return SQL_SUCCESS_WITH_INFO; + } else if(result != COPY_OK) { + stmt->errormsg = "Unrecognized return value from copy_and_convert_field."; + stmt->errornumber = STMT_INTERNAL_ERROR; + return SQL_ERROR; + } + + return SQL_SUCCESS; +} + +// Returns data for bound columns in the current row ("hstmt->iCursor"), +// advances the cursor. + +RETCODE SQL_API SQLFetch( + HSTMT hstmt) +{ +StatementClass *stmt = (StatementClass *) hstmt; +QResultClass *res; +int retval; +Int2 num_cols, lf; +Oid type; +char *value; +ColumnInfoClass *ci; + + + if ( ! stmt) + return SQL_INVALID_HANDLE; + + SC_clear_error(stmt); + + if ( ! (res = stmt->result)) { + stmt->errormsg = "Null statement result in SQLFetch."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + return SQL_ERROR; + } + + ci = QR_get_fields(res); /* the column info */ + + if (stmt->status == STMT_EXECUTING) { + stmt->errormsg = "Can't fetch while statement is still executing."; + stmt->errornumber = STMT_SEQUENCE_ERROR; + 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"; + 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; + return SQL_ERROR; + } + + + if ( stmt->manual_result) { + if (QR_get_num_tuples(res) -1 == stmt->currTuple || + (stmt->maxRows > 0 && stmt->currTuple == stmt->maxRows - 1)) + /* if we are at the end of a tuple list, we return a "no data found" */ + return SQL_NO_DATA_FOUND; + + mylog("**** SQLFetch: manual_result\n"); + (stmt->currTuple)++; + } + else { + + // read from the cache or the physical next tuple + retval = QR_next_tuple(res); + if (retval < 0) { + mylog("**** SQLFetch: end_tuples\n"); + return SQL_NO_DATA_FOUND; + } + else if (retval > 0) + (stmt->currTuple)++; // all is well + + else { + mylog("SQLFetch: error\n"); + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "Error fetching next row"; + return SQL_ERROR; + } + + } + + num_cols = QR_NumResultCols(res); + + for (lf=0; lf < num_cols; lf++) { + + mylog("fetch: cols=%d, lf=%d, buffer[] = %u\n", + num_cols, lf, stmt->bindings[lf].buffer); + + if (stmt->bindings[lf].buffer != NULL) { + // this column has a binding + + // type = QR_get_field_type(res, lf); + type = CI_get_oid(ci, lf); /* speed things up */ + + if (stmt->manual_result) + value = QR_get_value_manual(res, stmt->currTuple, lf); + else + value = QR_get_value_backend(res, lf); + + retval = copy_and_convert_field_bindinfo(type, value, &(stmt->bindings[lf])); + + // check whether the complete result was copied + if(retval == COPY_UNSUPPORTED_TYPE) { + stmt->errormsg = "Received an unsupported type from Postgres."; + stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR; + return SQL_ERROR; + + } else if(retval == COPY_UNSUPPORTED_CONVERSION) { + stmt->errormsg = "Couldn't handle the necessary data type conversion."; + stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR; + return SQL_ERROR; + + } else if(retval == COPY_RESULT_TRUNCATED) { + /* The result has been truncated during the copy */ + /* this will generate a SQL_SUCCESS_WITH_INFO result */ + stmt->errornumber = STMT_TRUNCATED; + stmt->errormsg = "A buffer was too small for the return value to fit in"; + return SQL_SUCCESS_WITH_INFO; + + } else if(retval != COPY_OK) { + stmt->errormsg = "Unrecognized return value from copy_and_convert_field."; + stmt->errornumber = STMT_INTERNAL_ERROR; + return SQL_ERROR; + + } + } + } + + return SQL_SUCCESS; +} + +// This fetchs a block of data (rowset). + +RETCODE SQL_API SQLExtendedFetch( + HSTMT hstmt, + UWORD fFetchType, + SDWORD irow, + UDWORD FAR *pcrow, + UWORD FAR *rgfRowStatus) +{ +StatementClass *stmt = (StatementClass *) hstmt; + + if ( ! stmt) + return SQL_INVALID_HANDLE; + + /* Currently, only for manual results can this be done + because not all the tuples are read in ahead of time. + */ + if ( ! stmt->manual_result) + return SQL_ERROR; + + // CC: we currently only support fetches in one row bits + if (NULL != pcrow) + *pcrow = 1; + if (NULL != rgfRowStatus) + *rgfRowStatus = SQL_ROW_SUCCESS; + + switch (fFetchType) { + case SQL_FETCH_NEXT: + return SQLFetch(hstmt); + case SQL_FETCH_PRIOR: + if (stmt->currTuple <= 0) + return SQL_ERROR; + stmt->currTuple--; + return SQLFetch(hstmt); + case SQL_FETCH_FIRST: + stmt->currTuple = -1; + return SQLFetch(hstmt); + case SQL_FETCH_LAST: + stmt->currTuple = QR_get_num_tuples(stmt->result)-1; + return SQLFetch(hstmt); + case SQL_FETCH_ABSOLUTE: + if (irow == 0) { + stmt->currTuple = stmt->currTuple > 0 ? stmt->currTuple-2 : -1; + } else if (irow > 0) { + stmt->currTuple = irow-2; + return SQLFetch(hstmt); + } else { + // CC: ??? not sure about the specification in that case + return SQL_ERROR; + } + default: + return SQL_ERROR; + } + 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 SQLMoreResults( + HSTMT hstmt) +{ + return SQL_NO_DATA_FOUND; +} + +// This positions the cursor within a block of data. + +RETCODE SQL_API SQLSetPos( + HSTMT hstmt, + UWORD irow, + UWORD fOption, + UWORD fLock) +{ + return SQL_ERROR; +} + +// Sets options that control the behavior of cursors. + +RETCODE SQL_API SQLSetScrollOptions( + HSTMT hstmt, + UWORD fConcurrency, + SDWORD crowKeyset, + UWORD crowRowset) +{ + return SQL_ERROR; +} + + +// Set the cursor name on a statement handle + +RETCODE SQL_API SQLSetCursorName( + HSTMT hstmt, + UCHAR FAR *szCursor, + SWORD cbCursor) +{ + return SQL_SUCCESS; +} + +// Return the cursor name for a statement handle + +RETCODE SQL_API SQLGetCursorName( + HSTMT hstmt, + UCHAR FAR *szCursor, + SWORD cbCursorMax, + SWORD FAR *pcbCursor) +{ + return SQL_ERROR; +} + + diff --git a/src/interfaces/odbc/setup.c b/src/interfaces/odbc/setup.c new file mode 100644 index 0000000000..05f076a1e5 --- /dev/null +++ b/src/interfaces/odbc/setup.c @@ -0,0 +1,679 @@ + +/* 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. + * + *************************************************************************************/ + +/* +** SETUP.C - This is the ODBC sample driver code for +** setup. +** +** This code is furnished on an as-is basis as part of the ODBC SDK and is +** intended for example purposes only. +** +*/ +/*-------------------------------------------------------------------------- + setup.c -- Sample ODBC setup + + This code demonstrates how to interact with the ODBC Installer. These + functions may be part of your ODBC driver or in a separate DLL. + + The ODBC Installer allows a driver to control the management of + data sources by calling the ConfigDSN entry point in the appropriate + DLL. When called, ConfigDSN receives four parameters: + + hwndParent ---- Handle of the parent window for any dialogs which + may need to be created. If this handle is NULL, + then no dialogs should be displayed (that is, the + request should be processed silently). + + fRequest ------ Flag indicating the type of request (add, configure + (edit), or remove). + + lpszDriver ---- Far pointer to a null-terminated string containing + the name of your driver. This is the same string you + supply in the ODBC.INF file as your section header + and which ODBC Setup displays to the user in lieu + of the actual driver filename. This string needs to + be passed back to the ODBC Installer when adding a + new data source name. + + lpszAttributes- Far pointer to a list of null-terminated attribute + keywords. This list is similar to the list passed + to SQLDriverConnect, except that each key-value + pair is separated by a null-byte rather than a + semicolon. The entire list is then terminated with + a null-byte (that is, two consecutive null-bytes + mark the end of the list). The keywords accepted + should be those for SQLDriverConnect which are + applicable, any new keywords you define for ODBC.INI, + and any additional keywords you decide to document. + + ConfigDSN should return TRUE if the requested operation succeeds and + FALSE otherwise. The complete prototype for ConfigDSN is: + + BOOL FAR PASCAL ConfigDSN(HWND hwndParent, + WORD fRequest, + LPSTR lpszDriver, + LPCSTR lpszAttributes) + + Your setup code should not write to ODBC.INI directly to add or remove + data source names. Instead, link with ODBCINST.LIB (the ODBC Installer + library) and call SQLWriteDSNToIni and SQLRemoveDSNFromIni. + Use SQLWriteDSNToIni to add data source names. If the data source name + already exists, SQLWriteDSNToIni will delete it (removing all of its + associated keys) and rewrite it. SQLRemoveDSNToIni removes a data + source name and all of its associated keys. + + For NT compatibility, the driver code should not use the + Get/WritePrivateProfileString windows functions for ODBC.INI, but instead, + use SQLGet/SQLWritePrivateProfileString functions that are macros (16 bit) or + calls to the odbcinst.dll (32 bit). + +--------------------------------------------------------------------------*/ + + +// Includes ---------------------------------------------------------------- +#include "psqlodbc.h" // Local include files +#include +#include +#include // ODBC installer prototypes +#include // C include files +#include +#include "resource.h" + +#define INTFUNC __stdcall + +extern HINSTANCE NEAR s_hModule; /* Saved module handle. */ +extern GLOBAL_VALUES globals; + +// Constants --------------------------------------------------------------- +#define MIN(x,y) ((x) < (y) ? (x) : (y)) + +#define MAXPATHLEN (255+1) // Max path length +#define MAXKEYLEN (15+1) // Max keyword length +#define MAXDESC (255+1) // Max description length +#define MAXDSNAME (32+1) // Max data source name length + +static char far EMPTYSTR []= ""; +static char far OPTIONON []= "Yes"; +static char far OPTIONOFF []= "No"; + +// Attribute key indexes (into an array of Attr structs, see below) +#define KEY_DSN 0 +#define KEY_DESC 1 +#define KEY_PORT 2 +#define KEY_SERVER 3 +#define KEY_DATABASE 4 +#define KEY_USER 5 +#define KEY_PASSWORD 6 +#define KEY_DEBUG 7 +#define KEY_FETCH 8 +#define KEY_READONLY 9 +#define KEY_PROTOCOL 10 +#define NUMOFKEYS 11 // Number of keys supported + +// Attribute string look-up table (maps keys to associated indexes) +static struct { + char szKey[MAXKEYLEN]; + int iKey; +} s_aLookup[] = { "DSN", KEY_DSN, + INI_KDESC, KEY_DESC, + INI_PORT, KEY_PORT, + INI_SERVER, KEY_SERVER, + INI_DATABASE, KEY_DATABASE, + INI_USER, KEY_USER, + INI_PASSWORD, KEY_PASSWORD, + INI_DEBUG, KEY_DEBUG, + INI_FETCH, KEY_FETCH, + INI_READONLY, KEY_READONLY, + INI_PROTOCOL, KEY_PROTOCOL, + "", 0 + }; + + + +// Types ------------------------------------------------------------------- +typedef struct tagAttr { + BOOL fSupplied; + char szAttr[MAXPATHLEN]; +} Attr, FAR * LPAttr; + + +// Globals ----------------------------------------------------------------- +// NOTE: All these are used by the dialog procedures +typedef struct tagSETUPDLG { + HWND hwndParent; // Parent window handle + LPCSTR lpszDrvr; // Driver description + Attr aAttr[NUMOFKEYS]; // Attribute array + 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); + +/* CC: SetDSNAttributes is declared as "INTFUNC" below, but here it is declared as + "CALLBACK" -- Watcom complained about disagreeing modifiers. Changed + "CALLBACK" to "INTFUNC" here. + BOOL CALLBACK SetDSNAttributes(HWND hwnd, 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->aAttr[KEY_DSN].fSupplied) + lstrcpy(lpsetupdlg->szDSN, lpsetupdlg->aAttr[KEY_DSN].szAttr); + else + lpsetupdlg->szDSN[0] = '\0'; + + // Remove data source + if (ODBC_REMOVE_DSN == fRequest) { + // Fail if no data source name was supplied + if (!lpsetupdlg->aAttr[KEY_DSN].fSupplied) + fSuccess = FALSE; + + // Otherwise remove data source from ODBC.INI + else + fSuccess = SQLRemoveDSNFromIni(lpsetupdlg->aAttr[KEY_DSN].szAttr); + } + + // 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->aAttr[KEY_DSN].szAttr, INI_DSN); + + // Display the appropriate dialog (if parent window handle supplied) + if (hwnd) { + // Display dialog(s) + fSuccess = (IDOK == DialogBoxParam(s_hModule, + MAKEINTRESOURCE(CONFIGDSN), + hwnd, + ConfigDlgProc, + (LONG)(LPSTR)lpsetupdlg)); + } + + else if (lpsetupdlg->aAttr[KEY_DSN].fSupplied) + 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) +{ + + switch (wMsg) { + // Initialize the dialog + case WM_INITDIALOG: + { + LPSETUPDLG lpsetupdlg; + LPCSTR lpszDSN; + + SetWindowLong(hdlg, DWL_USER, lParam); + CenterDialog(hdlg); // Center dialog + + lpsetupdlg = (LPSETUPDLG) lParam; + lpszDSN = lpsetupdlg->aAttr[KEY_DSN].szAttr; + // Initialize dialog fields + // NOTE: Values supplied in the attribute string will always + // override settings in ODBC.INI + SetDlgItemText(hdlg, IDC_DSNAME, lpszDSN); + + // Description + if (!lpsetupdlg->aAttr[KEY_DESC].fSupplied) + SQLGetPrivateProfileString(lpszDSN, INI_KDESC, + EMPTYSTR, + lpsetupdlg->aAttr[KEY_DESC].szAttr, + sizeof(lpsetupdlg->aAttr[KEY_DESC].szAttr), + ODBC_INI); + SetDlgItemText(hdlg, IDC_DESC, lpsetupdlg->aAttr[KEY_DESC].szAttr); + + // Database + if (!lpsetupdlg->aAttr[KEY_DATABASE].fSupplied) + SQLGetPrivateProfileString(lpszDSN, INI_DATABASE, + EMPTYSTR, + lpsetupdlg->aAttr[KEY_DATABASE].szAttr, + sizeof(lpsetupdlg->aAttr[KEY_DATABASE].szAttr), + ODBC_INI); + SetDlgItemText(hdlg, IDC_DATABASE, lpsetupdlg->aAttr[KEY_DATABASE].szAttr); + + // Server + if (!lpsetupdlg->aAttr[KEY_SERVER].fSupplied) + SQLGetPrivateProfileString(lpszDSN, INI_SERVER, + EMPTYSTR, + lpsetupdlg->aAttr[KEY_SERVER].szAttr, + sizeof(lpsetupdlg->aAttr[KEY_SERVER].szAttr), + ODBC_INI); + SetDlgItemText(hdlg, IDC_SERVER, lpsetupdlg->aAttr[KEY_SERVER].szAttr); + + // Port + if (!lpsetupdlg->aAttr[KEY_PORT].fSupplied) + SQLGetPrivateProfileString(lpszDSN, INI_PORT, + EMPTYSTR, + lpsetupdlg->aAttr[KEY_PORT].szAttr, + sizeof(lpsetupdlg->aAttr[KEY_PORT].szAttr), + ODBC_INI); + if (lpsetupdlg->aAttr[KEY_PORT].szAttr[0] == '\0') + strcpy(lpsetupdlg->aAttr[KEY_PORT].szAttr, DEFAULT_PORT); + SetDlgItemText(hdlg, IDC_PORT, lpsetupdlg->aAttr[KEY_PORT].szAttr); + + /* Username */ + if (!lpsetupdlg->aAttr[KEY_USER].fSupplied) + SQLGetPrivateProfileString(lpszDSN, INI_USER, + EMPTYSTR, + lpsetupdlg->aAttr[KEY_USER].szAttr, + sizeof(lpsetupdlg->aAttr[KEY_USER].szAttr), + ODBC_INI); + SetDlgItemText(hdlg, IDC_USER, lpsetupdlg->aAttr[KEY_USER].szAttr); + + // Password + if (!lpsetupdlg->aAttr[KEY_PASSWORD].fSupplied) + SQLGetPrivateProfileString(lpszDSN, INI_PASSWORD, + EMPTYSTR, + lpsetupdlg->aAttr[KEY_PASSWORD].szAttr, + sizeof(lpsetupdlg->aAttr[KEY_PASSWORD].szAttr), + ODBC_INI); + SetDlgItemText(hdlg, IDC_PASSWORD, lpsetupdlg->aAttr[KEY_PASSWORD].szAttr); + + // ReadOnly Parameter + if (!lpsetupdlg->aAttr[KEY_READONLY].fSupplied) { + SQLGetPrivateProfileString(lpszDSN, INI_READONLY, + EMPTYSTR, + lpsetupdlg->aAttr[KEY_READONLY].szAttr, + sizeof(lpsetupdlg->aAttr[KEY_READONLY].szAttr), + ODBC_INI); + } + if (lpsetupdlg->aAttr[KEY_READONLY].szAttr[0] == '\0') + strcpy(lpsetupdlg->aAttr[KEY_READONLY].szAttr, DEFAULT_READONLY); + CheckDlgButton(hdlg, IDC_READONLY, atoi(lpsetupdlg->aAttr[KEY_READONLY].szAttr)); + + // Protocol Parameter + if (!lpsetupdlg->aAttr[KEY_PROTOCOL].fSupplied) { + SQLGetPrivateProfileString(lpszDSN, INI_PROTOCOL, + EMPTYSTR, + lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr, + sizeof(lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr), + ODBC_INI); + } + if (strncmp(lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr, PG62, strlen(PG62)) == 0) + CheckDlgButton(hdlg, IDC_PG62, 1); + else + CheckDlgButton(hdlg, IDC_PG62, 0); + + + // CommLog Parameter (this is global) + CheckDlgButton(hdlg, IDC_COMMLOG, globals.commlog); + + + 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; + + lpsetupdlg = (LPSETUPDLG)GetWindowLong(hdlg, DWL_USER); + // Retrieve dialog values + if (!lpsetupdlg->fDefault) + GetDlgItemText(hdlg, IDC_DSNAME, + lpsetupdlg->aAttr[KEY_DSN].szAttr, + sizeof(lpsetupdlg->aAttr[KEY_DSN].szAttr)); + GetDlgItemText(hdlg, IDC_DESC, + lpsetupdlg->aAttr[KEY_DESC].szAttr, + sizeof(lpsetupdlg->aAttr[KEY_DESC].szAttr)); + + GetDlgItemText(hdlg, IDC_DATABASE, + lpsetupdlg->aAttr[KEY_DATABASE].szAttr, + sizeof(lpsetupdlg->aAttr[KEY_DATABASE].szAttr)); + + GetDlgItemText(hdlg, IDC_PORT, + lpsetupdlg->aAttr[KEY_PORT].szAttr, + sizeof(lpsetupdlg->aAttr[KEY_PORT].szAttr)); + + GetDlgItemText(hdlg, IDC_SERVER, + lpsetupdlg->aAttr[KEY_SERVER].szAttr, + sizeof(lpsetupdlg->aAttr[KEY_SERVER].szAttr)); + + GetDlgItemText(hdlg, IDC_USER, + lpsetupdlg->aAttr[KEY_USER].szAttr, + sizeof(lpsetupdlg->aAttr[KEY_USER].szAttr)); + + GetDlgItemText(hdlg, IDC_PASSWORD, + lpsetupdlg->aAttr[KEY_PASSWORD].szAttr, + sizeof(lpsetupdlg->aAttr[KEY_PASSWORD].szAttr)); + + if ( IsDlgButtonChecked(hdlg, IDC_PG62)) + strcpy(lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr, PG62); + else + lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr[0] = '\0'; + + sprintf(lpsetupdlg->aAttr[KEY_READONLY].szAttr, "%d", IsDlgButtonChecked(hdlg, IDC_READONLY)); + + globals.commlog = IsDlgButtonChecked(hdlg, IDC_COMMLOG); + + + // Update ODBC.INI + SetDSNAttributes(hdlg, lpsetupdlg); + } + + // Return to caller + case IDCANCEL: + EndDialog(hdlg, wParam); + 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 iElement; + int cbKey; + + 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) + iElement = -1; + cbKey = lpsz - lpszStart; + if (cbKey < sizeof(aszKey)) + { + register int j; + + _fmemcpy(aszKey, lpszStart, cbKey); + aszKey[cbKey] = '\0'; + for (j = 0; *s_aLookup[j].szKey; j++) + { + if (!lstrcmpi(s_aLookup[j].szKey, aszKey)) + { + iElement = s_aLookup[j].iKey; + break; + } + } + } + + // Locate end of key value + lpszStart = ++lpsz; + for (; *lpsz; lpsz++); + + // Save value if key is known + // NOTE: This code assumes the szAttr buffers in aAttr have been + // zero initialized + if (iElement >= 0) + { + lpsetupdlg->aAttr[iElement].fSupplied = TRUE; + _fmemcpy(lpsetupdlg->aAttr[iElement].szAttr, + lpszStart, + MIN(lpsz-lpszStart+1, sizeof(lpsetupdlg->aAttr[0].szAttr)-1)); + } + } + 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->aAttr[KEY_DSN].szAttr; + + // Validate arguments + if (lpsetupdlg->fNewDSN && !*lpsetupdlg->aAttr[KEY_DSN].szAttr) + return FALSE; + + // Write the data source name + if (!SQLWriteDSNToIni(lpszDSN, lpsetupdlg->lpszDrvr)) + { + if (hwndParent) + { + char szBuf[MAXPATHLEN]; + char szMsg[MAXPATHLEN]; + + 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 + // Save the value if the data source is new, if it was edited, or if + // it was explicitly supplied + if (hwndParent || lpsetupdlg->aAttr[KEY_DESC].fSupplied ) + SQLWritePrivateProfileString(lpszDSN, + INI_KDESC, + lpsetupdlg->aAttr[KEY_DESC].szAttr, + ODBC_INI); + + if (hwndParent || lpsetupdlg->aAttr[KEY_DATABASE].fSupplied ) + SQLWritePrivateProfileString(lpszDSN, + INI_DATABASE, + lpsetupdlg->aAttr[KEY_DATABASE].szAttr, + ODBC_INI); + + if (hwndParent || lpsetupdlg->aAttr[KEY_PORT].fSupplied ) + SQLWritePrivateProfileString(lpszDSN, + INI_PORT, + lpsetupdlg->aAttr[KEY_PORT].szAttr, + ODBC_INI); + + if (hwndParent || lpsetupdlg->aAttr[KEY_SERVER].fSupplied ) + SQLWritePrivateProfileString(lpszDSN, + INI_SERVER, + lpsetupdlg->aAttr[KEY_SERVER].szAttr, + ODBC_INI); + + if (hwndParent || lpsetupdlg->aAttr[KEY_USER].fSupplied ) + SQLWritePrivateProfileString(lpszDSN, + INI_USER, + lpsetupdlg->aAttr[KEY_USER].szAttr, + ODBC_INI); + + if (hwndParent || lpsetupdlg->aAttr[KEY_PASSWORD].fSupplied ) + SQLWritePrivateProfileString(lpszDSN, + INI_PASSWORD, + lpsetupdlg->aAttr[KEY_PASSWORD].szAttr, + ODBC_INI); + + if (hwndParent || lpsetupdlg->aAttr[KEY_READONLY].fSupplied ) + SQLWritePrivateProfileString(lpszDSN, + INI_READONLY, + lpsetupdlg->aAttr[KEY_READONLY].szAttr, + ODBC_INI); + + if (hwndParent || lpsetupdlg->aAttr[KEY_PROTOCOL].fSupplied ) + SQLWritePrivateProfileString(lpszDSN, + INI_PROTOCOL, + lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr, + ODBC_INI); + + // CommLog Parameter -- write to ODBCINST_INI (for the whole driver) + if (hwndParent ) { + updateGlobals(); + } + + // If the data source name has changed, remove the old name + if (lpsetupdlg->aAttr[KEY_DSN].fSupplied && + lstrcmpi(lpsetupdlg->szDSN, lpsetupdlg->aAttr[KEY_DSN].szAttr)) + { + SQLRemoveDSNFromIni(lpsetupdlg->szDSN); + } + return TRUE; +} diff --git a/src/interfaces/odbc/socket.c b/src/interfaces/odbc/socket.c new file mode 100644 index 0000000000..40318ea502 --- /dev/null +++ b/src/interfaces/odbc/socket.c @@ -0,0 +1,289 @@ + +/* 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" + +extern GLOBAL_VALUES globals; + +void +SOCK_clear_error(SocketClass *self) +{ + self->errornumber = 0; + self->errormsg = NULL; +} + +SocketClass * +SOCK_Constructor() +{ +SocketClass *rv; + + rv = (SocketClass *) malloc(sizeof(SocketClass)); + + if (rv != NULL) { + rv->socket = (SOCKET) -1; + rv->buffer_filled_in = 0; + rv->buffer_filled_out = 0; + rv->buffer_read_in = 0; + + rv->buffer_in = (unsigned char *) malloc(globals.socket_buffersize); + if ( ! rv->buffer_in) + return NULL; + + rv->buffer_out = (unsigned char *) malloc(globals.socket_buffersize); + if ( ! rv->buffer_out) + return NULL; + + rv->errormsg = NULL; + rv->errornumber = 0; + + rv->reverse = FALSE; + } + return rv; + +} + +void +SOCK_Destructor(SocketClass *self) +{ + if (self->socket != -1) { + if ( ! shutdown(self->socket, 2)) /* no sends or receives */ + 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; + + if (self->socket != -1) { + self->errornumber = SOCKET_ALREADY_CONNECTED; + self->errormsg = "Socket is already connected"; + return 0; + } + + host = gethostbyname(hostname); + if (host == NULL) { + self->errornumber = SOCKET_HOST_NOT_FOUND; + self->errormsg = "Could not resolve hostname."; + return 0; + } + + memset((char *)&sadr, 0, sizeof(sadr)); + memcpy(&(sadr.sin_addr), host->h_addr, host->h_length); + 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 = (SOCKET) -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. +*/ +void +SOCK_get_string(SocketClass *self, char *buffer, int bufsize) +{ +register int lf = 0; + + for (lf = 0; lf < bufsize; lf++) + if ( ! (buffer[lf] = SOCK_get_next_byte(self))) + return; + + buffer[bufsize-1] = '\0'; +} + + +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) +{ +char buf[4]; + + switch (len) { + case 2: + SOCK_get_n_char(self, buf, len); + if (self->reverse) + return *((unsigned short *) buf); + else + return ntohs( *((unsigned short *) buf) ); + + case 4: + SOCK_get_n_char(self, buf, len); + if (self->reverse) + return *((unsigned int *) buf); + else + return ntohl( *((unsigned int *) 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 -> + // reload the buffer + + self->buffer_read_in = 0; + self->buffer_filled_in = recv(self->socket, (char *)self->buffer_in, globals.socket_buffersize, 0); + + mylog("read %d, global_socket_buffersize=%d\n", self->buffer_filled_in, globals.socket_buffersize); + + if (self->buffer_filled_in == -1) { + self->errornumber = SOCKET_READ_ERROR; + self->errormsg = "Error while reading from the socket."; + self->buffer_filled_in = 0; + } + if (self->buffer_filled_in == 0) { + self->errornumber = SOCKET_CLOSED; + self->errormsg = "Socket has been closed."; + self->buffer_filled_in = 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 == globals.socket_buffersize) { + // buffer is full, so write it out + bytes_sent = send(self->socket, (char *)self->buffer_out, globals.socket_buffersize, 0); + if (bytes_sent != globals.socket_buffersize) { + self->errornumber = SOCKET_WRITE_ERROR; + self->errormsg = "Error while writing to the socket."; + } + self->buffer_filled_out = 0; + } +} diff --git a/src/interfaces/odbc/socket.h b/src/interfaces/odbc/socket.h new file mode 100644 index 0000000000..458adc716c --- /dev/null +++ b/src/interfaces/odbc/socket.h @@ -0,0 +1,69 @@ + +/* File: socket.h + * + * Description: See "socket.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __SOCKET_H__ +#define __SOCKET_H__ + +#include +#include "psqlodbc.h" + +#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_filled_in; + int buffer_filled_out; + int buffer_read_in; + unsigned char *buffer_in; + unsigned char *buffer_out; + + SOCKET 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(); +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); +void 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/statement.c b/src/interfaces/odbc/statement.c new file mode 100644 index 0000000000..bea5968dc2 --- /dev/null +++ b/src/interfaces/odbc/statement.c @@ -0,0 +1,545 @@ + +/* 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 + +extern GLOBAL_VALUES globals; + + +RETCODE SQL_API SQLAllocStmt(HDBC hdbc, + HSTMT FAR *phstmt) +{ +ConnectionClass *conn = (ConnectionClass *) hdbc; +StatementClass *stmt; + + if( ! conn) + return SQL_INVALID_HANDLE; + + stmt = SC_Constructor(); + + mylog("**** SQLAllocStmt: 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; + return SQL_ERROR; + } + + if ( ! CC_add_statement(conn, stmt)) { + conn->errormsg = "Maximum number of connections exceeded."; + conn->errornumber = CONN_STMT_ALLOC_ERROR; + SC_Destructor(stmt); + *phstmt = SQL_NULL_HSTMT; + return SQL_ERROR; + } + + *phstmt = (HSTMT) stmt; + + return SQL_SUCCESS; +} + + +RETCODE SQL_API SQLFreeStmt(HSTMT hstmt, + UWORD fOption) +{ +StatementClass *stmt = (StatementClass *) hstmt; + + mylog("**** enter SQLFreeStmt: hstmt=%u, fOption=%d\n", hstmt, fOption); + + if ( ! stmt) + return SQL_INVALID_HANDLE; + + 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."; + 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) { + ConnectionClass *conn = stmt->hdbc; + + /* 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 + return SQL_ERROR; + + } else if(fOption == SQL_RESET_PARAMS) { + SC_free_params(stmt, STMT_FREE_PARAMS_ALL); + + } else { + stmt->errormsg = "Invalid option passed to SQLFreeStmt."; + stmt->errornumber = STMT_OPTION_OUT_OF_RANGE_ERROR; + return SQL_ERROR; + } + + return SQL_SUCCESS; +} + + +/********************************************************************** + * StatementClass implementation + */ + +StatementClass * +SC_Constructor() +{ +StatementClass *rv; + + rv = (StatementClass *) malloc(sizeof(StatementClass)); + if (rv) { + rv->hdbc = NULL; /* no connection associated yet */ + rv->result = NULL; + rv->manual_result = FALSE; + rv->prepare = FALSE; + rv->status = STMT_ALLOCATED; + rv->maxRows = 0; // driver returns all rows + rv->errormsg = NULL; + rv->errornumber = 0; + rv->errormsg_created = FALSE; + rv->statement = NULL; + rv->stmt_with_params[0] = '\0'; + rv->statement_type = STMT_TYPE_UNKNOWN; + rv->bindings = NULL; + rv->bindings_allocated = 0; + rv->parameters_allocated = 0; + rv->parameters = 0; + rv->currTuple = -1; + rv->result = 0; + rv->data_at_exec = -1; + rv->current_exec_param = -1; + rv->put_data = FALSE; + } + return rv; +} + +char +SC_Destructor(StatementClass *self) +{ + + mylog("SC_Destructor: self=%u, self->result=%u, self->hdbc=%u\n", self, self->result, self->hdbc); + 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); + + SC_free_params(self, STMT_FREE_PARAMS_ALL); + + /* the memory pointed to by the bindings is not deallocated by the driver */ + /* by by the application that uses that driver, so we don't have to care */ + /* about that here. */ + if (self->bindings) + free(self->bindings); + + free(self); + + 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; + + 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) { + 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; + } +} + +int +statement_type(char *statement) +{ + if(strnicmp(statement, "SELECT", 6) == 0) + return STMT_TYPE_SELECT; + + else if(strnicmp(statement, "INSERT", 6) == 0) + return STMT_TYPE_INSERT; + + else if(strnicmp(statement, "UPDATE", 6) == 0) + return STMT_TYPE_UPDATE; + + else if(strnicmp(statement, "DELETE", 6) == 0) + return STMT_TYPE_DELETE; + + else + 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; + + /* This would not happen */ + if (self->status == STMT_EXECUTING) { + self->errornumber = STMT_SEQUENCE_ERROR; + self->errormsg = "Statement is currently executing a transaction."; + return FALSE; + } + + self->errormsg = NULL; + self->errornumber = 0; + self->errormsg_created = 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)) { + + CC_send_query(conn, "ABORT", NULL, NULL); + CC_set_no_trans(conn); + } + break; + + case STMT_FINISHED: + break; + + default: + self->errormsg = "An internal error occured while recycling statements"; + self->errornumber = STMT_INTERNAL_ERROR; + return FALSE; + } + + + /* Free any cursors */ + if (self->result) { + QR_Destructor(self->result); + self->result = NULL; + } + + self->status = STMT_READY; + self->currTuple = -1; + + self->errormsg = NULL; + self->errornumber = 0; + self->errormsg_created = FALSE; + + // 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"); + + SQLExecute(self); + + if (self->status == STMT_FINISHED) { + mylog(" preprocess: after status = FINISHED, so set PREMATURE\n"); + 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].buflen = 0; + self->bindings[lf].buffer = NULL; + self->bindings[lf].used = NULL; + self->bindings[lf].returntype = SQL_C_CHAR; + } + + return 1; +} + +void +SC_clear_error(StatementClass *self) +{ + self->errornumber = 0; + self->errormsg = NULL; + self->errormsg_created = 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); + } + } + + 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; + self->errormsg = NULL; + } + + rv = (self->errornumber != 0); + self->errornumber = 0; + + return rv; +} + +RETCODE SC_execute(StatementClass *self) +{ +ConnectionClass *conn; +QResultClass *res; +char ok, was_ok, was_nonfatal; +Int2 oldstatus, numcols; + + + conn = SC_get_conn(self); + + /* Begin a transaction if one is not already in progress */ + /* The reason is because we can't use declare/fetch cursors without + starting a transaction first. + */ + + if ( ! CC_is_in_trans(conn)) { + mylog(" about to begin a transaction on statement = %u\n", self); + res = CC_send_query(conn, "BEGIN", NULL, NULL); + if ( ! res) { + self->errormsg = "Could not begin a transaction"; + self->errornumber = STMT_EXEC_ERROR; + return SQL_ERROR; + } + + ok = QR_command_successful(res); + + mylog("SQLExecute: 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; + return SQL_ERROR; + } + else + CC_set_in_trans(conn); + } + + + + oldstatus = conn->status; + conn->status = CONN_EXECUTING; + self->status = STMT_EXECUTING; + + + // If its 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 cursor[32]; + char fetch[64]; + + sprintf(cursor, "C%u", self); + + mylog(" Sending SELECT statement on stmt=%u\n", self); + + /* send the declare/select */ + self->result = CC_send_query(conn, self->stmt_with_params, NULL, NULL); + if (self->result != NULL) { + /* That worked, so now send the fetch to start getting data back */ + sprintf(fetch, "fetch %d in %s", globals.fetch_max, cursor); + + // Save the cursor in the result for later use + self->result = CC_send_query( conn, fetch, NULL, cursor); + } + + mylog(" done sending the query:\n"); + + } + else { // not a SELECT statement so don't use a cursor + mylog(" its NOT a select statement: stmt=%u\n", self); + self->result = CC_send_query(conn, self->stmt_with_params, NULL, NULL); + + // If we are in autocommit, we must send the commit. + if (CC_is_in_autocommit(conn)) { + CC_send_query(conn, "COMMIT", NULL, NULL); + 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; + + self->currTuple = -1; /* set cursor before the first tuple in the list */ + + /* 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"; + return SQL_ERROR; + } + } + + } else { /* Bad Error -- The error message will be in the Connection */ + + self->errornumber = STMT_EXEC_ERROR; + self->errormsg = "Error while executing the query"; + + CC_abort(conn); + } + + if (self->errornumber == STMT_OK) + return SQL_SUCCESS; + + else if (self->errornumber == STMT_INFO_ONLY) + return SQL_SUCCESS_WITH_INFO; + + else + return SQL_ERROR; +} diff --git a/src/interfaces/odbc/statement.h b/src/interfaces/odbc/statement.h new file mode 100644 index 0000000000..cc8e103d84 --- /dev/null +++ b/src/interfaces/odbc/statement.h @@ -0,0 +1,119 @@ + +/* File: statement.h + * + * Description: See "statement.c" + * + * Comments: See "notice.txt" for copyright and license information. + * + */ + +#ifndef __STATEMENT_H__ +#define __STATEMENT_H__ + +#include +#include +#include "psqlodbc.h" + +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_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 + + +/* statement types */ +#define STMT_TYPE_SELECT 0 +#define STMT_TYPE_INSERT 1 +#define STMT_TYPE_UPDATE 2 +#define STMT_TYPE_DELETE 3 +#define STMT_TYPE_OTHER 4 +#define STMT_TYPE_UNKNOWN 666 // 'unknown' means we don't have the statement yet, + // or haven't looked at it to see what type it is. + // 'other' means we looked, but couldn't tell. + + +/******** Statement Handle ***********/ +struct StatementClass_ { + ConnectionClass *hdbc; /* pointer to ConnectionClass this statement belongs to */ + + QResultClass *result; /* result of the current statement */ + + STMT_Status status; + char *errormsg; + int errornumber; + int maxRows; + + /* information on bindings */ + BindInfoClass *bindings; /* array to store the binding information */ + int bindings_allocated; + + /* information on statement parameters */ + int parameters_allocated; + ParameterInfoClass *parameters; + + Int4 currTuple; + + char *statement; /* if non--null pointer to the SQL statement that has been executed */ + + 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 stmt_with_params[65536 /* MAX_STATEMENT_LEN */]; /* statement after parameter substitution */ + +}; + +#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 + +/* Statement prototypes */ +StatementClass *SC_Constructor(); +char SC_Destructor(StatementClass *self); +int statement_type(char *statement); +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 *stmt); +void SC_free_params(StatementClass *self, char option); + +#endif diff --git a/src/interfaces/odbc/tuple.c b/src/interfaces/odbc/tuple.c new file mode 100644 index 0000000000..9b50ba5f3d --- /dev/null +++ b/src/interfaces/odbc/tuple.c @@ -0,0 +1,56 @@ + +/* 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 = 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/tuple.h b/src/interfaces/odbc/tuple.h new file mode 100644 index 0000000000..3d581079df --- /dev/null +++ b/src/interfaces/odbc/tuple.h @@ -0,0 +1,44 @@ + +/* 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/tuplelist.c b/src/interfaces/odbc/tuplelist.c new file mode 100644 index 0000000000..80b962482f --- /dev/null +++ b/src/interfaces/odbc/tuplelist.c @@ -0,0 +1,188 @@ + +/* 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 +#include +#include "tuplelist.h" +#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/tuplelist.h b/src/interfaces/odbc/tuplelist.h new file mode 100644 index 0000000000..5097cc6fdc --- /dev/null +++ b/src/interfaces/odbc/tuplelist.h @@ -0,0 +1,33 @@ + +/* 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