OSDN Git Service

Replace old PostODBC driver with new one...
authorMarc G. Fournier <scrappy@hub.org>
Mon, 13 Apr 1998 15:02:05 +0000 (15:02 +0000)
committerMarc G. Fournier <scrappy@hub.org>
Mon, 13 Apr 1998 15:02:05 +0000 (15:02 +0000)
This one is based on an older PostODBC driver, rewritten and maintained by
InsightDist(?)

37 files changed:
src/interfaces/odbc/bind.c [new file with mode: 0644]
src/interfaces/odbc/bind.h [new file with mode: 0644]
src/interfaces/odbc/columninfo.c [new file with mode: 0644]
src/interfaces/odbc/columninfo.h [new file with mode: 0644]
src/interfaces/odbc/connection.c [new file with mode: 0644]
src/interfaces/odbc/connection.h [new file with mode: 0644]
src/interfaces/odbc/convert.c [new file with mode: 0644]
src/interfaces/odbc/convert.h [new file with mode: 0644]
src/interfaces/odbc/drvconn.c [new file with mode: 0644]
src/interfaces/odbc/environ.c [new file with mode: 0644]
src/interfaces/odbc/environ.h [new file with mode: 0644]
src/interfaces/odbc/execute.c [new file with mode: 0644]
src/interfaces/odbc/info.c [new file with mode: 0644]
src/interfaces/odbc/license.txt [new file with mode: 0644]
src/interfaces/odbc/misc.c [new file with mode: 0644]
src/interfaces/odbc/misc.h [new file with mode: 0644]
src/interfaces/odbc/notice.txt [new file with mode: 0644]
src/interfaces/odbc/options.c [new file with mode: 0644]
src/interfaces/odbc/pgtypes.c [new file with mode: 0644]
src/interfaces/odbc/pgtypes.h [new file with mode: 0644]
src/interfaces/odbc/psqlodbc.c [new file with mode: 0644]
src/interfaces/odbc/psqlodbc.def [new file with mode: 0644]
src/interfaces/odbc/psqlodbc.h [new file with mode: 0644]
src/interfaces/odbc/psqlodbc.rc [new file with mode: 0644]
src/interfaces/odbc/qresult.c [new file with mode: 0644]
src/interfaces/odbc/qresult.h [new file with mode: 0644]
src/interfaces/odbc/resource.h [new file with mode: 0644]
src/interfaces/odbc/results.c [new file with mode: 0644]
src/interfaces/odbc/setup.c [new file with mode: 0644]
src/interfaces/odbc/socket.c [new file with mode: 0644]
src/interfaces/odbc/socket.h [new file with mode: 0644]
src/interfaces/odbc/statement.c [new file with mode: 0644]
src/interfaces/odbc/statement.h [new file with mode: 0644]
src/interfaces/odbc/tuple.c [new file with mode: 0644]
src/interfaces/odbc/tuple.h [new file with mode: 0644]
src/interfaces/odbc/tuplelist.c [new file with mode: 0644]
src/interfaces/odbc/tuplelist.h [new file with mode: 0644]

diff --git a/src/interfaces/odbc/bind.c b/src/interfaces/odbc/bind.c
new file mode 100644 (file)
index 0000000..c01f61a
--- /dev/null
@@ -0,0 +1,340 @@
+\r
+/* Module:          bind.c\r
+ *\r
+ * Description:     This module contains routines related to binding \r
+ *                  columns and parameters.\r
+ *\r
+ * Classes:         BindInfoClass, ParameterInfoClass\r
+ *\r
+ * API functions:   SQLBindParameter, SQLBindCol, SQLDescribeParam, SQLNumParams,\r
+ *                  SQLParamOptions(NI)\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+#include "bind.h"
+#include "environ.h"
+#include "statement.h"
+#include "qresult.h"
+#include "pgtypes.h"
+#include <stdlib.h>
+#include <malloc.h>
+#include <sql.h>\r
+#include <sqlext.h>\r
+
+//      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;\r
+                       stmt->parameters[i].data_at_exec = FALSE;\r
+                       stmt->parameters[i].EXEC_used = NULL;\r
+                       stmt->parameters[i].EXEC_buffer = NULL;\r
+               }
+       }
+
+       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;\r
+\r
+       /*      If rebinding a parameter that had data-at-exec stuff in it,\r
+               then free that stuff\r
+       */\r
+       if (stmt->parameters[ipar].EXEC_used) {\r
+               free(stmt->parameters[ipar].EXEC_used);\r
+               stmt->parameters[ipar].EXEC_used = NULL;\r
+       }\r
+\r
+       if (stmt->parameters[ipar].EXEC_buffer) {\r
+               free(stmt->parameters[ipar].EXEC_buffer);\r
+               stmt->parameters[ipar].EXEC_buffer = NULL;\r
+       }\r
+\r
+       if (pcbValue && *pcbValue <= SQL_LEN_DATA_AT_EXEC_OFFSET)\r
+               stmt->parameters[ipar].data_at_exec = TRUE;\r
+       else\r
+               stmt->parameters[ipar].data_at_exec = FALSE;\r
+\r
+
+       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; i<stmt->bindings_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 (file)
index 0000000..7ce3321
--- /dev/null
@@ -0,0 +1,45 @@
+\r
+/* File:            bind.h\r
+ *\r
+ * Description:     See "bind.c"\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#ifndef __BIND_H__
+#define __BIND_H__
+
+#include "psqlodbc.h"
+
+/*\r
+ * BindInfoClass -- stores information about a bound column\r
+ */\r
+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;\r
+       Int4 *EXEC_used;\r
+       char *EXEC_buffer;\r
+       char data_at_exec;\r
+};
+
+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 (file)
index 0000000..4f029e9
--- /dev/null
@@ -0,0 +1,159 @@
+\r
+/* Module:          columninfo.c\r
+ *\r
+ * Description:     This module contains routines related to \r
+ *                  reading and storing the field information from a query.\r
+ *\r
+ * Classes:         ColumnInfoClass (Functions prefix: "CI_")\r
+ *\r
+ * API functions:   none\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "columninfo.h"\r
+#include "socket.h"
+#include <stdlib.h>
+#include <malloc.h>
+
+ColumnInfoClass *
+CI_Constructor()
+{
+ColumnInfoClass *rv;
+
+       rv = (ColumnInfoClass *) malloc(sizeof(ColumnInfoClass));
+
+       if (rv) {
+               rv->num_fields = 0;
+               rv->name = NULL;
+               rv->adtid = NULL;
+               rv->adtsize = NULL;
+       }
+
+       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];
+\r
+
+       /* 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]);
+       }
+\r
+       /*      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 (file)
index 0000000..05d3dce
--- /dev/null
@@ -0,0 +1,40 @@
+\r
+/* File:            columninfo.h\r
+ *\r
+ * Description:     See "columninfo.c"\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#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])
+\r
+
+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 (file)
index 0000000..e0ab2a9
--- /dev/null
@@ -0,0 +1,979 @@
+\r
+/* Module:          connection.c\r
+ *\r
+ * Description:     This module contains routines related to \r
+ *                  connecting to and disconnecting from the Postgres DBMS.\r
+ *\r
+ * Classes:         ConnectionClass (Functions prefix: "CC_")\r
+ *\r
+ * API functions:   SQLAllocConnect, SQLConnect, SQLDisconnect, SQLFreeConnect,\r
+ *                  SQLBrowseConnect(NI)\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "environ.h"
+#include "connection.h"
+#include "socket.h"
+#include "statement.h"
+#include "qresult.h"
+#include <stdio.h>
+#include <odbcinst.h>
+
+#define STMT_INCREMENT 16  /* how many statement holders to allocate at a time */
+
+extern GLOBAL_VALUES globals;\r
+
+
+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);
+\r
+       if (conn->status == CONN_EXECUTING) {\r
+               conn->errornumber = CONN_IN_USE;\r
+               conn->errormsg = "A transaction is currently being executed";\r
+               return SQL_ERROR;\r
+       }\r
+
+       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 */\r
+       if ( ! EN_remove_connection(conn->henv, conn)) {\r
+               conn->errornumber = CONN_IN_USE;\r
+               conn->errormsg = "A transaction is currently being executed";\r
+               return SQL_ERROR;\r
+       }\r
+
+       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);
+\r
+       if ( ci->protocol[0] == '\0' || overwrite)\r
+               SQLGetPrivateProfileString(DSN, INI_PROTOCOL, "", ci->protocol, sizeof(ci->protocol), ODBC_INI);\r
+\r
+       if ( ci->conn_settings[0] == '\0' || overwrite)\r
+               SQLGetPrivateProfileString(DSN, INI_CONNSETTINGS, "", ci->conn_settings, sizeof(ci->conn_settings), ODBC_INI);\r
+\r
+       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", \r
+               self, DSN, \r
+               ci->server,\r
+               ci->database,\r
+               ci->username,\r
+               ci->password,\r
+               ci->port,\r
+               ci->readonly,\r
+               ci->protocol,\r
+               ci->conn_settings);
+}
+
+
+char 
+CC_connect(ConnectionClass *self, char do_password)
+{
+StartupPacket sp;\r
+StartupPacket6_2 sp62;
+QResultClass *res;
+SocketClass *sock;
+ConnInfo *ci = &(self->connInfo);
+int areq = -1;
+int beresp;
+char msgbuffer[ERROR_MSG_LENGTH]; 
+char salt[2];\r
+
+       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");
+\r
+               if ( PROTOCOL_62(ci)) {\r
+                       sock->reverse = TRUE;           /* make put_int and get_int work for 6.2 */\r
+\r
+                       memset(&sp62, 0, sizeof(StartupPacket6_2));\r
+                       SOCK_put_int(sock, htonl(4+sizeof(StartupPacket6_2)), 4);\r
+                       sp62.authtype = htonl(NO_AUTHENTICATION);\r
+                       strncpy(sp62.database, ci->database, PATH_SIZE);\r
+                       strncpy(sp62.user, ci->username, NAMEDATALEN);\r
+                       SOCK_put_n_char(sock, (char *) &sp62, sizeof(StartupPacket6_2));\r
+                       SOCK_flush_output(sock);\r
+               }\r
+               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);
+               }\r
+
+               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
+       // ***************************************************\r
+
+       if ( ! PROTOCOL_62(ci)) do {\r
+
+               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");
+
+\r
+       /**********************************************/\r
+       /*******   Send any initial settings  *********/\r
+       /**********************************************/\r
+\r
+       CC_send_settings(self);\r
+\r
+
+       CC_clear_error(self);   /* clear any initial command errors */\r
+       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\r
+       error message with its socket error message.\r
+*/
+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;
+}
+
+\r
+/*     The "result_in" is only used by QR_next_tuple() to fetch another group of rows into\r
+       the same existing QResultClass (this occurs when the tuple cache is depleted and\r
+       needs to be re-filled).\r
+\r
+       The "cursor" is used by SQLExecute to associate a statement handle as the cursor name\r
+       (i.e., C3326857) for SQL select statements.  This cursor is then used in future \r
+       'declare cursor C3326857 for ...' and 'fetch 100 in C3326857' statements.\r
+*/
+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);\r
+                                       mylog("send_query: read command '%s'\n", cmdbuffer);\r
+                                       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;
+               }
+       }
+}
+\r
+char\r
+CC_send_settings(ConnectionClass *self)\r
+{\r
+char ini_query[MAX_MESSAGE_LEN];\r
+ConnInfo *ci = &(self->connInfo);\r
+QResultClass *res;\r
+\r
+       ini_query[0] = '\0';\r
+\r
+       /*      Turn on/off genetic optimizer based on global flag */\r
+       if (globals.optimizer[0] != '\0')\r
+               sprintf(ini_query, "set geqo to '%s'", globals.optimizer);\r
+\r
+       /*      Global settings */\r
+       if (globals.conn_settings[0] != '\0')\r
+               sprintf(&ini_query[strlen(ini_query)], "%s%s", \r
+                       ini_query[0] != '\0' ? "; " : "",\r
+                       globals.conn_settings);\r
+\r
+       /*      Per Datasource settings */\r
+       if (ci->conn_settings[0] != '\0')\r
+               sprintf(&ini_query[strlen(ini_query)], "%s%s",\r
+                       ini_query[0] != '\0' ? "; " : "",\r
+                       ci->conn_settings);\r
+\r
+       if (ini_query[0] != '\0') {\r
+               mylog("Sending Initial Connection query: '%s'\n", ini_query);\r
+\r
+               res = CC_send_query(self, ini_query, NULL, NULL);\r
+               if (res && QR_get_status(res) != PGRES_FATAL_ERROR) {\r
+                       mylog("Initial Query response: '%s'\n", QR_get_notice(res));\r
+               }\r
+\r
+               if ( res == NULL || \r
+                       QR_get_status(res) == PGRES_BAD_RESPONSE ||\r
+                       QR_get_status(res) == PGRES_FATAL_ERROR || \r
+                       QR_get_status(res) == PGRES_INTERNAL_ERROR) {\r
+\r
+                       self->errornumber = CONNECTION_COULD_NOT_SEND;\r
+                       self->errormsg = "Error sending ConnSettings";\r
+                       if (res)\r
+                               QR_Destructor(res);\r
+                       return 0;\r
+               }\r
+\r
+               if (res)\r
+                       QR_Destructor(res);\r
+       }\r
+       return TRUE;\r
+}\r
+\r
diff --git a/src/interfaces/odbc/connection.h b/src/interfaces/odbc/connection.h
new file mode 100644 (file)
index 0000000..30d5672
--- /dev/null
@@ -0,0 +1,183 @@
+\r
+/* File:            connection.h\r
+ *\r
+ * Description:     See "connection.c"\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#ifndef __CONNECTION_H__
+#define __CONNECTION_H__
+
+#include <windows.h>
+#include <sql.h>
+#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\r
+#define CONN_INVALID_AUTHENTICATION 10
+#define CONN_AUTH_TYPE_UNSUPPORTED 11\r
+
+
+/* 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
+\r
+/*     Old 6.2 protocol defines */\r
+#define NO_AUTHENTICATION      7\r
+#define PATH_SIZE                      64\r
+#define ARGV_SIZE                      64\r
+#define NAMEDATALEN                    16\r
+
+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) */\r
+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;
+\r
+\r
+/*     This startup packet is to support pre-Postgres 6.3 protocol */
+typedef struct _StartupPacket6_2\r
+{\r
+       unsigned int    authtype;\r
+       char                    database[PATH_SIZE];\r
+       char                    user[NAMEDATALEN];\r
+       char                    options[ARGV_SIZE];\r
+       char                    execfile[ARGV_SIZE];\r
+       char                    tty[PATH_SIZE];\r
+} StartupPacket6_2;\r
+\r
+\r
+/*     Structure to hold all the connection attributes for a specific\r
+       connection (used for both registry and file, DSN and DRIVER)\r
+*/
+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];\r
+       char    conn_settings[LARGE_REGISTRY_LEN];\r
+       char    protocol[SMALL_REGISTRY_LEN];
+       char    port[SMALL_REGISTRY_LEN];
+       char    readonly[SMALL_REGISTRY_LEN];   \r
+       char    focus_password;
+} ConnInfo;
+\r
+/*     Macro to determine is the connection using 6.2 protocol? */
+#define PROTOCOL_62(conninfo_)         (strncmp((conninfo_)->protocol, PG62, strlen(PG62)) == 0)\r
+\r
+\r
+/*******       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 
+\r
+\r
+/*     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);\r
+
+#endif
diff --git a/src/interfaces/odbc/convert.c b/src/interfaces/odbc/convert.c
new file mode 100644 (file)
index 0000000..e340c83
--- /dev/null
@@ -0,0 +1,995 @@
+\r
+/* Module:         convert.c\r
+ *\r
+ * Description:           This module contains routines related to \r
+ *                 converting parameters and columns into requested data types.\r
+ *                 Parameters are converted from their SQL_C data types into\r
+ *                 the appropriate postgres type.  Columns are converted from\r
+ *                 their postgres type (SQL type) into the appropriate SQL_C \r
+ *                 data type.\r
+ *\r
+ * Classes:        n/a\r
+ *\r
+ * API functions:  none\r
+ *\r
+ * Comments:       See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include <stdio.h>
+#include <string.h>
+#include <windows.h>
+#include <sql.h>\r
+#include <sqlext.h>
+#include <time.h>\r
+#include <math.h>
+#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;\r
+
+
+       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;\r
+
+       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:\r
+                       if (strnicmp(value, "invalid", 7) != 0) {\r
+                               nf = sscanf(value, "%3s %3s %2d %2d:%2d:%2d %4d %3s", &day, &mon, &st.d, &st.hh, &st.mm, &st.ss, &st.y, &tz);\r
+\r
+                               if (nf == 7 || nf == 8) {
+                                       /* convert month name to month number */
+                                       st.m = monthToNumber(mon);\r
+                               }\r
+                       } else {        /* The timestamp is invalid so set something conspicuous, like the epoch */\r
+                               t = 0;\r
+                               tim = localtime(&t);\r
+                               st.m = tim->tm_mon + 1;\r
+                               st.d = tim->tm_mday;\r
+                               st.y = tim->tm_year + 1900;\r
+                               st.hh = tim->tm_hour;\r
+                               st.mm = tim->tm_min;\r
+                               st.ss = tim->tm_sec;\r
+                       }
+                       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;\r
+                       else
+                               bool = 0;\r
+                       }
+                       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) {\r
+                                       strcpy((char *) rgbValue, bool ? "1" : "0");\r
+                                       mylog("PG_TYPE_BOOL: rgbValue = '%s'\n", rgbValue);\r
+                               }
+                               break;\r
+\r
+                       case PG_TYPE_BYTEA:             // convert binary data to hex strings (i.e, 255 = "FF")\r
+                               len = convert_pgbinary_to_char(value, rgbValue, cbValueMax);\r
+                               break;
+\r
+                       default:\r
+                               /* convert linefeeds to carriage-return/linefeed */\r
+                               convert_linefeeds( (char *) value, rgbValue, cbValueMax);\r
+                       len = strlen(rgbValue);\r
+\r
+                               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:                                   \r
+                               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:      \r
+                               //      truncate if necessary\r
+                               //      convert octal escapes to bytes\r
+                               len = convert_from_pgbinary(value, rgbValue, cbValueMax);\r
+                               mylog("SQL_C_BINARY: len = %d\n", len);\r
+                               break;\r
+                               
+                       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;\r
+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;
+\r
+
+       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\r
+               if (old_statement[opos] == '\r' && opos+1<strlen(old_statement) && old_statement[opos+1] == '\n') {
+                       continue;
+               }\r
+
+               //      Handle literals (date, time, timestamp)
+               else if (old_statement[opos] == '{') {
+                       char *esc;
+                       char *begin = &old_statement[opos + 1];
+                       char *end = strchr(begin, '}');
+
+                       if ( ! end)
+                               continue;
+
+                       *end = '\0';
+
+                       esc = convert_escape(begin);
+                       if (esc) {
+                               memcpy(&new_statement[npos], esc, strlen(esc));
+                               npos += strlen(esc);
+                       }
+
+                       opos += end - begin + 2;
+
+                       *end = '}';
+
+                       continue;
+               }
+
+               else if (old_statement[opos] != '?') {          // a regular character
+                       new_statement[npos++] = old_statement[opos];
+                       continue;
+               }
+
+               /****************************************************/
+               /*       Its a '?' parameter alright                */
+               /****************************************************/
+
+               param_number++;
+
+           if (param_number >= stmt->parameters_allocated)
+                       break;
+\r
+               /*      Assign correct buffers based on data at exec param or not */\r
+               if ( stmt->parameters[param_number].data_at_exec) {\r
+                       used = stmt->parameters[param_number].EXEC_used;\r
+                       buffer = stmt->parameters[param_number].EXEC_buffer;\r
+               }\r
+               else {\r
+                       used = stmt->parameters[param_number].used;\r
+                       buffer = stmt->parameters[param_number].buffer;\r
+               }\r
+\r
+               /*      Handle NULL parameter data */\r
+               if (used && *used == SQL_NULL_DATA) {\r
+                       strcpy(&new_statement[npos], "NULL");\r
+                       npos += 4;\r
+                       continue;\r
+               }\r
+\r
+               /*      If no buffer, and its not null, then what the hell is it? \r
+                       Just leave it alone then.\r
+               */
+               if ( ! buffer) {
+                       new_statement[npos++] = '?';
+                       continue;
+               }\r
+
+               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) {\r
+               case SQL_C_BINARY:\r
+               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:\r
+
+                       new_statement[npos++] = '\'';   /*    Open Quote */
+
+                       /* it was a SQL_C_CHAR */
+                       if (buf) {
+                               convert_returns(buf, &new_statement[npos], used ? *used : SQL_NTS);\r
+                               npos += strlen(&new_statement[npos]);\r
+                       }
+
+                       /* 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);\r
+                               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);\r
+                               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);\r
+                               parse_datetime(cbuf, &st);
+                       }
+
+                       tbuf = convert_time(&st);
+
+                       sprintf(&new_statement[npos], "'%s'", tbuf);
+                       npos += strlen(tbuf) + 2;
+
+                       break;
+                                                       }\r
+
+               case SQL_BINARY:\r
+               case SQL_VARBINARY:\r
+               case SQL_LONGVARBINARY:         /* non-ascii characters should be converted to octal */\r
+\r
+                       new_statement[npos++] = '\'';   /*    Open Quote */\r
+\r
+                       mylog("SQL_LONGVARBINARY: about to call convert_to_pgbinary, *used = %d\n", *used);\r
+\r
+                       npos += convert_to_pgbinary(buf, &new_statement[npos], *used);\r
+\r
+                       new_statement[npos++] = '\'';   /*    Close Quote */\r
+                       \r
+                       break;\r
+
+               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;
+}
+\r
+/*     Change linefeed to carriage-return/linefeed */\r
+char *\r
+convert_linefeeds(char *si, char *dst, size_t max)\r
+{\r
+size_t i = 0, out = 0;\r
+static char sout[TEXT_FIELD_SIZE+5];\r
+char *p;\r
+\r
+       if (dst)\r
+               p = dst;\r
+       else {\r
+               p = sout;\r
+               max = sizeof(sout);\r
+       }\r
+\r
+       p[0] = '\0';\r
+\r
+       for (i = 0; i < strlen(si) && out < max-2; i++) {\r
+               if (si[i] == '\n') {\r
+                       p[out++] = '\r';\r
+                       p[out++] = '\n';\r
+               }\r
+               else\r
+                       p[out++] = si[i];\r
+       }\r
+       p[out] = '\0';\r
+       return p;\r
+}\r
+\r
+/*     Change carriage-return/linefeed to just linefeed */\r
+char *\r
+convert_returns(char *si, char *dst, int used)\r
+{\r
+size_t i = 0, out = 0, max;\r
+static char sout[TEXT_FIELD_SIZE+5];\r
+char *p;\r
+\r
+       if (dst)\r
+               p = dst;\r
+       else\r
+               p = sout;\r
+\r
+       p[0] = '\0';\r
+\r
+       if (used == SQL_NTS)\r
+               max = strlen(si);\r
+       else\r
+               max = used;\r
+\r
+       for (i = 0; i < max; i++) {\r
+               if (si[i] == '\r' && i+1 < strlen(si) && si[i+1] == '\n') \r
+                       continue;\r
+               else\r
+                       p[out++] = si[i];\r
+       }\r
+       p[out] = '\0';\r
+       return p;\r
+}\r
+\r
+int\r
+convert_pgbinary_to_char(char *value, char *rgbValue, int cbValueMax)\r
+{\r
+       return 0;\r
+}\r
+\r
+unsigned int\r
+conv_from_octal(unsigned char *s)\r
+{\r
+int i, y=0;\r
+\r
+       for (i = 1; i <= 3; i++) {\r
+               y += (s[i] - 48) * (int) pow(8, 3-i);\r
+       }\r
+\r
+       return y;\r
+\r
+}\r
+\r
+//     convert octal escapes to bytes\r
+int\r
+convert_from_pgbinary(unsigned char *value, unsigned char *rgbValue, int cbValueMax)\r
+{\r
+size_t i;\r
+int o=0;\r
+       \r
+       for (i = 0; i < strlen(value); ) {\r
+               if (value[i] == '\\') {\r
+                       rgbValue[o] = conv_from_octal(&value[i]);\r
+                       i += 4;\r
+               }\r
+               else {\r
+                       rgbValue[o] = value[i++];\r
+               }\r
+               mylog("convert_from_pgbinary: i=%d, rgbValue[%d] = %d, %c\n", i, o, rgbValue[o], rgbValue[o]);\r
+               o++;\r
+       }\r
+       return o;\r
+}\r
+\r
+\r
+char *\r
+conv_to_octal(unsigned char val)\r
+{\r
+int i;\r
+static char x[6];\r
+\r
+       x[0] = '\\';\r
+       x[1] = '\\';\r
+       x[5] = '\0';\r
+\r
+       for (i = 4; i > 1; i--) {\r
+               x[i] = (val & 7) + 48;\r
+               val >>= 3;\r
+       }\r
+\r
+       return x;\r
+}\r
+\r
+//     convert non-ascii bytes to octal escape sequences\r
+int\r
+convert_to_pgbinary(unsigned char *in, char *out, int len)\r
+{\r
+int i, o=0;\r
+\r
+\r
+       for (i = 0; i < len; i++) {\r
+               mylog("convert_to_pgbinary: in[%d] = %d, %c\n", i, in[i], in[i]);\r
+               if (in[i] < 32 || in[i] > 126) {\r
+                       strcpy(&out[o], conv_to_octal(in[i])); \r
+                       o += 5;\r
+               }\r
+               else\r
+                       out[o++] = in[i];\r
+\r
+       }\r
+\r
+       mylog("convert_to_pgbinary: returning %d, out='%.*s'\n", o, o, out);\r
+\r
+       return o;\r
+}\r
+\r
diff --git a/src/interfaces/odbc/convert.h b/src/interfaces/odbc/convert.h
new file mode 100644 (file)
index 0000000..2826589
--- /dev/null
@@ -0,0 +1,47 @@
+\r
+/* File:            convert.h\r
+ *\r
+ * Description:     See "convert.c"\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#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);\r
+char *convert_returns(char *si, char *dst, int used);\r
+\r
+int convert_pgbinary_to_char(char *value, char *rgbValue, int cbValueMax);\r
+int convert_from_pgbinary(unsigned char *value, unsigned char *rgbValue, int cbValueMax);\r
+int convert_to_pgbinary(unsigned char *in, char *out, int len);\r
+
+#endif
diff --git a/src/interfaces/odbc/drvconn.c b/src/interfaces/odbc/drvconn.c
new file mode 100644 (file)
index 0000000..ee0d825
--- /dev/null
@@ -0,0 +1,327 @@
+\r
+/* Module:          drvconn.c\r
+ *\r
+ * Description:     This module contains only routines related to \r
+ *                  implementing SQLDriverConnect.\r
+ *\r
+ * Classes:         n/a\r
+ *\r
+ * API functions:   SQLDriverConnect\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "psqlodbc.h"
+#include "connection.h"
+
+#include <winsock.h>
+#include <sqlext.h>
+#include <string.h>
+#include <windows.h>
+#include <windowsx.h>
+#include <odbcinst.h>
+#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.\r
+               For now, I think its better to just let the application ask over and over until\r
+               a password is entered (the user can always hit Cancel to get out)\r
+       */\r
+       if( ci->username[0] == '\0' ||
+               ci->server[0] == '\0' ||
+               ci->database[0] == '\0' || 
+               ci->port[0] == '\0') {\r
+//             (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,\r
+                       ci->protocol,\r
+                       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));\r
+\r
+               CheckDlgButton(hdlg, PG62_EDIT, PROTOCOL_62(ci));
+\r
+               /*      The driver connect dialog box allows manipulating this global variable */\r
+               CheckDlgButton(hdlg, COMMLOG_EDIT, globals.commlog);\r
+
+               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));\r
+\r
+                       if (IsDlgButtonChecked(hdlg, PG62_EDIT))\r
+                               strcpy(ci->protocol, PG62);\r
+                       else\r
+                               ci->protocol[0] = '\0';
+\r
+                       /*      The driver connect dialog box allows manipulating this global variable */\r
+                       globals.commlog = IsDlgButtonChecked(hdlg, COMMLOG_EDIT);\r
+                       updateGlobals();\r
+
+               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);\r
+\r
+               else if (stricmp(attribute, "protocol") == 0)\r
+                       strcpy(ci->protocol, value);\r
+
+               else if (stricmp(attribute, "connsettings") == 0)\r
+                       strcpy(ci->conn_settings, value);\r
+\r
+       }
+
+
+       free(our_connect_string);
+}
diff --git a/src/interfaces/odbc/environ.c b/src/interfaces/odbc/environ.c
new file mode 100644 (file)
index 0000000..b7d8084
--- /dev/null
@@ -0,0 +1,416 @@
+\r
+/* Module:          environ.c\r
+ *\r
+ * Description:     This module contains routines related to \r
+ *                  the environment, such as storing connection handles,\r
+ *                  and returning errors.\r
+ *\r
+ * Classes:         EnvironmentClass (Functions prefix: "EN_")\r
+ *\r
+ * API functions:   SQLAllocEnv, SQLFreeEnv, SQLError\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "environ.h"
+#include "connection.h"
+#include "statement.h"
+#include <stdlib.h>
+#include <malloc.h>
+
+/* 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;\r
+                               case CONN_INVALID_AUTHENTICATION:\r
+                               case CONN_AUTH_TYPE_UNSUPPORTED:\r
+                                       strcpy(szSqlState, "28000");\r
+                                       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;
+       }
+\r
+    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++) {\r
+               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;
+\r
+mylog("EN_add_connection: self = %u, conn = %u\n", self, conn);\r
+
+       for (i = 0; i < MAX_CONNECTIONS; i++) {
+               if ( ! conns[i]) {\r
+                       conn->henv = self;
+                       conns[i] = conn;\r
+\r
+                       mylog("       added at i =%d, conn->henv = %u, conns[i]->henv = %u\n",\r
+                               i, conn->henv, conns[i]->henv);\r
+
+                       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 (file)
index 0000000..0989163
--- /dev/null
@@ -0,0 +1,32 @@
+\r
+/* File:            environ.h\r
+ *\r
+ * Description:     See "environ.c"\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#ifndef __ENVIRON_H__
+#define __ENVIRON_H__
+
+#include "psqlodbc.h"
+#include <windows.h>
+#include <sql.h>
+\r
+#define ENV_ALLOC_ERROR 1
+\r
+/**********            Environment Handle      *************/
+struct EnvironmentClass_ {
+       char *errormsg;
+       int errornumber;
+};
+\r
+/*     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 (file)
index 0000000..6afbe30
--- /dev/null
@@ -0,0 +1,535 @@
+\r
+/* Module:          execute.c\r
+ *\r
+ * Description:     This module contains routines related to \r
+ *                  preparing and executing an SQL statement.\r
+ *\r
+ * Classes:         n/a\r
+ *\r
+ * API functions:   SQLPrepare, SQLExecute, SQLExecDirect, SQLTransact,\r
+ *                  SQLCancel, SQLNativeSql, SQLParamData, SQLPutData\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "psqlodbc.h"
+#include <stdio.h>
+#include <string.h>
+#include <windows.h>
+#include <sqlext.h>
+
+#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;\r
+ConnectionClass *conn;
+int i, retval;\r
+\r
+
+       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;
+       }\r
+\r
+\r
+       /*      The bound parameters could have possibly changed since the last execute\r
+               of this statement?  Therefore check for params and re-copy.\r
+       */\r
+       stmt->data_at_exec = -1;\r
+       for (i = 0; i < stmt->parameters_allocated; i++) {\r
+               /*      Check for data at execution parameters */\r
+               if ( stmt->parameters[i].data_at_exec == TRUE) {\r
+                       if (stmt->data_at_exec < 0)\r
+                               stmt->data_at_exec = 1;\r
+                       else\r
+                               stmt->data_at_exec++;\r
+               }\r
+       }\r
+       //      If there are some data at execution parameters, return need data\r
+       //      SQLParamData and SQLPutData will be used to send params and execute the statement.\r
+       if (stmt->data_at_exec > 0)\r
+               return SQL_NEED_DATA;\r
+\r
+\r
+       mylog("SQLExecute: copying statement params: trans_status=%d, len=%d, stmt='%s'\n", conn->transact_status, strlen(stmt->statement), stmt->statement);\r
+\r
+       //      Create the statement with parameters substituted.\r
+       retval = copy_statement_with_parameters(stmt);\r
+       if( retval != SQL_SUCCESS)\r
+               /* error msg passed from above */\r
+               return retval;\r
+\r
+       mylog("   stmt_with_params = '%s'\n", stmt->stmt_with_params);\r
+\r
+\r
+       return SC_execute(stmt);\r
+
+}
+\r
+\r
+\r
+
+//      -       -       -       -       -       -       -       -       -
+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 <MAX_CONNECTIONS; lf++) {
+                       conn = conns[lf];
+
+                       if (conn && conn->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;\r
+\r
+       //      Check if this can handle canceling in the middle of a SQLPutData?\r
+       if ( ! stmt)\r
+               return SQL_INVALID_HANDLE;\r
+\r
+       //      Not in the middle of SQLParamData/SQLPutData so cancel like a close.\r
+       if (stmt->data_at_exec < 0)\r
+               return SQLFreeStmt(hstmt, SQL_CLOSE);\r
+\r
+       //      In the middle of SQLParamData/SQLPutData, so cancel that.\r
+       //      Note, any previous data-at-exec buffers will be freed in the recycle\r
+       //      if they call SQLExecDirect or SQLExecute again.\r
+\r
+       stmt->data_at_exec = -1;\r
+       stmt->current_exec_param = -1;\r
+       stmt->put_data = FALSE;\r
+\r
+}
+
+//      -       -       -       -       -       -       -       -       -
+
+//      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)
+{\r
+StatementClass *stmt = (StatementClass *) hstmt;\r
+int i, retval;\r
+\r
+       if ( ! stmt)\r
+               return SQL_INVALID_HANDLE;\r
+\r
+       if (stmt->data_at_exec < 0) {\r
+               stmt->errornumber = STMT_SEQUENCE_ERROR;\r
+               stmt->errormsg = "No execution-time parameters for this statement";\r
+               return SQL_ERROR;\r
+       }\r
+\r
+       if (stmt->data_at_exec > stmt->parameters_allocated) {\r
+               stmt->errornumber = STMT_SEQUENCE_ERROR;\r
+               stmt->errormsg = "Too many execution-time parameters were present";\r
+               return SQL_ERROR;\r
+       }\r
+\r
+       /*      Done, now copy the params and then execute the statement */\r
+       if (stmt->data_at_exec == 0) {\r
+               retval = copy_statement_with_parameters(stmt);\r
+               if (retval != SQL_SUCCESS)\r
+                       return retval;\r
+\r
+               return SC_execute(stmt);\r
+       }\r
+\r
+       /*      At least 1 data at execution parameter, so Fill in the token value */\r
+       for (i = 0; i < stmt->parameters_allocated; i++) {\r
+               if (stmt->parameters[i].data_at_exec == TRUE) {\r
+                       stmt->data_at_exec--;\r
+                       stmt->current_exec_param = i;\r
+                       stmt->put_data = FALSE;\r
+                       *prgbValue = stmt->parameters[i].buffer;        /* token */\r
+               }\r
+       }\r
+\r
+       return SQL_NEED_DATA;\r
+}
+
+//      -       -       -       -       -       -       -       -       -
+
+//      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;\r
+char *buffer;\r
+SDWORD *used;\r
+int old_pos;\r
+\r
+\r
+       if ( ! stmt)\r
+               return SQL_INVALID_HANDLE;\r
+\r
+       \r
+       if (stmt->current_exec_param < 0) {\r
+               stmt->errornumber = STMT_SEQUENCE_ERROR;\r
+               stmt->errormsg = "Previous call was not SQLPutData or SQLParamData";\r
+               return SQL_ERROR;\r
+       }\r
+\r
+       if ( ! stmt->put_data) {        /* first call */\r
+\r
+               mylog("SQLPutData: (1) cbValue = %d\n", cbValue);\r
+\r
+               stmt->put_data = TRUE;\r
+\r
+               used = (SDWORD *) malloc(sizeof(SDWORD));\r
+               if ( ! used) {\r
+                       stmt->errornumber = STMT_NO_MEMORY_ERROR;\r
+                       stmt->errormsg = "Out of memory in SQLPutData (1)";\r
+                       return SQL_ERROR;\r
+               }\r
+\r
+               *used = cbValue;\r
+               stmt->parameters[stmt->current_exec_param].EXEC_used = used;\r
+\r
+               if (cbValue == SQL_NULL_DATA)\r
+                       return SQL_SUCCESS;\r
+\r
+               if (cbValue == SQL_NTS) {\r
+                       buffer = strdup(rgbValue);\r
+                       if ( ! buffer) {\r
+                               stmt->errornumber = STMT_NO_MEMORY_ERROR;\r
+                               stmt->errormsg = "Out of memory in SQLPutData (2)";\r
+                               return SQL_ERROR;\r
+                       }\r
+               }\r
+               else {\r
+                       buffer = malloc(cbValue + 1);\r
+                       if ( ! buffer) {\r
+                               stmt->errornumber = STMT_NO_MEMORY_ERROR;\r
+                               stmt->errormsg = "Out of memory in SQLPutData (2)";\r
+                               return SQL_ERROR;\r
+                       }\r
+                       memcpy(buffer, rgbValue, cbValue);\r
+                       buffer[cbValue] = '\0';\r
+               }\r
+\r
+               stmt->parameters[stmt->current_exec_param].EXEC_buffer = buffer;\r
+       }\r
+\r
+       else {  /* calling SQLPutData more than once */\r
+\r
+               mylog("SQLPutData: (>1) cbValue = %d\n", cbValue);\r
+\r
+               used = stmt->parameters[stmt->current_exec_param].EXEC_used;\r
+               buffer = stmt->parameters[stmt->current_exec_param].EXEC_buffer;\r
+\r
+               if (cbValue == SQL_NTS) {\r
+                       buffer = realloc(buffer, strlen(buffer) + strlen(rgbValue) + 1);\r
+                       if ( ! buffer) {\r
+                               stmt->errornumber = STMT_NO_MEMORY_ERROR;\r
+                               stmt->errormsg = "Out of memory in SQLPutData (3)";\r
+                               return SQL_ERROR;\r
+                       }\r
+                       strcat(buffer, rgbValue);\r
+\r
+                       mylog("       cbValue = SQL_NTS: strlen(buffer) = %d\n", strlen(buffer));\r
+\r
+                       *used = cbValue;\r
+\r
+               }\r
+               else if (cbValue > 0) {\r
+\r
+                       old_pos = *used;\r
+\r
+                       *used += cbValue;\r
+\r
+                       mylog("        cbValue = %d, old_pos = %d, *used = %d\n", cbValue, old_pos, *used);\r
+\r
+                       buffer = realloc(buffer, *used + 1);\r
+                       if ( ! buffer) {\r
+                               stmt->errornumber = STMT_NO_MEMORY_ERROR;\r
+                               stmt->errormsg = "Out of memory in SQLPutData (3)";\r
+                               return SQL_ERROR;\r
+                       }\r
+\r
+                       memcpy(&buffer[old_pos], rgbValue, cbValue);\r
+                       buffer[*used] = '\0';\r
+\r
+               }\r
+               else\r
+                       return SQL_ERROR;\r
+               \r
+\r
+               /*      reassign buffer incase realloc moved it */\r
+               stmt->parameters[stmt->current_exec_param].EXEC_buffer = buffer;\r
+\r
+       }\r
+\r
+\r
+       return SQL_SUCCESS;\r
+}
+
+
diff --git a/src/interfaces/odbc/info.c b/src/interfaces/odbc/info.c
new file mode 100644 (file)
index 0000000..b87efbd
--- /dev/null
@@ -0,0 +1,2180 @@
+\r
+/* Module:          info.c\r
+ *\r
+ * Description:     This module contains routines related to\r
+ *                  ODBC informational functions.\r
+ *\r
+ * Classes:         n/a\r
+ *\r
+ * API functions:   SQLGetInfo, SQLGetTypeInfo, SQLGetFunctions, \r
+ *                  SQLTables, SQLColumns, SQLStatistics, SQLSpecialColumns,\r
+ *                  SQLPrimaryKeys, SQLForeignKeys, \r
+ *                  SQLProcedureColumns(NI), SQLProcedures(NI), \r
+ *                  SQLTablePrivileges(NI), SQLColumnPrivileges(NI)\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include <string.h>
+#include <stdio.h>
+#include "psqlodbc.h"
+#include <windows.h>
+#include <sql.h> 
+#include <sqlext.h>
+#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;\r
+               /*      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;
+\r
+       // **********************************************************************
+       //      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)",\r
+               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);
+\r
+\r
+               /*      Some Notes about Postgres Data Types:\r
+\r
+                       VARCHAR - the length is stored in the pg_attribute.atttypmod field\r
+                       BPCHAR  - the length is also stored as varchar is\r
+                       NAME    - the length is fixed and stored in pg_attribute.attlen field (32 on my system)\r
+\r
+               */
+        if((field_type == PG_TYPE_VARCHAR) ||
+                  (field_type == PG_TYPE_NAME) ||\r
+                  (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 (file)
index 0000000..c5e8acf
--- /dev/null
@@ -0,0 +1,962 @@
+                 GNU LIBRARY GENERAL PUBLIC LICENSE
+\r
+                      Version 2, June 1991
+\r
+
+\r
+ Copyright (C) 1991 Free Software Foundation, Inc.
+\r
+                    675 Mass Ave, Cambridge, MA 02139, USA
+\r
+ Everyone is permitted to copy and distribute verbatim copies
+\r
+ of this license document, but changing it is not allowed.
+\r
+
+\r
+[This is the first released version of the library GPL.  It is
+\r
+ numbered 2 because it goes with version 2 of the ordinary GPL.]
+\r
+
+\r
+                           Preamble
+\r
+
+\r
+  The licenses for most software are designed to take away your
+\r
+freedom to share and change it.  By contrast, the GNU General Public
+\r
+Licenses are intended to guarantee your freedom to share and change
+\r
+free software--to make sure the software is free for all its users.
+\r
+
+\r
+  This license, the Library General Public License, applies to some
+\r
+specially designated Free Software Foundation software, and to any
+\r
+other libraries whose authors decide to use it.  You can use it for
+\r
+your libraries, too.
+\r
+
+\r
+  When we speak of free software, we are referring to freedom, not
+\r
+price.  Our General Public Licenses are designed to make sure that you
+\r
+have the freedom to distribute copies of free software (and charge for
+\r
+this service if you wish), that you receive source code or can get it
+\r
+if you want it, that you can change the software or use pieces of it
+\r
+in new free programs; and that you know you can do these things.
+\r
+
+\r
+  To protect your rights, we need to make restrictions that forbid
+\r
+anyone to deny you these rights or to ask you to surrender the rights.
+\r
+These restrictions translate to certain responsibilities for you if
+\r
+you distribute copies of the library, or if you modify it.
+\r
+
+\r
+  For example, if you distribute copies of the library, whether gratis
+\r
+or for a fee, you must give the recipients all the rights that we gave
+\r
+you.  You must make sure that they, too, receive or can get the source
+\r
+code.  If you link a program with the library, you must provide
+\r
+complete object files to the recipients so that they can relink them
+\r
+with the library, after making changes to the library and recompiling
+\r
+it.  And you must show them these terms so they know their rights.
+\r
+
+\r
+  Our method of protecting your rights has two steps: (1) copyright
+\r
+the library, and (2) offer you this license which gives you legal
+\r
+permission to copy, distribute and/or modify the library.
+\r
+
+\r
+  Also, for each distributor's protection, we want to make certain
+\r
+that everyone understands that there is no warranty for this free
+\r
+library.  If the library is modified by someone else and passed on, we
+\r
+want its recipients to know that what they have is not the original
+\r
+version, so that any problems introduced by others will not reflect on
+\r
+the original authors' reputations.
+\r
+\f
+\r
+  Finally, any free program is threatened constantly by software
+\r
+patents.  We wish to avoid the danger that companies distributing free
+\r
+software will individually obtain patent licenses, thus in effect
+\r
+transforming the program into proprietary software.  To prevent this,
+\r
+we have made it clear that any patent must be licensed for everyone's
+\r
+free use or not licensed at all.
+\r
+
+\r
+  Most GNU software, including some libraries, is covered by the ordinary
+\r
+GNU General Public License, which was designed for utility programs.  This
+\r
+license, the GNU Library General Public License, applies to certain
+\r
+designated libraries.  This license is quite different from the ordinary
+\r
+one; be sure to read it in full, and don't assume that anything in it is
+\r
+the same as in the ordinary license.
+\r
+
+\r
+  The reason we have a separate public license for some libraries is that
+\r
+they blur the distinction we usually make between modifying or adding to a
+\r
+program and simply using it.  Linking a program with a library, without
+\r
+changing the library, is in some sense simply using the library, and is
+\r
+analogous to running a utility program or application program.  However, in
+\r
+a textual and legal sense, the linked executable is a combined work, a
+\r
+derivative of the original library, and the ordinary General Public License
+\r
+treats it as such.
+\r
+
+\r
+  Because of this blurred distinction, using the ordinary General
+\r
+Public License for libraries did not effectively promote software
+\r
+sharing, because most developers did not use the libraries.  We
+\r
+concluded that weaker conditions might promote sharing better.
+\r
+
+\r
+  However, unrestricted linking of non-free programs would deprive the
+\r
+users of those programs of all benefit from the free status of the
+\r
+libraries themselves.  This Library General Public License is intended to
+\r
+permit developers of non-free programs to use free libraries, while
+\r
+preserving your freedom as a user of such programs to change the free
+\r
+libraries that are incorporated in them.  (We have not seen how to achieve
+\r
+this as regards changes in header files, but we have achieved it as regards
+\r
+changes in the actual functions of the Library.)  The hope is that this
+\r
+will lead to faster development of free libraries.
+\r
+
+\r
+  The precise terms and conditions for copying, distribution and
+\r
+modification follow.  Pay close attention to the difference between a
+\r
+"work based on the library" and a "work that uses the library".  The
+\r
+former contains code derived from the library, while the latter only
+\r
+works together with the library.
+\r
+
+\r
+  Note that it is possible for a library to be covered by the ordinary
+\r
+General Public License rather than by this special one.
+\r
+\f
+\r
+                 GNU LIBRARY GENERAL PUBLIC LICENSE
+\r
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+\r
+
+\r
+  0. This License Agreement applies to any software library which
+\r
+contains a notice placed by the copyright holder or other authorized
+\r
+party saying it may be distributed under the terms of this Library
+\r
+General Public License (also called "this License").  Each licensee is
+\r
+addressed as "you".
+\r
+
+\r
+  A "library" means a collection of software functions and/or data
+\r
+prepared so as to be conveniently linked with application programs
+\r
+(which use some of those functions and data) to form executables.
+\r
+
+\r
+  The "Library", below, refers to any such software library or work
+\r
+which has been distributed under these terms.  A "work based on the
+\r
+Library" means either the Library or any derivative work under
+\r
+copyright law: that is to say, a work containing the Library or a
+\r
+portion of it, either verbatim or with modifications and/or translated
+\r
+straightforwardly into another language.  (Hereinafter, translation is
+\r
+included without limitation in the term "modification".)
+\r
+
+\r
+  "Source code" for a work means the preferred form of the work for
+\r
+making modifications to it.  For a library, complete source code means
+\r
+all the source code for all modules it contains, plus any associated
+\r
+interface definition files, plus the scripts used to control compilation
+\r
+and installation of the library.
+\r
+
+\r
+  Activities other than copying, distribution and modification are not
+\r
+covered by this License; they are outside its scope.  The act of
+\r
+running a program using the Library is not restricted, and output from
+\r
+such a program is covered only if its contents constitute a work based
+\r
+on the Library (independent of the use of the Library in a tool for
+\r
+writing it).  Whether that is true depends on what the Library does
+\r
+and what the program that uses the Library does.
+\r
+  
+\r
+  1. You may copy and distribute verbatim copies of the Library's
+\r
+complete source code as you receive it, in any medium, provided that
+\r
+you conspicuously and appropriately publish on each copy an
+\r
+appropriate copyright notice and disclaimer of warranty; keep intact
+\r
+all the notices that refer to this License and to the absence of any
+\r
+warranty; and distribute a copy of this License along with the
+\r
+Library.
+\r
+
+\r
+  You may charge a fee for the physical act of transferring a copy,
+\r
+and you may at your option offer warranty protection in exchange for a
+\r
+fee.
+\r
+\f
+\r
+  2. You may modify your copy or copies of the Library or any portion
+\r
+of it, thus forming a work based on the Library, and copy and
+\r
+distribute such modifications or work under the terms of Section 1
+\r
+above, provided that you also meet all of these conditions:
+\r
+
+\r
+    a) The modified work must itself be a software library.
+\r
+
+\r
+    b) You must cause the files modified to carry prominent notices
+\r
+    stating that you changed the files and the date of any change.
+\r
+
+\r
+    c) You must cause the whole of the work to be licensed at no
+\r
+    charge to all third parties under the terms of this License.
+\r
+
+\r
+    d) If a facility in the modified Library refers to a function or a
+\r
+    table of data to be supplied by an application program that uses
+\r
+    the facility, other than as an argument passed when the facility
+\r
+    is invoked, then you must make a good faith effort to ensure that,
+\r
+    in the event an application does not supply such function or
+\r
+    table, the facility still operates, and performs whatever part of
+\r
+    its purpose remains meaningful.
+\r
+
+\r
+    (For example, a function in a library to compute square roots has
+\r
+    a purpose that is entirely well-defined independent of the
+\r
+    application.  Therefore, Subsection 2d requires that any
+\r
+    application-supplied function or table used by this function must
+\r
+    be optional: if the application does not supply it, the square
+\r
+    root function must still compute square roots.)
+\r
+
+\r
+These requirements apply to the modified work as a whole.  If
+\r
+identifiable sections of that work are not derived from the Library,
+\r
+and can be reasonably considered independent and separate works in
+\r
+themselves, then this License, and its terms, do not apply to those
+\r
+sections when you distribute them as separate works.  But when you
+\r
+distribute the same sections as part of a whole which is a work based
+\r
+on the Library, the distribution of the whole must be on the terms of
+\r
+this License, whose permissions for other licensees extend to the
+\r
+entire whole, and thus to each and every part regardless of who wrote
+\r
+it.
+\r
+
+\r
+Thus, it is not the intent of this section to claim rights or contest
+\r
+your rights to work written entirely by you; rather, the intent is to
+\r
+exercise the right to control the distribution of derivative or
+\r
+collective works based on the Library.
+\r
+
+\r
+In addition, mere aggregation of another work not based on the Library
+\r
+with the Library (or with a work based on the Library) on a volume of
+\r
+a storage or distribution medium does not bring the other work under
+\r
+the scope of this License.
+\r
+
+\r
+  3. You may opt to apply the terms of the ordinary GNU General Public
+\r
+License instead of this License to a given copy of the Library.  To do
+\r
+this, you must alter all the notices that refer to this License, so
+\r
+that they refer to the ordinary GNU General Public License, version 2,
+\r
+instead of to this License.  (If a newer version than version 2 of the
+\r
+ordinary GNU General Public License has appeared, then you can specify
+\r
+that version instead if you wish.)  Do not make any other change in
+\r
+these notices.
+\r
+\f
+\r
+  Once this change is made in a given copy, it is irreversible for
+\r
+that copy, so the ordinary GNU General Public License applies to all
+\r
+subsequent copies and derivative works made from that copy.
+\r
+
+\r
+  This option is useful when you wish to copy part of the code of
+\r
+the Library into a program that is not a library.
+\r
+
+\r
+  4. You may copy and distribute the Library (or a portion or
+\r
+derivative of it, under Section 2) in object code or executable form
+\r
+under the terms of Sections 1 and 2 above provided that you accompany
+\r
+it with the complete corresponding machine-readable source code, which
+\r
+must be distributed under the terms of Sections 1 and 2 above on a
+\r
+medium customarily used for software interchange.
+\r
+
+\r
+  If distribution of object code is made by offering access to copy
+\r
+from a designated place, then offering equivalent access to copy the
+\r
+source code from the same place satisfies the requirement to
+\r
+distribute the source code, even though third parties are not
+\r
+compelled to copy the source along with the object code.
+\r
+
+\r
+  5. A program that contains no derivative of any portion of the
+\r
+Library, but is designed to work with the Library by being compiled or
+\r
+linked with it, is called a "work that uses the Library".  Such a
+\r
+work, in isolation, is not a derivative work of the Library, and
+\r
+therefore falls outside the scope of this License.
+\r
+
+\r
+  However, linking a "work that uses the Library" with the Library
+\r
+creates an executable that is a derivative of the Library (because it
+\r
+contains portions of the Library), rather than a "work that uses the
+\r
+library".  The executable is therefore covered by this License.
+\r
+Section 6 states terms for distribution of such executables.
+\r
+
+\r
+  When a "work that uses the Library" uses material from a header file
+\r
+that is part of the Library, the object code for the work may be a
+\r
+derivative work of the Library even though the source code is not.
+\r
+Whether this is true is especially significant if the work can be
+\r
+linked without the Library, or if the work is itself a library.  The
+\r
+threshold for this to be true is not precisely defined by law.
+\r
+
+\r
+  If such an object file uses only numerical parameters, data
+\r
+structure layouts and accessors, and small macros and small inline
+\r
+functions (ten lines or less in length), then the use of the object
+\r
+file is unrestricted, regardless of whether it is legally a derivative
+\r
+work.  (Executables containing this object code plus portions of the
+\r
+Library will still fall under Section 6.)
+\r
+
+\r
+  Otherwise, if the work is a derivative of the Library, you may
+\r
+distribute the object code for the work under the terms of Section 6.
+\r
+Any executables containing that work also fall under Section 6,
+\r
+whether or not they are linked directly with the Library itself.
+\r
+\f
+\r
+  6. As an exception to the Sections above, you may also compile or
+\r
+link a "work that uses the Library" with the Library to produce a
+\r
+work containing portions of the Library, and distribute that work
+\r
+under terms of your choice, provided that the terms permit
+\r
+modification of the work for the customer's own use and reverse
+\r
+engineering for debugging such modifications.
+\r
+
+\r
+  You must give prominent notice with each copy of the work that the
+\r
+Library is used in it and that the Library and its use are covered by
+\r
+this License.  You must supply a copy of this License.  If the work
+\r
+during execution displays copyright notices, you must include the
+\r
+copyright notice for the Library among them, as well as a reference
+\r
+directing the user to the copy of this License.  Also, you must do one
+\r
+of these things:
+\r
+
+\r
+    a) Accompany the work with the complete corresponding
+\r
+    machine-readable source code for the Library including whatever
+\r
+    changes were used in the work (which must be distributed under
+\r
+    Sections 1 and 2 above); and, if the work is an executable linked
+\r
+    with the Library, with the complete machine-readable "work that
+\r
+    uses the Library", as object code and/or source code, so that the
+\r
+    user can modify the Library and then relink to produce a modified
+\r
+    executable containing the modified Library.  (It is understood
+\r
+    that the user who changes the contents of definitions files in the
+\r
+    Library will not necessarily be able to recompile the application
+\r
+    to use the modified definitions.)
+\r
+
+\r
+    b) Accompany the work with a written offer, valid for at
+\r
+    least three years, to give the same user the materials
+\r
+    specified in Subsection 6a, above, for a charge no more
+\r
+    than the cost of performing this distribution.
+\r
+
+\r
+    c) If distribution of the work is made by offering access to copy
+\r
+    from a designated place, offer equivalent access to copy the above
+\r
+    specified materials from the same place.
+\r
+
+\r
+    d) Verify that the user has already received a copy of these
+\r
+    materials or that you have already sent this user a copy.
+\r
+
+\r
+  For an executable, the required form of the "work that uses the
+\r
+Library" must include any data and utility programs needed for
+\r
+reproducing the executable from it.  However, as a special exception,
+\r
+the source code distributed need not include anything that is normally
+\r
+distributed (in either source or binary form) with the major
+\r
+components (compiler, kernel, and so on) of the operating system on
+\r
+which the executable runs, unless that component itself accompanies
+\r
+the executable.
+\r
+
+\r
+  It may happen that this requirement contradicts the license
+\r
+restrictions of other proprietary libraries that do not normally
+\r
+accompany the operating system.  Such a contradiction means you cannot
+\r
+use both them and the Library together in an executable that you
+\r
+distribute.
+\r
+\f
+\r
+  7. You may place library facilities that are a work based on the
+\r
+Library side-by-side in a single library together with other library
+\r
+facilities not covered by this License, and distribute such a combined
+\r
+library, provided that the separate distribution of the work based on
+\r
+the Library and of the other library facilities is otherwise
+\r
+permitted, and provided that you do these two things:
+\r
+
+\r
+    a) Accompany the combined library with a copy of the same work
+\r
+    based on the Library, uncombined with any other library
+\r
+    facilities.  This must be distributed under the terms of the
+\r
+    Sections above.
+\r
+
+\r
+    b) Give prominent notice with the combined library of the fact
+\r
+    that part of it is a work based on the Library, and explaining
+\r
+    where to find the accompanying uncombined form of the same work.
+\r
+
+\r
+  8. You may not copy, modify, sublicense, link with, or distribute
+\r
+the Library except as expressly provided under this License.  Any
+\r
+attempt otherwise to copy, modify, sublicense, link with, or
+\r
+distribute the Library is void, and will automatically terminate your
+\r
+rights under this License.  However, parties who have received copies,
+\r
+or rights, from you under this License will not have their licenses
+\r
+terminated so long as such parties remain in full compliance.
+\r
+
+\r
+  9. You are not required to accept this License, since you have not
+\r
+signed it.  However, nothing else grants you permission to modify or
+\r
+distribute the Library or its derivative works.  These actions are
+\r
+prohibited by law if you do not accept this License.  Therefore, by
+\r
+modifying or distributing the Library (or any work based on the
+\r
+Library), you indicate your acceptance of this License to do so, and
+\r
+all its terms and conditions for copying, distributing or modifying
+\r
+the Library or works based on it.
+\r
+
+\r
+  10. Each time you redistribute the Library (or any work based on the
+\r
+Library), the recipient automatically receives a license from the
+\r
+original licensor to copy, distribute, link with or modify the Library
+\r
+subject to these terms and conditions.  You may not impose any further
+\r
+restrictions on the recipients' exercise of the rights granted herein.
+\r
+You are not responsible for enforcing compliance by third parties to
+\r
+this License.
+\r
+\f
+\r
+  11. If, as a consequence of a court judgment or allegation of patent
+\r
+infringement or for any other reason (not limited to patent issues),
+\r
+conditions are imposed on you (whether by court order, agreement or
+\r
+otherwise) that contradict the conditions of this License, they do not
+\r
+excuse you from the conditions of this License.  If you cannot
+\r
+distribute so as to satisfy simultaneously your obligations under this
+\r
+License and any other pertinent obligations, then as a consequence you
+\r
+may not distribute the Library at all.  For example, if a patent
+\r
+license would not permit royalty-free redistribution of the Library by
+\r
+all those who receive copies directly or indirectly through you, then
+\r
+the only way you could satisfy both it and this License would be to
+\r
+refrain entirely from distribution of the Library.
+\r
+
+\r
+If any portion of this section is held invalid or unenforceable under any
+\r
+particular circumstance, the balance of the section is intended to apply,
+\r
+and the section as a whole is intended to apply in other circumstances.
+\r
+
+\r
+It is not the purpose of this section to induce you to infringe any
+\r
+patents or other property right claims or to contest validity of any
+\r
+such claims; this section has the sole purpose of protecting the
+\r
+integrity of the free software distribution system which is
+\r
+implemented by public license practices.  Many people have made
+\r
+generous contributions to the wide range of software distributed
+\r
+through that system in reliance on consistent application of that
+\r
+system; it is up to the author/donor to decide if he or she is willing
+\r
+to distribute software through any other system and a licensee cannot
+\r
+impose that choice.
+\r
+
+\r
+This section is intended to make thoroughly clear what is believed to
+\r
+be a consequence of the rest of this License.
+\r
+
+\r
+  12. If the distribution and/or use of the Library is restricted in
+\r
+certain countries either by patents or by copyrighted interfaces, the
+\r
+original copyright holder who places the Library under this License may add
+\r
+an explicit geographical distribution limitation excluding those countries,
+\r
+so that distribution is permitted only in or among countries not thus
+\r
+excluded.  In such case, this License incorporates the limitation as if
+\r
+written in the body of this License.
+\r
+
+\r
+  13. The Free Software Foundation may publish revised and/or new
+\r
+versions of the Library General Public License from time to time.
+\r
+Such new versions will be similar in spirit to the present version,
+\r
+but may differ in detail to address new problems or concerns.
+\r
+
+\r
+Each version is given a distinguishing version number.  If the Library
+\r
+specifies a version number of this License which applies to it and
+\r
+"any later version", you have the option of following the terms and
+\r
+conditions either of that version or of any later version published by
+\r
+the Free Software Foundation.  If the Library does not specify a
+\r
+license version number, you may choose any version ever published by
+\r
+the Free Software Foundation.
+\r
+\f
+\r
+  14. If you wish to incorporate parts of the Library into other free
+\r
+programs whose distribution conditions are incompatible with these,
+\r
+write to the author to ask for permission.  For software which is
+\r
+copyrighted by the Free Software Foundation, write to the Free
+\r
+Software Foundation; we sometimes make exceptions for this.  Our
+\r
+decision will be guided by the two goals of preserving the free status
+\r
+of all derivatives of our free software and of promoting the sharing
+\r
+and reuse of software generally.
+\r
+
+\r
+                           NO WARRANTY
+\r
+
+\r
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+\r
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+\r
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+\r
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+\r
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+\r
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+\r
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+\r
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+\r
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+\r
+
+\r
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+\r
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+\r
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+\r
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+\r
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+\r
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+\r
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+\r
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+\r
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+\r
+DAMAGES.
+\r
+
+\r
+                    END OF TERMS AND CONDITIONS
+\r
+\f
+\r
+     Appendix: How to Apply These Terms to Your New Libraries
+\r
+
+\r
+  If you develop a new library, and you want it to be of the greatest
+\r
+possible use to the public, we recommend making it free software that
+\r
+everyone can redistribute and change.  You can do so by permitting
+\r
+redistribution under these terms (or, alternatively, under the terms of the
+\r
+ordinary General Public License).
+\r
+
+\r
+  To apply these terms, attach the following notices to the library.  It is
+\r
+safest to attach them to the start of each source file to most effectively
+\r
+convey the exclusion of warranty; and each file should have at least the
+\r
+"copyright" line and a pointer to where the full notice is found.
+\r
+
+\r
+    <one line to give the library's name and a brief idea of what it does.>
+\r
+    Copyright (C) <year>  <name of author>
+\r
+
+\r
+    This library is free software; you can redistribute it and/or
+\r
+    modify it under the terms of the GNU Library General Public
+\r
+    License as published by the Free Software Foundation; either
+\r
+    version 2 of the License, or (at your option) any later version.
+\r
+
+\r
+    This library is distributed in the hope that it will be useful,
+\r
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+\r
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+\r
+    Library General Public License for more details.
+\r
+
+\r
+    You should have received a copy of the GNU Library General Public
+\r
+    License along with this library; if not, write to the Free
+\r
+    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+\r
+
+\r
+Also add information on how to contact you by electronic and paper mail.
+\r
+
+\r
+You should also get your employer (if you work as a programmer) or your
+\r
+school, if any, to sign a "copyright disclaimer" for the library, if
+\r
+necessary.  Here is a sample; alter the names:
+\r
+
+\r
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+\r
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+\r
+
+\r
+  <signature of Ty Coon>, 1 April 1990
+\r
+  Ty Coon, President of Vice
+\r
+
+\r
+That's all there is to it!
+\r
diff --git a/src/interfaces/odbc/misc.c b/src/interfaces/odbc/misc.c
new file mode 100644 (file)
index 0000000..7ce8348
--- /dev/null
@@ -0,0 +1,224 @@
+\r
+/* Module:          misc.c\r
+ *\r
+ * Description:     This module contains miscellaneous routines\r
+ *                  such as for debugging/logging and string functions.\r
+ *\r
+ * Classes:         n/a\r
+ *\r
+ * API functions:   none\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include <stdio.h>
+#include <windows.h>
+#include <sql.h>
+
+#include "psqlodbc.h"
+\r
+extern GLOBAL_VALUES globals;
+
+
+#ifdef MY_LOG
+#include <varargs.h>
+
+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 <varargs.h>
+
+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);\r
+               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';\r
+               }
+               else { 
+                       memcpy(dst, src, dst_len-1);
+                       dst[dst_len-1] = '\0';  /* truncated */
+                       return STRCPY_TRUNCATED;
+               }
+       }\r
+
+       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++) {\r
+                       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 (file)
index 0000000..b9047f1
--- /dev/null
@@ -0,0 +1,58 @@
+\r
+/* File:            misc.h\r
+ *\r
+ * Description:     See "misc.c"\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#ifndef __MISC_H__
+#define __MISC_H__
+
+#include <stdio.h>
+
+/*     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),\r
+       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 (file)
index 0000000..695ba6b
--- /dev/null
@@ -0,0 +1,35 @@
+\r
+/********************************************************************\r
+\r
+  PSQLODBC.DLL - A library to talk to the PostgreSQL DBMS using ODBC.\r
+\r
+\r
+  Copyright (C) 1998; Insight Distribution Systems\r
+\r
+  The code contained in this library is based on code written by \r
+  Christian Czezatke and Dan McGuirk, (C) 1996.\r
+\r
+\r
+  This library is free software; you can redistribute it and/or modify\r
+  it under the terms of the GNU Library General Public License as \r
+  published by the Free Software Foundation; either version 2 of the \r
+  License, or (at your option) any later version.\r
+\r
+  This library is distributed in the hope that it will be useful, but\r
+  WITHOUT ANY WARRANTY; without even the implied warranty of\r
+  MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
+  Library General Public License for more details.\r
+\r
+  You should have received a copy of the GNU Library General Public\r
+  License along with this library (see "license.txt"); if not, write to\r
+  the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA\r
+  02139, USA.\r
+\r
+\r
+  How to contact the author:\r
+\r
+  email:  byronn@insightdist.com       (Byron Nikolaidis)\r
+\r
+\r
+***********************************************************************/\r
+\r
diff --git a/src/interfaces/odbc/options.c b/src/interfaces/odbc/options.c
new file mode 100644 (file)
index 0000000..1649eed
--- /dev/null
@@ -0,0 +1,210 @@
+\r
+/* Module:          options.c\r
+ *\r
+ * Description:     This module contains routines for getting/setting\r
+ *                  connection and statement options.\r
+ *\r
+ * Classes:         n/a\r
+ *\r
+ * API functions:   SQLSetConnectOption, SQLSetStmtOption, SQLGetConnectOption,\r
+ *                  SQLGetStmtOption\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "psqlodbc.h"
+#include <windows.h>
+#include <sql.h>
+#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 (file)
index 0000000..e1762b3
--- /dev/null
@@ -0,0 +1,441 @@
+\r
+/* Module:          pgtypes.c\r
+ *\r
+ * Description:     This module contains routines for getting information\r
+ *                  about the supported Postgres data types.  Only the function\r
+ *                  pgtype_to_sqltype() returns an unknown condition.  All other\r
+ *                  functions return a suitable default so that even data types that\r
+ *                  are not directly supported can be used (it is handled as char data).\r
+ *\r
+ * Classes:         n/a\r
+ *\r
+ * API functions:   none\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "psqlodbc.h"
+#include "pgtypes.h"
+#include <windows.h>
+#include <sql.h>
+#include <sqlext.h>
+
+/* 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,\r
+                               PG_TYPE_BYTEA,\r
+                           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:\r
+    case PG_TYPE_NAME:          \r
+    case PG_TYPE_VARCHAR:              return SQL_VARCHAR;\r
+
+    case PG_TYPE_TEXT:                 return SQL_LONGVARCHAR;\r
+       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;
+\r
+       case PG_TYPE_BYTEA:                     return SQL_C_BINARY;\r
+
+    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";\r
+       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 \r
+       override this length with the atttypmod length from pg_attribute \r
+*/
+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;\r
+
+       case PG_TYPE_NAME:                      return 32;\r
+\r
+       case PG_TYPE_VARCHAR:\r
+       case PG_TYPE_BPCHAR:            return MAX_VARCHAR_SIZE;\r
+
+       case PG_TYPE_INT2:          return 5;\r
+\r
+       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 \r
+       override this length with the atttypmod length from pg_attribute \r
+*/\r
+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;\r
+
+       case PG_TYPE_NAME:                      return 32;\r
+\r
+       case PG_TYPE_VARCHAR:\r
+       case PG_TYPE_BPCHAR:            return MAX_VARCHAR_SIZE;\r
+\r
+       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 */\r
+    }
+}
+
+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 (file)
index 0000000..0b4be5f
--- /dev/null
@@ -0,0 +1,81 @@
+\r
+/* File:            pgtypes.h\r
+ *\r
+ * Description:     See "pgtypes.c"\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#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 (file)
index 0000000..08a22a2
--- /dev/null
@@ -0,0 +1,146 @@
+\r
+/* Module:          psqlodbc.c\r
+ *\r
+ * Description:     This module contains the main entry point (DllMain) for the library.\r
+ *                  It also contains functions to get and set global variables for the\r
+ *                  driver in the registry.\r
+ *\r
+ * Classes:         n/a\r
+ *\r
+ * API functions:   none\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "psqlodbc.h"
+#include <winsock.h>
+#include <windows.h>
+#include <sql.h>
+#include <odbcinst.h>
+
+HINSTANCE NEAR s_hModule;               /* Saved module handle. */
+GLOBAL_VALUES globals;\r
+\r
+
+/*     This function reads the ODBCINST.INI portion of
+       the registry and gets any driver defaults.
+*/
+void getGlobalDefaults(void)
+{
+char temp[128];
+\r
+\r
+       //      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);\r
+       else\r
+               globals.fetch_max = FETCH_MAX;
+\r
+
+       //      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);\r
+       else\r
+               globals.socket_buffersize = SOCK_BUFFER_SIZE;
+\r
+
+       //      Debug is stored in the driver section
+       SQLGetPrivateProfileString(DBMS_NAME, INI_DEBUG, "0", 
+                                                       temp, sizeof(temp), ODBCINST_INI);
+       globals.debug = atoi(temp);
+\r
+\r
+       //      CommLog is stored in the driver section\r
+       SQLGetPrivateProfileString(DBMS_NAME, INI_COMMLOG, "0", \r
+                                                       temp, sizeof(temp), ODBCINST_INI);\r
+       globals.commlog = atoi(temp);\r
+\r
+\r
+       //      Optimizer is stored in the driver section only (OFF, ON, or ON=x)\r
+       SQLGetPrivateProfileString(DBMS_NAME, INI_OPTIMIZER, "", \r
+                               globals.optimizer, sizeof(globals.optimizer), ODBCINST_INI);\r
+\r
+\r
+       //      ConnSettings is stored in the driver section and per datasource for override\r
+       SQLGetPrivateProfileString(DBMS_NAME, INI_CONNSETTINGS, "", \r
+                               globals.conn_settings, sizeof(globals.conn_settings), ODBCINST_INI);\r
+}
+\r
+\r
+/*     This function writes any global parameters (that can be manipulated)\r
+       to the ODBCINST.INI portion of the registry \r
+*/\r
+void updateGlobals(void)\r
+{\r
+char tmp[128];\r
+\r
+       sprintf(tmp, "%d", globals.commlog);\r
+       SQLWritePrivateProfileString(DBMS_NAME,\r
+               INI_COMMLOG, tmp, ODBCINST_INI);\r
+}
+
+/*     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 (file)
index 0000000..23a5a82
--- /dev/null
@@ -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 (file)
index 0000000..a08acf6
--- /dev/null
@@ -0,0 +1,120 @@
+\r
+/* File:            psqlodbc.h\r
+ *\r
+ * Description:     This file contains defines and declarations that are related to\r
+ *                  the entire driver.\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#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\r
+#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 */\r
+#define MAX_CONNECTIONS                                128             /* conns per environment (arbitrary)  */
+#define MAX_FIELDS                                     512
+#define BYTELEN                                                8
+#define VARHDRSZ                                       sizeof(Int4)
+\r
+/*     Registry length limits */
+#define LARGE_REGISTRY_LEN                     4096    /* used for special cases */\r
+#define MEDIUM_REGISTRY_LEN                    128             /* normal size for user,database,etc. */\r
+#define SMALL_REGISTRY_LEN                     10              /* for 1/0 settings */\r
+
+
+/*     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"
+\r
+\r
+#define PG62   "6.2"           /* "Protocol" key setting to force Postgres 6.2 */\r
+
+/* 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 */\r
+#define INI_COMMLOG       "CommLog"                    /* Communication to backend logging */
+#define INI_PROTOCOL      "Protocol"           /* What protocol (6.2) */\r
+#define INI_OPTIMIZER     "Optimizer"          /* Use backend genetic optimizer */\r
+#define INI_CONNSETTINGS  "ConnSettings"       /* Anything to send to backend on successful connection */\r
+\r
+
+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;
+\r
+\r
+typedef struct GlobalValues_\r
+{\r
+       int                                     fetch_max;\r
+       int                                     socket_buffersize;\r
+       int                                     debug;\r
+       int                                     commlog;\r
+       char                            optimizer[MEDIUM_REGISTRY_LEN];\r
+       char                            conn_settings[LARGE_REGISTRY_LEN];\r
+} GLOBAL_VALUES;\r
+\r
+\r
+/* sizes */
+#define TEXT_FIELD_SIZE                        4094    /* size of text fields (not including null term) */\r
+#define MAX_VARCHAR_SIZE               254             /* maximum size of a varchar (not including null term) */\r
+
+\r
+/* global prototypes */\r
+void updateGlobals(void);\r
+\r
+
+#include "misc.h"
+
+#endif
diff --git a/src/interfaces/odbc/psqlodbc.rc b/src/interfaces/odbc/psqlodbc.rc
new file mode 100644 (file)
index 0000000..b84bb86
--- /dev/null
@@ -0,0 +1,205 @@
+//Microsoft Developer Studio generated resource script.\r
+//\r
+#include "resource.h"\r
+\r
+#define APSTUDIO_READONLY_SYMBOLS\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// Generated from the TEXTINCLUDE 2 resource.\r
+//\r
+#include "afxres.h"\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+#undef APSTUDIO_READONLY_SYMBOLS\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+// English (U.S.) resources\r
+\r
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r
+#ifdef _WIN32\r
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US\r
+#pragma code_page(1252)\r
+#endif //_WIN32\r
+\r
+#ifdef APSTUDIO_INVOKED\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// TEXTINCLUDE\r
+//\r
+\r
+1 TEXTINCLUDE DISCARDABLE \r
+BEGIN\r
+    "resource.h\0"\r
+END\r
+\r
+2 TEXTINCLUDE DISCARDABLE \r
+BEGIN\r
+    "#include ""afxres.h""\r\n"\r
+    "\0"\r
+END\r
+\r
+3 TEXTINCLUDE DISCARDABLE \r
+BEGIN\r
+    "\r\n"\r
+    "\0"\r
+END\r
+\r
+#endif    // APSTUDIO_INVOKED\r
+\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// Dialog\r
+//\r
+\r
+DRIVERCONNDIALOG DIALOG DISCARDABLE  0, 0, 269, 133\r
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU\r
+CAPTION "PostgreSQL Connection"\r
+FONT 8, "MS Sans Serif"\r
+BEGIN\r
+    RTEXT           "&Database:",IDC_STATIC,16,25,37,8\r
+    EDITTEXT        DATABASE_EDIT,55,25,72,12,ES_AUTOHSCROLL\r
+    RTEXT           "&Server:",IDC_STATIC,26,40,27,8\r
+    EDITTEXT        SERVER_EDIT,55,40,72,12,ES_AUTOHSCROLL\r
+    RTEXT           "&Port:",IDC_STATIC,150,40,20,8\r
+    EDITTEXT        PORT_EDIT,172,40,72,12,ES_AUTOHSCROLL\r
+    RTEXT           "&User Name:",IDC_STATIC,16,56,37,8\r
+    EDITTEXT        USERNAME_EDIT,55,56,72,12,ES_AUTOHSCROLL\r
+    RTEXT           "Pass&word:",IDC_STATIC,137,56,33,8\r
+    EDITTEXT        PASSWORD_EDIT,172,56,72,12,ES_PASSWORD | ES_AUTOHSCROLL\r
+    GROUPBOX        "Options:",IDC_STATIC,25,71,200,25\r
+    CONTROL         "&ReadOnly:",READONLY_EDIT,"Button",BS_AUTOCHECKBOX | \r
+                    BS_LEFTTEXT | BS_RIGHT | WS_GROUP | WS_TABSTOP,45,80,45,\r
+                    14\r
+    CONTROL         "&CommLog (Global):",COMMLOG_EDIT,"Button",\r
+                    BS_AUTOCHECKBOX | BS_LEFTTEXT | BS_RIGHT | WS_TABSTOP,\r
+                    100,80,75,14\r
+    CONTROL         "6.2",PG62_EDIT,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | \r
+                    BS_RIGHT | WS_TABSTOP,185,80,25,14\r
+    DEFPUSHBUTTON   "OK",IDOK,84,108,40,14,WS_GROUP\r
+    PUSHBUTTON      "Cancel",IDCANCEL,146,108,40,14\r
+    CTEXT           "Please supply any missing information needed to connect.",\r
+                    IDC_STATIC,40,7,188,11\r
+END\r
+\r
+CONFIGDSN DIALOG DISCARDABLE  65, 43, 292, 151\r
+STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | \r
+    WS_SYSMENU\r
+CAPTION "PostgreSQL Driver Setup"\r
+FONT 8, "MS Sans Serif"\r
+BEGIN\r
+    RTEXT           "&Data Source:",IDC_DSNAMETEXT,5,30,50,12,NOT WS_GROUP\r
+    EDITTEXT        IDC_DSNAME,57,30,72,12,ES_AUTOHSCROLL | WS_GROUP\r
+    RTEXT           "Des&cription:",IDC_STATIC,135,30,39,12,NOT WS_GROUP\r
+    EDITTEXT        IDC_DESC,175,30,108,12,ES_AUTOHSCROLL\r
+    RTEXT           "Data&base:",IDC_STATIC,17,45,38,12,NOT WS_GROUP\r
+    EDITTEXT        IDC_DATABASE,57,45,72,12,ES_AUTOHSCROLL\r
+    RTEXT           "&Server:",IDC_STATIC,27,60,29,12,NOT WS_GROUP\r
+    EDITTEXT        IDC_SERVER,57,60,72,12,ES_AUTOHSCROLL\r
+    RTEXT           "&Port:",IDC_STATIC,153,60,22,12\r
+    EDITTEXT        IDC_PORT,175,60,37,12,ES_AUTOHSCROLL\r
+    RTEXT           "&User Name:",IDC_STATIC,17,75,39,12\r
+    EDITTEXT        IDC_USER,57,75,72,12,ES_AUTOHSCROLL\r
+    RTEXT           "Pass&word:",IDC_STATIC,141,75,34,12\r
+    EDITTEXT        IDC_PASSWORD,175,75,72,12,ES_PASSWORD | ES_AUTOHSCROLL\r
+    GROUPBOX        "Options:",IDC_STATIC,35,92,205,25\r
+    CONTROL         "&ReadOnly:",IDC_READONLY,"Button",BS_AUTOCHECKBOX | \r
+                    BS_LEFTTEXT | BS_RIGHT | WS_GROUP | WS_TABSTOP,50,100,45,\r
+                    14\r
+    CONTROL         "&CommLog (Global):",IDC_COMMLOG,"Button",\r
+                    BS_AUTOCHECKBOX | BS_LEFTTEXT | BS_RIGHT | WS_TABSTOP,\r
+                    105,100,75,14\r
+    CONTROL         "6.2",IDC_PG62,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | \r
+                    BS_RIGHT | WS_TABSTOP,195,100,25,14\r
+    DEFPUSHBUTTON   "OK",IDOK,85,129,40,14,WS_GROUP\r
+    PUSHBUTTON      "Cancel",IDCANCEL,145,129,40,14\r
+    CTEXT           "Change data source name, description, or options.  Then choose OK.",\r
+                    IDC_STATIC,44,5,180,17\r
+END\r
+\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// DESIGNINFO\r
+//\r
+\r
+#ifdef APSTUDIO_INVOKED\r
+GUIDELINES DESIGNINFO DISCARDABLE \r
+BEGIN\r
+    DRIVERCONNDIALOG, DIALOG\r
+    BEGIN\r
+        RIGHTMARGIN, 268\r
+    END\r
+END\r
+#endif    // APSTUDIO_INVOKED\r
+\r
+\r
+#ifndef _MAC\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// Version\r
+//\r
+\r
+VS_VERSION_INFO VERSIONINFO\r
+ FILEVERSION 6,30,0,0\r
+ PRODUCTVERSION 6,30,0,0\r
+ FILEFLAGSMASK 0x3L\r
+#ifdef _DEBUG\r
+ FILEFLAGS 0x1L\r
+#else\r
+ FILEFLAGS 0x0L\r
+#endif\r
+ FILEOS 0x4L\r
+ FILETYPE 0x2L\r
+ FILESUBTYPE 0x0L\r
+BEGIN\r
+    BLOCK "StringFileInfo"\r
+    BEGIN\r
+        BLOCK "040904e4"\r
+        BEGIN\r
+            VALUE "Comments", "PostgreSQL ODBC driver for Windows 95\0"\r
+            VALUE "CompanyName", "Insight Distribution Systems\0"\r
+            VALUE "FileDescription", "PostgreSQL Driver\0"\r
+            VALUE "FileVersion", " 6.30.0000\0"\r
+            VALUE "InternalName", "psqlodbc\0"\r
+            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"\r
+            VALUE "OriginalFilename", "psqlodbc.dll\0"\r
+            VALUE "ProductName", "Microsoft Open Database Connectivity\0"\r
+            VALUE "ProductVersion", " 6.30.0000\0"\r
+        END\r
+    END\r
+    BLOCK "VarFileInfo"\r
+    BEGIN\r
+        VALUE "Translation", 0x409, 1252\r
+    END\r
+END\r
+\r
+#endif    // !_MAC\r
+\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// String Table\r
+//\r
+\r
+STRINGTABLE DISCARDABLE \r
+BEGIN\r
+    IDS_BADDSN              "Invalid DSN entry, please recheck."\r
+    IDS_MSGTITLE            "Invalid DSN"\r
+END\r
+\r
+#endif    // English (U.S.) resources\r
+/////////////////////////////////////////////////////////////////////////////\r
+\r
+\r
+\r
+#ifndef APSTUDIO_INVOKED\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// Generated from the TEXTINCLUDE 3 resource.\r
+//\r
+\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+#endif    // not APSTUDIO_INVOKED\r
+\r
diff --git a/src/interfaces/odbc/qresult.c b/src/interfaces/odbc/qresult.c
new file mode 100644 (file)
index 0000000..148e469
--- /dev/null
@@ -0,0 +1,472 @@
+\r
+/* Module:          qresult.c\r
+ *\r
+ * Description:     This module contains functions related to \r
+ *                  managing result information (i.e, fetching rows from the backend,\r
+ *                  managing the tuple cache, etc.) and retrieving it.\r
+ *                  Depending on the situation, a QResultClass will hold either data\r
+ *                  from the backend or a manually built result (see "qresult.h" to\r
+ *                  see which functions/macros are for manual or backend results.\r
+ *                  For manually built results, the QResultClass simply points to \r
+ *                  TupleList and ColumnInfo structures, which actually hold the data.\r
+ *\r
+ * Classes:         QResultClass (Functions prefix: "QR_")\r
+ *\r
+ * API functions:   none\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "qresult.h"
+#include "misc.h"
+#include <stdio.h>
+
+extern GLOBAL_VALUES globals;
+
+/*  Used for building a Manual Result only */\r
+/*     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)\r
+               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);
+\r
+
+       /* 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 (file)
index 0000000..b425a34
--- /dev/null
@@ -0,0 +1,105 @@
+\r
+/* File:            qresult.h\r
+ *\r
+ * Description:     See "qresult.c"\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#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\r
+
+       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))\r
+#define QR_get_value_backend(self, fieldno)                    (self->tupleField[fieldno].value) \r
+\r
+/*     These functions are used by both manual and backend results */
+#define QR_NumResultCols(self)                         (CI_get_num_fields(self->fields))\r
+#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_))\r
+\r
+/*     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)\r
+#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))\r
+
+/* 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);\r
+
+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 (file)
index 0000000..c58a0b7
--- /dev/null
@@ -0,0 +1,39 @@
+//{{NO_DEPENDENCIES}}\r
+// Microsoft Developer Studio generated include file.\r
+// Used by psqlodbc.rc\r
+//\r
+#define IDS_BADDSN                      1\r
+#define IDS_MSGTITLE                    2\r
+#define DRIVERCONNDIALOG                101\r
+#define IDC_DSNAME                      400\r
+#define IDC_DSNAMETEXT                  401\r
+#define IDC_DESC                        404\r
+#define IDC_SERVER                      407\r
+#define IDC_DATABASE                    408\r
+#define CONFIGDSN                       1001\r
+#define IDC_PORT                        1002\r
+#define IDC_USER                        1006\r
+#define IDC_PASSWORD                    1009\r
+#define IDC_READONLY                    1011\r
+#define READONLY_EDIT                   1012\r
+#define SAVEPASSWORD_EDIT               1013\r
+#define IDC_COMMLOG                     1014\r
+#define COMMLOG_EDIT                    1015\r
+#define IDC_PG62                        1016\r
+#define PG62_EDIT                       1017\r
+#define SERVER_EDIT                     1501\r
+#define PORT_EDIT                       1502\r
+#define DATABASE_EDIT                   1503\r
+#define USERNAME_EDIT                   1504\r
+#define PASSWORD_EDIT                   1505\r
+\r
+// Next default values for new objects\r
+// \r
+#ifdef APSTUDIO_INVOKED\r
+#ifndef APSTUDIO_READONLY_SYMBOLS\r
+#define _APS_NEXT_RESOURCE_VALUE        102\r
+#define _APS_NEXT_COMMAND_VALUE         40001\r
+#define _APS_NEXT_CONTROL_VALUE         1018\r
+#define _APS_NEXT_SYMED_VALUE           101\r
+#endif\r
+#endif\r
diff --git a/src/interfaces/odbc/results.c b/src/interfaces/odbc/results.c
new file mode 100644 (file)
index 0000000..1396083
--- /dev/null
@@ -0,0 +1,694 @@
+\r
+/* Module:          results.c\r
+ *\r
+ * Description:     This module contains functions related to \r
+ *                  retrieving result information through the ODBC API.\r
+ *\r
+ * Classes:         n/a\r
+ *\r
+ * API functions:   SQLRowCount, SQLNumResultCols, SQLDescribeCol, SQLColAttributes,\r
+ *                  SQLGetData, SQLFetch, SQLExtendedFetch, \r
+ *                  SQLMoreResults(NI), SQLSetPos(NI), SQLSetScrollOptions(NI),\r
+ *                  SQLSetCursorName(NI), SQLGetCursorName(NI)\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include <string.h>
+#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 <stdio.h>
+#include <windows.h>
+#include <sqlext.h>
+
+
+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);\r
+\r
+               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 (file)
index 0000000..05f076a
--- /dev/null
@@ -0,0 +1,679 @@
+\r
+/* Module:          setup.c\r
+ *\r
+ * Description:     This module contains the setup functions for \r
+ *                  adding/modifying a Data Source in the ODBC.INI portion\r
+ *                  of the registry.\r
+ *\r
+ * Classes:         n/a\r
+ *\r
+ * API functions:   ConfigDSN\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ *************************************************************************************/\r
+
+/*
+** 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  <windows.h>
+#include  <windowsx.h>
+#include  <odbcinst.h>                                  // ODBC installer prototypes
+#include  <string.h>                                    // C include files
+#include  <stdlib.h>
+#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\r
+#define KEY_PROTOCOL                   10\r
+#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,\r
+                                  INI_PROTOCOL,         KEY_PROTOCOL,\r
+                   "",                           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\r
+                if (!lpsetupdlg->aAttr[KEY_PROTOCOL].fSupplied) {\r
+                  SQLGetPrivateProfileString(lpszDSN, INI_PROTOCOL,\r
+                        EMPTYSTR,\r
+                        lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr,\r
+                        sizeof(lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr),\r
+                        ODBC_INI);\r
+                               }\r
+                               if (strncmp(lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr, PG62, strlen(PG62)) == 0)\r
+                                       CheckDlgButton(hdlg, IDC_PG62, 1);\r
+                               else\r
+                                       CheckDlgButton(hdlg, IDC_PG62, 0);\r
+\r
+\r
+                               //      CommLog Parameter (this is global)\r
+                               CheckDlgButton(hdlg, IDC_COMMLOG, globals.commlog);\r
+
+
+                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));
+\r
+                                               if ( IsDlgButtonChecked(hdlg, IDC_PG62))\r
+                                                       strcpy(lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr, PG62);\r
+                                               else\r
+                                                       lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr[0] = '\0';\r
+
+                                               sprintf(lpsetupdlg->aAttr[KEY_READONLY].szAttr, "%d", IsDlgButtonChecked(hdlg, IDC_READONLY));\r
+\r
+                                               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);\r
+\r
+        if (hwndParent || lpsetupdlg->aAttr[KEY_PROTOCOL].fSupplied )\r
+                SQLWritePrivateProfileString(lpszDSN,\r
+                        INI_PROTOCOL,\r
+                        lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr,\r
+                        ODBC_INI);\r
+\r
+               //      CommLog Parameter -- write to ODBCINST_INI (for the whole driver)
+        if (hwndParent ) {\r
+                       updateGlobals();\r
+               }\r
+
+        // 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 (file)
index 0000000..40318ea
--- /dev/null
@@ -0,0 +1,289 @@
+\r
+/* Module:          socket.c\r
+ *\r
+ * Description:     This module contains functions for low level socket\r
+ *                  operations (connecting/reading/writing to the backend)\r
+ *\r
+ * Classes:         SocketClass (Functions prefix: "SOCK_")\r
+ *\r
+ * API functions:   none\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#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;\r
+\r
+               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)\r
+                       return *((unsigned short *) buf);\r
+               else\r
+                       return ntohs( *((unsigned short *) buf) );
+
+       case 4:
+               SOCK_get_n_char(self, buf, len);\r
+               if (self->reverse)\r
+                       return *((unsigned int *) buf);\r
+               else
+                       return ntohl( *((unsigned int *) buf) );
+
+       default:
+               self->errornumber = SOCKET_GET_INT_WRONG_LENGTH;
+               self->errormsg = "Cannot read ints of that length";
+               return 0;
+       }
+}\r
+
+
+void 
+SOCK_put_int(SocketClass *self, int value, short len)
+{
+unsigned int rv;
+
+       switch (len) {
+       case 2:\r
+               rv = self->reverse ? value : htons( (unsigned short) value);
+               SOCK_put_n_char(self, (char *) &rv, 2);
+               return;
+
+       case 4:\r
+               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);\r
+
+               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 (file)
index 0000000..458adc7
--- /dev/null
@@ -0,0 +1,69 @@
+\r
+/* File:            socket.h\r
+ *\r
+ * Description:     See "socket.c"\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#ifndef __SOCKET_H__
+#define __SOCKET_H__
+
+#include <winsock.h>
+#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;\r
+\r
+       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)
+\r
+
+/* Socket prototypes */\r
+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 (file)
index 0000000..bea5968
--- /dev/null
@@ -0,0 +1,545 @@
+\r
+/* Module:          statement.c\r
+ *\r
+ * Description:     This module contains functions related to creating\r
+ *                  and manipulating a statement.\r
+ *\r
+ * Classes:         StatementClass (Functions prefix: "SC_")\r
+ *\r
+ * API functions:   SQLAllocStmt, SQLFreeStmt\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "statement.h"
+#include "bind.h"
+#include "connection.h"
+#include "qresult.h"
+#include "convert.h"
+#include "environ.h"
+#include <stdio.h>
+
+#include <windows.h>
+#include <sql.h>
+
+extern GLOBAL_VALUES globals;\r
+\r
+
+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) {\r
+               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;\r
+               rv->stmt_with_params[0] = '\0';\r
+               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;\r
+               rv->current_exec_param = -1;\r
+               rv->put_data = FALSE;\r
+       }
+       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);
+\r
+       SC_free_params(self, STMT_FREE_PARAMS_ALL);\r
+\r
+       /* 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;
+}
+\r
+/*     Free parameters and free the memory from the \r
+       data-at-execution parameters that was allocated in SQLPutData.\r
+*/\r
+void\r
+SC_free_params(StatementClass *self, char option)\r
+{\r
+int i;\r
+\r
+       if( ! self->parameters)\r
+               return;\r
+\r
+       for (i = 0; i < self->parameters_allocated; i++) {\r
+               if (self->parameters[i].data_at_exec == TRUE) {\r
+\r
+                       if (self->parameters[i].EXEC_used) {\r
+                               free(self->parameters[i].EXEC_used);\r
+                               self->parameters[i].EXEC_used = NULL;\r
+                       }\r
+\r
+                       if (self->parameters[i].EXEC_buffer) {\r
+                               free(self->parameters[i].EXEC_buffer);\r
+                               self->parameters[i].EXEC_buffer = NULL;\r
+                       }\r
+               }\r
+       }\r
+       self->data_at_exec = -1;\r
+       self->current_exec_param = -1;\r
+       self->put_data = FALSE;\r
+\r
+       if (option == STMT_FREE_PARAMS_ALL) {\r
+               free(self->parameters);\r
+               self->parameters = NULL;\r
+               self->parameters_allocated = 0;\r
+       }\r
+}\r
+
+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;
+\r
+       //      Free any data at exec params before the statement is executed\r
+       //      again.  If not, then there will be a memory leak when\r
+       //      the next SQLParamData/SQLPutData is called.\r
+       SC_free_params(self, STMT_FREE_PARAMS_DATA_AT_EXEC_ONLY);\r
+
+       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)\r
+{\r
+ConnectionClass *conn;\r
+QResultClass *res;\r
+char ok, was_ok, was_nonfatal;\r
+Int2 oldstatus, numcols;\r
+\r
+\r
+       conn = SC_get_conn(self);\r
+\r
+       /*      Begin a transaction if one is not already in progress */\r
+       /*      The reason is because we can't use declare/fetch cursors without\r
+               starting a transaction first.\r
+       */\r
+\r
+       if ( ! CC_is_in_trans(conn))    {\r
+               mylog("   about to begin a transaction on statement = %u\n", self);\r
+               res = CC_send_query(conn, "BEGIN", NULL, NULL);\r
+               if ( ! res) {\r
+                       self->errormsg = "Could not begin a transaction";\r
+                       self->errornumber = STMT_EXEC_ERROR;\r
+                       return SQL_ERROR;\r
+               }\r
+               \r
+               ok = QR_command_successful(res);   \r
+               \r
+               mylog("SQLExecute: ok = %d, status = %d\n", ok, QR_get_status(res));\r
+               \r
+               QR_Destructor(res);\r
+               \r
+               if (!ok) {\r
+                       self->errormsg = "Could not begin a transaction";\r
+                       self->errornumber = STMT_EXEC_ERROR;\r
+                       return SQL_ERROR;\r
+               }\r
+               else\r
+                       CC_set_in_trans(conn);\r
+       }\r
+\r
+\r
+\r
+       oldstatus = conn->status;\r
+       conn->status = CONN_EXECUTING;\r
+       self->status = STMT_EXECUTING;\r
+\r
+\r
+       //      If its a SELECT statement, use a cursor.\r
+       //      Note that the declare cursor has already been prepended to the statement\r
+       //      in copy_statement...\r
+       if (self->statement_type == STMT_TYPE_SELECT) {\r
+\r
+               char cursor[32];\r
+               char fetch[64];\r
+\r
+               sprintf(cursor, "C%u", self);\r
+\r
+               mylog("       Sending SELECT statement on stmt=%u\n", self);\r
+\r
+               /*      send the declare/select */\r
+               self->result = CC_send_query(conn, self->stmt_with_params, NULL, NULL);\r
+               if (self->result != NULL) {\r
+                       /*      That worked, so now send the fetch to start getting data back */\r
+                       sprintf(fetch, "fetch %d in %s", globals.fetch_max, cursor);\r
+                       \r
+                       //      Save the cursor in the result for later use\r
+                       self->result = CC_send_query( conn, fetch, NULL, cursor);\r
+               }\r
+\r
+               mylog("     done sending the query:\n");\r
+               \r
+       }\r
+       else  { // not a SELECT statement so don't use a cursor                  \r
+               mylog("      its NOT a select statement: stmt=%u\n", self);\r
+               self->result = CC_send_query(conn, self->stmt_with_params, NULL, NULL);\r
+               \r
+               //      If we are in autocommit, we must send the commit.\r
+               if (CC_is_in_autocommit(conn)) {\r
+                       CC_send_query(conn, "COMMIT", NULL, NULL);\r
+                       CC_set_no_trans(conn);\r
+               }\r
+               \r
+       }\r
+\r
+       conn->status = oldstatus;\r
+       self->status = STMT_FINISHED;\r
+\r
+       /*      Check the status of the result */\r
+       if (self->result) {\r
+\r
+               was_ok = QR_command_successful(self->result);\r
+               was_nonfatal = QR_command_nonfatal(self->result);\r
+               \r
+               if ( was_ok)\r
+                       self->errornumber = STMT_OK;\r
+               else\r
+                       self->errornumber = was_nonfatal ? STMT_INFO_ONLY : STMT_ERROR_TAKEN_FROM_BACKEND;\r
+               \r
+               self->currTuple = -1; /* set cursor before the first tuple in the list */\r
+               \r
+               /* see if the query did return any result columns */\r
+               numcols = QR_NumResultCols(self->result);\r
+               \r
+               /* now allocate the array to hold the binding info */\r
+               if (numcols > 0) {\r
+                       extend_bindings(self, numcols);\r
+                       if (self->bindings == NULL) {\r
+                               self->errornumber = STMT_NO_MEMORY_ERROR;\r
+                               self->errormsg = "Could not get enough free memory to store the binding information";\r
+                               return SQL_ERROR;\r
+                       }\r
+               }\r
+               \r
+       } else {                /* Bad Error -- The error message will be in the Connection */\r
+               \r
+               self->errornumber = STMT_EXEC_ERROR;\r
+               self->errormsg = "Error while executing the query";\r
+\r
+               CC_abort(conn);\r
+       }\r
+\r
+       if (self->errornumber == STMT_OK)\r
+               return SQL_SUCCESS;\r
+\r
+       else if (self->errornumber == STMT_INFO_ONLY)\r
+               return SQL_SUCCESS_WITH_INFO;\r
+\r
+       else \r
+               return SQL_ERROR;\r
+}\r
diff --git a/src/interfaces/odbc/statement.h b/src/interfaces/odbc/statement.h
new file mode 100644 (file)
index 0000000..cc8e103
--- /dev/null
@@ -0,0 +1,119 @@
+\r
+/* File:            statement.h\r
+ *\r
+ * Description:     See "statement.c"\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#ifndef __STATEMENT_H__
+#define __STATEMENT_H__
+
+#include <windows.h>
+#include <sql.h>
+#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 */
+\r
+    int statement_type;                        /* According to the defines above */
+       int data_at_exec;                       /* Number of params needing SQLPutData */\r
+       int current_exec_param;         /* The current parameter for SQLPutData */\r
+\r
+       char put_data;                          /* Has SQLPutData been called yet? */\r
+
+       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 */\r
+\r
+       char stmt_with_params[65536 /* MAX_STATEMENT_LEN */];           /* statement after parameter substitution */\r
+
+};
+
+#define SC_get_conn(a)    (a->hdbc)
+#define SC_get_Result(a)  (a->result);
+\r
+/*     options for SC_free_params() */\r
+#define STMT_FREE_PARAMS_ALL                           0\r
+#define STMT_FREE_PARAMS_DATA_AT_EXEC_ONLY     1\r
+\r
+/*     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);\r
+void SC_free_params(StatementClass *self, char option);\r
+
+#endif
diff --git a/src/interfaces/odbc/tuple.c b/src/interfaces/odbc/tuple.c
new file mode 100644 (file)
index 0000000..9b50ba5
--- /dev/null
@@ -0,0 +1,56 @@
+\r
+/* Module:          tuple.c\r
+ *\r
+ * Description:     This module contains functions for setting the data for individual\r
+ *                  fields (TupleField structure) of a manual result set.\r
+ *\r
+ * Important Note:  These functions are ONLY used in building manual result sets for \r
+ *                  info functions (SQLTables, SQLColumns, etc.)\r
+ *\r
+ * Classes:         n/a\r
+ *\r
+ * API functions:   none\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "tuple.h"
+#include <string.h>
+#include <stdlib.h>
+
+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 (file)
index 0000000..3d58107
--- /dev/null
@@ -0,0 +1,44 @@
+\r
+/* File:            tuple.h\r
+ *\r
+ * Description:     See "tuple.c"\r
+ *\r
+ * Important NOTE:  The TupleField structure is used both to hold backend data and\r
+ *                  manual result set data.  The "set_" functions and the TupleNode\r
+ *                  structure are only used for manual result sets by info routines.  \r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#ifndef __TUPLE_H__
+#define __TUPLE_H__
+
+#include "psqlodbc.h"
+\r
+/*     Used by backend data AND manual result sets */
+struct TupleField_ {
+    Int4 len;     /* length of the current Tuple */
+    void *value;  /* an array representing the value */
+};
+\r
+/*     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 (file)
index 0000000..80b9624
--- /dev/null
@@ -0,0 +1,188 @@
+\r
+/* Module:          tuplelist.c\r
+ *\r
+ * Description:     This module contains functions for creating a manual result set\r
+ *                  (the TupleList) and retrieving data from it for a specific row/column.\r
+ *\r
+ * Classes:         TupleListClass (Functions prefix: "TL_")\r
+ *\r
+ * API functions:   none\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include <stdlib.h>
+#include <malloc.h>
+#include "tuplelist.h"
+#include "tuple.h"
+
+TupleListClass *
+TL_Constructor(UInt4 fieldcnt)
+{
+TupleListClass *rv;
+\r
+       mylog("in TL_Constructor\n");\r
+
+       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");\r
+\r
+       return rv;
+}
+
+void
+TL_Destructor(TupleListClass *self)
+{
+int lf;
+TupleNode *node, *tp;
+
+       mylog("TupleList: in DESTRUCTOR\n");\r
+\r
+    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;
+    }\r
+\r
+       free(self);\r
+
+       mylog("TupleList: exit DESTRUCTOR\n");\r
+}
+                       
+
+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 (file)
index 0000000..5097cc6
--- /dev/null
@@ -0,0 +1,33 @@
+\r
+/* File:            tuplelist.h\r
+ *\r
+ * Description:     See "tuplelist.c"\r
+ *\r
+ * Important Note:  This structure and its functions are ONLY used in building manual result\r
+ *                  sets for info functions (SQLTables, SQLColumns, etc.)\r
+ *\r
+ * Comments:        See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#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