--- /dev/null
+This directory is to save the changes about psqlodbc driver under
+win32 while the main development tree(the parent directory) is
+nearly freezed(i.e. during beta, a while after the release until
+the next branch is made etc). The changes would be reflected
+to the main directory later after all. However note that the
+trial binary version would be sometimes made using the source
+and put on the ftp server for download.
+
--- /dev/null
+/*-------
+ * Module: bind.c
+ *
+ * Description: This module contains routines related to binding
+ * columns and parameters.
+ *
+ * Classes: BindInfoClass, ParameterInfoClass
+ *
+ * API functions: SQLBindParameter, SQLBindCol, SQLDescribeParam, SQLNumParams,
+ * SQLParamOptions(NI)
+ *
+ * Comments: See "notice.txt" for copyright and license information.
+ *-------
+ */
+
+#include "bind.h"
+
+#include "environ.h"
+#include "statement.h"
+#include "qresult.h"
+#include "pgtypes.h"
+#include <stdlib.h>
+#include <string.h>
+
+#include "pgapifunc.h"
+
+
+/* Bind parameters on a statement handle */
+RETCODE SQL_API
+PGAPI_BindParameter(
+ HSTMT hstmt,
+ UWORD ipar,
+ SWORD fParamType,
+ SWORD fCType,
+ SWORD fSqlType,
+ UDWORD cbColDef,
+ SWORD ibScale,
+ PTR rgbValue,
+ SDWORD cbValueMax,
+ SDWORD FAR * pcbValue)
+{
+ StatementClass *stmt = (StatementClass *) hstmt;
+ static char *func = "PGAPI_BindParameter";
+
+ mylog("%s: entering...\n", func);
+
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+ SC_clear_error(stmt);
+
+ if (stmt->parameters_allocated < ipar)
+ {
+ ParameterInfoClass *old_parameters;
+ int i,
+ old_parameters_allocated;
+
+ old_parameters = stmt->parameters;
+ old_parameters_allocated = stmt->parameters_allocated;
+
+ stmt->parameters = (ParameterInfoClass *) malloc(sizeof(ParameterInfoClass) * (ipar));
+ if (!stmt->parameters)
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Could not allocate memory for statement parameters";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ stmt->parameters_allocated = ipar;
+
+ /* copy the old parameters over */
+ for (i = 0; i < old_parameters_allocated; i++)
+ {
+ /* a structure copy should work */
+ stmt->parameters[i] = old_parameters[i];
+ }
+
+ /* get rid of the old parameters, if there were any */
+ if (old_parameters)
+ free(old_parameters);
+
+ /*
+ * zero out the newly allocated parameters (in case they skipped
+ * some,
+ */
+ /* so we don't accidentally try to use them later) */
+ for (; i < stmt->parameters_allocated; i++)
+ {
+ stmt->parameters[i].buflen = 0;
+ stmt->parameters[i].buffer = 0;
+ stmt->parameters[i].used = 0;
+ stmt->parameters[i].paramType = 0;
+ stmt->parameters[i].CType = 0;
+ stmt->parameters[i].SQLType = 0;
+ stmt->parameters[i].precision = 0;
+ stmt->parameters[i].scale = 0;
+ stmt->parameters[i].data_at_exec = FALSE;
+ stmt->parameters[i].lobj_oid = 0;
+ stmt->parameters[i].EXEC_used = NULL;
+ stmt->parameters[i].EXEC_buffer = NULL;
+ }
+ }
+
+ /* use zero based column numbers for the below part */
+ ipar--;
+
+ /* store the given info */
+ stmt->parameters[ipar].buflen = cbValueMax;
+ stmt->parameters[ipar].buffer = rgbValue;
+ stmt->parameters[ipar].used = pcbValue;
+ stmt->parameters[ipar].paramType = fParamType;
+ stmt->parameters[ipar].CType = fCType;
+ stmt->parameters[ipar].SQLType = fSqlType;
+ stmt->parameters[ipar].precision = cbColDef;
+ stmt->parameters[ipar].scale = ibScale;
+
+ /*
+ * If rebinding a parameter that had data-at-exec stuff in it, then
+ * free that stuff
+ */
+ if (stmt->parameters[ipar].EXEC_used)
+ {
+ free(stmt->parameters[ipar].EXEC_used);
+ stmt->parameters[ipar].EXEC_used = NULL;
+ }
+
+ if (stmt->parameters[ipar].EXEC_buffer)
+ {
+ if (stmt->parameters[ipar].SQLType != SQL_LONGVARBINARY)
+ free(stmt->parameters[ipar].EXEC_buffer);
+ stmt->parameters[ipar].EXEC_buffer = NULL;
+ }
+
+ /* Data at exec macro only valid for C char/binary data */
+ if (pcbValue && (*pcbValue == SQL_DATA_AT_EXEC ||
+ *pcbValue <= SQL_LEN_DATA_AT_EXEC_OFFSET))
+ stmt->parameters[ipar].data_at_exec = TRUE;
+ else
+ stmt->parameters[ipar].data_at_exec = FALSE;
+
+ /* Clear premature result */
+ if (stmt->status == STMT_PREMATURE)
+ SC_recycle_statement(stmt);
+
+ mylog("PGAPI_BindParamater: ipar=%d, paramType=%d, fCType=%d, fSqlType=%d, cbColDef=%d, ibScale=%d, rgbValue=%d, *pcbValue = %d, data_at_exec = %d\n", ipar, fParamType, fCType, fSqlType, cbColDef, ibScale, rgbValue, pcbValue ? *pcbValue : -777, stmt->parameters[ipar].data_at_exec);
+
+ return SQL_SUCCESS;
+}
+
+
+/* Associate a user-supplied buffer with a database column. */
+RETCODE SQL_API
+PGAPI_BindCol(
+ HSTMT hstmt,
+ UWORD icol,
+ SWORD fCType,
+ PTR rgbValue,
+ SDWORD cbValueMax,
+ SDWORD FAR * pcbValue)
+{
+ StatementClass *stmt = (StatementClass *) hstmt;
+ static char *func = "PGAPI_BindCol";
+
+ mylog("%s: entering...\n", func);
+
+ mylog("**** PGAPI_BindCol: stmt = %u, icol = %d\n", stmt, icol);
+ mylog("**** : fCType=%d rgb=%x valusMax=%d pcb=%x\n", fCType, rgbValue, cbValueMax, pcbValue);
+
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+
+
+ SC_clear_error(stmt);
+
+ if (stmt->status == STMT_EXECUTING)
+ {
+ stmt->errormsg = "Can't bind columns while statement is still executing.";
+ stmt->errornumber = STMT_SEQUENCE_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ /* If the bookmark column is being bound, then just save it */
+ if (icol == 0)
+ {
+ if (rgbValue == NULL)
+ {
+ stmt->bookmark.buffer = NULL;
+ stmt->bookmark.used = NULL;
+ }
+ else
+ {
+ /* Make sure it is the bookmark data type */
+ if (fCType != SQL_C_BOOKMARK)
+ {
+ stmt->errormsg = "Column 0 is not of type SQL_C_BOOKMARK";
+ stmt->errornumber = STMT_PROGRAM_TYPE_OUT_OF_RANGE;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ stmt->bookmark.buffer = rgbValue;
+ stmt->bookmark.used = pcbValue;
+ }
+ return SQL_SUCCESS;
+ }
+
+ /*
+ * Allocate enough bindings if not already done. Most likely,
+ * execution of a statement would have setup the necessary bindings.
+ * But some apps call BindCol before any statement is executed.
+ */
+ if (icol > stmt->bindings_allocated)
+ extend_bindings(stmt, icol);
+
+ /* check to see if the bindings were allocated */
+ if (!stmt->bindings)
+ {
+ stmt->errormsg = "Could not allocate memory for bindings.";
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ /* use zero based col numbers from here out */
+ icol--;
+
+ /* Reset for SQLGetData */
+ stmt->bindings[icol].data_left = -1;
+
+ if (rgbValue == NULL)
+ {
+ /* we have to unbind the column */
+ stmt->bindings[icol].buflen = 0;
+ stmt->bindings[icol].buffer = NULL;
+ stmt->bindings[icol].used = NULL;
+ stmt->bindings[icol].returntype = SQL_C_CHAR;
+ }
+ else
+ {
+ /* ok, bind that column */
+ stmt->bindings[icol].buflen = cbValueMax;
+ stmt->bindings[icol].buffer = rgbValue;
+ stmt->bindings[icol].used = pcbValue;
+ stmt->bindings[icol].returntype = fCType;
+
+ mylog(" bound buffer[%d] = %u\n", icol, stmt->bindings[icol].buffer);
+ }
+
+ return SQL_SUCCESS;
+}
+
+
+/*
+ * Returns the description of a parameter marker.
+ * This function is listed as not being supported by SQLGetFunctions() because it is
+ * used to describe "parameter markers" (not bound parameters), in which case,
+ * the dbms should return info on the markers. Since Postgres doesn't support that,
+ * it is best to say this function is not supported and let the application assume a
+ * data type (most likely varchar).
+ */
+RETCODE SQL_API
+PGAPI_DescribeParam(
+ HSTMT hstmt,
+ UWORD ipar,
+ SWORD FAR * pfSqlType,
+ UDWORD FAR * pcbColDef,
+ SWORD FAR * pibScale,
+ SWORD FAR * pfNullable)
+{
+ StatementClass *stmt = (StatementClass *) hstmt;
+ static char *func = "PGAPI_DescribeParam";
+
+ mylog("%s: entering...\n", func);
+
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+ SC_clear_error(stmt);
+
+ if ((ipar < 1) || (ipar > stmt->parameters_allocated))
+ {
+ stmt->errormsg = "Invalid parameter number for PGAPI_DescribeParam.";
+ stmt->errornumber = STMT_BAD_PARAMETER_NUMBER_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ ipar--;
+
+ /*
+ * This implementation is not very good, since it is supposed to
+ * describe
+ */
+ /* parameter markers, not bound parameters. */
+ if (pfSqlType)
+ *pfSqlType = stmt->parameters[ipar].SQLType;
+
+ if (pcbColDef)
+ *pcbColDef = stmt->parameters[ipar].precision;
+
+ if (pibScale)
+ *pibScale = stmt->parameters[ipar].scale;
+
+ if (pfNullable)
+ *pfNullable = pgtype_nullable(stmt, stmt->parameters[ipar].paramType);
+
+ return SQL_SUCCESS;
+}
+
+
+/* Sets multiple values (arrays) for the set of parameter markers. */
+RETCODE SQL_API
+PGAPI_ParamOptions(
+ HSTMT hstmt,
+ UDWORD crow,
+ UDWORD FAR * pirow)
+{
+ static char *func = "PGAPI_ParamOptions";
+ StatementClass *stmt = (StatementClass *) hstmt;
+
+ mylog("%s: entering... %d %x\n", func, crow, pirow);
+
+ if (crow == 1) /* temporary solution and must be
+ * rewritten later */
+ {
+ if (pirow)
+ *pirow = 1;
+ return SQL_SUCCESS;
+ }
+ stmt->errornumber = CONN_UNSUPPORTED_OPTION;
+ stmt->errormsg = "Function not implemented";
+ SC_log_error(func, "Function not implemented", (StatementClass *) hstmt);
+ return SQL_ERROR;
+}
+
+
+/*
+ * This function should really talk to the dbms to determine the number of
+ * "parameter markers" (not bound parameters) in the statement. But, since
+ * Postgres doesn't support that, the driver should just count the number of markers
+ * and return that. The reason the driver just can't say this function is unsupported
+ * like it does for SQLDescribeParam is that some applications don't care and try
+ * to call it anyway.
+ * If the statement does not have parameters, it should just return 0.
+ */
+RETCODE SQL_API
+PGAPI_NumParams(
+ HSTMT hstmt,
+ SWORD FAR * pcpar)
+{
+ StatementClass *stmt = (StatementClass *) hstmt;
+ char in_quote = FALSE;
+ unsigned int i;
+ static char *func = "PGAPI_NumParams";
+
+ mylog("%s: entering...\n", func);
+
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+ SC_clear_error(stmt);
+
+ if (pcpar)
+ *pcpar = 0;
+ else
+ {
+ SC_log_error(func, "pcpar was null", stmt);
+ return SQL_ERROR;
+ }
+
+
+ if (!stmt->statement)
+ {
+ /* no statement has been allocated */
+ stmt->errormsg = "PGAPI_NumParams called with no statement ready.";
+ stmt->errornumber = STMT_SEQUENCE_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+ else
+ {
+ for (i = 0; i < strlen(stmt->statement); i++)
+ {
+ if (stmt->statement[i] == '?' && !in_quote)
+ (*pcpar)++;
+ else
+ {
+ if (stmt->statement[i] == '\'')
+ in_quote = (in_quote ? FALSE : TRUE);
+ }
+ }
+ return SQL_SUCCESS;
+ }
+}
+
+
+/*
+ * Bindings Implementation
+ */
+BindInfoClass *
+create_empty_bindings(int num_columns)
+{
+ BindInfoClass *new_bindings;
+ int i;
+
+ new_bindings = (BindInfoClass *) malloc(num_columns * sizeof(BindInfoClass));
+ if (!new_bindings)
+ return 0;
+
+ for (i = 0; i < num_columns; i++)
+ {
+ new_bindings[i].buflen = 0;
+ new_bindings[i].buffer = NULL;
+ new_bindings[i].used = NULL;
+ new_bindings[i].data_left = -1;
+ new_bindings[i].ttlbuf = NULL;
+ new_bindings[i].ttlbuflen = 0;
+ }
+
+ return new_bindings;
+}
+
+
+void
+extend_bindings(StatementClass *stmt, int num_columns)
+{
+ static char *func = "extend_bindings";
+ BindInfoClass *new_bindings;
+ int i;
+
+ mylog("%s: entering ... stmt=%u, bindings_allocated=%d, num_columns=%d\n", func, stmt, stmt->bindings_allocated, num_columns);
+
+ /*
+ * if we have too few, allocate room for more, and copy the old
+ * entries into the new structure
+ */
+ if (stmt->bindings_allocated < num_columns)
+ {
+ new_bindings = create_empty_bindings(num_columns);
+ if (!new_bindings)
+ {
+ mylog("%s: unable to create %d new bindings from %d old bindings\n", func, num_columns, stmt->bindings_allocated);
+
+ if (stmt->bindings)
+ {
+ free(stmt->bindings);
+ stmt->bindings = NULL;
+ }
+ stmt->bindings_allocated = 0;
+ return;
+ }
+
+ if (stmt->bindings)
+ {
+ for (i = 0; i < stmt->bindings_allocated; i++)
+ new_bindings[i] = stmt->bindings[i];
+
+ free(stmt->bindings);
+ }
+
+ stmt->bindings = new_bindings;
+ stmt->bindings_allocated = num_columns;
+ }
+
+ /*
+ * There is no reason to zero out extra bindings if there are more
+ * than needed. If an app has allocated extra bindings, let it worry
+ * about it by unbinding those columns.
+ */
+
+ /* SQLBindCol(1..) ... SQLBindCol(10...) # got 10 bindings */
+ /* SQLExecDirect(...) # returns 5 cols */
+ /* SQLExecDirect(...) # returns 10 cols (now OK) */
+
+ mylog("exit extend_bindings\n");
+}
--- /dev/null
+/* File: bind.h
+ *
+ * Description: See "bind.c"
+ *
+ * Comments: See "notice.txt" for copyright and license information.
+ *
+ */
+
+#ifndef __BIND_H__
+#define __BIND_H__
+
+#include "psqlodbc.h"
+
+/*
+ * BindInfoClass -- stores information about a bound column
+ */
+struct BindInfoClass_
+{
+ Int4 buflen; /* size of buffer */
+ Int4 data_left; /* amount of data left to read
+ * (SQLGetData) */
+ char *buffer; /* pointer to the buffer */
+ Int4 *used; /* used space in the buffer (for strings
+ * not counting the '\0') */
+ char *ttlbuf; /* to save the large result */
+ Int4 ttlbuflen; /* the buffer length */
+ Int2 returntype; /* kind of conversion to be applied when
+ * returning (SQL_C_DEFAULT,
+ * SQL_C_CHAR...) */
+};
+
+/*
+ * ParameterInfoClass -- stores information about a bound parameter
+ */
+struct ParameterInfoClass_
+{
+ Int4 buflen;
+ char *buffer;
+ Int4 *used;
+ Int2 paramType;
+ Int2 CType;
+ Int2 SQLType;
+ UInt4 precision;
+ Int2 scale;
+ Oid lobj_oid;
+ Int4 *EXEC_used; /* amount of data OR the oid of the large
+ * object */
+ char *EXEC_buffer; /* the data or the FD of the large object */
+ char data_at_exec;
+};
+
+BindInfoClass *create_empty_bindings(int num_columns);
+void extend_bindings(StatementClass *stmt, int num_columns);
+
+#endif
--- /dev/null
+/*-------
+ * Module: columninfo.c
+ *
+ * Description: This module contains routines related to
+ * reading and storing the field information from a query.
+ *
+ * Classes: ColumnInfoClass (Functions prefix: "CI_")
+ *
+ * API functions: none
+ *
+ * Comments: See "notice.txt" for copyright and license information.
+ *-------
+ */
+
+#include "pgtypes.h"
+#include "columninfo.h"
+
+#include "connection.h"
+#include "socket.h"
+#include <stdlib.h>
+#include <string.h>
+#include "pgapifunc.h"
+
+ColumnInfoClass *
+CI_Constructor()
+{
+ ColumnInfoClass *rv;
+
+ rv = (ColumnInfoClass *) malloc(sizeof(ColumnInfoClass));
+
+ if (rv)
+ {
+ rv->num_fields = 0;
+ rv->name = NULL;
+ rv->adtid = NULL;
+ rv->adtsize = NULL;
+ rv->display_size = NULL;
+ rv->atttypmod = NULL;
+ }
+
+ return rv;
+}
+
+
+void
+CI_Destructor(ColumnInfoClass *self)
+{
+ CI_free_memory(self);
+
+ free(self);
+}
+
+
+/*
+ * Read in field descriptions.
+ * If self is not null, then also store the information.
+ * If self is null, then just read, don't store.
+ */
+char
+CI_read_fields(ColumnInfoClass *self, ConnectionClass *conn)
+{
+ Int2 lf;
+ int new_num_fields;
+ Oid new_adtid;
+ Int2 new_adtsize;
+ Int4 new_atttypmod = -1;
+
+ /* MAX_COLUMN_LEN may be sufficient but for safety */
+ char new_field_name[2 * MAX_COLUMN_LEN + 1];
+ SocketClass *sock;
+ ConnInfo *ci;
+
+ sock = CC_get_socket(conn);
+ ci = &conn->connInfo;
+
+ /* at first read in the number of fields that are in the query */
+ new_num_fields = (Int2) SOCK_get_int(sock, sizeof(Int2));
+
+ mylog("num_fields = %d\n", new_num_fields);
+
+ if (self)
+ /* according to that allocate memory */
+ CI_set_num_fields(self, new_num_fields);
+
+ /* now read in the descriptions */
+ for (lf = 0; lf < new_num_fields; lf++)
+ {
+ SOCK_get_string(sock, new_field_name, 2 * MAX_COLUMN_LEN);
+ new_adtid = (Oid) SOCK_get_int(sock, 4);
+ new_adtsize = (Int2) SOCK_get_int(sock, 2);
+
+ /* If 6.4 protocol, then read the atttypmod field */
+ if (PG_VERSION_GE(conn, 6.4))
+ {
+ mylog("READING ATTTYPMOD\n");
+ new_atttypmod = (Int4) SOCK_get_int(sock, 4);
+
+ /* Subtract the header length */
+ switch (new_adtid)
+ {
+ case PG_TYPE_DATETIME:
+ case PG_TYPE_TIMESTAMP_NO_TMZONE:
+ case PG_TYPE_TIME:
+ case PG_TYPE_TIME_WITH_TMZONE:
+ break;
+ default:
+ new_atttypmod -= 4;
+ }
+ if (new_atttypmod < 0)
+ new_atttypmod = -1;
+
+ }
+
+ mylog("CI_read_fields: fieldname='%s', adtid=%d, adtsize=%d, atttypmod=%d\n", new_field_name, new_adtid, new_adtsize, new_atttypmod);
+
+ if (self)
+ CI_set_field_info(self, lf, new_field_name, new_adtid, new_adtsize, new_atttypmod);
+ }
+
+ return (SOCK_get_errcode(sock) == 0);
+}
+
+
+void
+CI_free_memory(ColumnInfoClass *self)
+{
+ register Int2 lf;
+ int num_fields = self->num_fields;
+
+ for (lf = 0; lf < num_fields; lf++)
+ {
+ if (self->name[lf])
+ {
+ free(self->name[lf]);
+ self->name[lf] = NULL;
+ }
+ }
+
+ /* Safe to call even if null */
+ self->num_fields = 0;
+ if (self->name)
+ free(self->name);
+ self->name = NULL;
+ if (self->adtid)
+ free(self->adtid);
+ self->adtid = NULL;
+ if (self->adtsize)
+ free(self->adtsize);
+ self->adtsize = NULL;
+ if (self->display_size)
+ free(self->display_size);
+ self->display_size = NULL;
+
+ if (self->atttypmod)
+ free(self->atttypmod);
+ self->atttypmod = NULL;
+}
+
+
+void
+CI_set_num_fields(ColumnInfoClass *self, int new_num_fields)
+{
+ CI_free_memory(self); /* always safe to call */
+
+ self->num_fields = new_num_fields;
+
+ self->name = (char **) malloc(sizeof(char *) * self->num_fields);
+ memset(self->name, 0, sizeof(char *) * self->num_fields);
+ self->adtid = (Oid *) malloc(sizeof(Oid) * self->num_fields);
+ self->adtsize = (Int2 *) malloc(sizeof(Int2) * self->num_fields);
+ self->display_size = (Int2 *) malloc(sizeof(Int2) * self->num_fields);
+ self->atttypmod = (Int4 *) malloc(sizeof(Int4) * self->num_fields);
+}
+
+
+void
+CI_set_field_info(ColumnInfoClass *self, int field_num, char *new_name,
+ Oid new_adtid, Int2 new_adtsize, Int4 new_atttypmod)
+{
+ /* check bounds */
+ if ((field_num < 0) || (field_num >= self->num_fields))
+ return;
+
+ /* store the info */
+ self->name[field_num] = strdup(new_name);
+ self->adtid[field_num] = new_adtid;
+ self->adtsize[field_num] = new_adtsize;
+ self->atttypmod[field_num] = new_atttypmod;
+
+ self->display_size[field_num] = 0;
+}
--- /dev/null
+/* File: columninfo.h
+ *
+ * Description: See "columninfo.c"
+ *
+ * Comments: See "notice.txt" for copyright and license information.
+ *
+ */
+
+#ifndef __COLUMNINFO_H__
+#define __COLUMNINFO_H__
+
+#include "psqlodbc.h"
+
+struct ColumnInfoClass_
+{
+ Int2 num_fields;
+ char **name; /* list of type names */
+ Oid *adtid; /* list of type ids */
+ Int2 *adtsize; /* list type sizes */
+ Int2 *display_size; /* the display size (longest row) */
+ Int4 *atttypmod; /* the length of bpchar/varchar */
+};
+
+#define CI_get_num_fields(self) (self->num_fields)
+#define CI_get_oid(self, col) (self->adtid[col])
+#define CI_get_fieldname(self, col) (self->name[col])
+#define CI_get_fieldsize(self, col) (self->adtsize[col])
+#define CI_get_display_size(self, col) (self->display_size[col])
+#define CI_get_atttypmod(self, col) (self->atttypmod[col])
+
+ColumnInfoClass *CI_Constructor(void);
+void CI_Destructor(ColumnInfoClass *self);
+void CI_free_memory(ColumnInfoClass *self);
+char CI_read_fields(ColumnInfoClass *self, ConnectionClass *conn);
+
+/* functions for setting up the fields from within the program, */
+/* without reading from a socket */
+void CI_set_num_fields(ColumnInfoClass *self, int new_num_fields);
+void CI_set_field_info(ColumnInfoClass *self, int field_num, char *new_name,
+ Oid new_adtid, Int2 new_adtsize, Int4 atttypmod);
+
+#endif
--- /dev/null
+/*------
+ * Module: connection.c
+ *
+ * Description: This module contains routines related to
+ * connecting to and disconnecting from the Postgres DBMS.
+ *
+ * Classes: ConnectionClass (Functions prefix: "CC_")
+ *
+ * API functions: SQLAllocConnect, SQLConnect, SQLDisconnect, SQLFreeConnect,
+ * SQLBrowseConnect(NI)
+ *
+ * Comments: See "notice.txt" for copyright and license information.
+ *-------
+ */
+/* Multibyte support Eiji Tokuya 2001-03-15 */
+
+#include "connection.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "environ.h"
+#include "socket.h"
+#include "statement.h"
+#include "qresult.h"
+#include "lobj.h"
+#include "dlg_specific.h"
+
+#ifdef MULTIBYTE
+#include "multibyte.h"
+#endif
+
+#include "pgapifunc.h"
+#include "md5.h"
+
+#define STMT_INCREMENT 16 /* how many statement holders to allocate
+ * at a time */
+
+#define PRN_NULLCHECK
+
+extern GLOBAL_VALUES globals;
+
+
+RETCODE SQL_API
+PGAPI_AllocConnect(
+ HENV henv,
+ HDBC FAR * phdbc)
+{
+ EnvironmentClass *env = (EnvironmentClass *) henv;
+ ConnectionClass *conn;
+ static char *func = "PGAPI_AllocConnect";
+
+ mylog("%s: entering...\n", func);
+
+ conn = CC_Constructor();
+ mylog("**** %s: henv = %u, conn = %u\n", func, henv, conn);
+
+ if (!conn)
+ {
+ env->errormsg = "Couldn't allocate memory for Connection object.";
+ env->errornumber = ENV_ALLOC_ERROR;
+ *phdbc = SQL_NULL_HDBC;
+ EN_log_error(func, "", env);
+ return SQL_ERROR;
+ }
+
+ if (!EN_add_connection(env, conn))
+ {
+ env->errormsg = "Maximum number of connections exceeded.";
+ env->errornumber = ENV_ALLOC_ERROR;
+ CC_Destructor(conn);
+ *phdbc = SQL_NULL_HDBC;
+ EN_log_error(func, "", env);
+ return SQL_ERROR;
+ }
+
+ *phdbc = (HDBC) conn;
+
+ return SQL_SUCCESS;
+}
+
+
+RETCODE SQL_API
+PGAPI_Connect(
+ HDBC hdbc,
+ UCHAR FAR * szDSN,
+ SWORD cbDSN,
+ UCHAR FAR * szUID,
+ SWORD cbUID,
+ UCHAR FAR * szAuthStr,
+ SWORD cbAuthStr)
+{
+ ConnectionClass *conn = (ConnectionClass *) hdbc;
+ ConnInfo *ci;
+ static char *func = "PGAPI_Connect";
+
+ mylog("%s: entering...\n", func);
+
+ if (!conn)
+ {
+ CC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+
+ ci = &conn->connInfo;
+
+ make_string(szDSN, cbDSN, ci->dsn);
+
+ /* get the values for the DSN from the registry */
+ getDSNinfo(ci, CONN_OVERWRITE);
+ logs_on_off(1, ci->drivers.debug, ci->drivers.commlog);
+ /* initialize pg_version from connInfo.protocol */
+ CC_initialize_pg_version(conn);
+
+ /*
+ * override values from DSN info with UID and authStr(pwd) This only
+ * occurs if the values are actually there.
+ */
+ make_string(szUID, cbUID, ci->username);
+ make_string(szAuthStr, cbAuthStr, ci->password);
+
+ /* fill in any defaults */
+ getDSNdefaults(ci);
+
+ qlog("conn = %u, %s(DSN='%s', UID='%s', PWD='%s')\n", conn, func, ci->dsn, ci->username, ci->password);
+
+ if (CC_connect(conn, FALSE) <= 0)
+ {
+ /* Error messages are filled in */
+ CC_log_error(func, "Error on CC_connect", conn);
+ return SQL_ERROR;
+ }
+
+ mylog("%s: returning...\n", func);
+
+ return SQL_SUCCESS;
+}
+
+
+RETCODE SQL_API
+PGAPI_BrowseConnect(
+ HDBC hdbc,
+ UCHAR FAR * szConnStrIn,
+ SWORD cbConnStrIn,
+ UCHAR FAR * szConnStrOut,
+ SWORD cbConnStrOutMax,
+ SWORD FAR * pcbConnStrOut)
+{
+ static char *func = "PGAPI_BrowseConnect";
+
+ mylog("%s: entering...\n", func);
+
+ return SQL_SUCCESS;
+}
+
+
+/* Drop any hstmts open on hdbc and disconnect from database */
+RETCODE SQL_API
+PGAPI_Disconnect(
+ HDBC hdbc)
+{
+ ConnectionClass *conn = (ConnectionClass *) hdbc;
+ static char *func = "PGAPI_Disconnect";
+
+
+ mylog("%s: entering...\n", func);
+
+ if (!conn)
+ {
+ CC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+
+ qlog("conn=%u, %s\n", conn, func);
+
+ if (conn->status == CONN_EXECUTING)
+ {
+ conn->errornumber = CONN_IN_USE;
+ conn->errormsg = "A transaction is currently being executed";
+ CC_log_error(func, "", conn);
+ return SQL_ERROR;
+ }
+
+ logs_on_off(-1, conn->connInfo.drivers.debug, conn->connInfo.drivers.commlog);
+ mylog("%s: about to CC_cleanup\n", func);
+
+ /* Close the connection and free statements */
+ CC_cleanup(conn);
+
+ mylog("%s: done CC_cleanup\n", func);
+ mylog("%s: returning...\n", func);
+
+ return SQL_SUCCESS;
+}
+
+
+RETCODE SQL_API
+PGAPI_FreeConnect(
+ HDBC hdbc)
+{
+ ConnectionClass *conn = (ConnectionClass *) hdbc;
+ static char *func = "PGAPI_FreeConnect";
+
+ mylog("%s: entering...\n", func);
+ mylog("**** in %s: hdbc=%u\n", func, hdbc);
+
+ if (!conn)
+ {
+ CC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+
+ /* Remove the connection from the environment */
+ if (!EN_remove_connection(conn->henv, conn))
+ {
+ conn->errornumber = CONN_IN_USE;
+ conn->errormsg = "A transaction is currently being executed";
+ CC_log_error(func, "", conn);
+ return SQL_ERROR;
+ }
+
+ CC_Destructor(conn);
+
+ mylog("%s: returning...\n", func);
+
+ return SQL_SUCCESS;
+}
+
+
+/*
+ * IMPLEMENTATION CONNECTION CLASS
+ */
+ConnectionClass *
+CC_Constructor()
+{
+ ConnectionClass *rv;
+
+ rv = (ConnectionClass *) malloc(sizeof(ConnectionClass));
+
+ if (rv != NULL)
+ {
+ rv->henv = NULL; /* not yet associated with an environment */
+
+ rv->errormsg = NULL;
+ rv->errornumber = 0;
+ rv->errormsg_created = FALSE;
+
+ rv->status = CONN_NOT_CONNECTED;
+ rv->transact_status = CONN_IN_AUTOCOMMIT; /* autocommit by default */
+
+ memset(&rv->connInfo, 0, sizeof(ConnInfo));
+#ifdef DRIVER_CURSOR_IMPLEMENT
+ rv->connInfo.updatable_cursors = 1;
+#endif /* DRIVER_CURSOR_IMPLEMENT */
+ memcpy(&(rv->connInfo.drivers), &globals, sizeof(globals));
+ rv->sock = SOCK_Constructor(rv);
+ if (!rv->sock)
+ return NULL;
+
+ rv->stmts = (StatementClass **) malloc(sizeof(StatementClass *) * STMT_INCREMENT);
+ if (!rv->stmts)
+ return NULL;
+ memset(rv->stmts, 0, sizeof(StatementClass *) * STMT_INCREMENT);
+
+ rv->num_stmts = STMT_INCREMENT;
+
+ rv->lobj_type = PG_TYPE_LO;
+
+ rv->ntables = 0;
+ rv->col_info = NULL;
+
+ rv->translation_option = 0;
+ rv->translation_handle = NULL;
+ rv->DataSourceToDriver = NULL;
+ rv->DriverToDataSource = NULL;
+ rv->driver_version = ODBCVER;
+ memset(rv->pg_version, 0, sizeof(rv->pg_version));
+ rv->pg_version_number = .0;
+ rv->pg_version_major = 0;
+ rv->pg_version_minor = 0;
+ rv->ms_jet = 0;
+#ifdef MULTIBYTE
+ rv->client_encoding = NULL;
+ rv->server_encoding = NULL;
+#endif /* MULTIBYTE */
+
+
+ /* Initialize statement options to defaults */
+ /* Statements under this conn will inherit these options */
+
+ InitializeStatementOptions(&rv->stmtOptions);
+
+
+ }
+ return rv;
+}
+
+
+char
+CC_Destructor(ConnectionClass *self)
+{
+ mylog("enter CC_Destructor, self=%u\n", self);
+
+ if (self->status == CONN_EXECUTING)
+ return 0;
+
+ CC_cleanup(self); /* cleanup socket and statements */
+
+ mylog("after CC_Cleanup\n");
+
+#ifdef MULTIBYTE
+ if (self->client_encoding)
+ free(self->client_encoding);
+ if (self->server_encoding)
+ free(self->server_encoding);
+#endif /* MULTIBYTE */
+ /* Free up statement holders */
+ if (self->stmts)
+ {
+ free(self->stmts);
+ self->stmts = NULL;
+ }
+ mylog("after free statement holders\n");
+
+ /* Free cached table info */
+ if (self->col_info)
+ {
+ int i;
+
+ for (i = 0; i < self->ntables; i++)
+ {
+ if (self->col_info[i]->result) /* Free the SQLColumns
+ * result structure */
+ QR_Destructor(self->col_info[i]->result);
+
+ free(self->col_info[i]);
+ }
+ free(self->col_info);
+ }
+
+
+ free(self);
+
+ mylog("exit CC_Destructor\n");
+
+ return 1;
+}
+
+
+/* Return how many cursors are opened on this connection */
+int
+CC_cursor_count(ConnectionClass *self)
+{
+ StatementClass *stmt;
+ int i,
+ count = 0;
+
+ mylog("CC_cursor_count: self=%u, num_stmts=%d\n", self, self->num_stmts);
+
+ for (i = 0; i < self->num_stmts; i++)
+ {
+ stmt = self->stmts[i];
+ if (stmt && stmt->result && stmt->result->cursor)
+ count++;
+ }
+
+ mylog("CC_cursor_count: returning %d\n", count);
+
+ return count;
+}
+
+
+void
+CC_clear_error(ConnectionClass *self)
+{
+ self->errornumber = 0;
+ self->errormsg = NULL;
+ self->errormsg_created = FALSE;
+}
+
+
+/*
+ * Used to cancel a transaction.
+ * We are almost always in the middle of a transaction.
+ */
+char
+CC_abort(ConnectionClass *self)
+{
+ QResultClass *res;
+
+ if (CC_is_in_trans(self))
+ {
+ res = NULL;
+
+ mylog("CC_abort: sending ABORT!\n");
+
+ res = CC_send_query(self, "ABORT", NULL);
+ CC_set_no_trans(self);
+
+ if (res != NULL)
+ QR_Destructor(res);
+ else
+ return FALSE;
+
+ }
+
+ return TRUE;
+}
+
+
+/* This is called by SQLDisconnect also */
+char
+CC_cleanup(ConnectionClass *self)
+{
+ int i;
+ StatementClass *stmt;
+
+ if (self->status == CONN_EXECUTING)
+ return FALSE;
+
+ mylog("in CC_Cleanup, self=%u\n", self);
+
+ /* Cancel an ongoing transaction */
+ /* We are always in the middle of a transaction, */
+ /* even if we are in auto commit. */
+ if (self->sock)
+ CC_abort(self);
+
+ mylog("after CC_abort\n");
+
+ /* This actually closes the connection to the dbase */
+ if (self->sock)
+ {
+ SOCK_Destructor(self->sock);
+ self->sock = NULL;
+ }
+
+ mylog("after SOCK destructor\n");
+
+ /* Free all the stmts on this connection */
+ for (i = 0; i < self->num_stmts; i++)
+ {
+ stmt = self->stmts[i];
+ if (stmt)
+ {
+ stmt->hdbc = NULL; /* prevent any more dbase interactions */
+
+ SC_Destructor(stmt);
+
+ self->stmts[i] = NULL;
+ }
+ }
+
+ /* Check for translation dll */
+#ifdef WIN32
+ if (self->translation_handle)
+ {
+ FreeLibrary(self->translation_handle);
+ self->translation_handle = NULL;
+ }
+#endif
+
+ mylog("exit CC_Cleanup\n");
+ return TRUE;
+}
+
+
+int
+CC_set_translation(ConnectionClass *self)
+{
+
+#ifdef WIN32
+
+ if (self->translation_handle != NULL)
+ {
+ FreeLibrary(self->translation_handle);
+ self->translation_handle = NULL;
+ }
+
+ if (self->connInfo.translation_dll[0] == 0)
+ return TRUE;
+
+ self->translation_option = atoi(self->connInfo.translation_option);
+ self->translation_handle = LoadLibrary(self->connInfo.translation_dll);
+
+ if (self->translation_handle == NULL)
+ {
+ self->errornumber = CONN_UNABLE_TO_LOAD_DLL;
+ self->errormsg = "Could not load the translation DLL.";
+ return FALSE;
+ }
+
+ self->DataSourceToDriver
+ = (DataSourceToDriverProc) GetProcAddress(self->translation_handle,
+ "SQLDataSourceToDriver");
+
+ self->DriverToDataSource
+ = (DriverToDataSourceProc) GetProcAddress(self->translation_handle,
+ "SQLDriverToDataSource");
+
+ if (self->DataSourceToDriver == NULL || self->DriverToDataSource == NULL)
+ {
+ self->errornumber = CONN_UNABLE_TO_LOAD_DLL;
+ self->errormsg = "Could not find translation DLL functions.";
+ return FALSE;
+ }
+#endif
+ return TRUE;
+}
+
+static int
+md5_auth_send(ConnectionClass *self, const char *salt)
+{
+ char *pwd1 = NULL, *pwd2 = NULL;
+ ConnInfo *ci = &(self->connInfo);
+ SocketClass *sock = self->sock;
+
+mylog("MD5 user=%s password=%s\n", ci->username, ci->password);
+ if (!(pwd1 = malloc(MD5_PASSWD_LEN + 1)))
+ return 1;
+ if (!EncryptMD5(ci->password, ci->username, strlen(ci->username), pwd1))
+ {
+ free(pwd1);
+ return 1;
+ }
+ if (!(pwd2 = malloc(MD5_PASSWD_LEN + 1)))
+ {
+ free(pwd1);
+ return 1;
+ }
+ if (!EncryptMD5(pwd1 + strlen("md5"), salt, 4, pwd2))
+ {
+ free(pwd2);
+ free(pwd1);
+ return 1;
+ }
+ free(pwd1);
+ SOCK_put_int(sock, 4 + strlen(pwd2) + 1, 4);
+ SOCK_put_n_char(sock, pwd2, strlen(pwd2) + 1);
+ SOCK_flush_output(sock);
+ free(pwd2);
+ return 0;
+}
+
+char
+CC_connect(ConnectionClass *self, char do_password)
+{
+ StartupPacket sp;
+ StartupPacket6_2 sp62;
+ QResultClass *res;
+ SocketClass *sock;
+ ConnInfo *ci = &(self->connInfo);
+ int areq = -1;
+ int beresp;
+ char msgbuffer[ERROR_MSG_LENGTH];
+ char salt[5];
+ static char *func = "CC_connect";
+
+#ifdef MULTIBYTE
+ char *encoding;
+#endif /* MULTIBYTE */
+
+ mylog("%s: entering...\n", func);
+
+ if (do_password)
+
+ sock = self->sock; /* already connected, just authenticate */
+
+ else
+ {
+ qlog("Global Options: Version='%s', fetch=%d, socket=%d, unknown_sizes=%d, max_varchar_size=%d, max_longvarchar_size=%d\n",
+ POSTGRESDRIVERVERSION,
+ ci->drivers.fetch_max,
+ ci->drivers.socket_buffersize,
+ ci->drivers.unknown_sizes,
+ ci->drivers.max_varchar_size,
+ ci->drivers.max_longvarchar_size);
+ qlog(" disable_optimizer=%d, ksqo=%d, unique_index=%d, use_declarefetch=%d\n",
+ ci->drivers.disable_optimizer,
+ ci->drivers.ksqo,
+ ci->drivers.unique_index,
+ ci->drivers.use_declarefetch);
+ qlog(" text_as_longvarchar=%d, unknowns_as_longvarchar=%d, bools_as_char=%d\n",
+ ci->drivers.text_as_longvarchar,
+ ci->drivers.unknowns_as_longvarchar,
+ ci->drivers.bools_as_char);
+
+#ifdef MULTIBYTE
+ encoding = check_client_encoding(ci->conn_settings);
+ if (encoding && strcmp(encoding, "OTHER"))
+ self->client_encoding = strdup(encoding);
+ else
+ {
+ encoding = check_client_encoding(ci->drivers.conn_settings);
+ if (encoding && strcmp(encoding, "OTHER"))
+ self->client_encoding = strdup(encoding);
+ }
+ qlog(" extra_systable_prefixes='%s', conn_settings='%s' conn_encoding='%s'\n",
+ ci->drivers.extra_systable_prefixes,
+ ci->drivers.conn_settings,
+ encoding ? encoding : "");
+#else
+ qlog(" extra_systable_prefixes='%s', conn_settings='%s'\n",
+ ci->drivers.extra_systable_prefixes,
+ ci->drivers.conn_settings);
+#endif
+
+ if (self->status != CONN_NOT_CONNECTED)
+ {
+ self->errormsg = "Already connected.";
+ self->errornumber = CONN_OPENDB_ERROR;
+ return 0;
+ }
+
+ if (ci->server[0] == '\0' || ci->port[0] == '\0' || ci->database[0] == '\0')
+ {
+ self->errornumber = CONN_INIREAD_ERROR;
+ self->errormsg = "Missing server name, port, or database name in call to CC_connect.";
+ return 0;
+ }
+
+ mylog("CC_connect(): DSN = '%s', server = '%s', port = '%s', database = '%s', username = '%s', password='%s'\n", ci->dsn, ci->server, ci->port, ci->database, ci->username, ci->password);
+
+another_version_retry:
+
+ /*
+ * If the socket was closed for some reason (like a SQLDisconnect,
+ * but no SQLFreeConnect then create a socket now.
+ */
+ if (!self->sock)
+ {
+ self->sock = SOCK_Constructor(self);
+ if (!self->sock)
+ {
+ self->errornumber = CONNECTION_SERVER_NOT_REACHED;
+ self->errormsg = "Could not open a socket to the server";
+ return 0;
+ }
+ }
+
+ sock = self->sock;
+
+ mylog("connecting to the server socket...\n");
+
+ SOCK_connect_to(sock, (short) atoi(ci->port), ci->server);
+ if (SOCK_get_errcode(sock) != 0)
+ {
+ mylog("connection to the server socket failed.\n");
+ self->errornumber = CONNECTION_SERVER_NOT_REACHED;
+ self->errormsg = "Could not connect to the server";
+ return 0;
+ }
+ mylog("connection to the server socket succeeded.\n");
+
+ if (PROTOCOL_62(ci))
+ {
+ sock->reverse = TRUE; /* make put_int and get_int work
+ * for 6.2 */
+
+ memset(&sp62, 0, sizeof(StartupPacket6_2));
+ SOCK_put_int(sock, htonl(4 + sizeof(StartupPacket6_2)), 4);
+ sp62.authtype = htonl(NO_AUTHENTICATION);
+ strncpy(sp62.database, ci->database, PATH_SIZE);
+ strncpy(sp62.user, ci->username, NAMEDATALEN);
+ SOCK_put_n_char(sock, (char *) &sp62, sizeof(StartupPacket6_2));
+ SOCK_flush_output(sock);
+ }
+ else
+ {
+ memset(&sp, 0, sizeof(StartupPacket));
+
+ mylog("sizeof startup packet = %d\n", sizeof(StartupPacket));
+
+ /* Send length of Authentication Block */
+ SOCK_put_int(sock, 4 + sizeof(StartupPacket), 4);
+
+ if (PROTOCOL_63(ci))
+ sp.protoVersion = (ProtocolVersion) htonl(PG_PROTOCOL_63);
+ else
+ sp.protoVersion = (ProtocolVersion) htonl(PG_PROTOCOL_LATEST);
+
+ strncpy(sp.database, ci->database, SM_DATABASE);
+ strncpy(sp.user, ci->username, SM_USER);
+
+ SOCK_put_n_char(sock, (char *) &sp, sizeof(StartupPacket));
+ SOCK_flush_output(sock);
+ }
+
+ mylog("sent the authentication block.\n");
+
+ if (sock->errornumber != 0)
+ {
+ mylog("couldn't send the authentication block properly.\n");
+ self->errornumber = CONN_INVALID_AUTHENTICATION;
+ self->errormsg = "Sending the authentication packet failed";
+ return 0;
+ }
+ mylog("sent the authentication block successfully.\n");
+ }
+
+
+ mylog("gonna do authentication\n");
+
+
+ /*
+ * Now get the authentication request from backend
+ */
+
+ if (!PROTOCOL_62(ci))
+ {
+ BOOL before_64 = PG_VERSION_LT(self, 6.4),
+ ReadyForQuery = FALSE;
+
+ do
+ {
+ if (do_password)
+ beresp = 'R';
+ else
+ {
+ beresp = SOCK_get_char(sock);
+ mylog("auth got '%c'\n", beresp);
+ }
+
+ switch (beresp)
+ {
+ case 'E':
+
+ SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH);
+ self->errornumber = CONN_INVALID_AUTHENTICATION;
+ self->errormsg = msgbuffer;
+ qlog("ERROR from backend during authentication: '%s'\n", self->errormsg);
+ if (strncmp(msgbuffer, "Unsupported frontend protocol", 29) == 0)
+ { /* retry older version */
+ if (PROTOCOL_63(ci))
+ strcpy(ci->protocol, PG62);
+ else
+ strcpy(ci->protocol, PG63);
+ SOCK_Destructor(sock);
+ self->sock = (SocketClass *) 0;
+ CC_initialize_pg_version(self);
+ goto another_version_retry;
+ }
+
+ return 0;
+ case 'R':
+
+ if (do_password)
+ {
+ mylog("in 'R' do_password\n");
+ areq = AUTH_REQ_PASSWORD;
+ do_password = FALSE;
+ }
+ else
+ {
+
+ areq = SOCK_get_int(sock, 4);
+ if (areq == AUTH_REQ_MD5)
+ SOCK_get_n_char(sock, salt, 4);
+ if (areq == AUTH_REQ_CRYPT)
+ SOCK_get_n_char(sock, salt, 2);
+
+ mylog("areq = %d\n", areq);
+ }
+ switch (areq)
+ {
+ case AUTH_REQ_OK:
+ break;
+
+ case AUTH_REQ_KRB4:
+ self->errormsg = "Kerberos 4 authentication not supported";
+ self->errornumber = CONN_AUTH_TYPE_UNSUPPORTED;
+ return 0;
+
+ case AUTH_REQ_KRB5:
+ self->errormsg = "Kerberos 5 authentication not supported";
+ self->errornumber = CONN_AUTH_TYPE_UNSUPPORTED;
+ return 0;
+
+ case AUTH_REQ_PASSWORD:
+ mylog("in AUTH_REQ_PASSWORD\n");
+
+ if (ci->password[0] == '\0')
+ {
+ self->errornumber = CONNECTION_NEED_PASSWORD;
+ self->errormsg = "A password is required for this connection.";
+ return -1; /* need password */
+ }
+
+ mylog("past need password\n");
+
+ SOCK_put_int(sock, 4 + strlen(ci->password) + 1, 4);
+ SOCK_put_n_char(sock, ci->password, strlen(ci->password) + 1);
+ SOCK_flush_output(sock);
+
+ mylog("past flush\n");
+ break;
+
+ case AUTH_REQ_CRYPT:
+ self->errormsg = "Password crypt authentication not supported";
+ self->errornumber = CONN_AUTH_TYPE_UNSUPPORTED;
+ return 0;
+ case AUTH_REQ_MD5:
+ mylog("in AUTH_REQ_MD5\n");
+ if (ci->password[0] == '\0')
+ {
+ self->errornumber = CONNECTION_NEED_PASSWORD;
+ self->errormsg = "A password is required for this connection.";
+ return -1; /* need password */
+ }
+ if (md5_auth_send(self, salt))
+ {
+ self->errormsg = "md5 hashing failed";
+ self->errornumber = CONN_INVALID_AUTHENTICATION;
+ return 0;
+ }
+ break;
+
+ case AUTH_REQ_SCM_CREDS:
+ self->errormsg = "Unix socket credential authentication not supported";
+ self->errornumber = CONN_AUTH_TYPE_UNSUPPORTED;
+ return 0;
+
+ default:
+ self->errormsg = "Unknown authentication type";
+ self->errornumber = CONN_AUTH_TYPE_UNSUPPORTED;
+ return 0;
+ }
+ break;
+ case 'K': /* Secret key (6.4 protocol) */
+ (void) SOCK_get_int(sock, 4); /* pid */
+ (void) SOCK_get_int(sock, 4); /* key */
+
+ break;
+ case 'Z': /* Backend is ready for new query (6.4) */
+ ReadyForQuery = TRUE;
+ break;
+ default:
+ self->errormsg = "Unexpected protocol character during authentication";
+ self->errornumber = CONN_INVALID_AUTHENTICATION;
+ return 0;
+ }
+
+ /*
+ * There were no ReadyForQuery responce before 6.4.
+ */
+ if (before_64 && areq == AUTH_REQ_OK)
+ ReadyForQuery = TRUE;
+ } while (!ReadyForQuery);
+ }
+
+
+ CC_clear_error(self); /* clear any password error */
+
+ /*
+ * send an empty query in order to find out whether the specified
+ * database really exists on the server machine
+ */
+ mylog("sending an empty query...\n");
+
+ res = CC_send_query(self, " ", NULL);
+ if (res == NULL || QR_get_status(res) != PGRES_EMPTY_QUERY)
+ {
+ mylog("got no result from the empty query. (probably database does not exist)\n");
+ self->errornumber = CONNECTION_NO_SUCH_DATABASE;
+ self->errormsg = "The database does not exist on the server\nor user authentication failed.";
+ if (res != NULL)
+ QR_Destructor(res);
+ return 0;
+ }
+ if (res)
+ QR_Destructor(res);
+
+ mylog("empty query seems to be OK.\n");
+
+ CC_set_translation(self);
+
+ /*
+ * Send any initial settings
+ */
+
+ /*
+ * Since these functions allocate statements, and since the connection
+ * is not established yet, it would violate odbc state transition
+ * rules. Therefore, these functions call the corresponding local
+ * function instead.
+ */
+ CC_send_settings(self);
+ CC_lookup_lo(self); /* a hack to get the oid of our large
+ * object oid type */
+ CC_lookup_pg_version(self); /* Get PostgreSQL version for SQLGetInfo
+ * use */
+
+ CC_clear_error(self); /* clear any initial command errors */
+ self->status = CONN_CONNECTED;
+
+ mylog("%s: returning...\n", func);
+
+ return 1;
+
+}
+
+
+char
+CC_add_statement(ConnectionClass *self, StatementClass *stmt)
+{
+ int i;
+
+ mylog("CC_add_statement: self=%u, stmt=%u\n", self, stmt);
+
+ for (i = 0; i < self->num_stmts; i++)
+ {
+ if (!self->stmts[i])
+ {
+ stmt->hdbc = self;
+ self->stmts[i] = stmt;
+ return TRUE;
+ }
+ }
+
+ /* no more room -- allocate more memory */
+ self->stmts = (StatementClass **) realloc(self->stmts, sizeof(StatementClass *) * (STMT_INCREMENT + self->num_stmts));
+ if (!self->stmts)
+ return FALSE;
+
+ memset(&self->stmts[self->num_stmts], 0, sizeof(StatementClass *) * STMT_INCREMENT);
+
+ stmt->hdbc = self;
+ self->stmts[self->num_stmts] = stmt;
+
+ self->num_stmts += STMT_INCREMENT;
+
+ return TRUE;
+}
+
+
+char
+CC_remove_statement(ConnectionClass *self, StatementClass *stmt)
+{
+ int i;
+
+ for (i = 0; i < self->num_stmts; i++)
+ {
+ if (self->stmts[i] == stmt && stmt->status != STMT_EXECUTING)
+ {
+ self->stmts[i] = NULL;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+/*
+ * Create a more informative error message by concatenating the connection
+ * error message with its socket error message.
+ */
+char *
+CC_create_errormsg(ConnectionClass *self)
+{
+ SocketClass *sock = self->sock;
+ int pos;
+ static char msg[4096];
+
+ mylog("enter CC_create_errormsg\n");
+
+ msg[0] = '\0';
+
+ if (self->errormsg)
+ strcpy(msg, self->errormsg);
+
+ mylog("msg = '%s'\n", msg);
+
+ if (sock && sock->errormsg && sock->errormsg[0] != '\0')
+ {
+ pos = strlen(msg);
+ sprintf(&msg[pos], ";\n%s", sock->errormsg);
+ }
+
+ mylog("exit CC_create_errormsg\n");
+ return msg;
+}
+
+
+char
+CC_get_error(ConnectionClass *self, int *number, char **message)
+{
+ int rv;
+
+ mylog("enter CC_get_error\n");
+
+ /* Create a very informative errormsg if it hasn't been done yet. */
+ if (!self->errormsg_created)
+ {
+ self->errormsg = CC_create_errormsg(self);
+ self->errormsg_created = TRUE;
+ }
+
+ if (self->errornumber)
+ {
+ *number = self->errornumber;
+ *message = self->errormsg;
+ }
+ rv = (self->errornumber != 0);
+
+ self->errornumber = 0; /* clear the error */
+
+ mylog("exit CC_get_error\n");
+
+ return rv;
+}
+
+
+/*
+ * The "result_in" is only used by QR_next_tuple() to fetch another group of rows into
+ * the same existing QResultClass (this occurs when the tuple cache is depleted and
+ * needs to be re-filled).
+ *
+ * The "cursor" is used by SQLExecute to associate a statement handle as the cursor name
+ * (i.e., C3326857) for SQL select statements. This cursor is then used in future
+ * 'declare cursor C3326857 for ...' and 'fetch 100 in C3326857' statements.
+ */
+QResultClass *
+CC_send_query(ConnectionClass *self, char *query, QueryInfo *qi)
+{
+ QResultClass *result_in = NULL,
+ *res = NULL,
+ *retres = NULL;
+ char swallow,
+ *wq;
+ int id;
+ SocketClass *sock = self->sock;
+ int maxlen,
+ empty_reqs;
+ BOOL msg_truncated,
+ ReadyToReturn,
+ tuples_return = FALSE,
+ query_completed = FALSE,
+ before_64 = PG_VERSION_LT(self, 6.4);
+
+ /* ERROR_MSG_LENGTH is suffcient */
+ static char msgbuffer[ERROR_MSG_LENGTH + 1];
+
+ /* QR_set_command() dups this string so doesn't need static */
+ char cmdbuffer[ERROR_MSG_LENGTH + 1];
+
+ mylog("send_query(): conn=%u, query='%s'\n", self, query);
+ qlog("conn=%u, query='%s'\n", self, query);
+
+ /* Indicate that we are sending a query to the backend */
+ maxlen = CC_get_max_query_len(self);
+ if (maxlen > 0 && maxlen < (int) strlen(query) + 1)
+ {
+ self->errornumber = CONNECTION_MSG_TOO_LONG;
+ self->errormsg = "Query string is too long";
+ return NULL;
+ }
+
+ if ((NULL == query) || (query[0] == '\0'))
+ return NULL;
+
+ if (SOCK_get_errcode(sock) != 0)
+ {
+ self->errornumber = CONNECTION_COULD_NOT_SEND;
+ self->errormsg = "Could not send Query to backend";
+ CC_set_no_trans(self);
+ return NULL;
+ }
+
+ SOCK_put_char(sock, 'Q');
+ if (SOCK_get_errcode(sock) != 0)
+ {
+ self->errornumber = CONNECTION_COULD_NOT_SEND;
+ self->errormsg = "Could not send Query to backend";
+ CC_set_no_trans(self);
+ return NULL;
+ }
+
+ SOCK_put_string(sock, query);
+ SOCK_flush_output(sock);
+
+ if (SOCK_get_errcode(sock) != 0)
+ {
+ self->errornumber = CONNECTION_COULD_NOT_SEND;
+ self->errormsg = "Could not send Query to backend";
+ CC_set_no_trans(self);
+ return NULL;
+ }
+
+ mylog("send_query: done sending query\n");
+
+ ReadyToReturn = FALSE;
+ empty_reqs = 0;
+ for (wq = query; isspace((unsigned char) *wq); wq++)
+ ;
+ if (*wq == '\0')
+ empty_reqs = 1;
+ while (!ReadyToReturn)
+ {
+ /* what type of message is coming now ? */
+ id = SOCK_get_char(sock);
+
+ if ((SOCK_get_errcode(sock) != 0) || (id == EOF))
+ {
+ self->errornumber = CONNECTION_NO_RESPONSE;
+ self->errormsg = "No response from the backend";
+
+ mylog("send_query: 'id' - %s\n", self->errormsg);
+ CC_set_no_trans(self);
+ ReadyToReturn = TRUE;
+ retres = NULL;
+ break;
+ }
+
+ mylog("send_query: got id = '%c'\n", id);
+
+ switch (id)
+ {
+ case 'A': /* Asynchronous Messages are ignored */
+ (void) SOCK_get_int(sock, 4); /* id of notification */
+ SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH);
+ /* name of the relation the message comes from */
+ break;
+ case 'C': /* portal query command, no tuples
+ * returned */
+ /* read in the return message from the backend */
+ SOCK_get_string(sock, cmdbuffer, ERROR_MSG_LENGTH);
+ if (SOCK_get_errcode(sock) != 0)
+ {
+ self->errornumber = CONNECTION_NO_RESPONSE;
+ self->errormsg = "No response from backend while receiving a portal query command";
+ mylog("send_query: 'C' - %s\n", self->errormsg);
+ CC_set_no_trans(self);
+ ReadyToReturn = TRUE;
+ retres = NULL;
+ }
+ else
+ {
+ mylog("send_query: ok - 'C' - %s\n", cmdbuffer);
+
+ if (res == NULL) /* allow for "show" style notices */
+ res = QR_Constructor();
+
+ mylog("send_query: setting cmdbuffer = '%s'\n", cmdbuffer);
+
+ /* Only save the first command */
+ if (QR_command_successful(res))
+ QR_set_status(res, PGRES_COMMAND_OK);
+ QR_set_command(res, cmdbuffer);
+ query_completed = TRUE;
+ mylog("send_query: returning res = %u\n", res);
+ if (!before_64)
+ break;
+
+ /*
+ * (Quotation from the original comments) since
+ * backend may produce more than one result for some
+ * commands we need to poll until clear so we send an
+ * empty query, and keep reading out of the pipe until
+ * an 'I' is received
+ */
+
+ if (empty_reqs == 0)
+ {
+ SOCK_put_string(sock, "Q ");
+ SOCK_flush_output(sock);
+ empty_reqs++;
+ }
+ }
+ break;
+ case 'Z': /* Backend is ready for new query (6.4) */
+ if (empty_reqs == 0)
+ {
+ ReadyToReturn = TRUE;
+ if (res && QR_get_aborted(res))
+ retres = res;
+ else if (tuples_return)
+ retres = result_in;
+ else if (query_completed)
+ retres = res;
+ else
+ ReadyToReturn = FALSE;
+ }
+ break;
+ case 'N': /* NOTICE: */
+ msg_truncated = SOCK_get_string(sock, cmdbuffer, ERROR_MSG_LENGTH);
+ if (!res)
+ res = QR_Constructor();
+ if (QR_command_successful(res))
+ QR_set_status(res, PGRES_NONFATAL_ERROR);
+ QR_set_notice(res, cmdbuffer); /* will dup this string */
+
+ mylog("~~~ NOTICE: '%s'\n", cmdbuffer);
+ qlog("NOTICE from backend during send_query: '%s'\n", cmdbuffer);
+ while (msg_truncated)
+ msg_truncated = SOCK_get_string(sock, cmdbuffer, ERROR_MSG_LENGTH);
+
+ continue; /* dont return a result -- continue
+ * reading */
+
+ case 'I': /* The server sends an empty query */
+ /* There is a closing '\0' following the 'I', so we eat it */
+ swallow = SOCK_get_char(sock);
+ if (!res)
+ res = QR_Constructor();
+ if ((swallow != '\0') || SOCK_get_errcode(sock) != 0)
+ {
+ self->errornumber = CONNECTION_BACKEND_CRAZY;
+ self->errormsg = "Unexpected protocol character from backend (send_query - I)";
+ QR_set_status(res, PGRES_FATAL_ERROR);
+ ReadyToReturn = TRUE;
+ retres = res;
+ break;
+ }
+ else
+ {
+ /* We return the empty query */
+ QR_set_status(res, PGRES_EMPTY_QUERY);
+ }
+ if (empty_reqs > 0)
+ {
+ if (--empty_reqs == 0)
+ query_completed = TRUE;
+ }
+ break;
+ case 'E':
+ msg_truncated = SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH);
+
+ /* Remove a newline */
+ if (msgbuffer[0] != '\0' && msgbuffer[strlen(msgbuffer) - 1] == '\n')
+ msgbuffer[strlen(msgbuffer) - 1] = '\0';
+
+ self->errormsg = msgbuffer;
+
+ mylog("send_query: 'E' - %s\n", self->errormsg);
+ qlog("ERROR from backend during send_query: '%s'\n", self->errormsg);
+
+ /* We should report that an error occured. Zoltan */
+ if (!res)
+ res = QR_Constructor();
+
+ if (!strncmp(self->errormsg, "FATAL", 5))
+ {
+ self->errornumber = CONNECTION_SERVER_REPORTED_ERROR;
+ CC_set_no_trans(self);
+ }
+ else
+ self->errornumber = CONNECTION_SERVER_REPORTED_WARNING;
+ QR_set_status(res, PGRES_FATAL_ERROR);
+ QR_set_aborted(res, TRUE);
+ while (msg_truncated)
+ msg_truncated = SOCK_get_string(sock, cmdbuffer, ERROR_MSG_LENGTH);
+
+ query_completed = TRUE;
+ break;
+
+ case 'P': /* get the Portal name */
+ SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH);
+ break;
+ case 'T': /* Tuple results start here */
+ result_in = qi ? qi->result_in : NULL;
+
+ if (result_in == NULL)
+ {
+ result_in = QR_Constructor();
+ mylog("send_query: 'T' no result_in: res = %u\n", result_in);
+ if (!result_in)
+ {
+ self->errornumber = CONNECTION_COULD_NOT_RECEIVE;
+ self->errormsg = "Could not create result info in send_query.";
+ ReadyToReturn = TRUE;
+ retres = NULL;
+ break;
+ }
+
+ if (qi)
+ QR_set_cache_size(result_in, qi->row_size);
+
+ if (!QR_fetch_tuples(result_in, self, qi ? qi->cursor : NULL))
+ {
+ self->errornumber = CONNECTION_COULD_NOT_RECEIVE;
+ self->errormsg = QR_get_message(result_in);
+ ReadyToReturn = TRUE;
+ retres = NULL;
+ break;
+ }
+ }
+ else
+ { /* next fetch, so reuse an existing result */
+
+ /*
+ * called from QR_next_tuple and must return
+ * immediately.
+ */
+ ReadyToReturn = TRUE;
+ if (!QR_fetch_tuples(result_in, NULL, NULL))
+ {
+ self->errornumber = CONNECTION_COULD_NOT_RECEIVE;
+ self->errormsg = QR_get_message(result_in);
+ retres = NULL;
+ break;
+ }
+ retres = result_in;
+ }
+
+ tuples_return = TRUE;
+ break;
+ case 'D': /* Copy in command began successfully */
+ if (!res)
+ res = QR_Constructor();
+ if (QR_command_successful(res))
+ QR_set_status(res, PGRES_COPY_IN);
+ ReadyToReturn = TRUE;
+ retres = res;
+ break;
+ case 'B': /* Copy out command began successfully */
+ if (!res)
+ res = QR_Constructor();
+ if (QR_command_successful(res))
+ QR_set_status(res, PGRES_COPY_OUT);
+ ReadyToReturn = TRUE;
+ retres = res;
+ break;
+ default:
+ self->errornumber = CONNECTION_BACKEND_CRAZY;
+ self->errormsg = "Unexpected protocol character from backend (send_query)";
+ CC_set_no_trans(self);
+
+ mylog("send_query: error - %s\n", self->errormsg);
+ ReadyToReturn = TRUE;
+ retres = NULL;
+ break;
+ }
+
+ /*
+ * There were no ReadyForQuery response before 6.4.
+ */
+ if (before_64)
+ {
+ if (empty_reqs == 0 && (query_completed || tuples_return))
+ break;
+ }
+ }
+
+ /*
+ * Break before being ready to return.
+ */
+ if (!ReadyToReturn)
+ {
+ if (res && QR_get_aborted(res))
+ retres = res;
+ else if (tuples_return)
+ retres = result_in;
+ else
+ retres = res;
+ }
+
+ /*
+ * set notice message to result_in.
+ */
+ if (result_in && res && retres == result_in)
+ {
+ if (QR_command_successful(result_in))
+ QR_set_status(result_in, QR_get_status(res));
+ QR_set_notice(result_in, QR_get_notice(res));
+ }
+
+ /*
+ * Cleanup garbage results before returning.
+ */
+ if (res && retres != res)
+ QR_Destructor(res);
+ if (result_in && retres != result_in)
+ {
+ if (qi && qi->result_in)
+ ;
+ else
+ QR_Destructor(result_in);
+ }
+ return retres;
+}
+
+
+int
+CC_send_function(ConnectionClass *self, int fnid, void *result_buf, int *actual_result_len, int result_is_int, LO_ARG *args, int nargs)
+{
+ char id,
+ c,
+ done;
+ SocketClass *sock = self->sock;
+
+ /* ERROR_MSG_LENGTH is sufficient */
+ static char msgbuffer[ERROR_MSG_LENGTH + 1];
+ int i;
+
+ mylog("send_function(): conn=%u, fnid=%d, result_is_int=%d, nargs=%d\n", self, fnid, result_is_int, nargs);
+
+ if (SOCK_get_errcode(sock) != 0)
+ {
+ self->errornumber = CONNECTION_COULD_NOT_SEND;
+ self->errormsg = "Could not send function to backend";
+ CC_set_no_trans(self);
+ return FALSE;
+ }
+
+ SOCK_put_string(sock, "F ");
+ if (SOCK_get_errcode(sock) != 0)
+ {
+ self->errornumber = CONNECTION_COULD_NOT_SEND;
+ self->errormsg = "Could not send function to backend";
+ CC_set_no_trans(self);
+ return FALSE;
+ }
+
+ SOCK_put_int(sock, fnid, 4);
+ SOCK_put_int(sock, nargs, 4);
+
+
+ mylog("send_function: done sending function\n");
+
+ for (i = 0; i < nargs; ++i)
+ {
+ mylog(" arg[%d]: len = %d, isint = %d, integer = %d, ptr = %u\n", i, args[i].len, args[i].isint, args[i].u.integer, args[i].u.ptr);
+
+ SOCK_put_int(sock, args[i].len, 4);
+ if (args[i].isint)
+ SOCK_put_int(sock, args[i].u.integer, 4);
+ else
+ SOCK_put_n_char(sock, (char *) args[i].u.ptr, args[i].len);
+
+
+ }
+
+ mylog(" done sending args\n");
+
+ SOCK_flush_output(sock);
+ mylog(" after flush output\n");
+
+ done = FALSE;
+ while (!done)
+ {
+ id = SOCK_get_char(sock);
+ mylog(" got id = %c\n", id);
+
+ switch (id)
+ {
+ case 'V':
+ done = TRUE;
+ break; /* ok */
+
+ case 'N':
+ SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH);
+ mylog("send_function(V): 'N' - %s\n", msgbuffer);
+ /* continue reading */
+ break;
+
+ case 'E':
+ SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH);
+ self->errormsg = msgbuffer;
+
+ mylog("send_function(V): 'E' - %s\n", self->errormsg);
+ qlog("ERROR from backend during send_function: '%s'\n", self->errormsg);
+
+ return FALSE;
+
+ case 'Z':
+ break;
+
+ default:
+ self->errornumber = CONNECTION_BACKEND_CRAZY;
+ self->errormsg = "Unexpected protocol character from backend (send_function, args)";
+ CC_set_no_trans(self);
+
+ mylog("send_function: error - %s\n", self->errormsg);
+ return FALSE;
+ }
+ }
+
+ id = SOCK_get_char(sock);
+ for (;;)
+ {
+ switch (id)
+ {
+ case 'G': /* function returned properly */
+ mylog(" got G!\n");
+
+ *actual_result_len = SOCK_get_int(sock, 4);
+ mylog(" actual_result_len = %d\n", *actual_result_len);
+
+ if (result_is_int)
+ *((int *) result_buf) = SOCK_get_int(sock, 4);
+ else
+ SOCK_get_n_char(sock, (char *) result_buf, *actual_result_len);
+
+ mylog(" after get result\n");
+
+ c = SOCK_get_char(sock); /* get the last '0' */
+
+ mylog(" after get 0\n");
+
+ return TRUE;
+
+ case 'E':
+ SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH);
+ self->errormsg = msgbuffer;
+
+ mylog("send_function(G): 'E' - %s\n", self->errormsg);
+ qlog("ERROR from backend during send_function: '%s'\n", self->errormsg);
+
+ return FALSE;
+
+ case 'N':
+ SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH);
+
+ mylog("send_function(G): 'N' - %s\n", msgbuffer);
+ qlog("NOTICE from backend during send_function: '%s'\n", msgbuffer);
+
+ continue; /* dont return a result -- continue
+ * reading */
+
+ case '0': /* empty result */
+ return TRUE;
+
+ default:
+ self->errornumber = CONNECTION_BACKEND_CRAZY;
+ self->errormsg = "Unexpected protocol character from backend (send_function, result)";
+ CC_set_no_trans(self);
+
+ mylog("send_function: error - %s\n", self->errormsg);
+ return FALSE;
+ }
+ }
+}
+
+
+char
+CC_send_settings(ConnectionClass *self)
+{
+ /* char ini_query[MAX_MESSAGE_LEN]; */
+ ConnInfo *ci = &(self->connInfo);
+
+/* QResultClass *res; */
+ HSTMT hstmt;
+ StatementClass *stmt;
+ RETCODE result;
+ char status = TRUE;
+ char *cs,
+ *ptr;
+ static char *func = "CC_send_settings";
+
+
+ mylog("%s: entering...\n", func);
+
+/*
+ * This function must use the local odbc API functions since the odbc state
+ * has not transitioned to "connected" yet.
+ */
+
+ result = PGAPI_AllocStmt(self, &hstmt);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ return FALSE;
+ stmt = (StatementClass *) hstmt;
+
+ stmt->internal = TRUE; /* ensure no BEGIN/COMMIT/ABORT stuff */
+
+ /* Set the Datestyle to the format the driver expects it to be in */
+ result = PGAPI_ExecDirect(hstmt, "set DateStyle to 'ISO'", SQL_NTS);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ status = FALSE;
+
+ mylog("%s: result %d, status %d from set DateStyle\n", func, result, status);
+
+ /* Disable genetic optimizer based on global flag */
+ if (ci->drivers.disable_optimizer)
+ {
+ result = PGAPI_ExecDirect(hstmt, "set geqo to 'OFF'", SQL_NTS);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ status = FALSE;
+
+ mylog("%s: result %d, status %d from set geqo\n", func, result, status);
+
+ }
+
+ /* KSQO */
+ if (ci->drivers.ksqo)
+ {
+ result = PGAPI_ExecDirect(hstmt, "set ksqo to 'ON'", SQL_NTS);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ status = FALSE;
+
+ mylog("%s: result %d, status %d from set ksqo\n", func, result, status);
+
+ }
+
+ /* Global settings */
+ if (ci->drivers.conn_settings[0] != '\0')
+ {
+ cs = strdup(ci->drivers.conn_settings);
+ ptr = strtok(cs, ";");
+ while (ptr)
+ {
+ result = PGAPI_ExecDirect(hstmt, ptr, SQL_NTS);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ status = FALSE;
+
+ mylog("%s: result %d, status %d from '%s'\n", func, result, status, ptr);
+
+ ptr = strtok(NULL, ";");
+ }
+
+ free(cs);
+ }
+
+ /* Per Datasource settings */
+ if (ci->conn_settings[0] != '\0')
+ {
+ cs = strdup(ci->conn_settings);
+ ptr = strtok(cs, ";");
+ while (ptr)
+ {
+ result = PGAPI_ExecDirect(hstmt, ptr, SQL_NTS);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ status = FALSE;
+
+ mylog("%s: result %d, status %d from '%s'\n", func, result, status, ptr);
+
+ ptr = strtok(NULL, ";");
+ }
+
+ free(cs);
+ }
+
+
+ PGAPI_FreeStmt(hstmt, SQL_DROP);
+
+ return status;
+}
+
+
+/*
+ * This function is just a hack to get the oid of our Large Object oid type.
+ * If a real Large Object oid type is made part of Postgres, this function
+ * will go away and the define 'PG_TYPE_LO' will be updated.
+ */
+void
+CC_lookup_lo(ConnectionClass *self)
+{
+ HSTMT hstmt;
+ StatementClass *stmt;
+ RETCODE result;
+ static char *func = "CC_lookup_lo";
+
+ mylog("%s: entering...\n", func);
+
+/*
+ * This function must use the local odbc API functions since the odbc state
+ * has not transitioned to "connected" yet.
+ */
+ result = PGAPI_AllocStmt(self, &hstmt);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ return;
+ stmt = (StatementClass *) hstmt;
+
+ result = PGAPI_ExecDirect(hstmt, "select oid from pg_type where typname='" PG_TYPE_LO_NAME "'", SQL_NTS);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ PGAPI_FreeStmt(hstmt, SQL_DROP);
+ return;
+ }
+
+ result = PGAPI_Fetch(hstmt);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ PGAPI_FreeStmt(hstmt, SQL_DROP);
+ return;
+ }
+
+ result = PGAPI_GetData(hstmt, 1, SQL_C_SLONG, &self->lobj_type, sizeof(self->lobj_type), NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ PGAPI_FreeStmt(hstmt, SQL_DROP);
+ return;
+ }
+
+ mylog("Got the large object oid: %d\n", self->lobj_type);
+ qlog(" [ Large Object oid = %d ]\n", self->lobj_type);
+
+ result = PGAPI_FreeStmt(hstmt, SQL_DROP);
+}
+
+
+/*
+ * This function initializes the version of PostgreSQL from
+ * connInfo.protocol that we're connected to.
+ * h-inoue 01-2-2001
+ */
+void
+CC_initialize_pg_version(ConnectionClass *self)
+{
+ strcpy(self->pg_version, self->connInfo.protocol);
+ if (PROTOCOL_62(&self->connInfo))
+ {
+ self->pg_version_number = (float) 6.2;
+ self->pg_version_major = 6;
+ self->pg_version_minor = 2;
+ }
+ else if (PROTOCOL_63(&self->connInfo))
+ {
+ self->pg_version_number = (float) 6.3;
+ self->pg_version_major = 6;
+ self->pg_version_minor = 3;
+ }
+ else
+ {
+ self->pg_version_number = (float) 6.4;
+ self->pg_version_major = 6;
+ self->pg_version_minor = 4;
+ }
+}
+
+
+/*
+ * This function gets the version of PostgreSQL that we're connected to.
+ * This is used to return the correct info in SQLGetInfo
+ * DJP - 25-1-2001
+ */
+void
+CC_lookup_pg_version(ConnectionClass *self)
+{
+ HSTMT hstmt;
+ StatementClass *stmt;
+ RETCODE result;
+ char szVersion[32];
+ int major,
+ minor;
+ static char *func = "CC_lookup_pg_version";
+
+ mylog("%s: entering...\n", func);
+
+/*
+ * This function must use the local odbc API functions since the odbc state
+ * has not transitioned to "connected" yet.
+ */
+ result = PGAPI_AllocStmt(self, &hstmt);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ return;
+ stmt = (StatementClass *) hstmt;
+
+ /* get the server's version if possible */
+ result = PGAPI_ExecDirect(hstmt, "select version()", SQL_NTS);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ PGAPI_FreeStmt(hstmt, SQL_DROP);
+ return;
+ }
+
+ result = PGAPI_Fetch(hstmt);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ PGAPI_FreeStmt(hstmt, SQL_DROP);
+ return;
+ }
+
+ result = PGAPI_GetData(hstmt, 1, SQL_C_CHAR, self->pg_version, MAX_INFO_STRING, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ PGAPI_FreeStmt(hstmt, SQL_DROP);
+ return;
+ }
+
+ /*
+ * Extract the Major and Minor numbers from the string. This assumes
+ * the string starts 'Postgresql X.X'
+ */
+ strcpy(szVersion, "0.0");
+ if (sscanf(self->pg_version, "%*s %d.%d", &major, &minor) >= 2)
+ {
+ sprintf(szVersion, "%d.%d", major, minor);
+ self->pg_version_major = major;
+ self->pg_version_minor = minor;
+ }
+ self->pg_version_number = (float) atof(szVersion);
+
+ mylog("Got the PostgreSQL version string: '%s'\n", self->pg_version);
+ mylog("Extracted PostgreSQL version number: '%1.1f'\n", self->pg_version_number);
+ qlog(" [ PostgreSQL version string = '%s' ]\n", self->pg_version);
+ qlog(" [ PostgreSQL version number = '%1.1f' ]\n", self->pg_version_number);
+
+ result = PGAPI_FreeStmt(hstmt, SQL_DROP);
+}
+
+
+void
+CC_log_error(char *func, char *desc, ConnectionClass *self)
+{
+#ifdef PRN_NULLCHECK
+#define nullcheck(a) (a ? a : "(NULL)")
+#endif
+
+ if (self)
+ {
+ qlog("CONN ERROR: func=%s, desc='%s', errnum=%d, errmsg='%s'\n", func, desc, self->errornumber, nullcheck(self->errormsg));
+ mylog("CONN ERROR: func=%s, desc='%s', errnum=%d, errmsg='%s'\n", func, desc, self->errornumber, nullcheck(self->errormsg));
+ qlog(" ------------------------------------------------------------\n");
+ qlog(" henv=%u, conn=%u, status=%u, num_stmts=%d\n", self->henv, self, self->status, self->num_stmts);
+ qlog(" sock=%u, stmts=%u, lobj_type=%d\n", self->sock, self->stmts, self->lobj_type);
+
+ qlog(" ---------------- Socket Info -------------------------------\n");
+ if (self->sock)
+ {
+ SocketClass *sock = self->sock;
+
+ qlog(" socket=%d, reverse=%d, errornumber=%d, errormsg='%s'\n", sock->socket, sock->reverse, sock->errornumber, nullcheck(sock->errormsg));
+ qlog(" buffer_in=%u, buffer_out=%u\n", sock->buffer_in, sock->buffer_out);
+ qlog(" buffer_filled_in=%d, buffer_filled_out=%d, buffer_read_in=%d\n", sock->buffer_filled_in, sock->buffer_filled_out, sock->buffer_read_in);
+ }
+ }
+ else
+ qlog("INVALID CONNECTION HANDLE ERROR: func=%s, desc='%s'\n", func, desc);
+#undef PRN_NULLCHECK
+}
+
+int
+CC_get_max_query_len(const ConnectionClass *conn)
+{
+ int value;
+
+ /* Long Queries in 7.0+ */
+ if (PG_VERSION_GE(conn, 7.0))
+ value = 0 /* MAX_STATEMENT_LEN */ ;
+ /* Prior to 7.0 we used 2*BLCKSZ */
+ else if (PG_VERSION_GE(conn, 6.5))
+ value = (2 * BLCKSZ);
+ else
+ /* Prior to 6.5 we used BLCKSZ */
+ value = BLCKSZ;
+ return value;
+}
--- /dev/null
+/* File: connection.h
+ *
+ * Description: See "connection.c"
+ *
+ * Comments: See "notice.txt" for copyright and license information.
+ *
+ */
+
+#ifndef __CONNECTION_H__
+#define __CONNECTION_H__
+
+#include "psqlodbc.h"
+
+#include <stdlib.h>
+#include <string.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 101
+#define CONNECTION_MSG_TOO_LONG 103
+#define CONNECTION_COULD_NOT_SEND 104
+#define CONNECTION_NO_SUCH_DATABASE 105
+#define CONNECTION_BACKEND_CRAZY 106
+#define CONNECTION_NO_RESPONSE 107
+#define CONNECTION_SERVER_REPORTED_ERROR 108
+#define CONNECTION_COULD_NOT_RECEIVE 109
+#define CONNECTION_SERVER_REPORTED_WARNING 110
+#define CONNECTION_NEED_PASSWORD 112
+
+/* These errors correspond to specific SQL states */
+#define CONN_INIREAD_ERROR 201
+#define CONN_OPENDB_ERROR 202
+#define CONN_STMT_ALLOC_ERROR 203
+#define CONN_IN_USE 204
+#define CONN_UNSUPPORTED_OPTION 205
+/* Used by SetConnectoption to indicate unsupported options */
+#define CONN_INVALID_ARGUMENT_NO 206
+/* SetConnectOption: corresponds to ODBC--"S1009" */
+#define CONN_TRANSACT_IN_PROGRES 207
+#define CONN_NO_MEMORY_ERROR 208
+#define CONN_NOT_IMPLEMENTED_ERROR 209
+#define CONN_INVALID_AUTHENTICATION 210
+#define CONN_AUTH_TYPE_UNSUPPORTED 211
+#define CONN_UNABLE_TO_LOAD_DLL 212
+
+#define CONN_OPTION_VALUE_CHANGED 213
+#define CONN_VALUE_OUT_OF_RANGE 214
+
+#define CONN_TRUNCATED 215
+
+/* Conn_status defines */
+#define CONN_IN_AUTOCOMMIT 0x01
+#define CONN_IN_TRANSACTION 0x02
+
+/* AutoCommit functions */
+#define CC_set_autocommit_off(x) (x->transact_status &= ~CONN_IN_AUTOCOMMIT)
+#define CC_set_autocommit_on(x) (x->transact_status |= CONN_IN_AUTOCOMMIT)
+#define CC_is_in_autocommit(x) (x->transact_status & CONN_IN_AUTOCOMMIT)
+
+/* Transaction in/not functions */
+#define CC_set_in_trans(x) (x->transact_status |= CONN_IN_TRANSACTION)
+#define CC_set_no_trans(x) (x->transact_status &= ~CONN_IN_TRANSACTION)
+#define CC_is_in_trans(x) (x->transact_status & CONN_IN_TRANSACTION)
+
+
+/* Authentication types */
+#define AUTH_REQ_OK 0
+#define AUTH_REQ_KRB4 1
+#define AUTH_REQ_KRB5 2
+#define AUTH_REQ_PASSWORD 3
+#define AUTH_REQ_CRYPT 4
+#define AUTH_REQ_MD5 5
+#define AUTH_REQ_SCM_CREDS 6
+
+/* Startup Packet sizes */
+#define SM_DATABASE 64
+#define SM_USER 32
+#define SM_OPTIONS 64
+#define SM_UNUSED 64
+#define SM_TTY 64
+
+/* Old 6.2 protocol defines */
+#define NO_AUTHENTICATION 7
+#define PATH_SIZE 64
+#define ARGV_SIZE 64
+#define NAMEDATALEN 16
+
+typedef unsigned int ProtocolVersion;
+
+#define PG_PROTOCOL(major, minor) (((major) << 16) | (minor))
+#define PG_PROTOCOL_LATEST PG_PROTOCOL(2, 0)
+#define PG_PROTOCOL_63 PG_PROTOCOL(1, 0)
+#define PG_PROTOCOL_62 PG_PROTOCOL(0, 0)
+
+/* This startup packet is to support latest Postgres protocol (6.4, 6.3) */
+typedef struct _StartupPacket
+{
+ ProtocolVersion protoVersion;
+ char database[SM_DATABASE];
+ char user[SM_USER];
+ char options[SM_OPTIONS];
+ char unused[SM_UNUSED];
+ char tty[SM_TTY];
+} StartupPacket;
+
+
+/* This startup packet is to support pre-Postgres 6.3 protocol */
+typedef struct _StartupPacket6_2
+{
+ unsigned int authtype;
+ char database[PATH_SIZE];
+ char user[NAMEDATALEN];
+ char options[ARGV_SIZE];
+ char execfile[ARGV_SIZE];
+ char tty[PATH_SIZE];
+} StartupPacket6_2;
+
+
+/* Structure to hold all the connection attributes for a specific
+ connection (used for both registry and file, DSN and DRIVER)
+*/
+typedef struct
+{
+ char dsn[MEDIUM_REGISTRY_LEN];
+ char desc[MEDIUM_REGISTRY_LEN];
+ char driver[MEDIUM_REGISTRY_LEN];
+ char server[MEDIUM_REGISTRY_LEN];
+ char database[MEDIUM_REGISTRY_LEN];
+ char username[MEDIUM_REGISTRY_LEN];
+ char password[MEDIUM_REGISTRY_LEN];
+ char conn_settings[LARGE_REGISTRY_LEN];
+ char protocol[SMALL_REGISTRY_LEN];
+ char port[SMALL_REGISTRY_LEN];
+ char onlyread[SMALL_REGISTRY_LEN];
+ char fake_oid_index[SMALL_REGISTRY_LEN];
+ char show_oid_column[SMALL_REGISTRY_LEN];
+ char row_versioning[SMALL_REGISTRY_LEN];
+ char show_system_tables[SMALL_REGISTRY_LEN];
+ char translation_dll[MEDIUM_REGISTRY_LEN];
+ char translation_option[SMALL_REGISTRY_LEN];
+ char focus_password;
+ char disallow_premature;
+ char updatable_cursors;
+ GLOBAL_VALUES drivers; /* moved from driver's option */
+} ConnInfo;
+
+/* Macro to determine is the connection using 6.2 protocol? */
+#define PROTOCOL_62(conninfo_) (strncmp((conninfo_)->protocol, PG62, strlen(PG62)) == 0)
+
+/* Macro to determine is the connection using 6.3 protocol? */
+#define PROTOCOL_63(conninfo_) (strncmp((conninfo_)->protocol, PG63, strlen(PG63)) == 0)
+
+/*
+ * Macros to compare the server's version with a specified version
+ * 1st parameter: pointer to a ConnectionClass object
+ * 2nd parameter: major version number
+ * 3rd parameter: minor version number
+ */
+#define SERVER_VERSION_GT(conn, major, minor) \
+ ((conn)->pg_version_major > major || \
+ ((conn)->pg_version_major == major && (conn)->pg_version_minor > minor))
+#define SERVER_VERSION_GE(conn, major, minor) \
+ ((conn)->pg_version_major > major || \
+ ((conn)->pg_version_major == major && (conn)->pg_version_minor >= minor))
+#define SERVER_VERSION_EQ(conn, major, minor) \
+ ((conn)->pg_version_major == major && (conn)->pg_version_minor == minor)
+#define SERVER_VERSION_LE(conn, major, minor) (! SERVER_VERSION_GT(conn, major, minor))
+#define SERVER_VERSION_LT(conn, major, minor) (! SERVER_VERSION_GE(conn, major, minor))
+/*#if ! defined(HAVE_CONFIG_H) || defined(HAVE_STRINGIZE)*/
+#define STRING_AFTER_DOT(string) (strchr(#string, '.') + 1)
+/*#else
+#define STRING_AFTER_DOT(str) (strchr("str", '.') + 1)
+#endif*/
+/*
+ * Simplified macros to compare the server's version with a
+ * specified version
+ * Note: Never pass a variable as the second parameter.
+ * It must be a decimal constant of the form %d.%d .
+ */
+#define PG_VERSION_GT(conn, ver) \
+ (SERVER_VERSION_GT(conn, (int) ver, atoi(STRING_AFTER_DOT(ver))))
+#define PG_VERSION_GE(conn, ver) \
+ (SERVER_VERSION_GE(conn, (int) ver, atoi(STRING_AFTER_DOT(ver))))
+#define PG_VERSION_EQ(conn, ver) \
+ (SERVER_VERSION_EQ(conn, (int) ver, atoi(STRING_AFTER_DOT(ver))))
+#define PG_VERSION_LE(conn, ver) (! PG_VERSION_GT(conn, ver))
+#define PG_VERSION_LT(conn, ver) (! PG_VERSION_GE(conn, ver))
+
+/* This is used to store cached table information in the connection */
+struct col_info
+{
+ QResultClass *result;
+ char name[MAX_TABLE_LEN + 1];
+};
+
+ /* Translation DLL entry points */
+#ifdef WIN32
+#define DLLHANDLE HINSTANCE
+#else
+#define WINAPI CALLBACK
+#define DLLHANDLE void *
+#define HINSTANCE void *
+#endif
+
+typedef BOOL (FAR WINAPI * DataSourceToDriverProc) (UDWORD,
+ SWORD,
+ PTR,
+ SDWORD,
+ PTR,
+ SDWORD,
+ SDWORD FAR *,
+ UCHAR FAR *,
+ SWORD,
+ SWORD FAR *);
+
+typedef BOOL (FAR WINAPI * DriverToDataSourceProc) (UDWORD,
+ SWORD,
+ PTR,
+ SDWORD,
+ PTR,
+ SDWORD,
+ SDWORD FAR *,
+ UCHAR FAR *,
+ SWORD,
+ SWORD FAR *);
+
+/******* The Connection handle ************/
+struct ConnectionClass_
+{
+ HENV henv; /* environment this connection was created
+ * on */
+ StatementOptions stmtOptions;
+ char *errormsg;
+ int errornumber;
+ CONN_Status status;
+ ConnInfo connInfo;
+ StatementClass **stmts;
+ int num_stmts;
+ SocketClass *sock;
+ int lobj_type;
+ int ntables;
+ COL_INFO **col_info;
+ long translation_option;
+ HINSTANCE translation_handle;
+ DataSourceToDriverProc DataSourceToDriver;
+ DriverToDataSourceProc DriverToDataSource;
+ Int2 driver_version; /* prepared for ODBC3.0 */
+ char transact_status;/* Is a transaction is currently in
+ * progress */
+ char errormsg_created; /* has an informative error msg
+ * been created? */
+ char pg_version[MAX_INFO_STRING]; /* Version of PostgreSQL
+ * we're connected to -
+ * DJP 25-1-2001 */
+ float pg_version_number;
+ Int2 pg_version_major;
+ Int2 pg_version_minor;
+ char ms_jet;
+#ifdef MULTIBYTE
+ char *client_encoding;
+ char *server_encoding;
+#endif /* MULTIBYTE */
+};
+
+
+/* Accessor functions */
+#define CC_get_socket(x) (x->sock)
+#define CC_get_database(x) (x->connInfo.database)
+#define CC_get_server(x) (x->connInfo.server)
+#define CC_get_DSN(x) (x->connInfo.dsn)
+#define CC_get_username(x) (x->connInfo.username)
+#define CC_is_onlyread(x) (x->connInfo.onlyread[0] == '1')
+
+
+/* for CC_DSN_info */
+#define CONN_DONT_OVERWRITE 0
+#define CONN_OVERWRITE 1
+
+
+/* prototypes */
+ConnectionClass *CC_Constructor(void);
+char CC_Destructor(ConnectionClass *self);
+int CC_cursor_count(ConnectionClass *self);
+char CC_cleanup(ConnectionClass *self);
+char CC_abort(ConnectionClass *self);
+int CC_set_translation(ConnectionClass *self);
+char CC_connect(ConnectionClass *self, char do_password);
+char CC_add_statement(ConnectionClass *self, StatementClass *stmt);
+char CC_remove_statement(ConnectionClass *self, StatementClass *stmt);
+char CC_get_error(ConnectionClass *self, int *number, char **message);
+QResultClass *CC_send_query(ConnectionClass *self, char *query, QueryInfo *qi);
+void CC_clear_error(ConnectionClass *self);
+char *CC_create_errormsg(ConnectionClass *self);
+int CC_send_function(ConnectionClass *conn, int fnid, void *result_buf, int *actual_result_len, int result_is_int, LO_ARG *argv, int nargs);
+char CC_send_settings(ConnectionClass *self);
+void CC_lookup_lo(ConnectionClass *conn);
+void CC_lookup_pg_version(ConnectionClass *conn);
+void CC_initialize_pg_version(ConnectionClass *conn);
+void CC_log_error(char *func, char *desc, ConnectionClass *self);
+int CC_get_max_query_len(const ConnectionClass *self);
+
+#endif
--- /dev/null
+/*-------
+ * Module: convert.c
+ *
+ * Description: This module contains routines related to
+ * converting parameters and columns into requested data types.
+ * Parameters are converted from their SQL_C data types into
+ * the appropriate postgres type. Columns are converted from
+ * their postgres type (SQL type) into the appropriate SQL_C
+ * data type.
+ *
+ * Classes: n/a
+ *
+ * API functions: none
+ *
+ * Comments: See "notice.txt" for copyright and license information.
+ *-------
+ */
+/* Multibyte support Eiji Tokuya 2001-03-15 */
+
+#include "convert.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef MULTIBYTE
+#include "multibyte.h"
+#endif
+
+#include <time.h>
+#include <math.h>
+#include <stdlib.h>
+#include "statement.h"
+#include "qresult.h"
+#include "bind.h"
+#include "pgtypes.h"
+#include "lobj.h"
+#include "connection.h"
+#include "pgapifunc.h"
+
+#ifdef __CYGWIN__
+#define TIMEZONE_GLOBAL _timezone
+#elif defined(WIN32) || defined(HAVE_INT_TIMEZONE)
+#define TIMEZONE_GLOBAL timezone
+#endif
+
+/*
+ * How to map ODBC scalar functions {fn func(args)} to Postgres.
+ * This is just a simple substitution. List augmented from:
+ * http://www.merant.com/datadirect/download/docs/odbc16/Odbcref/rappc.htm
+ * - thomas 2000-04-03
+ */
+char *mapFuncs[][2] = {
+/* { "ASCII", "ascii" }, */
+ {"CHAR", "chr"},
+ {"CONCAT", "textcat"},
+/* { "DIFFERENCE", "difference" }, */
+/* { "INSERT", "insert" }, */
+ {"LCASE", "lower"},
+ {"LEFT", "ltrunc"},
+ {"LOCATE", "strpos"},
+ {"LENGTH", "char_length"},
+/* { "LTRIM", "ltrim" }, */
+ {"RIGHT", "rtrunc"},
+/* { "REPEAT", "repeat" }, */
+/* { "REPLACE", "replace" }, */
+/* { "RTRIM", "rtrim" }, */
+/* { "SOUNDEX", "soundex" }, */
+ {"SUBSTRING", "substr"},
+ {"UCASE", "upper"},
+
+/* { "ABS", "abs" }, */
+/* { "ACOS", "acos" }, */
+/* { "ASIN", "asin" }, */
+/* { "ATAN", "atan" }, */
+/* { "ATAN2", "atan2" }, */
+ {"CEILING", "ceil"},
+/* { "COS", "cos" }, */
+/* { "COT", "cot" }, */
+/* { "DEGREES", "degrees" }, */
+/* { "EXP", "exp" }, */
+/* { "FLOOR", "floor" }, */
+ {"LOG", "ln"},
+ {"LOG10", "log"},
+/* { "MOD", "mod" }, */
+/* { "PI", "pi" }, */
+ {"POWER", "pow"},
+/* { "RADIANS", "radians" }, */
+ {"RAND", "random"},
+/* { "ROUND", "round" }, */
+/* { "SIGN", "sign" }, */
+/* { "SIN", "sin" }, */
+/* { "SQRT", "sqrt" }, */
+/* { "TAN", "tan" }, */
+ {"TRUNCATE", "trunc"},
+
+ {"CURRENT_DATE", "curdate"},
+ {"CURRENT_TIME", "curtime"},
+ {"CURRENT_TIMESTAMP", "odbc_timestamp"},
+ {"CURRENT_USER", "odbc_current_user"},
+ {"SESSION_USER", "odbc_session_user"},
+/* { "CURDATE", "curdate" }, */
+/* { "CURTIME", "curtime" }, */
+/* { "DAYNAME", "dayname" }, */
+/* { "DAYOFMONTH", "dayofmonth" }, */
+/* { "DAYOFWEEK", "dayofweek" }, */
+/* { "DAYOFYEAR", "dayofyear" }, */
+/* { "HOUR", "hour" }, */
+/* { "MINUTE", "minute" }, */
+/* { "MONTH", "month" }, */
+/* { "MONTHNAME", "monthname" }, */
+/* { "NOW", "now" }, */
+/* { "QUARTER", "quarter" }, */
+/* { "SECOND", "second" }, */
+/* { "WEEK", "week" }, */
+/* { "YEAR", "year" }, */
+
+/* { "DATABASE", "database" }, */
+ {"IFNULL", "coalesce"},
+ {"USER", "odbc_user"},
+ {0, 0}
+};
+
+static char *mapFunction(const char *func);
+static unsigned int conv_from_octal(const unsigned char *s);
+static unsigned int conv_from_hex(const unsigned char *s);
+static char *conv_to_octal(unsigned char val);
+
+/*---------
+ * A Guide for date/time/timestamp conversions
+ *
+ * field_type fCType Output
+ * ---------- ------ ----------
+ * PG_TYPE_DATE SQL_C_DEFAULT SQL_C_DATE
+ * PG_TYPE_DATE SQL_C_DATE SQL_C_DATE
+ * PG_TYPE_DATE SQL_C_TIMESTAMP SQL_C_TIMESTAMP (time = 0 (midnight))
+ * PG_TYPE_TIME SQL_C_DEFAULT SQL_C_TIME
+ * PG_TYPE_TIME SQL_C_TIME SQL_C_TIME
+ * PG_TYPE_TIME SQL_C_TIMESTAMP SQL_C_TIMESTAMP (date = current date)
+ * PG_TYPE_ABSTIME SQL_C_DEFAULT SQL_C_TIMESTAMP
+ * PG_TYPE_ABSTIME SQL_C_DATE SQL_C_DATE (time is truncated)
+ * PG_TYPE_ABSTIME SQL_C_TIME SQL_C_TIME (date is truncated)
+ * PG_TYPE_ABSTIME SQL_C_TIMESTAMP SQL_C_TIMESTAMP
+ *---------
+ */
+
+
+
+/*
+ * TIMESTAMP <-----> SIMPLE_TIME
+ * precision support since 7.2.
+ * time zone support is unavailable(the stuff is unreliable)
+ */
+static BOOL
+timestamp2stime(const char *str, SIMPLE_TIME *st, BOOL *bZone, int *zone)
+{
+ char rest[64],
+ *ptr;
+ int scnt,
+ i;
+ long timediff;
+ BOOL withZone = *bZone;
+
+ *bZone = FALSE;
+ *zone = 0;
+ st->fr = 0;
+ if ((scnt = sscanf(str, "%4d-%2d-%2d %2d:%2d:%2d%s", &st->y, &st->m, &st->d, &st->hh, &st->mm, &st->ss, rest)) < 6)
+ return FALSE;
+ else if (scnt == 6)
+ return TRUE;
+ switch (rest[0])
+ {
+ case '+':
+ *bZone = TRUE;
+ *zone = atoi(&rest[1]);
+ break;
+ case '-':
+ *bZone = TRUE;
+ *zone = -atoi(&rest[1]);
+ break;
+ case '.':
+ if ((ptr = strchr(rest, '+')) != NULL)
+ {
+ *bZone = TRUE;
+ *zone = atoi(&ptr[1]);
+ *ptr = '\0';
+ }
+ else if ((ptr = strchr(rest, '-')) != NULL)
+ {
+ *bZone = TRUE;
+ *zone = -atoi(&ptr[1]);
+ *ptr = '\0';
+ }
+ for (i = 1; i < 10; i++)
+ {
+ if (!isdigit((unsigned char) rest[i]))
+ break;
+ }
+ for (; i < 10; i++)
+ rest[i] = '0';
+ rest[i] = '\0';
+ st->fr = atoi(&rest[1]);
+ break;
+ default:
+ return TRUE;
+ }
+ if (!withZone || !*bZone || st->y < 1970)
+ return TRUE;
+#if defined(WIN32) || defined(HAVE_INT_TIMEZONE)
+ if (!tzname[0] || !tzname[0][0])
+ {
+ *bZone = FALSE;
+ return TRUE;
+ }
+ timediff = TIMEZONE_GLOBAL + (*zone) * 3600;
+ if (!daylight && timediff == 0) /* the same timezone */
+ return TRUE;
+ else
+ {
+ struct tm tm,
+ *tm2;
+ time_t time0;
+
+ *bZone = FALSE;
+ tm.tm_year = st->y - 1900;
+ tm.tm_mon = st->m - 1;
+ tm.tm_mday = st->d;
+ tm.tm_hour = st->hh;
+ tm.tm_min = st->mm;
+ tm.tm_sec = st->ss;
+ tm.tm_isdst = -1;
+ time0 = mktime(&tm);
+ if (time0 < 0)
+ return TRUE;
+ if (tm.tm_isdst > 0)
+ timediff -= 3600;
+ if (timediff == 0) /* the same time zone */
+ return TRUE;
+ time0 -= timediff;
+ if (time0 >= 0 && (tm2 = localtime(&time0)) != NULL)
+ {
+ st->y = tm2->tm_year + 1900;
+ st->m = tm2->tm_mon + 1;
+ st->d = tm2->tm_mday;
+ st->hh = tm2->tm_hour;
+ st->mm = tm2->tm_min;
+ st->ss = tm2->tm_sec;
+ *bZone = TRUE;
+ }
+ }
+#endif /* WIN32 */
+ return TRUE;
+}
+
+static BOOL
+stime2timestamp(const SIMPLE_TIME *st, char *str, BOOL bZone, BOOL precision)
+{
+ char precstr[16],
+ zonestr[16];
+ int i;
+
+ precstr[0] = '\0';
+ if (precision && st->fr)
+ {
+ sprintf(precstr, ".%09d", st->fr);
+ for (i = 9; i > 0; i--)
+ {
+ if (precstr[i] != '0')
+ break;
+ precstr[i] = '\0';
+ }
+ }
+ zonestr[0] = '\0';
+#if defined(WIN32) || defined(HAVE_INT_TIMEZONE)
+ if (bZone && tzname[0] && tzname[0][0] && st->y >= 1970)
+ {
+ long zoneint;
+ struct tm tm;
+ time_t time0;
+
+ zoneint = TIMEZONE_GLOBAL;
+ if (daylight && st->y >= 1900)
+ {
+ tm.tm_year = st->y - 1900;
+ tm.tm_mon = st->m - 1;
+ tm.tm_mday = st->d;
+ tm.tm_hour = st->hh;
+ tm.tm_min = st->mm;
+ tm.tm_sec = st->ss;
+ tm.tm_isdst = -1;
+ time0 = mktime(&tm);
+ if (time0 >= 0 && tm.tm_isdst > 0)
+ zoneint -= 3600;
+ }
+ if (zoneint > 0)
+ sprintf(zonestr, "-%02d", (int) zoneint / 3600);
+ else
+ sprintf(zonestr, "+%02d", -(int) zoneint / 3600);
+ }
+#endif /* WIN32 */
+ sprintf(str, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d%s%s", st->y, st->m, st->d, st->hh, st->mm, st->ss, precstr, zonestr);
+ return TRUE;
+}
+
+/* This is called by SQLFetch() */
+int
+copy_and_convert_field_bindinfo(StatementClass *stmt, Int4 field_type, void *value, int col)
+{
+ BindInfoClass *bic = &(stmt->bindings[col]);
+
+ return copy_and_convert_field(stmt, field_type, value, (Int2) bic->returntype, (PTR) bic->buffer,
+ (SDWORD) bic->buflen, (SDWORD *) bic->used);
+}
+
+
+/* This is called by SQLGetData() */
+int
+copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2 fCType,
+ PTR rgbValue, SDWORD cbValueMax, SDWORD *pcbValue)
+{
+ Int4 len = 0,
+ copy_len = 0;
+ SIMPLE_TIME st;
+ time_t t = time(NULL);
+ struct tm *tim;
+ int pcbValueOffset,
+ rgbValueOffset;
+ char *rgbValueBindRow;
+ const char *ptr;
+ int bind_row = stmt->bind_row;
+ int bind_size = stmt->options.bind_size;
+ int result = COPY_OK;
+ BOOL changed;
+ const char *neut_str = value;
+ char midtemp[2][32];
+ int mtemp_cnt = 0;
+ static BindInfoClass sbic;
+ BindInfoClass *pbic;
+
+ if (stmt->current_col >= 0)
+ {
+ pbic = &stmt->bindings[stmt->current_col];
+ if (pbic->data_left == -2)
+ pbic->data_left = (cbValueMax > 0) ? 0 : -1; /* This seems to be *
+ * needed for ADO ? */
+ if (pbic->data_left == 0)
+ {
+ if (pbic->ttlbuf != NULL)
+ {
+ free(pbic->ttlbuf);
+ pbic->ttlbuf = NULL;
+ pbic->ttlbuflen = 0;
+ }
+ pbic->data_left = -2; /* needed by ADO ? */
+ return COPY_NO_DATA_FOUND;
+ }
+ }
+ /*---------
+ * rgbValueOffset is *ONLY* for character and binary data.
+ * pcbValueOffset is for computing any pcbValue location
+ *---------
+ */
+
+ if (bind_size > 0)
+ pcbValueOffset = rgbValueOffset = (bind_size * bind_row);
+ else
+ {
+ pcbValueOffset = bind_row * sizeof(SDWORD);
+ rgbValueOffset = bind_row * cbValueMax;
+
+ }
+
+ memset(&st, 0, sizeof(SIMPLE_TIME));
+
+ /* Initialize current date */
+ tim = localtime(&t);
+ st.m = tim->tm_mon + 1;
+ st.d = tim->tm_mday;
+ st.y = tim->tm_year + 1900;
+
+ mylog("copy_and_convert: field_type = %d, fctype = %d, value = '%s', cbValueMax=%d\n", field_type, fCType, (value == NULL) ? "<NULL>" : value, cbValueMax);
+
+ if (!value)
+ {
+ /*
+ * handle a null just by returning SQL_NULL_DATA in pcbValue, and
+ * doing nothing to the buffer.
+ */
+ if (pcbValue)
+ *(SDWORD *) ((char *) pcbValue + pcbValueOffset) = SQL_NULL_DATA;
+ return COPY_OK;
+ }
+
+ if (stmt->hdbc->DataSourceToDriver != NULL)
+ {
+ int length = strlen(value);
+
+ stmt->hdbc->DataSourceToDriver(stmt->hdbc->translation_option,
+ SQL_CHAR,
+ value, length,
+ value, length, NULL,
+ NULL, 0, NULL);
+ }
+
+ /*
+ * First convert any specific postgres types into more useable data.
+ *
+ * NOTE: Conversions from PG char/varchar of a date/time/timestamp value
+ * to SQL_C_DATE,SQL_C_TIME, SQL_C_TIMESTAMP not supported
+ */
+ switch (field_type)
+ {
+ /*
+ * $$$ need to add parsing for date/time/timestamp strings in
+ * PG_TYPE_CHAR,VARCHAR $$$
+ */
+ case PG_TYPE_DATE:
+ sscanf(value, "%4d-%2d-%2d", &st.y, &st.m, &st.d);
+ break;
+
+ case PG_TYPE_TIME:
+ sscanf(value, "%2d:%2d:%2d", &st.hh, &st.mm, &st.ss);
+ break;
+
+ case PG_TYPE_ABSTIME:
+ case PG_TYPE_DATETIME:
+ case PG_TYPE_TIMESTAMP:
+ st.fr = 0;
+ if (strnicmp(value, "invalid", 7) != 0)
+ {
+ BOOL bZone = (field_type != PG_TYPE_TIMESTAMP_NO_TMZONE && PG_VERSION_GE(SC_get_conn(stmt), 7.2));
+ int zone;
+
+ /*
+ * sscanf(value, "%4d-%2d-%2d %2d:%2d:%2d", &st.y, &st.m,
+ * &st.d, &st.hh, &st.mm, &st.ss);
+ */
+ bZone = FALSE; /* time zone stuff is unreliable */
+ timestamp2stime(value, &st, &bZone, &zone);
+ }
+ else
+ {
+ /*
+ * The timestamp is invalid so set something conspicuous,
+ * like the epoch
+ */
+ t = 0;
+ tim = localtime(&t);
+ st.m = tim->tm_mon + 1;
+ st.d = tim->tm_mday;
+ st.y = tim->tm_year + 1900;
+ st.hh = tim->tm_hour;
+ st.mm = tim->tm_min;
+ st.ss = tim->tm_sec;
+ }
+ break;
+
+ case PG_TYPE_BOOL:
+ { /* change T/F to 1/0 */
+ char *s;
+
+ s = midtemp[mtemp_cnt];
+ strcpy(s, (char *) value);
+ if (s[0] == 'f' || s[0] == 'F' || s[0] == 'n' || s[0] == 'N' || s[0] == '0')
+ s[0] = '0';
+ else
+ s[0] = '1';
+ s[1] = '\0';
+ neut_str = midtemp[mtemp_cnt];
+ mtemp_cnt++;
+
+ }
+ break;
+
+ /* This is for internal use by SQLStatistics() */
+ case PG_TYPE_INT2VECTOR:
+ {
+ int nval,
+ i;
+ const char *vp;
+
+ /* this is an array of eight integers */
+ short *short_array = (short *) ((char *) rgbValue + rgbValueOffset);
+
+ len = 32;
+ vp = value;
+ nval = 0;
+ mylog("index=(");
+ for (i = 0; i < 16; i++)
+ {
+ if (sscanf(vp, "%hd", &short_array[i]) != 1)
+ break;
+
+ mylog(" %d", short_array[i]);
+ nval++;
+
+ /* skip the current token */
+ while ((*vp != '\0') && (!isspace((unsigned char) *vp)))
+ vp++;
+ /* and skip the space to the next token */
+ while ((*vp != '\0') && (isspace((unsigned char) *vp)))
+ vp++;
+ if (*vp == '\0')
+ break;
+ }
+ mylog(") nval = %d\n", nval);
+
+ for (i = nval; i < 16; i++)
+ short_array[i] = 0;
+
+#if 0
+ sscanf(value, "%hd %hd %hd %hd %hd %hd %hd %hd",
+ &short_array[0],
+ &short_array[1],
+ &short_array[2],
+ &short_array[3],
+ &short_array[4],
+ &short_array[5],
+ &short_array[6],
+ &short_array[7]);
+#endif
+
+ /* There is no corresponding fCType for this. */
+ if (pcbValue)
+ *(SDWORD *) ((char *) pcbValue + pcbValueOffset) = len;
+
+ return COPY_OK; /* dont go any further or the data will be
+ * trashed */
+ }
+
+ /*
+ * This is a large object OID, which is used to store
+ * LONGVARBINARY objects.
+ */
+ case PG_TYPE_LO:
+
+ return convert_lo(stmt, value, fCType, ((char *) rgbValue + rgbValueOffset), cbValueMax, (SDWORD *) ((char *) pcbValue + pcbValueOffset));
+
+ default:
+
+ if (field_type == stmt->hdbc->lobj_type) /* hack until permanent
+ * type available */
+ return convert_lo(stmt, value, fCType, ((char *) rgbValue + rgbValueOffset), cbValueMax, (SDWORD *) ((char *) pcbValue + pcbValueOffset));
+ }
+
+ /* Change default into something useable */
+ if (fCType == SQL_C_DEFAULT)
+ {
+ fCType = pgtype_to_ctype(stmt, field_type);
+
+ mylog("copy_and_convert, SQL_C_DEFAULT: fCType = %d\n", fCType);
+ }
+
+ rgbValueBindRow = (char *) rgbValue + rgbValueOffset;
+
+ if (fCType == SQL_C_CHAR)
+ {
+ /* Special character formatting as required */
+
+ /*
+ * These really should return error if cbValueMax is not big
+ * enough.
+ */
+ switch (field_type)
+ {
+ case PG_TYPE_DATE:
+ len = 10;
+ if (cbValueMax > len)
+ sprintf(rgbValueBindRow, "%.4d-%.2d-%.2d", st.y, st.m, st.d);
+ break;
+
+ case PG_TYPE_TIME:
+ len = 8;
+ if (cbValueMax > len)
+ sprintf(rgbValueBindRow, "%.2d:%.2d:%.2d", st.hh, st.mm, st.ss);
+ break;
+
+ case PG_TYPE_ABSTIME:
+ case PG_TYPE_DATETIME:
+ case PG_TYPE_TIMESTAMP:
+ len = 19;
+ if (cbValueMax > len)
+ sprintf(rgbValueBindRow, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d",
+ st.y, st.m, st.d, st.hh, st.mm, st.ss);
+ break;
+
+ case PG_TYPE_BOOL:
+ len = 1;
+ if (cbValueMax > len)
+ {
+ strcpy(rgbValueBindRow, neut_str);
+ mylog("PG_TYPE_BOOL: rgbValueBindRow = '%s'\n", rgbValueBindRow);
+ }
+ break;
+
+ /*
+ * Currently, data is SILENTLY TRUNCATED for BYTEA and
+ * character data types if there is not enough room in
+ * cbValueMax because the driver can't handle multiple
+ * calls to SQLGetData for these, yet. Most likely, the
+ * buffer passed in will be big enough to handle the
+ * maximum limit of postgres, anyway.
+ *
+ * LongVarBinary types are handled correctly above, observing
+ * truncation and all that stuff since there is
+ * essentially no limit on the large object used to store
+ * those.
+ */
+ case PG_TYPE_BYTEA:/* convert binary data to hex strings
+ * (i.e, 255 = "FF") */
+ len = convert_pgbinary_to_char(neut_str, rgbValueBindRow, cbValueMax);
+
+ /***** THIS IS NOT PROPERLY IMPLEMENTED *****/
+ break;
+
+ default:
+ if (stmt->current_col < 0)
+ {
+ pbic = &sbic;
+ pbic->data_left = -1;
+ }
+ else
+ pbic = &stmt->bindings[stmt->current_col];
+ if (pbic->data_left < 0)
+ {
+ /* convert linefeeds to carriage-return/linefeed */
+ len = convert_linefeeds(neut_str, NULL, 0, &changed);
+ if (cbValueMax == 0) /* just returns length
+ * info */
+ {
+ result = COPY_RESULT_TRUNCATED;
+ break;
+ }
+ if (!pbic->ttlbuf)
+ pbic->ttlbuflen = 0;
+ if (changed || len >= cbValueMax)
+ {
+ if (len >= (int) pbic->ttlbuflen)
+ {
+ pbic->ttlbuf = realloc(pbic->ttlbuf, len + 1);
+ pbic->ttlbuflen = len + 1;
+ }
+ convert_linefeeds(neut_str, pbic->ttlbuf, pbic->ttlbuflen, &changed);
+ ptr = pbic->ttlbuf;
+ }
+ else
+ {
+ if (pbic->ttlbuf)
+ {
+ free(pbic->ttlbuf);
+ pbic->ttlbuf = NULL;
+ }
+ ptr = neut_str;
+ }
+ }
+ else
+ ptr = pbic->ttlbuf;
+
+ mylog("DEFAULT: len = %d, ptr = '%s'\n", len, ptr);
+
+ if (stmt->current_col >= 0)
+ {
+ if (pbic->data_left > 0)
+ {
+ ptr += strlen(ptr) - pbic->data_left;
+ len = pbic->data_left;
+ }
+ else
+ pbic->data_left = len;
+ }
+
+ if (cbValueMax > 0)
+ {
+ copy_len = (len >= cbValueMax) ? cbValueMax - 1 : len;
+
+ /* Copy the data */
+ memcpy(rgbValueBindRow, ptr, copy_len);
+ rgbValueBindRow[copy_len] = '\0';
+
+ /* Adjust data_left for next time */
+ if (stmt->current_col >= 0)
+ pbic->data_left -= copy_len;
+ }
+
+ /*
+ * Finally, check for truncation so that proper status can
+ * be returned
+ */
+ if (cbValueMax > 0 && len >= cbValueMax)
+ result = COPY_RESULT_TRUNCATED;
+ else
+ {
+ if (pbic->ttlbuf != NULL)
+ {
+ free(pbic->ttlbuf);
+ pbic->ttlbuf = NULL;
+ }
+ }
+
+
+ mylog(" SQL_C_CHAR, default: len = %d, cbValueMax = %d, rgbValueBindRow = '%s'\n", len, cbValueMax, rgbValueBindRow);
+ break;
+ }
+
+
+ }
+ else
+ {
+ /*
+ * for SQL_C_CHAR, it's probably ok to leave currency symbols in.
+ * But to convert to numeric types, it is necessary to get rid of
+ * those.
+ */
+ if (field_type == PG_TYPE_MONEY)
+ {
+ if (convert_money(neut_str, midtemp[mtemp_cnt], sizeof(midtemp[0])))
+ {
+ neut_str = midtemp[mtemp_cnt];
+ mtemp_cnt++;
+ }
+ else
+ return COPY_UNSUPPORTED_TYPE;
+ }
+
+ switch (fCType)
+ {
+ case SQL_C_DATE:
+#if (ODBCVER >= 0x0300)
+ case SQL_C_TYPE_DATE: /* 91 */
+#endif
+ len = 6;
+ {
+ DATE_STRUCT *ds;
+
+ if (bind_size > 0)
+ ds = (DATE_STRUCT *) ((char *) rgbValue + (bind_row * bind_size));
+ else
+ ds = (DATE_STRUCT *) rgbValue + bind_row;
+ ds->year = st.y;
+ ds->month = st.m;
+ ds->day = st.d;
+ }
+ break;
+
+ case SQL_C_TIME:
+#if (ODBCVER >= 0x0300)
+ case SQL_C_TYPE_TIME: /* 92 */
+#endif
+ len = 6;
+ {
+ TIME_STRUCT *ts;
+
+ if (bind_size > 0)
+ ts = (TIME_STRUCT *) ((char *) rgbValue + (bind_row * bind_size));
+ else
+ ts = (TIME_STRUCT *) rgbValue + bind_row;
+ ts->hour = st.hh;
+ ts->minute = st.mm;
+ ts->second = st.ss;
+ }
+ break;
+
+ case SQL_C_TIMESTAMP:
+#if (ODBCVER >= 0x0300)
+ case SQL_C_TYPE_TIMESTAMP: /* 93 */
+#endif
+ len = 16;
+ {
+ TIMESTAMP_STRUCT *ts;
+
+ if (bind_size > 0)
+ ts = (TIMESTAMP_STRUCT *) ((char *) rgbValue + (bind_row * bind_size));
+ else
+ ts = (TIMESTAMP_STRUCT *) rgbValue + bind_row;
+ ts->year = st.y;
+ ts->month = st.m;
+ ts->day = st.d;
+ ts->hour = st.hh;
+ ts->minute = st.mm;
+ ts->second = st.ss;
+ ts->fraction = st.fr;
+ }
+ break;
+
+ case SQL_C_BIT:
+ len = 1;
+ if (bind_size > 0)
+ *(UCHAR *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(neut_str);
+ else
+ *((UCHAR *) rgbValue + bind_row) = atoi(neut_str);
+
+ /*
+ * mylog("SQL_C_BIT: bind_row = %d val = %d, cb = %d,
+ * rgb=%d\n", bind_row, atoi(neut_str), cbValueMax,
+ * *((UCHAR *)rgbValue));
+ */
+ break;
+
+ case SQL_C_STINYINT:
+ case SQL_C_TINYINT:
+ len = 1;
+ if (bind_size > 0)
+ *(SCHAR *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(neut_str);
+ else
+ *((SCHAR *) rgbValue + bind_row) = atoi(neut_str);
+ break;
+
+ case SQL_C_UTINYINT:
+ len = 1;
+ if (bind_size > 0)
+ *(UCHAR *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(neut_str);
+ else
+ *((UCHAR *) rgbValue + bind_row) = atoi(neut_str);
+ break;
+
+ case SQL_C_FLOAT:
+ len = 4;
+ if (bind_size > 0)
+ *(SFLOAT *) ((char *) rgbValue + (bind_row * bind_size)) = (float) atof(neut_str);
+ else
+ *((SFLOAT *) rgbValue + bind_row) = (float) atof(neut_str);
+ break;
+
+ case SQL_C_DOUBLE:
+ len = 8;
+ if (bind_size > 0)
+ *(SDOUBLE *) ((char *) rgbValue + (bind_row * bind_size)) = atof(neut_str);
+ else
+ *((SDOUBLE *) rgbValue + bind_row) = atof(neut_str);
+ break;
+
+ case SQL_C_SSHORT:
+ case SQL_C_SHORT:
+ len = 2;
+ if (bind_size > 0)
+ *(SWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(neut_str);
+ else
+ *((SWORD *) rgbValue + bind_row) = atoi(neut_str);
+ break;
+
+ case SQL_C_USHORT:
+ len = 2;
+ if (bind_size > 0)
+ *(UWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(neut_str);
+ else
+ *((UWORD *) rgbValue + bind_row) = atoi(neut_str);
+ break;
+
+ case SQL_C_SLONG:
+ case SQL_C_LONG:
+ len = 4;
+ if (bind_size > 0)
+ *(SDWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atol(neut_str);
+ else
+ *((SDWORD *) rgbValue + bind_row) = atol(neut_str);
+ break;
+
+ case SQL_C_ULONG:
+ len = 4;
+ if (bind_size > 0)
+ *(UDWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atol(neut_str);
+ else
+ *((UDWORD *) rgbValue + bind_row) = atol(neut_str);
+ break;
+
+ case SQL_C_BINARY:
+
+ /* truncate if necessary */
+ /* convert octal escapes to bytes */
+
+ if (stmt->current_col < 0)
+ {
+ pbic = &sbic;
+ pbic->data_left = -1;
+ }
+ else
+ pbic = &stmt->bindings[stmt->current_col];
+ if (!pbic->ttlbuf)
+ pbic->ttlbuflen = 0;
+ if (len = strlen(neut_str), len >= (int) pbic->ttlbuflen)
+ {
+ pbic->ttlbuf = realloc(pbic->ttlbuf, len + 1);
+ pbic->ttlbuflen = len + 1;
+ }
+ len = convert_from_pgbinary(neut_str, pbic->ttlbuf, pbic->ttlbuflen);
+ ptr = pbic->ttlbuf;
+
+ if (stmt->current_col >= 0)
+ {
+ /*
+ * Second (or more) call to SQLGetData so move the
+ * pointer
+ */
+ if (pbic->data_left > 0)
+ {
+ ptr += len - pbic->data_left;
+ len = pbic->data_left;
+ }
+
+ /* First call to SQLGetData so initialize data_left */
+ else
+ pbic->data_left = len;
+
+ }
+
+ if (cbValueMax > 0)
+ {
+ copy_len = (len > cbValueMax) ? cbValueMax : len;
+
+ /* Copy the data */
+ memcpy(rgbValueBindRow, ptr, copy_len);
+
+ /* Adjust data_left for next time */
+ if (stmt->current_col >= 0)
+ pbic->data_left -= copy_len;
+ }
+
+ /*
+ * Finally, check for truncation so that proper status can
+ * be returned
+ */
+ if (len > cbValueMax)
+ result = COPY_RESULT_TRUNCATED;
+
+ if (pbic->ttlbuf)
+ {
+ free(pbic->ttlbuf);
+ pbic->ttlbuf = NULL;
+ }
+ mylog("SQL_C_BINARY: len = %d, copy_len = %d\n", len, copy_len);
+ break;
+
+ default:
+ return COPY_UNSUPPORTED_TYPE;
+ }
+ }
+
+ /* store the length of what was copied, if there's a place for it */
+ if (pcbValue)
+ *(SDWORD *) ((char *) pcbValue + pcbValueOffset) = len;
+
+ if (result == COPY_OK && stmt->current_col >= 0)
+ stmt->bindings[stmt->current_col].data_left = 0;
+ return result;
+
+}
+
+
+/*--------------------------------------------------------------------
+ * Functions/Macros to get rid of query size limit.
+ *
+ * I always used the follwoing macros to convert from
+ * old_statement to new_statement. Please improve it
+ * if you have a better way. Hiroshi 2001/05/22
+ *--------------------------------------------------------------------
+ */
+#define INIT_MIN_ALLOC 4096
+static int
+enlarge_statement(StatementClass *stmt, unsigned int newsize)
+{
+ unsigned int newalsize = INIT_MIN_ALLOC;
+ static char *func = "enlarge_statement";
+
+ if (stmt->stmt_size_limit > 0 && stmt->stmt_size_limit < (int) newsize)
+ {
+ stmt->errormsg = "Query buffer overflow in copy_statement_with_parameters";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ SC_log_error(func, "", stmt);
+ return -1;
+ }
+ while (newalsize <= newsize)
+ newalsize *= 2;
+ if (!(stmt->stmt_with_params = realloc(stmt->stmt_with_params, newalsize)))
+ {
+ stmt->errormsg = "Query buffer allocate error in copy_statement_with_parameters";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ SC_log_error(func, "", stmt);
+ return 0;
+ }
+ return newalsize;
+}
+
+/*----------
+ * Enlarge stmt_with_params if necessary.
+ *----------
+ */
+#define ENLARGE_NEWSTATEMENT(newpos) \
+ if (newpos >= new_stsize) \
+ { \
+ if ((new_stsize = enlarge_statement(stmt, newpos)) <= 0) \
+ return SQL_ERROR; \
+ new_statement = stmt->stmt_with_params; \
+ }
+/*----------
+ * Initialize stmt_with_params, new_statement etc.
+ *----------
+ */
+#define CVT_INIT(size) \
+do { \
+ if (stmt->stmt_with_params) \
+ free(stmt->stmt_with_params); \
+ if (stmt->stmt_size_limit > 0) \
+ new_stsize = stmt->stmt_size_limit; \
+ else \
+ { \
+ new_stsize = INIT_MIN_ALLOC; \
+ while (new_stsize <= size) \
+ new_stsize *= 2; \
+ } \
+ new_statement = malloc(new_stsize); \
+ stmt->stmt_with_params = new_statement; \
+ npos = 0; \
+ new_statement[0] = '\0'; \
+} while (0)
+
+/*----------
+ * Terminate the stmt_with_params string with NULL.
+ *----------
+ */
+#define CVT_TERMINATE \
+do { \
+ new_statement[npos] = '\0'; \
+} while (0)
+
+/*----------
+ * Append a data.
+ *----------
+ */
+#define CVT_APPEND_DATA(s, len) \
+do { \
+ unsigned int newpos = npos + len; \
+ ENLARGE_NEWSTATEMENT(newpos) \
+ memcpy(&new_statement[npos], s, len); \
+ npos = newpos; \
+ new_statement[npos] = '\0'; \
+} while (0)
+
+/*----------
+ * Append a string.
+ *----------
+ */
+#define CVT_APPEND_STR(s) \
+do { \
+ unsigned int len = strlen(s); \
+ CVT_APPEND_DATA(s, len); \
+} while (0)
+
+/*----------
+ * Append a char.
+ *----------
+ */
+#define CVT_APPEND_CHAR(c) \
+do { \
+ ENLARGE_NEWSTATEMENT(npos + 1); \
+ new_statement[npos++] = c; \
+} while (0)
+
+/*----------
+ * Append a binary data.
+ * Newly reqeuired size may be overestimated currently.
+ *----------
+ */
+#define CVT_APPEND_BINARY(buf, used) \
+do { \
+ unsigned int newlimit = npos + 5 * used; \
+ ENLARGE_NEWSTATEMENT(newlimit); \
+ npos += convert_to_pgbinary(buf, &new_statement[npos], used); \
+} while (0)
+
+/*----------
+ *
+ *----------
+ */
+#define CVT_SPECIAL_CHARS(buf, used) \
+do { \
+ int cnvlen = convert_special_chars(buf, NULL, used); \
+ unsigned int newlimit = npos + cnvlen; \
+\
+ ENLARGE_NEWSTATEMENT(newlimit); \
+ convert_special_chars(buf, &new_statement[npos], used); \
+ npos += cnvlen; \
+} while (0)
+
+/*----------
+ * Check if the statement is
+ * SELECT ... INTO table FROM .....
+ * This isn't really a strict check but ...
+ *----------
+ */
+static BOOL
+into_table_from(const char *stmt)
+{
+ if (strnicmp(stmt, "into", 4))
+ return FALSE;
+ stmt += 4;
+ if (!isspace((unsigned char) *stmt))
+ return FALSE;
+ while (isspace((unsigned char) *(++stmt)));
+ switch (*stmt)
+ {
+ case '\0':
+ case ',':
+ case '\'':
+ return FALSE;
+ case '\"': /* double quoted table name ? */
+ do
+ {
+ do
+ while (*(++stmt) != '\"' && *stmt);
+ while (*stmt && *(++stmt) == '\"');
+ while (*stmt && !isspace((unsigned char) *stmt) && *stmt != '\"')
+ stmt++;
+ }
+ while (*stmt == '\"');
+ break;
+ default:
+ while (!isspace((unsigned char) *(++stmt)));
+ break;
+ }
+ if (!*stmt)
+ return FALSE;
+ while (isspace((unsigned char) *(++stmt)));
+ if (strnicmp(stmt, "from", 4))
+ return FALSE;
+ return isspace((unsigned char) stmt[4]);
+}
+
+/*----------
+ * Check if the statement is
+ * SELECT ... FOR UPDATE .....
+ * This isn't really a strict check but ...
+ *----------
+ */
+static BOOL
+table_for_update(const char *stmt, int *endpos)
+{
+ const char *wstmt = stmt;
+
+ while (isspace((unsigned char) *(++wstmt)));
+ if (!*wstmt)
+ return FALSE;
+ if (strnicmp(wstmt, "update", 6))
+ return FALSE;
+ wstmt += 6;
+ *endpos = wstmt - stmt;
+ return !wstmt[0] || isspace((unsigned char) wstmt[0]);
+}
+
+/*
+ * This function inserts parameters into an SQL statements.
+ * It will also modify a SELECT statement for use with declare/fetch cursors.
+ * This function does a dynamic memory allocation to get rid of query size limit!
+ */
+int
+copy_statement_with_parameters(StatementClass *stmt)
+{
+ static char *func = "copy_statement_with_parameters";
+ unsigned int opos,
+ npos,
+ oldstmtlen;
+ char param_string[128],
+ tmp[256],
+ cbuf[PG_NUMERIC_MAX_PRECISION * 2]; /* seems big enough to
+ * handle the data in
+ * this function */
+ int param_number;
+ Int2 param_ctype,
+ param_sqltype;
+ char *old_statement = stmt->statement,
+ oldchar;
+ char *new_statement = stmt->stmt_with_params;
+ unsigned int new_stsize = 0;
+ SIMPLE_TIME st;
+ time_t t = time(NULL);
+ struct tm *tim;
+ SDWORD used;
+ char *buffer,
+ *buf;
+ BOOL in_quote = FALSE,
+ in_dquote = FALSE,
+ in_escape = FALSE;
+ Oid lobj_oid;
+ int lobj_fd,
+ retval;
+ BOOL check_cursor_ok = FALSE; /* check cursor
+ * restriction */
+ BOOL proc_no_param = TRUE;
+ unsigned int declare_pos = 0;
+ ConnectionClass *conn = SC_get_conn(stmt);
+ ConnInfo *ci = &(conn->connInfo);
+ BOOL prepare_dummy_cursor = FALSE,
+ begin_first = FALSE;
+ char token_save[64];
+ int token_len;
+ BOOL prev_token_end;
+
+#ifdef DRIVER_CURSOR_IMPLEMENT
+ BOOL search_from_pos = FALSE;
+#endif /* DRIVER_CURSOR_IMPLEMENT */
+ if (ci->disallow_premature)
+ prepare_dummy_cursor = stmt->pre_executing;
+
+ if (!old_statement)
+ {
+ SC_log_error(func, "No statement string", stmt);
+ return SQL_ERROR;
+ }
+
+ memset(&st, 0, sizeof(SIMPLE_TIME));
+
+ /* Initialize current date */
+ tim = localtime(&t);
+ st.m = tim->tm_mon + 1;
+ st.d = tim->tm_mday;
+ st.y = tim->tm_year + 1900;
+
+#ifdef DRIVER_CURSOR_IMPLEMENT
+ if (stmt->statement_type != STMT_TYPE_SELECT)
+ {
+ stmt->options.cursor_type = SQL_CURSOR_FORWARD_ONLY;
+ stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
+ }
+ else if (stmt->options.cursor_type == SQL_CURSOR_FORWARD_ONLY)
+ stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
+ else if (stmt->options.scroll_concurrency != SQL_CONCUR_READ_ONLY)
+ {
+ if (stmt->parse_status == STMT_PARSE_NONE)
+ parse_statement(stmt);
+ if (stmt->parse_status != STMT_PARSE_COMPLETE)
+ stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
+ else if (!stmt->ti || stmt->ntab != 1)
+ stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
+ else
+ search_from_pos = TRUE;
+ }
+#endif /* DRIVER_CURSOR_IMPLEMENT */
+
+ /* If the application hasn't set a cursor name, then generate one */
+ if (stmt->cursor_name[0] == '\0')
+ sprintf(stmt->cursor_name, "SQL_CUR%p", stmt);
+ oldstmtlen = strlen(old_statement);
+ CVT_INIT(oldstmtlen);
+
+ stmt->miscinfo = 0;
+ token_len = 0;
+ prev_token_end = TRUE;
+ /* For selects, prepend a declare cursor to the statement */
+ if (stmt->statement_type == STMT_TYPE_SELECT)
+ {
+ SC_set_pre_executable(stmt);
+ if (prepare_dummy_cursor || ci->drivers.use_declarefetch)
+ {
+ if (prepare_dummy_cursor)
+ {
+ if (!CC_is_in_trans(conn) && PG_VERSION_GE(conn, 7.1))
+ {
+ strcpy(new_statement, "BEGIN;");
+ begin_first = TRUE;
+ }
+ }
+ else if (ci->drivers.use_declarefetch)
+ SC_set_fetchcursor(stmt);
+ sprintf(new_statement, "%sdeclare %s cursor for ",
+ new_statement, stmt->cursor_name);
+ npos = strlen(new_statement);
+ check_cursor_ok = TRUE;
+ declare_pos = npos;
+ }
+ }
+ param_number = -1;
+#ifdef MULTIBYTE
+ multibyte_init();
+#endif
+
+ for (opos = 0; opos < oldstmtlen; opos++)
+ {
+ oldchar = old_statement[opos];
+#ifdef MULTIBYTE
+ if (multibyte_char_check(oldchar) != 0)
+ {
+ CVT_APPEND_CHAR(oldchar);
+ continue;
+ }
+
+ /*
+ * From here we are guaranteed to handle a 1-byte character.
+ */
+#endif
+
+ if (in_escape) /* escape check */
+ {
+ in_escape = FALSE;
+ CVT_APPEND_CHAR(oldchar);
+ continue;
+ }
+ else if (in_quote || in_dquote) /* quote/double quote check */
+ {
+ if (oldchar == '\\')
+ in_escape = TRUE;
+ else if (oldchar == '\'' && in_quote)
+ in_quote = FALSE;
+ else if (oldchar == '\"' && in_dquote)
+ in_dquote = FALSE;
+ CVT_APPEND_CHAR(oldchar);
+ continue;
+ }
+
+ /*
+ * From here we are guranteed to be in neither an escape, a quote
+ * nor a double quote.
+ */
+ /* Squeeze carriage-return/linefeed pairs to linefeed only */
+ else if (oldchar == '\r' && opos + 1 < oldstmtlen &&
+ old_statement[opos + 1] == '\n')
+ continue;
+
+ /*
+ * Handle literals (date, time, timestamp) and ODBC scalar
+ * functions
+ */
+ else if (oldchar == '{')
+ {
+ char *esc;
+ char *begin = &old_statement[opos + 1];
+
+#ifdef MULTIBYTE
+ char *end = multibyte_strchr(begin, '}');
+
+#else
+ char *end = strchr(begin, '}');
+#endif
+
+ if (!end)
+ continue;
+ /* procedure calls */
+ if (stmt->statement_type == STMT_TYPE_PROCCALL)
+ {
+ int lit_call_len = 4;
+
+ while (isspace((unsigned char) old_statement[++opos]));
+ /* '=?' to accept return values exists ? */
+ if (old_statement[opos] == '?')
+ {
+ param_number++;
+ while (isspace((unsigned char) old_statement[++opos]));
+ if (old_statement[opos] != '=')
+ {
+ opos--;
+ continue;
+ }
+ while (isspace((unsigned char) old_statement[++opos]));
+ }
+ if (strnicmp(&old_statement[opos], "call", lit_call_len) ||
+ !isspace((unsigned char) old_statement[opos + lit_call_len]))
+ {
+ opos--;
+ continue;
+ }
+ opos += lit_call_len;
+ CVT_APPEND_STR("SELECT ");
+#ifdef MULTIBYTE
+ if (multibyte_strchr(&old_statement[opos], '('))
+#else
+ if (strchr(&old_statement[opos], '('))
+#endif /* MULTIBYTE */
+ proc_no_param = FALSE;
+ continue;
+ }
+ *end = '\0';
+
+ esc = convert_escape(begin);
+ if (esc)
+ CVT_APPEND_STR(esc);
+ else
+ { /* it's not a valid literal so just copy */
+ *end = '}';
+ CVT_APPEND_CHAR(oldchar);
+ continue;
+ }
+
+ opos += end - begin + 1;
+ *end = '}';
+ continue;
+ }
+ /* End of a procedure call */
+ else if (oldchar == '}' && stmt->statement_type == STMT_TYPE_PROCCALL)
+ {
+ if (proc_no_param)
+ CVT_APPEND_STR("()");
+ continue;
+ }
+
+ /*
+ * Can you have parameter markers inside of quotes? I dont think
+ * so. All the queries I've seen expect the driver to put quotes
+ * if needed.
+ */
+ else if (oldchar == '?')
+ ; /* ok */
+ else
+ {
+ if (oldchar == '\'')
+ in_quote = TRUE;
+ else if (oldchar == '\\')
+ in_escape = TRUE;
+ else if (oldchar == '\"')
+ in_dquote = TRUE;
+ else
+ {
+ if (isspace((unsigned char) oldchar))
+ {
+ if (!prev_token_end)
+ {
+ prev_token_end = TRUE;
+ token_save[token_len] = '\0';
+ if (token_len == 4)
+ {
+ if (check_cursor_ok &&
+ into_table_from(&old_statement[opos - token_len]))
+ {
+ stmt->statement_type = STMT_TYPE_CREATE;
+ SC_no_pre_executable(stmt);
+ SC_no_fetchcursor(stmt);
+ stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
+ memmove(new_statement, new_statement + declare_pos, npos - declare_pos);
+ npos -= declare_pos;
+ }
+#ifdef DRIVER_CURSOR_IMPLEMENT
+ else if (search_from_pos && /* where's from clause */
+ strnicmp(token_save, "from", 4) == 0)
+ {
+ search_from_pos = FALSE;
+ npos -= 5;
+ CVT_APPEND_STR(", CTID, OID from");
+ }
+#endif /* DRIVER_CURSOR_IMPLEMENT */
+ }
+ if (token_len == 3)
+ {
+ int endpos;
+
+ if (check_cursor_ok &&
+ strnicmp(token_save, "for", 3) == 0 &&
+ table_for_update(&old_statement[opos], &endpos))
+ {
+ SC_no_fetchcursor(stmt);
+ stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
+ if (prepare_dummy_cursor)
+ {
+ npos -= 4;
+ opos += endpos;
+ }
+ else
+ {
+ memmove(new_statement, new_statement + declare_pos, npos - declare_pos);
+ npos -= declare_pos;
+ }
+ }
+ }
+ }
+ }
+ else if (prev_token_end)
+ {
+ prev_token_end = FALSE;
+ token_save[0] = oldchar;
+ token_len = 1;
+ }
+ else if (token_len + 1 < sizeof(token_save))
+ token_save[token_len++] = oldchar;
+ }
+ CVT_APPEND_CHAR(oldchar);
+ continue;
+ }
+
+ /*
+ * Its a '?' parameter alright
+ */
+ param_number++;
+
+ if (param_number >= stmt->parameters_allocated)
+ {
+ if (stmt->pre_executing)
+ {
+ CVT_APPEND_STR("NULL");
+ stmt->inaccurate_result = TRUE;
+ continue;
+ }
+ else
+ {
+ CVT_APPEND_CHAR('?');
+ continue;
+ }
+ }
+
+ /* Assign correct buffers based on data at exec param or not */
+ if (stmt->parameters[param_number].data_at_exec)
+ {
+ used = stmt->parameters[param_number].EXEC_used ? *stmt->parameters[param_number].EXEC_used : SQL_NTS;
+ buffer = stmt->parameters[param_number].EXEC_buffer;
+ }
+ else
+ {
+
+
+ used = stmt->parameters[param_number].used ? *stmt->parameters[param_number].used : SQL_NTS;
+
+ buffer = stmt->parameters[param_number].buffer;
+ }
+
+ /* Handle NULL parameter data */
+ if (used == SQL_NULL_DATA)
+ {
+ CVT_APPEND_STR("NULL");
+ continue;
+ }
+
+ /*
+ * If no buffer, and it's not null, then what the hell is it? Just
+ * leave it alone then.
+ */
+ if (!buffer)
+ {
+ if (stmt->pre_executing)
+ {
+ CVT_APPEND_STR("NULL");
+ stmt->inaccurate_result = TRUE;
+ continue;
+ }
+ else
+ {
+ CVT_APPEND_CHAR('?');
+ continue;
+ }
+ }
+
+ param_ctype = stmt->parameters[param_number].CType;
+ param_sqltype = stmt->parameters[param_number].SQLType;
+
+ mylog("copy_statement_with_params: from(fcType)=%d, to(fSqlType)=%d\n", param_ctype, param_sqltype);
+
+ /* replace DEFAULT with something we can use */
+ if (param_ctype == SQL_C_DEFAULT)
+ param_ctype = sqltype_to_default_ctype(param_sqltype);
+
+ buf = NULL;
+ param_string[0] = '\0';
+ cbuf[0] = '\0';
+
+ /* Convert input C type to a neutral format */
+ switch (param_ctype)
+ {
+ case SQL_C_BINARY:
+ case SQL_C_CHAR:
+ buf = buffer;
+ break;
+
+ case SQL_C_DOUBLE:
+ sprintf(param_string, "%.15g",
+ *((SDOUBLE *) buffer));
+ break;
+
+ case SQL_C_FLOAT:
+ sprintf(param_string, "%.6g",
+ *((SFLOAT *) buffer));
+ break;
+
+ case SQL_C_SLONG:
+ case SQL_C_LONG:
+ sprintf(param_string, "%ld",
+ *((SDWORD *) buffer));
+ break;
+
+ case SQL_C_SSHORT:
+ case SQL_C_SHORT:
+ sprintf(param_string, "%d",
+ *((SWORD *) buffer));
+ break;
+
+ case SQL_C_STINYINT:
+ case SQL_C_TINYINT:
+ sprintf(param_string, "%d",
+ *((SCHAR *) buffer));
+ break;
+
+ case SQL_C_ULONG:
+ sprintf(param_string, "%lu",
+ *((UDWORD *) buffer));
+ break;
+
+ case SQL_C_USHORT:
+ sprintf(param_string, "%u",
+ *((UWORD *) buffer));
+ break;
+
+ case SQL_C_UTINYINT:
+ sprintf(param_string, "%u",
+ *((UCHAR *) buffer));
+ break;
+
+ case SQL_C_BIT:
+ {
+ int i = *((UCHAR *) buffer);
+
+ sprintf(param_string, "%d", i ? 1 : 0);
+ break;
+ }
+
+ case SQL_C_DATE:
+#if (ODBCVER >= 0x0300)
+ case SQL_C_TYPE_DATE: /* 91 */
+#endif
+ {
+ DATE_STRUCT *ds = (DATE_STRUCT *) buffer;
+
+ st.m = ds->month;
+ st.d = ds->day;
+ st.y = ds->year;
+
+ break;
+ }
+
+ case SQL_C_TIME:
+#if (ODBCVER >= 0x0300)
+ case SQL_C_TYPE_TIME: /* 92 */
+#endif
+ {
+ TIME_STRUCT *ts = (TIME_STRUCT *) buffer;
+
+ st.hh = ts->hour;
+ st.mm = ts->minute;
+ st.ss = ts->second;
+
+ break;
+ }
+
+ case SQL_C_TIMESTAMP:
+#if (ODBCVER >= 0x0300)
+ case SQL_C_TYPE_TIMESTAMP: /* 93 */
+#endif
+ {
+ TIMESTAMP_STRUCT *tss = (TIMESTAMP_STRUCT *) buffer;
+
+ st.m = tss->month;
+ st.d = tss->day;
+ st.y = tss->year;
+ st.hh = tss->hour;
+ st.mm = tss->minute;
+ st.ss = tss->second;
+ st.fr = tss->fraction;
+
+ mylog("m=%d,d=%d,y=%d,hh=%d,mm=%d,ss=%d\n", st.m, st.d, st.y, st.hh, st.mm, st.ss);
+
+ break;
+
+ }
+ default:
+ /* error */
+ stmt->errormsg = "Unrecognized C_parameter type in copy_statement_with_parameters";
+ stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR;
+ CVT_TERMINATE; /* just in case */
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ /*
+ * Now that the input data is in a neutral format, convert it to
+ * the desired output format (sqltype)
+ */
+
+ switch (param_sqltype)
+ {
+ case SQL_CHAR:
+ case SQL_VARCHAR:
+ case SQL_LONGVARCHAR:
+
+ CVT_APPEND_CHAR('\''); /* Open Quote */
+
+ /* it was a SQL_C_CHAR */
+ if (buf)
+ CVT_SPECIAL_CHARS(buf, used);
+
+ /* it was a numeric type */
+ else if (param_string[0] != '\0')
+ CVT_APPEND_STR(param_string);
+
+ /* it was date,time,timestamp -- use m,d,y,hh,mm,ss */
+ else
+ {
+ sprintf(tmp, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d",
+ st.y, st.m, st.d, st.hh, st.mm, st.ss);
+
+ CVT_APPEND_STR(tmp);
+ }
+
+ CVT_APPEND_CHAR('\''); /* Close Quote */
+
+ break;
+
+ case SQL_DATE:
+#if (ODBCVER >= 0x0300)
+ case SQL_TYPE_DATE: /* 91 */
+#endif
+ if (buf)
+ { /* copy char data to time */
+ my_strcpy(cbuf, sizeof(cbuf), buf, used);
+ parse_datetime(cbuf, &st);
+ }
+
+ sprintf(tmp, "'%.4d-%.2d-%.2d'", st.y, st.m, st.d);
+
+ CVT_APPEND_STR(tmp);
+ break;
+
+ case SQL_TIME:
+#if (ODBCVER >= 0x0300)
+ case SQL_TYPE_TIME: /* 92 */
+#endif
+ if (buf)
+ { /* copy char data to time */
+ my_strcpy(cbuf, sizeof(cbuf), buf, used);
+ parse_datetime(cbuf, &st);
+ }
+
+ sprintf(tmp, "'%.2d:%.2d:%.2d'", st.hh, st.mm, st.ss);
+
+ CVT_APPEND_STR(tmp);
+ break;
+
+ case SQL_TIMESTAMP:
+#if (ODBCVER >= 0x0300)
+ case SQL_TYPE_TIMESTAMP: /* 93 */
+#endif
+
+ if (buf)
+ {
+ my_strcpy(cbuf, sizeof(cbuf), buf, used);
+ parse_datetime(cbuf, &st);
+ }
+
+ /*
+ * sprintf(tmp, "'%.4d-%.2d-%.2d %.2d:%.2d:%.2d'", st.y,
+ * st.m, st.d, st.hh, st.mm, st.ss);
+ */
+ tmp[0] = '\'';
+ /* Time zone stuff is unreliable */
+ stime2timestamp(&st, tmp + 1, FALSE, PG_VERSION_GE(conn, 7.2));
+ strcat(tmp, "'");
+
+ CVT_APPEND_STR(tmp);
+
+ break;
+
+ case SQL_BINARY:
+ case SQL_VARBINARY:/* non-ascii characters should be
+ * converted to octal */
+ CVT_APPEND_CHAR('\''); /* Open Quote */
+
+ mylog("SQL_VARBINARY: about to call convert_to_pgbinary, used = %d\n", used);
+
+ CVT_APPEND_BINARY(buf, used);
+
+ CVT_APPEND_CHAR('\''); /* Close Quote */
+
+ break;
+
+ case SQL_LONGVARBINARY:
+
+ if (stmt->parameters[param_number].data_at_exec)
+ lobj_oid = stmt->parameters[param_number].lobj_oid;
+ else
+ {
+ /* begin transaction if needed */
+ if (!CC_is_in_trans(conn))
+ {
+ QResultClass *res;
+ char ok;
+
+ res = CC_send_query(conn, "BEGIN", NULL);
+ if (!res)
+ {
+ stmt->errormsg = "Could not begin (in-line) a transaction";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+ ok = QR_command_successful(res);
+ QR_Destructor(res);
+ if (!ok)
+ {
+ stmt->errormsg = "Could not begin (in-line) a transaction";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ CC_set_in_trans(conn);
+ }
+
+ /* store the oid */
+ lobj_oid = lo_creat(conn, INV_READ | INV_WRITE);
+ if (lobj_oid == 0)
+ {
+ stmt->errornumber = STMT_EXEC_ERROR;
+ stmt->errormsg = "Couldnt create (in-line) large object.";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ /* store the fd */
+ lobj_fd = lo_open(conn, lobj_oid, INV_WRITE);
+ if (lobj_fd < 0)
+ {
+ stmt->errornumber = STMT_EXEC_ERROR;
+ stmt->errormsg = "Couldnt open (in-line) large object for writing.";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ retval = lo_write(conn, lobj_fd, buffer, used);
+
+ lo_close(conn, lobj_fd);
+
+ /* commit transaction if needed */
+ if (!ci->drivers.use_declarefetch && CC_is_in_autocommit(conn))
+ {
+ QResultClass *res;
+ char ok;
+
+ res = CC_send_query(conn, "COMMIT", NULL);
+ if (!res)
+ {
+ stmt->errormsg = "Could not commit (in-line) a transaction";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+ ok = QR_command_successful(res);
+ QR_Destructor(res);
+ if (!ok)
+ {
+ stmt->errormsg = "Could not commit (in-line) a transaction";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ CC_set_no_trans(conn);
+ }
+ }
+
+ /*
+ * the oid of the large object -- just put that in for the
+ * parameter marker -- the data has already been sent to
+ * the large object
+ */
+ sprintf(param_string, "'%d'", lobj_oid);
+ CVT_APPEND_STR(param_string);
+
+ break;
+
+ /*
+ * because of no conversion operator for bool and int4,
+ * SQL_BIT
+ */
+ /* must be quoted (0 or 1 is ok to use inside the quotes) */
+
+ case SQL_REAL:
+ if (buf)
+ my_strcpy(param_string, sizeof(param_string), buf, used);
+ sprintf(tmp, "'%s'::float4", param_string);
+ CVT_APPEND_STR(tmp);
+ break;
+ case SQL_FLOAT:
+ case SQL_DOUBLE:
+ if (buf)
+ my_strcpy(param_string, sizeof(param_string), buf, used);
+ sprintf(tmp, "'%s'::float8", param_string);
+ CVT_APPEND_STR(tmp);
+ break;
+ case SQL_NUMERIC:
+ if (buf)
+ {
+ cbuf[0] = '\'';
+ my_strcpy(cbuf + 1, sizeof(cbuf) - 12, buf, used); /* 12 = 1('\'') +
+ * strlen("'::numeric")
+ * + 1('\0') */
+ strcat(cbuf, "'::numeric");
+ }
+ else
+ sprintf(cbuf, "'%s'::numeric", param_string);
+ CVT_APPEND_STR(cbuf);
+ break;
+ default: /* a numeric type or SQL_BIT */
+ if (param_sqltype == SQL_BIT)
+ CVT_APPEND_CHAR('\''); /* Open Quote */
+
+ if (buf)
+ {
+ switch (used)
+ {
+ case SQL_NULL_DATA:
+ break;
+ case SQL_NTS:
+ CVT_APPEND_STR(buf);
+ break;
+ default:
+ CVT_APPEND_DATA(buf, used);
+ }
+ }
+ else
+ CVT_APPEND_STR(param_string);
+
+ if (param_sqltype == SQL_BIT)
+ CVT_APPEND_CHAR('\''); /* Close Quote */
+
+ break;
+ }
+ } /* end, for */
+
+ /* make sure new_statement is always null-terminated */
+ CVT_TERMINATE;
+
+ if (conn->DriverToDataSource != NULL)
+ {
+ int length = strlen(new_statement);
+
+ conn->DriverToDataSource(conn->translation_option,
+ SQL_CHAR,
+ new_statement, length,
+ new_statement, length, NULL,
+ NULL, 0, NULL);
+ }
+
+#ifdef DRIVER_CURSOR_IMPLEMENT
+ if (search_from_pos)
+ stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
+#endif /* DRIVER_CURSOR_IMPLEMENT */
+ if (prepare_dummy_cursor && SC_is_pre_executable(stmt))
+ {
+ char fetchstr[128];
+
+ sprintf(fetchstr, ";fetch backward in %s;close %s;",
+ stmt->cursor_name, stmt->cursor_name);
+ if (begin_first && CC_is_in_autocommit(conn))
+ strcat(fetchstr, "COMMIT;");
+ CVT_APPEND_STR(fetchstr);
+ stmt->inaccurate_result = TRUE;
+ }
+
+ return SQL_SUCCESS;
+}
+
+
+static char *
+mapFunction(const char *func)
+{
+ int i;
+
+ for (i = 0; mapFuncs[i][0]; i++)
+ if (!stricmp(mapFuncs[i][0], func))
+ return mapFuncs[i][1];
+
+ return NULL;
+}
+
+
+/*
+ * convert_escape()
+ *
+ * This function returns a pointer to static memory!
+ */
+char *
+convert_escape(char *value)
+{
+ static char escape[1024];
+ char key[33];
+
+ /* Separate off the key, skipping leading and trailing whitespace */
+ while ((*value != '\0') && isspace((unsigned char) *value))
+ value++;
+ sscanf(value, "%32s", key);
+ while ((*value != '\0') && (!isspace((unsigned char) *value)))
+ value++;
+ while ((*value != '\0') && isspace((unsigned char) *value))
+ value++;
+
+ mylog("convert_escape: key='%s', val='%s'\n", key, value);
+
+ if ((strcmp(key, "d") == 0) ||
+ (strcmp(key, "t") == 0) ||
+ (strcmp(key, "oj") == 0) || /* {oj syntax support for 7.1
+ * servers */
+ (strcmp(key, "ts") == 0))
+ {
+ /* Literal; return the escape part as-is */
+ strncpy(escape, value, sizeof(escape) - 1);
+ }
+ else if (strcmp(key, "fn") == 0)
+ {
+ /*
+ * Function invocation Separate off the func name, skipping
+ * trailing whitespace.
+ */
+ char *funcEnd = value;
+ char svchar;
+ char *mapFunc;
+
+ while ((*funcEnd != '\0') && (*funcEnd != '(') &&
+ (!isspace((unsigned char) *funcEnd)))
+ funcEnd++;
+ svchar = *funcEnd;
+ *funcEnd = '\0';
+ sscanf(value, "%32s", key);
+ *funcEnd = svchar;
+ while ((*funcEnd != '\0') && isspace((unsigned char) *funcEnd))
+ funcEnd++;
+
+ /*
+ * We expect left parenthesis here, else return fn body as-is
+ * since it is one of those "function constants".
+ */
+ if (*funcEnd != '(')
+ {
+ strncpy(escape, value, sizeof(escape) - 1);
+ return escape;
+ }
+ mapFunc = mapFunction(key);
+
+ /*
+ * We could have mapFunction() return key if not in table... -
+ * thomas 2000-04-03
+ */
+ if (mapFunc == NULL)
+ {
+ /* If unrecognized function name, return fn body as-is */
+ strncpy(escape, value, sizeof(escape) - 1);
+ return escape;
+ }
+ /* copy mapped name and remaining input string */
+ strcpy(escape, mapFunc);
+ strncat(escape, funcEnd, sizeof(escape) - 1 - strlen(mapFunc));
+ }
+ else
+ {
+ /* Bogus key, leave untranslated */
+ return NULL;
+ }
+
+ return escape;
+}
+
+
+BOOL
+convert_money(const char *s, char *sout, size_t soutmax)
+{
+ size_t i = 0,
+ out = 0;
+
+ for (i = 0; s[i]; i++)
+ {
+ if (s[i] == '$' || s[i] == ',' || s[i] == ')')
+ ; /* skip these characters */
+ else
+ {
+ if (out + 1 >= soutmax)
+ return FALSE; /* sout is too short */
+ if (s[i] == '(')
+ sout[out++] = '-';
+ else
+ sout[out++] = s[i];
+ }
+ }
+ sout[out] = '\0';
+ return TRUE;
+}
+
+
+/*
+ * This function parses a character string for date/time info and fills in SIMPLE_TIME
+ * It does not zero out SIMPLE_TIME in case it is desired to initialize it with a value
+ */
+char
+parse_datetime(char *buf, SIMPLE_TIME *st)
+{
+ int y,
+ m,
+ d,
+ hh,
+ mm,
+ ss;
+ int nf;
+
+ y = m = d = hh = mm = ss = 0;
+
+ /* escape sequence ? */
+ if (buf[0] == '{')
+ {
+ while (*(++buf) && *buf != '\'');
+ if (!(*buf))
+ return FALSE;
+ buf++;
+ }
+ if (buf[4] == '-') /* year first */
+ nf = sscanf(buf, "%4d-%2d-%2d %2d:%2d:%2d", &y, &m, &d, &hh, &mm, &ss);
+ else
+ nf = sscanf(buf, "%2d-%2d-%4d %2d:%2d:%2d", &m, &d, &y, &hh, &mm, &ss);
+
+ if (nf == 5 || nf == 6)
+ {
+ st->y = y;
+ st->m = m;
+ st->d = d;
+ st->hh = hh;
+ st->mm = mm;
+ st->ss = ss;
+
+ return TRUE;
+ }
+
+ if (buf[4] == '-') /* year first */
+ nf = sscanf(buf, "%4d-%2d-%2d", &y, &m, &d);
+ else
+ nf = sscanf(buf, "%2d-%2d-%4d", &m, &d, &y);
+
+ if (nf == 3)
+ {
+ st->y = y;
+ st->m = m;
+ st->d = d;
+
+ return TRUE;
+ }
+
+ nf = sscanf(buf, "%2d:%2d:%2d", &hh, &mm, &ss);
+ if (nf == 2 || nf == 3)
+ {
+ st->hh = hh;
+ st->mm = mm;
+ st->ss = ss;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/* Change linefeed to carriage-return/linefeed */
+int
+convert_linefeeds(const char *si, char *dst, size_t max, BOOL *changed)
+{
+ size_t i = 0,
+ out = 0;
+
+ if (max == 0)
+ max = 0xffffffff;
+ *changed = FALSE;
+ for (i = 0; si[i] && out < max - 1; i++)
+ {
+ if (si[i] == '\n')
+ {
+ /* Only add the carriage-return if needed */
+ if (i > 0 && si[i - 1] == '\r')
+ {
+ if (dst)
+ dst[out++] = si[i];
+ else
+ out++;
+ continue;
+ }
+ *changed = TRUE;
+
+ if (dst)
+ {
+ dst[out++] = '\r';
+ dst[out++] = '\n';
+ }
+ else
+ out += 2;
+ }
+ else
+ {
+ if (dst)
+ dst[out++] = si[i];
+ else
+ out++;
+ }
+ }
+ if (dst)
+ dst[out] = '\0';
+ return out;
+}
+
+
+/*
+ * Change carriage-return/linefeed to just linefeed
+ * Plus, escape any special characters.
+ */
+int
+convert_special_chars(const char *si, char *dst, int used)
+{
+ size_t i = 0,
+ out = 0,
+ max;
+ char *p = NULL;
+
+
+ if (used == SQL_NTS)
+ max = strlen(si);
+ else
+ max = used;
+ if (dst)
+ {
+ p = dst;
+ p[0] = '\0';
+ }
+#ifdef MULTIBYTE
+ multibyte_init();
+#endif
+
+ for (i = 0; i < max; i++)
+ {
+#ifdef MULTIBYTE
+ if (multibyte_char_check(si[i]) != 0)
+ {
+ if (p)
+ p[out] = si[i];
+ out++;
+ continue;
+ }
+#endif
+ if (si[i] == '\r' && si[i + 1] == '\n')
+ continue;
+ else if (si[i] == '\'' || si[i] == '\\')
+ {
+ if (p)
+ p[out++] = '\\';
+ else
+ out++;
+ }
+ if (p)
+ p[out++] = si[i];
+ else
+ out++;
+ }
+ if (p)
+ p[out] = '\0';
+ return out;
+}
+
+
+/* !!! Need to implement this function !!! */
+int
+convert_pgbinary_to_char(const char *value, char *rgbValue, int cbValueMax)
+{
+ mylog("convert_pgbinary_to_char: value = '%s'\n", value);
+
+ strncpy_null(rgbValue, value, cbValueMax);
+ return 0;
+}
+
+
+static unsigned int
+conv_from_octal(const unsigned char *s)
+{
+ int i,
+ y = 0;
+
+ for (i = 1; i <= 3; i++)
+ y += (s[i] - 48) * (int) pow(8, 3 - i);
+
+ return y;
+
+}
+
+
+static unsigned int
+conv_from_hex(const unsigned char *s)
+{
+ int i,
+ y = 0,
+ val;
+
+ for (i = 1; i <= 2; i++)
+ {
+ if (s[i] >= 'a' && s[i] <= 'f')
+ val = s[i] - 'a' + 10;
+ else if (s[i] >= 'A' && s[i] <= 'F')
+ val = s[i] - 'A' + 10;
+ else
+ val = s[i] - '0';
+
+ y += val * (int) pow(16, 2 - i);
+ }
+
+ return y;
+}
+
+
+/* convert octal escapes to bytes */
+int
+convert_from_pgbinary(const unsigned char *value, unsigned char *rgbValue, int cbValueMax)
+{
+ size_t i,
+ ilen = strlen(value);
+ int o = 0;
+
+
+ for (i = 0; i < ilen;)
+ {
+ if (value[i] == '\\')
+ {
+ if (value[i + 1] == '\\')
+ {
+ rgbValue[o] = value[i];
+ i += 2;
+ }
+ else
+ {
+ rgbValue[o] = conv_from_octal(&value[i]);
+ i += 4;
+ }
+ }
+ else
+ rgbValue[o] = value[i++];
+ mylog("convert_from_pgbinary: i=%d, rgbValue[%d] = %d, %c\n", i, o, rgbValue[o], rgbValue[o]);
+ o++;
+ }
+
+ rgbValue[o] = '\0'; /* extra protection */
+
+ return o;
+}
+
+
+static char *
+conv_to_octal(unsigned char val)
+{
+ int i;
+ static char x[6];
+
+ x[0] = '\\';
+ x[1] = '\\';
+ x[5] = '\0';
+
+ for (i = 4; i > 1; i--)
+ {
+ x[i] = (val & 7) + 48;
+ val >>= 3;
+ }
+
+ return x;
+}
+
+
+/* convert non-ascii bytes to octal escape sequences */
+int
+convert_to_pgbinary(const unsigned char *in, char *out, int len)
+{
+ int i,
+ o = 0;
+
+ for (i = 0; i < len; i++)
+ {
+ mylog("convert_to_pgbinary: in[%d] = %d, %c\n", i, in[i], in[i]);
+ if (isalnum(in[i]) || in[i] == ' ')
+ out[o++] = in[i];
+ else
+ {
+ strcpy(&out[o], conv_to_octal(in[i]));
+ o += 5;
+ }
+ }
+
+ mylog("convert_to_pgbinary: returning %d, out='%.*s'\n", o, o, out);
+
+ return o;
+}
+
+
+void
+encode(const char *in, char *out)
+{
+ unsigned int i,
+ ilen = strlen(in),
+ o = 0;
+
+ for (i = 0; i < ilen; i++)
+ {
+ if (in[i] == '+')
+ {
+ sprintf(&out[o], "%%2B");
+ o += 3;
+ }
+ else if (isspace((unsigned char) in[i]))
+ out[o++] = '+';
+ else if (!isalnum((unsigned char) in[i]))
+ {
+ sprintf(&out[o], "%%%02x", (unsigned char) in[i]);
+ o += 3;
+ }
+ else
+ out[o++] = in[i];
+ }
+ out[o++] = '\0';
+}
+
+
+void
+decode(const char *in, char *out)
+{
+ unsigned int i,
+ ilen = strlen(in),
+ o = 0;
+
+ for (i = 0; i < ilen; i++)
+ {
+ if (in[i] == '+')
+ out[o++] = ' ';
+ else if (in[i] == '%')
+ {
+ sprintf(&out[o++], "%c", conv_from_hex(&in[i]));
+ i += 2;
+ }
+ else
+ out[o++] = in[i];
+ }
+ out[o++] = '\0';
+}
+
+static const char *hextbl = "0123456789ABCDEF";
+static int
+pg_bin2hex(UCHAR *src, UCHAR *dst, int length)
+{
+ UCHAR chr,
+ *src_wk,
+ *dst_wk;
+ BOOL backwards;
+ int i;
+
+ backwards = FALSE;
+ if (dst < src)
+ {
+ if (dst + length > src + 1)
+ return -1;
+ }
+ else if (dst < src + length)
+ backwards = TRUE;
+ if (backwards)
+ {
+ for (i = 0, src_wk = src + length - 1, dst_wk = dst + 2 * length - 1; i < length; i++, src_wk--)
+ {
+ chr = *src_wk;
+ *dst_wk-- = hextbl[chr % 16];
+ *dst_wk-- = hextbl[chr >> 4];
+ }
+ }
+ else
+ {
+ for (i = 0, src_wk = src, dst_wk = dst; i < length; i++, src_wk++)
+ {
+ chr = *src_wk;
+ *dst_wk++ = hextbl[chr >> 4];
+ *dst_wk++ = hextbl[chr % 16];
+ }
+ }
+ dst[2 * length] = '\0';
+ return length;
+}
+
+/*-------
+ * 1. get oid (from 'value')
+ * 2. open the large object
+ * 3. read from the large object (handle multiple GetData)
+ * 4. close when read less than requested? -OR-
+ * lseek/read each time
+ * handle case where application receives truncated and
+ * decides not to continue reading.
+ *
+ * CURRENTLY, ONLY LONGVARBINARY is handled, since that is the only
+ * data type currently mapped to a PG_TYPE_LO. But, if any other types
+ * are desired to map to a large object (PG_TYPE_LO), then that would
+ * need to be handled here. For example, LONGVARCHAR could possibly be
+ * mapped to PG_TYPE_LO someday, instead of PG_TYPE_TEXT as it is now.
+ *-------
+ */
+int
+convert_lo(StatementClass *stmt, const void *value, Int2 fCType, PTR rgbValue,
+ SDWORD cbValueMax, SDWORD *pcbValue)
+{
+ Oid oid;
+ int retval,
+ result,
+ left = -1;
+ BindInfoClass *bindInfo = NULL;
+ ConnectionClass *conn = SC_get_conn(stmt);
+ ConnInfo *ci = &(conn->connInfo);
+ int factor = (fCType == SQL_C_CHAR ? 2 : 1);
+
+ /* If using SQLGetData, then current_col will be set */
+ if (stmt->current_col >= 0)
+ {
+ bindInfo = &stmt->bindings[stmt->current_col];
+ left = bindInfo->data_left;
+ }
+
+ /*
+ * if this is the first call for this column, open the large object
+ * for reading
+ */
+
+ if (!bindInfo || bindInfo->data_left == -1)
+ {
+ /* begin transaction if needed */
+ if (!CC_is_in_trans(conn))
+ {
+ QResultClass *res;
+ char ok;
+
+ res = CC_send_query(conn, "BEGIN", NULL);
+ if (!res)
+ {
+ stmt->errormsg = "Could not begin (in-line) a transaction";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ return COPY_GENERAL_ERROR;
+ }
+ ok = QR_command_successful(res);
+ QR_Destructor(res);
+ if (!ok)
+ {
+ stmt->errormsg = "Could not begin (in-line) a transaction";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ return COPY_GENERAL_ERROR;
+ }
+
+ CC_set_in_trans(conn);
+ }
+
+ oid = atoi(value);
+ stmt->lobj_fd = lo_open(conn, oid, INV_READ);
+ if (stmt->lobj_fd < 0)
+ {
+ stmt->errornumber = STMT_EXEC_ERROR;
+ stmt->errormsg = "Couldnt open large object for reading.";
+ return COPY_GENERAL_ERROR;
+ }
+
+ /* Get the size */
+ retval = lo_lseek(conn, stmt->lobj_fd, 0L, SEEK_END);
+ if (retval >= 0)
+ {
+ left = lo_tell(conn, stmt->lobj_fd);
+ if (bindInfo)
+ bindInfo->data_left = left;
+
+ /* return to beginning */
+ lo_lseek(conn, stmt->lobj_fd, 0L, SEEK_SET);
+ }
+ }
+ mylog("lo data left = %d\n", left);
+
+ if (left == 0)
+ return COPY_NO_DATA_FOUND;
+
+ if (stmt->lobj_fd < 0)
+ {
+ stmt->errornumber = STMT_EXEC_ERROR;
+ stmt->errormsg = "Large object FD undefined for multiple read.";
+ return COPY_GENERAL_ERROR;
+ }
+
+ retval = lo_read(conn, stmt->lobj_fd, (char *) rgbValue, factor > 1 ? (cbValueMax - 1) / factor : cbValueMax);
+ if (retval < 0)
+ {
+ lo_close(conn, stmt->lobj_fd);
+
+ /* commit transaction if needed */
+ if (!ci->drivers.use_declarefetch && CC_is_in_autocommit(conn))
+ {
+ QResultClass *res;
+ char ok;
+
+ res = CC_send_query(conn, "COMMIT", NULL);
+ if (!res)
+ {
+ stmt->errormsg = "Could not commit (in-line) a transaction";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ return COPY_GENERAL_ERROR;
+ }
+ ok = QR_command_successful(res);
+ QR_Destructor(res);
+ if (!ok)
+ {
+ stmt->errormsg = "Could not commit (in-line) a transaction";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ return COPY_GENERAL_ERROR;
+ }
+
+ CC_set_no_trans(conn);
+ }
+
+ stmt->lobj_fd = -1;
+
+ stmt->errornumber = STMT_EXEC_ERROR;
+ stmt->errormsg = "Error reading from large object.";
+ return COPY_GENERAL_ERROR;
+ }
+
+ if (factor > 1)
+ pg_bin2hex((char *) rgbValue, (char *) rgbValue, retval);
+ if (retval < left)
+ result = COPY_RESULT_TRUNCATED;
+ else
+ result = COPY_OK;
+
+ if (pcbValue)
+ *pcbValue = left < 0 ? SQL_NO_TOTAL : left * factor;
+
+ if (bindInfo && bindInfo->data_left > 0)
+ bindInfo->data_left -= retval;
+
+ if (!bindInfo || bindInfo->data_left == 0)
+ {
+ lo_close(conn, stmt->lobj_fd);
+
+ /* commit transaction if needed */
+ if (!ci->drivers.use_declarefetch && CC_is_in_autocommit(conn))
+ {
+ QResultClass *res;
+ char ok;
+
+ res = CC_send_query(conn, "COMMIT", NULL);
+ if (!res)
+ {
+ stmt->errormsg = "Could not commit (in-line) a transaction";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ return COPY_GENERAL_ERROR;
+ }
+ ok = QR_command_successful(res);
+ QR_Destructor(res);
+ if (!ok)
+ {
+ stmt->errormsg = "Could not commit (in-line) a transaction";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ return COPY_GENERAL_ERROR;
+ }
+
+ CC_set_no_trans(conn);
+ }
+
+ stmt->lobj_fd = -1; /* prevent further reading */
+ }
+
+ return result;
+}
--- /dev/null
+/* File: convert.h
+ *
+ * Description: See "convert.c"
+ *
+ * Comments: See "notice.txt" for copyright and license information.
+ *
+ */
+
+#ifndef __CONVERT_H__
+#define __CONVERT_H__
+
+#include "psqlodbc.h"
+
+/* copy_and_convert results */
+#define COPY_OK 0
+#define COPY_UNSUPPORTED_TYPE 1
+#define COPY_UNSUPPORTED_CONVERSION 2
+#define COPY_RESULT_TRUNCATED 3
+#define COPY_GENERAL_ERROR 4
+#define COPY_NO_DATA_FOUND 5
+
+typedef struct
+{
+ int m;
+ int d;
+ int y;
+ int hh;
+ int mm;
+ int ss;
+ int fr;
+} SIMPLE_TIME;
+
+int copy_and_convert_field_bindinfo(StatementClass *stmt, Int4 field_type, void *value, int col);
+int copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2 fCType,
+ PTR rgbValue, SDWORD cbValueMax, SDWORD *pcbValue);
+
+int copy_statement_with_parameters(StatementClass *stmt);
+char *convert_escape(char *value);
+BOOL convert_money(const char *s, char *sout, size_t soutmax);
+char parse_datetime(char *buf, SIMPLE_TIME *st);
+int convert_linefeeds(const char *s, char *dst, size_t max, BOOL *changed);
+int convert_special_chars(const char *si, char *dst, int used);
+
+int convert_pgbinary_to_char(const char *value, char *rgbValue, int cbValueMax);
+int convert_from_pgbinary(const unsigned char *value, unsigned char *rgbValue, int cbValueMax);
+int convert_to_pgbinary(const unsigned char *in, char *out, int len);
+void encode(const char *in, char *out);
+void decode(const char *in, char *out);
+int convert_lo(StatementClass *stmt, const void *value, Int2 fCType, PTR rgbValue,
+ SDWORD cbValueMax, SDWORD *pcbValue);
+
+#endif
--- /dev/null
+/*-------
+ * Module: dlg_specific.c
+ *
+ * Description: This module contains any specific code for handling
+ * dialog boxes such as driver/datasource options. Both the
+ * ConfigDSN() and the SQLDriverConnect() functions use
+ * functions in this module. If you were to add a new option
+ * to any dialog box, you would most likely only have to change
+ * things in here rather than in 2 separate places as before.
+ *
+ * Classes: none
+ *
+ * API functions: none
+ *
+ * Comments: See "notice.txt" for copyright and license information.
+ *-------
+ */
+/* Multibyte support Eiji Tokuya 2001-03-15 */
+
+#include "dlg_specific.h"
+
+#include "convert.h"
+
+#ifdef MULTIBYTE
+#include "multibyte.h"
+#endif
+#include "pgapifunc.h"
+
+#ifndef BOOL
+#define BOOL int
+#endif
+#ifndef FALSE
+#define FALSE (BOOL)0
+#endif
+#ifndef TRUE
+#define TRUE (BOOL)1
+#endif
+
+extern GLOBAL_VALUES globals;
+
+#ifdef WIN32
+static int driver_optionsDraw(HWND, const ConnInfo *, int src, BOOL enable);
+static int driver_options_update(HWND hdlg, ConnInfo *ci, BOOL);
+static void updateCommons(const ConnInfo *ci);
+#endif
+
+#ifdef WIN32
+void
+SetDlgStuff(HWND hdlg, const ConnInfo *ci)
+{
+ /*
+ * If driver attribute NOT present, then set the datasource name and
+ * description
+ */
+ if (ci->driver[0] == '\0')
+ {
+ SetDlgItemText(hdlg, IDC_DSNAME, ci->dsn);
+ SetDlgItemText(hdlg, IDC_DESC, ci->desc);
+ }
+
+ SetDlgItemText(hdlg, IDC_DATABASE, ci->database);
+ SetDlgItemText(hdlg, IDC_SERVER, ci->server);
+ SetDlgItemText(hdlg, IDC_USER, ci->username);
+ SetDlgItemText(hdlg, IDC_PASSWORD, ci->password);
+ SetDlgItemText(hdlg, IDC_PORT, ci->port);
+}
+
+
+void
+GetDlgStuff(HWND hdlg, ConnInfo *ci)
+{
+ GetDlgItemText(hdlg, IDC_DESC, ci->desc, sizeof(ci->desc));
+
+ GetDlgItemText(hdlg, IDC_DATABASE, ci->database, sizeof(ci->database));
+ GetDlgItemText(hdlg, IDC_SERVER, ci->server, sizeof(ci->server));
+ GetDlgItemText(hdlg, IDC_USER, ci->username, sizeof(ci->username));
+ GetDlgItemText(hdlg, IDC_PASSWORD, ci->password, sizeof(ci->password));
+ GetDlgItemText(hdlg, IDC_PORT, ci->port, sizeof(ci->port));
+}
+
+
+static int
+driver_optionsDraw(HWND hdlg, const ConnInfo *ci, int src, BOOL enable)
+{
+ const GLOBAL_VALUES *comval;
+ static BOOL defset = FALSE;
+ static GLOBAL_VALUES defval;
+
+ switch (src)
+ {
+ case 0: /* driver common */
+ comval = &globals;
+ break;
+ case 1: /* dsn specific */
+ comval = &(ci->drivers);
+ break;
+ case 2: /* default */
+ if (!defset)
+ {
+ defval.commlog = DEFAULT_COMMLOG;
+ defval.disable_optimizer = DEFAULT_OPTIMIZER;
+ defval.ksqo = DEFAULT_KSQO;
+ defval.unique_index = DEFAULT_UNIQUEINDEX;
+ defval.onlyread = DEFAULT_READONLY;
+ defval.use_declarefetch = DEFAULT_USEDECLAREFETCH;
+
+ defval.parse = DEFAULT_PARSE;
+ defval.cancel_as_freestmt = DEFAULT_CANCELASFREESTMT;
+ defval.debug = DEFAULT_DEBUG;
+
+ /* Unknown Sizes */
+ defval.unknown_sizes = DEFAULT_UNKNOWNSIZES;
+ defval.text_as_longvarchar = DEFAULT_TEXTASLONGVARCHAR;
+ defval.unknowns_as_longvarchar = DEFAULT_UNKNOWNSASLONGVARCHAR;
+ defval.bools_as_char = DEFAULT_BOOLSASCHAR;
+ }
+ defset = TRUE;
+ comval = &defval;
+ break;
+ }
+
+ CheckDlgButton(hdlg, DRV_COMMLOG, comval->commlog);
+ CheckDlgButton(hdlg, DRV_OPTIMIZER, comval->disable_optimizer);
+ CheckDlgButton(hdlg, DRV_KSQO, comval->ksqo);
+ CheckDlgButton(hdlg, DRV_UNIQUEINDEX, comval->unique_index);
+ EnableWindow(GetDlgItem(hdlg, DRV_UNIQUEINDEX), enable);
+ CheckDlgButton(hdlg, DRV_READONLY, comval->onlyread);
+ EnableWindow(GetDlgItem(hdlg, DRV_READONLY), enable);
+ CheckDlgButton(hdlg, DRV_USEDECLAREFETCH, comval->use_declarefetch);
+
+ /* Unknown Sizes clear */
+ CheckDlgButton(hdlg, DRV_UNKNOWN_DONTKNOW, 0);
+ CheckDlgButton(hdlg, DRV_UNKNOWN_LONGEST, 0);
+ CheckDlgButton(hdlg, DRV_UNKNOWN_MAX, 0);
+ /* Unknown (Default) Data Type sizes */
+ switch (comval->unknown_sizes)
+ {
+ case UNKNOWNS_AS_DONTKNOW:
+ CheckDlgButton(hdlg, DRV_UNKNOWN_DONTKNOW, 1);
+ break;
+ case UNKNOWNS_AS_LONGEST:
+ CheckDlgButton(hdlg, DRV_UNKNOWN_LONGEST, 1);
+ break;
+ case UNKNOWNS_AS_MAX:
+ default:
+ CheckDlgButton(hdlg, DRV_UNKNOWN_MAX, 1);
+ break;
+ }
+
+ CheckDlgButton(hdlg, DRV_TEXT_LONGVARCHAR, comval->text_as_longvarchar);
+ CheckDlgButton(hdlg, DRV_UNKNOWNS_LONGVARCHAR, comval->unknowns_as_longvarchar);
+ CheckDlgButton(hdlg, DRV_BOOLS_CHAR, comval->bools_as_char);
+ CheckDlgButton(hdlg, DRV_PARSE, comval->parse);
+ CheckDlgButton(hdlg, DRV_CANCELASFREESTMT, comval->cancel_as_freestmt);
+ CheckDlgButton(hdlg, DRV_DEBUG, comval->debug);
+ SetDlgItemInt(hdlg, DRV_CACHE_SIZE, comval->fetch_max, FALSE);
+ SetDlgItemInt(hdlg, DRV_VARCHAR_SIZE, comval->max_varchar_size, FALSE);
+ SetDlgItemInt(hdlg, DRV_LONGVARCHAR_SIZE, comval->max_longvarchar_size, TRUE);
+ SetDlgItemText(hdlg, DRV_EXTRASYSTABLEPREFIXES, comval->extra_systable_prefixes);
+
+ /* Driver Connection Settings */
+ SetDlgItemText(hdlg, DRV_CONNSETTINGS, comval->conn_settings);
+ EnableWindow(GetDlgItem(hdlg, DRV_CONNSETTINGS), enable);
+ return 0;
+}
+static int
+driver_options_update(HWND hdlg, ConnInfo *ci, BOOL updateProfile)
+{
+ GLOBAL_VALUES *comval;
+
+ if (ci)
+ comval = &(ci->drivers);
+ else
+ comval = &globals;
+ comval->commlog = IsDlgButtonChecked(hdlg, DRV_COMMLOG);
+ comval->disable_optimizer = IsDlgButtonChecked(hdlg, DRV_OPTIMIZER);
+ comval->ksqo = IsDlgButtonChecked(hdlg, DRV_KSQO);
+ if (!ci)
+ {
+ comval->unique_index = IsDlgButtonChecked(hdlg, DRV_UNIQUEINDEX);
+ comval->onlyread = IsDlgButtonChecked(hdlg, DRV_READONLY);
+ }
+ comval->use_declarefetch = IsDlgButtonChecked(hdlg, DRV_USEDECLAREFETCH);
+
+ /* Unknown (Default) Data Type sizes */
+ if (IsDlgButtonChecked(hdlg, DRV_UNKNOWN_MAX))
+ comval->unknown_sizes = UNKNOWNS_AS_MAX;
+ else if (IsDlgButtonChecked(hdlg, DRV_UNKNOWN_DONTKNOW))
+ comval->unknown_sizes = UNKNOWNS_AS_DONTKNOW;
+ else if (IsDlgButtonChecked(hdlg, DRV_UNKNOWN_LONGEST))
+ comval->unknown_sizes = UNKNOWNS_AS_LONGEST;
+ else
+ comval->unknown_sizes = UNKNOWNS_AS_MAX;
+
+ comval->text_as_longvarchar = IsDlgButtonChecked(hdlg, DRV_TEXT_LONGVARCHAR);
+ comval->unknowns_as_longvarchar = IsDlgButtonChecked(hdlg, DRV_UNKNOWNS_LONGVARCHAR);
+ comval->bools_as_char = IsDlgButtonChecked(hdlg, DRV_BOOLS_CHAR);
+
+ comval->parse = IsDlgButtonChecked(hdlg, DRV_PARSE);
+
+ comval->cancel_as_freestmt = IsDlgButtonChecked(hdlg, DRV_CANCELASFREESTMT);
+ comval->debug = IsDlgButtonChecked(hdlg, DRV_DEBUG);
+
+ comval->fetch_max = GetDlgItemInt(hdlg, DRV_CACHE_SIZE, NULL, FALSE);
+ comval->max_varchar_size = GetDlgItemInt(hdlg, DRV_VARCHAR_SIZE, NULL, FALSE);
+ comval->max_longvarchar_size = GetDlgItemInt(hdlg, DRV_LONGVARCHAR_SIZE, NULL, TRUE); /* allows for
+ * SQL_NO_TOTAL */
+
+ GetDlgItemText(hdlg, DRV_EXTRASYSTABLEPREFIXES, comval->extra_systable_prefixes, sizeof(comval->extra_systable_prefixes));
+
+ /* Driver Connection Settings */
+ if (!ci)
+ GetDlgItemText(hdlg, DRV_CONNSETTINGS, comval->conn_settings, sizeof(comval->conn_settings));
+
+ if (updateProfile)
+ updateCommons(ci);
+
+ /* fall through */
+ return 0;
+}
+
+int CALLBACK
+driver_optionsProc(HWND hdlg,
+ WORD wMsg,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ ConnInfo *ci;
+
+ switch (wMsg)
+ {
+ case WM_INITDIALOG:
+ SetWindowLong(hdlg, DWL_USER, lParam); /* save for OK etc */
+ ci = (ConnInfo *) lParam;
+ CheckDlgButton(hdlg, DRV_OR_DSN, 0);
+ if (ci && ci->dsn && ci->dsn[0])
+ SetWindowText(hdlg, "Advanced Options (per DSN)");
+ else
+ {
+ SetWindowText(hdlg, "Advanced Options (Connection)");
+ ShowWindow(GetDlgItem(hdlg, DRV_OR_DSN), SW_HIDE);
+ }
+ driver_optionsDraw(hdlg, ci, 1, FALSE);
+ break;
+
+ case WM_COMMAND:
+ switch (GET_WM_COMMAND_ID(wParam, lParam))
+ {
+ case IDOK:
+ ci = (ConnInfo *) GetWindowLong(hdlg, DWL_USER);
+ driver_options_update(hdlg, IsDlgButtonChecked(hdlg, DRV_OR_DSN) ? NULL : ci,
+ ci && ci->dsn && ci->dsn[0]);
+
+ case IDCANCEL:
+ EndDialog(hdlg, GET_WM_COMMAND_ID(wParam, lParam) == IDOK);
+ return TRUE;
+
+ case IDDEFAULTS:
+ if (IsDlgButtonChecked(hdlg, DRV_OR_DSN))
+ driver_optionsDraw(hdlg, NULL, 2, TRUE);
+ else
+ {
+ ConnInfo *ci = (ConnInfo *) GetWindowLong(hdlg, DWL_USER);
+
+ driver_optionsDraw(hdlg, ci, 0, FALSE);
+ }
+ break;
+
+ case DRV_OR_DSN:
+ if (GET_WM_COMMAND_CMD(wParam, lParam) == BN_CLICKED)
+ {
+ mylog("DRV_OR_DSN clicked\n");
+ if (IsDlgButtonChecked(hdlg, DRV_OR_DSN))
+ {
+ SetWindowText(hdlg, "Advanced Options (Common)");
+ driver_optionsDraw(hdlg, NULL, 0, TRUE);
+ }
+ else
+ {
+ ConnInfo *ci = (ConnInfo *) GetWindowLong(hdlg, DWL_USER);
+
+ SetWindowText(hdlg, "Advanced Options (per DSN)");
+ driver_optionsDraw(hdlg, ci, ci ? 1 : 0, ci == NULL);
+ }
+ }
+ break;
+ }
+ }
+
+ return FALSE;
+}
+
+
+int CALLBACK
+ds_optionsProc(HWND hdlg,
+ WORD wMsg,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ ConnInfo *ci;
+ char buf[128];
+
+ switch (wMsg)
+ {
+ case WM_INITDIALOG:
+ ci = (ConnInfo *) lParam;
+ SetWindowLong(hdlg, DWL_USER, lParam); /* save for OK */
+
+ /* Change window caption */
+ if (ci->driver[0])
+ SetWindowText(hdlg, "Advanced Options (Connection)");
+ else
+ {
+ sprintf(buf, "Advanced Options (%s)", ci->dsn);
+ SetWindowText(hdlg, buf);
+ }
+
+ /* Readonly */
+ CheckDlgButton(hdlg, DS_READONLY, atoi(ci->onlyread));
+
+ /* Protocol */
+ if (strncmp(ci->protocol, PG62, strlen(PG62)) == 0)
+ CheckDlgButton(hdlg, DS_PG62, 1);
+ else if (strncmp(ci->protocol, PG63, strlen(PG63)) == 0)
+ CheckDlgButton(hdlg, DS_PG63, 1);
+ else
+ /* latest */
+ CheckDlgButton(hdlg, DS_PG64, 1);
+
+ CheckDlgButton(hdlg, DS_SHOWOIDCOLUMN, atoi(ci->show_oid_column));
+ CheckDlgButton(hdlg, DS_FAKEOIDINDEX, atoi(ci->fake_oid_index));
+ CheckDlgButton(hdlg, DS_ROWVERSIONING, atoi(ci->row_versioning));
+ CheckDlgButton(hdlg, DS_SHOWSYSTEMTABLES, atoi(ci->show_system_tables));
+ CheckDlgButton(hdlg, DS_DISALLOWPREMATURE, ci->disallow_premature);
+
+ EnableWindow(GetDlgItem(hdlg, DS_FAKEOIDINDEX), atoi(ci->show_oid_column));
+
+ /* Datasource Connection Settings */
+ SetDlgItemText(hdlg, DS_CONNSETTINGS, ci->conn_settings);
+ break;
+
+ case WM_COMMAND:
+ switch (GET_WM_COMMAND_ID(wParam, lParam))
+ {
+ case DS_SHOWOIDCOLUMN:
+ mylog("WM_COMMAND: DS_SHOWOIDCOLUMN\n");
+ EnableWindow(GetDlgItem(hdlg, DS_FAKEOIDINDEX), IsDlgButtonChecked(hdlg, DS_SHOWOIDCOLUMN));
+ return TRUE;
+
+ case IDOK:
+ ci = (ConnInfo *) GetWindowLong(hdlg, DWL_USER);
+ mylog("IDOK: got ci = %u\n", ci);
+
+ /* Readonly */
+ sprintf(ci->onlyread, "%d", IsDlgButtonChecked(hdlg, DS_READONLY));
+
+ /* Protocol */
+ if (IsDlgButtonChecked(hdlg, DS_PG62))
+ strcpy(ci->protocol, PG62);
+ else if (IsDlgButtonChecked(hdlg, DS_PG63))
+ strcpy(ci->protocol, PG63);
+ else
+ /* latest */
+ strcpy(ci->protocol, PG64);
+
+ sprintf(ci->show_system_tables, "%d", IsDlgButtonChecked(hdlg, DS_SHOWSYSTEMTABLES));
+
+ sprintf(ci->row_versioning, "%d", IsDlgButtonChecked(hdlg, DS_ROWVERSIONING));
+ ci->disallow_premature = IsDlgButtonChecked(hdlg, DS_DISALLOWPREMATURE);
+
+ /* OID Options */
+ sprintf(ci->fake_oid_index, "%d", IsDlgButtonChecked(hdlg, DS_FAKEOIDINDEX));
+ sprintf(ci->show_oid_column, "%d", IsDlgButtonChecked(hdlg, DS_SHOWOIDCOLUMN));
+
+ /* Datasource Connection Settings */
+ GetDlgItemText(hdlg, DS_CONNSETTINGS, ci->conn_settings, sizeof(ci->conn_settings));
+
+ /* fall through */
+
+ case IDCANCEL:
+ EndDialog(hdlg, GET_WM_COMMAND_ID(wParam, lParam) == IDOK);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/*
+ * This function writes any global parameters (that can be manipulated)
+ * to the ODBCINST.INI portion of the registry
+ */
+static void
+updateCommons(const ConnInfo *ci)
+{
+ const char *sectionName;
+ const char *fileName;
+ const GLOBAL_VALUES *comval;
+ char tmp[128];
+
+ if (ci)
+ if (ci->dsn && ci->dsn[0])
+ {
+ mylog("DSN=%s updating\n", ci->dsn);
+ comval = &(ci->drivers);
+ sectionName = ci->dsn;
+ fileName = ODBC_INI;
+ }
+ else
+ {
+ mylog("ci but dsn==NULL\n");
+ return;
+ }
+ else
+ {
+ mylog("drivers updating\n");
+ comval = &globals;
+ sectionName = DBMS_NAME;
+ fileName = ODBCINST_INI;
+ }
+ sprintf(tmp, "%d", comval->fetch_max);
+ SQLWritePrivateProfileString(sectionName,
+ INI_FETCH, tmp, fileName);
+
+ sprintf(tmp, "%d", comval->commlog);
+ SQLWritePrivateProfileString(sectionName,
+ INI_COMMLOG, tmp, fileName);
+
+ sprintf(tmp, "%d", comval->debug);
+ SQLWritePrivateProfileString(sectionName,
+ INI_DEBUG, tmp, fileName);
+
+ sprintf(tmp, "%d", comval->disable_optimizer);
+ SQLWritePrivateProfileString(sectionName,
+ INI_OPTIMIZER, tmp, fileName);
+
+ sprintf(tmp, "%d", comval->ksqo);
+ SQLWritePrivateProfileString(sectionName,
+ INI_KSQO, tmp, fileName);
+
+ /*
+ * Never update the onlyread, unique_index from this module.
+ */
+ if (!ci)
+ {
+ sprintf(tmp, "%d", comval->unique_index);
+ SQLWritePrivateProfileString(sectionName, INI_UNIQUEINDEX, tmp,
+ fileName);
+
+ sprintf(tmp, "%d", comval->onlyread);
+ SQLWritePrivateProfileString(sectionName, INI_READONLY, tmp,
+ fileName);
+ }
+
+ sprintf(tmp, "%d", comval->use_declarefetch);
+ SQLWritePrivateProfileString(sectionName,
+ INI_USEDECLAREFETCH, tmp, fileName);
+
+ sprintf(tmp, "%d", comval->unknown_sizes);
+ SQLWritePrivateProfileString(sectionName,
+ INI_UNKNOWNSIZES, tmp, fileName);
+
+ sprintf(tmp, "%d", comval->text_as_longvarchar);
+ SQLWritePrivateProfileString(sectionName,
+ INI_TEXTASLONGVARCHAR, tmp, fileName);
+
+ sprintf(tmp, "%d", comval->unknowns_as_longvarchar);
+ SQLWritePrivateProfileString(sectionName,
+ INI_UNKNOWNSASLONGVARCHAR, tmp, fileName);
+
+ sprintf(tmp, "%d", comval->bools_as_char);
+ SQLWritePrivateProfileString(sectionName,
+ INI_BOOLSASCHAR, tmp, fileName);
+
+ sprintf(tmp, "%d", comval->parse);
+ SQLWritePrivateProfileString(sectionName,
+ INI_PARSE, tmp, fileName);
+
+ sprintf(tmp, "%d", comval->cancel_as_freestmt);
+ SQLWritePrivateProfileString(sectionName,
+ INI_CANCELASFREESTMT, tmp, fileName);
+
+ sprintf(tmp, "%d", comval->max_varchar_size);
+ SQLWritePrivateProfileString(sectionName,
+ INI_MAXVARCHARSIZE, tmp, fileName);
+
+ sprintf(tmp, "%d", comval->max_longvarchar_size);
+ SQLWritePrivateProfileString(sectionName,
+ INI_MAXLONGVARCHARSIZE, tmp, fileName);
+
+ SQLWritePrivateProfileString(sectionName,
+ INI_EXTRASYSTABLEPREFIXES, comval->extra_systable_prefixes, fileName);
+
+ /*
+ * Never update the conn_setting from this module
+ * SQLWritePrivateProfileString(sectionName, INI_CONNSETTINGS,
+ * comval->conn_settings, fileName);
+ */
+}
+#endif /* WIN32 */
+
+
+void
+makeConnectString(char *connect_string, const ConnInfo *ci, UWORD len)
+{
+ char got_dsn = (ci->dsn[0] != '\0');
+ char encoded_conn_settings[LARGE_REGISTRY_LEN];
+ UWORD hlen;
+ /*BOOL abbrev = (len <= 400);*/
+ BOOL abbrev = (len < 1024);
+
+ /* fundamental info */
+ sprintf(connect_string, "%s=%s;DATABASE=%s;SERVER=%s;PORT=%s;UID=%s;PWD=%s",
+ got_dsn ? "DSN" : "DRIVER",
+ got_dsn ? ci->dsn : ci->driver,
+ ci->database,
+ ci->server,
+ ci->port,
+ ci->username,
+ ci->password);
+
+ encode(ci->conn_settings, encoded_conn_settings);
+
+ /* extra info */
+ hlen = strlen(connect_string);
+ if (!abbrev)
+ sprintf(&connect_string[hlen],
+ ";READONLY=%s;PROTOCOL=%s;FAKEOIDINDEX=%s;SHOWOIDCOLUMN=%s;ROWVERSIONING=%s;SHOWSYSTEMTABLES=%s;CONNSETTINGS=%s;FETCH=%d;SOCKET=%d;UNKNOWNSIZES=%d;MAXVARCHARSIZE=%d;MAXLONGVARCHARSIZE=%d;DEBUG=%d;COMMLOG=%d;OPTIMIZER=%d;KSQO=%d;USEDECLAREFETCH=%d;TEXTASLONGVARCHAR=%d;UNKNOWNSASLONGVARCHAR=%d;BOOLSASCHAR=%d;PARSE=%d;CANCELASFREESTMT=%d;EXTRASYSTABLEPREFIXES=%s",
+ ci->onlyread,
+ ci->protocol,
+ ci->fake_oid_index,
+ ci->show_oid_column,
+ ci->row_versioning,
+ ci->show_system_tables,
+ encoded_conn_settings,
+ ci->drivers.fetch_max,
+ ci->drivers.socket_buffersize,
+ ci->drivers.unknown_sizes,
+ ci->drivers.max_varchar_size,
+ ci->drivers.max_longvarchar_size,
+ ci->drivers.debug,
+ ci->drivers.commlog,
+ ci->drivers.disable_optimizer,
+ ci->drivers.ksqo,
+ ci->drivers.use_declarefetch,
+ ci->drivers.text_as_longvarchar,
+ ci->drivers.unknowns_as_longvarchar,
+ ci->drivers.bools_as_char,
+ ci->drivers.parse,
+ ci->drivers.cancel_as_freestmt,
+ ci->drivers.extra_systable_prefixes);
+ /* Abbrebiation is needed ? */
+ if (abbrev || strlen(connect_string) >= len)
+ sprintf(&connect_string[hlen],
+ ";A0=%s;A1=%s;A2=%s;A3=%s;A4=%s;A5=%s;A6=%s;A7=%d;A8=%d;A9=%d;B0=%d;B1=%d;B2=%d;B3=%d;B4=%d;B5=%d;B6=%d;B7=%d;B8=%d;B9=%d;C0=%d;C1=%d;C2=%s",
+ ci->onlyread,
+ ci->protocol,
+ ci->fake_oid_index,
+ ci->show_oid_column,
+ ci->row_versioning,
+ ci->show_system_tables,
+ encoded_conn_settings,
+ ci->drivers.fetch_max,
+ ci->drivers.socket_buffersize,
+ ci->drivers.unknown_sizes,
+ ci->drivers.max_varchar_size,
+ ci->drivers.max_longvarchar_size,
+ ci->drivers.debug,
+ ci->drivers.commlog,
+ ci->drivers.disable_optimizer,
+ ci->drivers.ksqo,
+ ci->drivers.use_declarefetch,
+ ci->drivers.text_as_longvarchar,
+ ci->drivers.unknowns_as_longvarchar,
+ ci->drivers.bools_as_char,
+ ci->drivers.parse,
+ ci->drivers.cancel_as_freestmt,
+ ci->drivers.extra_systable_prefixes);
+}
+
+
+void
+copyAttributes(ConnInfo *ci, const char *attribute, const char *value)
+{
+ if (stricmp(attribute, "DSN") == 0)
+ strcpy(ci->dsn, value);
+
+ else if (stricmp(attribute, "driver") == 0)
+ strcpy(ci->driver, value);
+
+ else if (stricmp(attribute, INI_DATABASE) == 0)
+ strcpy(ci->database, value);
+
+ else if (stricmp(attribute, INI_SERVER) == 0 || stricmp(attribute, "server") == 0)
+ strcpy(ci->server, value);
+
+ else if (stricmp(attribute, INI_USER) == 0 || stricmp(attribute, "uid") == 0)
+ strcpy(ci->username, value);
+
+ else if (stricmp(attribute, INI_PASSWORD) == 0 || stricmp(attribute, "pwd") == 0)
+ strcpy(ci->password, value);
+
+ else if (stricmp(attribute, INI_PORT) == 0)
+ strcpy(ci->port, value);
+
+ else if (stricmp(attribute, INI_READONLY) == 0 || stricmp(attribute, "A0") == 0)
+ strcpy(ci->onlyread, value);
+
+ else if (stricmp(attribute, INI_PROTOCOL) == 0 || stricmp(attribute, "A1") == 0)
+ strcpy(ci->protocol, value);
+
+ else if (stricmp(attribute, INI_SHOWOIDCOLUMN) == 0 || stricmp(attribute, "A3") == 0)
+ strcpy(ci->show_oid_column, value);
+
+ else if (stricmp(attribute, INI_FAKEOIDINDEX) == 0 || stricmp(attribute, "A2") == 0)
+ strcpy(ci->fake_oid_index, value);
+
+ else if (stricmp(attribute, INI_ROWVERSIONING) == 0 || stricmp(attribute, "A4") == 0)
+ strcpy(ci->row_versioning, value);
+
+ else if (stricmp(attribute, INI_SHOWSYSTEMTABLES) == 0 || stricmp(attribute, "A5") == 0)
+ strcpy(ci->show_system_tables, value);
+
+ else if (stricmp(attribute, INI_CONNSETTINGS) == 0 || stricmp(attribute, "A6") == 0)
+ {
+ decode(value, ci->conn_settings);
+ /* strcpy(ci->conn_settings, value); */
+ }
+ else if (stricmp(attribute, INI_DISALLOWPREMATURE) == 0 || stricmp(attribute, "C3") == 0)
+ ci->disallow_premature = atoi(value);
+ else if (stricmp(attribute, INI_UPDATABLECURSORS) == 0 || stricmp(attribute, "C4") == 0)
+ ci->updatable_cursors = atoi(value);
+
+ mylog("copyAttributes: DSN='%s',server='%s',dbase='%s',user='%s',passwd='%s',port='%s',onlyread='%s',protocol='%s',conn_settings='%s',disallow_premature=%d)\n", ci->dsn, ci->server, ci->database, ci->username, ci->password, ci->port, ci->onlyread, ci->protocol, ci->conn_settings, ci->disallow_premature);
+}
+
+void
+copyCommonAttributes(ConnInfo *ci, const char *attribute, const char *value)
+{
+ if (stricmp(attribute, INI_FETCH) == 0 || stricmp(attribute, "A7") == 0)
+ ci->drivers.fetch_max = atoi(value);
+ else if (stricmp(attribute, INI_SOCKET) == 0 || stricmp(attribute, "A8") == 0)
+ ci->drivers.socket_buffersize = atoi(value);
+ else if (stricmp(attribute, INI_DEBUG) == 0 || stricmp(attribute, "B2") == 0)
+ ci->drivers.debug = atoi(value);
+ else if (stricmp(attribute, INI_COMMLOG) == 0 || stricmp(attribute, "B3") == 0)
+ ci->drivers.commlog = atoi(value);
+ else if (stricmp(attribute, INI_OPTIMIZER) == 0 || stricmp(attribute, "B4") == 0)
+ ci->drivers.disable_optimizer = atoi(value);
+ else if (stricmp(attribute, INI_KSQO) == 0 || stricmp(attribute, "B5") == 0)
+ ci->drivers.ksqo = atoi(value);
+
+ /*
+ * else if (stricmp(attribute, INI_UNIQUEINDEX) == 0 ||
+ * stricmp(attribute, "UIX") == 0) ci->drivers.unique_index =
+ * atoi(value);
+ */
+ else if (stricmp(attribute, INI_UNKNOWNSIZES) == 0 || stricmp(attribute, "A9") == 0)
+ ci->drivers.unknown_sizes = atoi(value);
+ else if (stricmp(attribute, INI_LIE) == 0)
+ ci->drivers.lie = atoi(value);
+ else if (stricmp(attribute, INI_PARSE) == 0 || stricmp(attribute, "C0") == 0)
+ ci->drivers.parse = atoi(value);
+ else if (stricmp(attribute, INI_CANCELASFREESTMT) == 0 || stricmp(attribute, "C1") == 0)
+ ci->drivers.cancel_as_freestmt = atoi(value);
+ else if (stricmp(attribute, INI_USEDECLAREFETCH) == 0 || stricmp(attribute, "B6") == 0)
+ ci->drivers.use_declarefetch = atoi(value);
+ else if (stricmp(attribute, INI_MAXVARCHARSIZE) == 0 || stricmp(attribute, "B0") == 0)
+ ci->drivers.max_varchar_size = atoi(value);
+ else if (stricmp(attribute, INI_MAXLONGVARCHARSIZE) == 0 || stricmp(attribute, "B1") == 0)
+ ci->drivers.max_longvarchar_size = atoi(value);
+ else if (stricmp(attribute, INI_TEXTASLONGVARCHAR) == 0 || stricmp(attribute, "B7") == 0)
+ ci->drivers.text_as_longvarchar = atoi(value);
+ else if (stricmp(attribute, INI_UNKNOWNSASLONGVARCHAR) == 0 || stricmp(attribute, "B8") == 0)
+ ci->drivers.unknowns_as_longvarchar = atoi(value);
+ else if (stricmp(attribute, INI_BOOLSASCHAR) == 0 || stricmp(attribute, "B9") == 0)
+ ci->drivers.bools_as_char = atoi(value);
+ else if (stricmp(attribute, INI_EXTRASYSTABLEPREFIXES) == 0 || stricmp(attribute, "C2") == 0)
+ strcpy(ci->drivers.extra_systable_prefixes, value);
+ mylog("CopyCommonAttributes: A7=%d;A8=%d;A9=%d;B0=%d;B1=%d;B2=%d;B3=%d;B4=%d;B5=%d;B6=%d;B7=%d;B8=%d;B9=%d;C0=%d;C1=%d;C2=%s",
+ ci->drivers.fetch_max,
+ ci->drivers.socket_buffersize,
+ ci->drivers.unknown_sizes,
+ ci->drivers.max_varchar_size,
+ ci->drivers.max_longvarchar_size,
+ ci->drivers.debug,
+ ci->drivers.commlog,
+ ci->drivers.disable_optimizer,
+ ci->drivers.ksqo,
+ ci->drivers.use_declarefetch,
+ ci->drivers.text_as_longvarchar,
+ ci->drivers.unknowns_as_longvarchar,
+ ci->drivers.bools_as_char,
+ ci->drivers.parse,
+ ci->drivers.cancel_as_freestmt,
+ ci->drivers.extra_systable_prefixes);
+}
+
+
+void
+getDSNdefaults(ConnInfo *ci)
+{
+ if (ci->port[0] == '\0')
+ strcpy(ci->port, DEFAULT_PORT);
+
+ if (ci->onlyread[0] == '\0')
+ sprintf(ci->onlyread, "%d", globals.onlyread);
+
+ if (ci->protocol[0] == '\0')
+ strcpy(ci->protocol, globals.protocol);
+
+ if (ci->fake_oid_index[0] == '\0')
+ sprintf(ci->fake_oid_index, "%d", DEFAULT_FAKEOIDINDEX);
+
+ if (ci->show_oid_column[0] == '\0')
+ sprintf(ci->show_oid_column, "%d", DEFAULT_SHOWOIDCOLUMN);
+
+ if (ci->show_system_tables[0] == '\0')
+ sprintf(ci->show_system_tables, "%d", DEFAULT_SHOWSYSTEMTABLES);
+
+ if (ci->row_versioning[0] == '\0')
+ sprintf(ci->row_versioning, "%d", DEFAULT_ROWVERSIONING);
+}
+
+
+void
+getDSNinfo(ConnInfo *ci, char overwrite)
+{
+ char *DSN = ci->dsn;
+ char encoded_conn_settings[LARGE_REGISTRY_LEN],
+ temp[SMALL_REGISTRY_LEN];
+
+/*
+ * If a driver keyword was present, then dont use a DSN and return.
+ * If DSN is null and no driver, then use the default datasource.
+ */
+ memcpy(&ci->drivers, &globals, sizeof(globals));
+ if (DSN[0] == '\0')
+ {
+ if (ci->driver[0] != '\0')
+ return;
+ else
+ strcpy(DSN, INI_DSN);
+ }
+
+ /* brute-force chop off trailing blanks... */
+ while (*(DSN + strlen(DSN) - 1) == ' ')
+ *(DSN + strlen(DSN) - 1) = '\0';
+
+ /* Proceed with getting info for the given DSN. */
+
+ if (ci->desc[0] == '\0' || overwrite)
+ SQLGetPrivateProfileString(DSN, INI_KDESC, "", ci->desc, sizeof(ci->desc), ODBC_INI);
+
+ if (ci->server[0] == '\0' || overwrite)
+ SQLGetPrivateProfileString(DSN, INI_SERVER, "", ci->server, sizeof(ci->server), ODBC_INI);
+
+ if (ci->database[0] == '\0' || overwrite)
+ SQLGetPrivateProfileString(DSN, INI_DATABASE, "", ci->database, sizeof(ci->database), ODBC_INI);
+
+ if (ci->username[0] == '\0' || overwrite)
+ SQLGetPrivateProfileString(DSN, INI_USER, "", ci->username, sizeof(ci->username), ODBC_INI);
+
+ if (ci->password[0] == '\0' || overwrite)
+ SQLGetPrivateProfileString(DSN, INI_PASSWORD, "", ci->password, sizeof(ci->password), ODBC_INI);
+
+ if (ci->port[0] == '\0' || overwrite)
+ SQLGetPrivateProfileString(DSN, INI_PORT, "", ci->port, sizeof(ci->port), ODBC_INI);
+
+ if (ci->onlyread[0] == '\0' || overwrite)
+ SQLGetPrivateProfileString(DSN, INI_READONLY, "", ci->onlyread, sizeof(ci->onlyread), ODBC_INI);
+
+ if (ci->show_oid_column[0] == '\0' || overwrite)
+ SQLGetPrivateProfileString(DSN, INI_SHOWOIDCOLUMN, "", ci->show_oid_column, sizeof(ci->show_oid_column), ODBC_INI);
+
+ if (ci->fake_oid_index[0] == '\0' || overwrite)
+ SQLGetPrivateProfileString(DSN, INI_FAKEOIDINDEX, "", ci->fake_oid_index, sizeof(ci->fake_oid_index), ODBC_INI);
+
+ if (ci->row_versioning[0] == '\0' || overwrite)
+ SQLGetPrivateProfileString(DSN, INI_ROWVERSIONING, "", ci->row_versioning, sizeof(ci->row_versioning), ODBC_INI);
+
+ if (ci->show_system_tables[0] == '\0' || overwrite)
+ SQLGetPrivateProfileString(DSN, INI_SHOWSYSTEMTABLES, "", ci->show_system_tables, sizeof(ci->show_system_tables), ODBC_INI);
+
+ if (ci->protocol[0] == '\0' || overwrite)
+ SQLGetPrivateProfileString(DSN, INI_PROTOCOL, "", ci->protocol, sizeof(ci->protocol), ODBC_INI);
+
+ if (ci->conn_settings[0] == '\0' || overwrite)
+ {
+ SQLGetPrivateProfileString(DSN, INI_CONNSETTINGS, "", encoded_conn_settings, sizeof(encoded_conn_settings), ODBC_INI);
+ decode(encoded_conn_settings, ci->conn_settings);
+ }
+
+ if (ci->translation_dll[0] == '\0' || overwrite)
+ SQLGetPrivateProfileString(DSN, INI_TRANSLATIONDLL, "", ci->translation_dll, sizeof(ci->translation_dll), ODBC_INI);
+
+ if (ci->translation_option[0] == '\0' || overwrite)
+ SQLGetPrivateProfileString(DSN, INI_TRANSLATIONOPTION, "", ci->translation_option, sizeof(ci->translation_option), ODBC_INI);
+
+ if (ci->disallow_premature == 0 || overwrite)
+ {
+ SQLGetPrivateProfileString(DSN, INI_DISALLOWPREMATURE, "", temp, sizeof(temp), ODBC_INI);
+ ci->disallow_premature = atoi(temp);
+ }
+
+ if (ci->updatable_cursors == 0 || overwrite)
+ {
+ SQLGetPrivateProfileString(DSN, INI_UPDATABLECURSORS, "", temp, sizeof(temp), ODBC_INI);
+ ci->updatable_cursors = atoi(temp);
+ }
+
+ /* Allow override of odbcinst.ini parameters here */
+ getCommonDefaults(DSN, ODBC_INI, ci);
+
+ qlog("DSN info: DSN='%s',server='%s',port='%s',dbase='%s',user='%s',passwd='%s'\n",
+ DSN,
+ ci->server,
+ ci->port,
+ ci->database,
+ ci->username,
+ ci->password);
+ qlog(" onlyread='%s',protocol='%s',showoid='%s',fakeoidindex='%s',showsystable='%s'\n",
+ ci->onlyread,
+ ci->protocol,
+ ci->show_oid_column,
+ ci->fake_oid_index,
+ ci->show_system_tables);
+
+#ifdef MULTIBYTE
+ check_client_encoding(ci->conn_settings);
+ qlog(" conn_settings='%s',conn_encoding='%s'\n",
+ ci->conn_settings,
+ check_client_encoding(ci->conn_settings));
+#else
+ qlog(" conn_settings='%s'\n",
+ ci->conn_settings);
+#endif
+
+ qlog(" translation_dll='%s',translation_option='%s'\n",
+ ci->translation_dll,
+ ci->translation_option);
+}
+
+
+/* This is for datasource based options only */
+void
+writeDSNinfo(const ConnInfo *ci)
+{
+ const char *DSN = ci->dsn;
+ char encoded_conn_settings[LARGE_REGISTRY_LEN],
+ temp[SMALL_REGISTRY_LEN];
+
+ encode(ci->conn_settings, encoded_conn_settings);
+
+ SQLWritePrivateProfileString(DSN,
+ INI_KDESC,
+ ci->desc,
+ ODBC_INI);
+
+ SQLWritePrivateProfileString(DSN,
+ INI_DATABASE,
+ ci->database,
+ ODBC_INI);
+
+ SQLWritePrivateProfileString(DSN,
+ INI_SERVER,
+ ci->server,
+ ODBC_INI);
+
+ SQLWritePrivateProfileString(DSN,
+ INI_PORT,
+ ci->port,
+ ODBC_INI);
+
+ SQLWritePrivateProfileString(DSN,
+ INI_USER,
+ ci->username,
+ ODBC_INI);
+
+ SQLWritePrivateProfileString(DSN,
+ INI_PASSWORD,
+ ci->password,
+ ODBC_INI);
+
+ SQLWritePrivateProfileString(DSN,
+ INI_READONLY,
+ ci->onlyread,
+ ODBC_INI);
+
+ SQLWritePrivateProfileString(DSN,
+ INI_SHOWOIDCOLUMN,
+ ci->show_oid_column,
+ ODBC_INI);
+
+ SQLWritePrivateProfileString(DSN,
+ INI_FAKEOIDINDEX,
+ ci->fake_oid_index,
+ ODBC_INI);
+
+ SQLWritePrivateProfileString(DSN,
+ INI_ROWVERSIONING,
+ ci->row_versioning,
+ ODBC_INI);
+
+ SQLWritePrivateProfileString(DSN,
+ INI_SHOWSYSTEMTABLES,
+ ci->show_system_tables,
+ ODBC_INI);
+
+ SQLWritePrivateProfileString(DSN,
+ INI_PROTOCOL,
+ ci->protocol,
+ ODBC_INI);
+
+ SQLWritePrivateProfileString(DSN,
+ INI_CONNSETTINGS,
+ encoded_conn_settings,
+ ODBC_INI);
+
+ sprintf(temp, "%d", ci->disallow_premature);
+ SQLWritePrivateProfileString(DSN,
+ INI_DISALLOWPREMATURE,
+ temp,
+ ODBC_INI);
+ sprintf(temp, "%d", ci->updatable_cursors);
+ SQLWritePrivateProfileString(DSN,
+ INI_UPDATABLECURSORS,
+ temp,
+ ODBC_INI);
+}
+
+
+/*
+ * This function reads the ODBCINST.INI portion of
+ * the registry and gets any driver defaults.
+ */
+void
+getCommonDefaults(const char *section, const char *filename, ConnInfo *ci)
+{
+ char temp[256];
+ GLOBAL_VALUES *comval;
+
+ if (ci)
+ comval = &(ci->drivers);
+ else
+ comval = &globals;
+ /* Fetch Count is stored in driver section */
+ SQLGetPrivateProfileString(section, INI_FETCH, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ {
+ comval->fetch_max = atoi(temp);
+ /* sanity check if using cursors */
+ if (comval->fetch_max <= 0)
+ comval->fetch_max = FETCH_MAX;
+ }
+ else if (!ci)
+ comval->fetch_max = FETCH_MAX;
+
+ /* Socket Buffersize is stored in driver section */
+ SQLGetPrivateProfileString(section, INI_SOCKET, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->socket_buffersize = atoi(temp);
+ else if (!ci)
+ comval->socket_buffersize = SOCK_BUFFER_SIZE;
+
+ /* Debug is stored in the driver section */
+ SQLGetPrivateProfileString(section, INI_DEBUG, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->debug = atoi(temp);
+ else if (!ci)
+ comval->debug = DEFAULT_DEBUG;
+
+ /* CommLog is stored in the driver section */
+ SQLGetPrivateProfileString(section, INI_COMMLOG, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->commlog = atoi(temp);
+ else if (!ci)
+ comval->commlog = DEFAULT_COMMLOG;
+
+ if (!ci)
+ logs_on_off(0, 0, 0);
+ /* Optimizer is stored in the driver section only */
+ SQLGetPrivateProfileString(section, INI_OPTIMIZER, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->disable_optimizer = atoi(temp);
+ else if (!ci)
+ comval->disable_optimizer = DEFAULT_OPTIMIZER;
+
+ /* KSQO is stored in the driver section only */
+ SQLGetPrivateProfileString(section, INI_KSQO, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->ksqo = atoi(temp);
+ else if (!ci)
+ comval->ksqo = DEFAULT_KSQO;
+
+ /* Recognize Unique Index is stored in the driver section only */
+ SQLGetPrivateProfileString(section, INI_UNIQUEINDEX, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->unique_index = atoi(temp);
+ else if (!ci)
+ comval->unique_index = DEFAULT_UNIQUEINDEX;
+
+
+ /* Unknown Sizes is stored in the driver section only */
+ SQLGetPrivateProfileString(section, INI_UNKNOWNSIZES, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->unknown_sizes = atoi(temp);
+ else if (!ci)
+ comval->unknown_sizes = DEFAULT_UNKNOWNSIZES;
+
+
+ /* Lie about supported functions? */
+ SQLGetPrivateProfileString(section, INI_LIE, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->lie = atoi(temp);
+ else if (!ci)
+ comval->lie = DEFAULT_LIE;
+
+ /* Parse statements */
+ SQLGetPrivateProfileString(section, INI_PARSE, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->parse = atoi(temp);
+ else if (!ci)
+ comval->parse = DEFAULT_PARSE;
+
+ /* SQLCancel calls SQLFreeStmt in Driver Manager */
+ SQLGetPrivateProfileString(section, INI_CANCELASFREESTMT, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->cancel_as_freestmt = atoi(temp);
+ else if (!ci)
+ comval->cancel_as_freestmt = DEFAULT_CANCELASFREESTMT;
+
+ /* UseDeclareFetch is stored in the driver section only */
+ SQLGetPrivateProfileString(section, INI_USEDECLAREFETCH, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->use_declarefetch = atoi(temp);
+ else if (!ci)
+ comval->use_declarefetch = DEFAULT_USEDECLAREFETCH;
+
+ /* Max Varchar Size */
+ SQLGetPrivateProfileString(section, INI_MAXVARCHARSIZE, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->max_varchar_size = atoi(temp);
+ else if (!ci)
+ comval->max_varchar_size = MAX_VARCHAR_SIZE;
+
+ /* Max TextField Size */
+ SQLGetPrivateProfileString(section, INI_MAXLONGVARCHARSIZE, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->max_longvarchar_size = atoi(temp);
+ else if (!ci)
+ comval->max_longvarchar_size = TEXT_FIELD_SIZE;
+
+ /* Text As LongVarchar */
+ SQLGetPrivateProfileString(section, INI_TEXTASLONGVARCHAR, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->text_as_longvarchar = atoi(temp);
+ else if (!ci)
+ comval->text_as_longvarchar = DEFAULT_TEXTASLONGVARCHAR;
+
+ /* Unknowns As LongVarchar */
+ SQLGetPrivateProfileString(section, INI_UNKNOWNSASLONGVARCHAR, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->unknowns_as_longvarchar = atoi(temp);
+ else if (!ci)
+ comval->unknowns_as_longvarchar = DEFAULT_UNKNOWNSASLONGVARCHAR;
+
+ /* Bools As Char */
+ SQLGetPrivateProfileString(section, INI_BOOLSASCHAR, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->bools_as_char = atoi(temp);
+ else if (!ci)
+ comval->bools_as_char = DEFAULT_BOOLSASCHAR;
+
+ /* Extra Systable prefixes */
+
+ /*
+ * Use @@@ to distinguish between blank extra prefixes and no key
+ * entry
+ */
+ SQLGetPrivateProfileString(section, INI_EXTRASYSTABLEPREFIXES, "@@@",
+ temp, sizeof(temp), filename);
+ if (strcmp(temp, "@@@"))
+ strcpy(comval->extra_systable_prefixes, temp);
+ else if (!ci)
+ strcpy(comval->extra_systable_prefixes, DEFAULT_EXTRASYSTABLEPREFIXES);
+
+ mylog("globals.extra_systable_prefixes = '%s'\n", comval->extra_systable_prefixes);
+
+
+ /* Dont allow override of an override! */
+ if (!ci)
+ {
+ /*
+ * ConnSettings is stored in the driver section and per datasource
+ * for override
+ */
+ SQLGetPrivateProfileString(section, INI_CONNSETTINGS, "",
+ comval->conn_settings, sizeof(comval->conn_settings), filename);
+
+ /* Default state for future DSN's Readonly attribute */
+ SQLGetPrivateProfileString(section, INI_READONLY, "",
+ temp, sizeof(temp), filename);
+ if (temp[0])
+ comval->onlyread = atoi(temp);
+ else
+ comval->onlyread = DEFAULT_READONLY;
+
+ /*
+ * Default state for future DSN's protocol attribute This isn't a
+ * real driver option YET. This is more intended for
+ * customization from the install.
+ */
+ SQLGetPrivateProfileString(section, INI_PROTOCOL, "@@@",
+ temp, sizeof(temp), filename);
+ if (strcmp(temp, "@@@"))
+ strcpy(comval->protocol, temp);
+ else
+ strcpy(comval->protocol, DEFAULT_PROTOCOL);
+ }
+}
--- /dev/null
+/* File: dlg_specific.h
+ *
+ * Description: See "dlg_specific.c"
+ *
+ * Comments: See "notice.txt" for copyright and license information.
+ *
+ */
+
+#ifndef __DLG_SPECIFIC_H__
+#define __DLG_SPECIFIC_H__
+
+#include "psqlodbc.h"
+#include "connection.h"
+
+#ifdef WIN32
+#include <windowsx.h>
+#include "resource.h"
+#endif
+
+/* Unknown data type sizes */
+#define UNKNOWNS_AS_MAX 0
+#define UNKNOWNS_AS_DONTKNOW 1
+#define UNKNOWNS_AS_LONGEST 2
+
+/* ODBC initialization files */
+#ifndef WIN32
+#define ODBC_INI ".odbc.ini"
+#define ODBCINST_INI "odbcinst.ini"
+#else
+#define ODBC_INI "ODBC.INI"
+#define ODBCINST_INI "ODBCINST.INI"
+#endif
+
+
+#define INI_DSN DBMS_NAME /* Name of default
+ * Datasource in ini
+ * file (not used?) */
+#define INI_KDESC "Description" /* Data source
+ * description */
+#define INI_SERVER "Servername" /* Name of Server
+ * running the Postgres
+ * service */
+#define INI_PORT "Port" /* Port on which the
+ * Postmaster is listening */
+#define INI_DATABASE "Database" /* Database Name */
+#define INI_USER "Username" /* Default User Name */
+#define INI_PASSWORD "Password" /* Default Password */
+#define INI_DEBUG "Debug" /* Debug flag */
+#define INI_FETCH "Fetch" /* Fetch Max Count */
+#define INI_SOCKET "Socket" /* Socket buffer size */
+#define INI_READONLY "ReadOnly" /* Database is read only */
+#define INI_COMMLOG "CommLog" /* Communication to
+ * backend logging */
+#define INI_PROTOCOL "Protocol" /* What protocol (6.2) */
+#define INI_OPTIMIZER "Optimizer" /* Use backend genetic
+ * optimizer */
+#define INI_KSQO "Ksqo" /* Keyset query
+ * optimization */
+#define INI_CONNSETTINGS "ConnSettings" /* Anything to send to
+ * backend on successful
+ * connection */
+#define INI_UNIQUEINDEX "UniqueIndex" /* Recognize unique
+ * indexes */
+#define INI_UNKNOWNSIZES "UnknownSizes" /* How to handle unknown
+ * result set sizes */
+
+#define INI_CANCELASFREESTMT "CancelAsFreeStmt"
+
+#define INI_USEDECLAREFETCH "UseDeclareFetch" /* Use Declare/Fetch
+ * cursors */
+
+/* More ini stuff */
+#define INI_TEXTASLONGVARCHAR "TextAsLongVarchar"
+#define INI_UNKNOWNSASLONGVARCHAR "UnknownsAsLongVarchar"
+#define INI_BOOLSASCHAR "BoolsAsChar"
+#define INI_MAXVARCHARSIZE "MaxVarcharSize"
+#define INI_MAXLONGVARCHARSIZE "MaxLongVarcharSize"
+
+#define INI_FAKEOIDINDEX "FakeOidIndex"
+#define INI_SHOWOIDCOLUMN "ShowOidColumn"
+#define INI_ROWVERSIONING "RowVersioning"
+#define INI_SHOWSYSTEMTABLES "ShowSystemTables"
+#define INI_LIE "Lie"
+#define INI_PARSE "Parse"
+#define INI_EXTRASYSTABLEPREFIXES "ExtraSysTablePrefixes"
+
+#define INI_TRANSLATIONNAME "TranslationName"
+#define INI_TRANSLATIONDLL "TranslationDLL"
+#define INI_TRANSLATIONOPTION "TranslationOption"
+#define INI_DISALLOWPREMATURE "DisallowPremature"
+#define INI_UPDATABLECURSORS "UpdatableCursors"
+
+
+/* Connection Defaults */
+#define DEFAULT_PORT "5432"
+#define DEFAULT_READONLY 0
+#define DEFAULT_PROTOCOL "6.4" /* the latest protocol is
+ * the default */
+#define DEFAULT_USEDECLAREFETCH 0
+#define DEFAULT_TEXTASLONGVARCHAR 1
+#define DEFAULT_UNKNOWNSASLONGVARCHAR 0
+#define DEFAULT_BOOLSASCHAR 1
+#define DEFAULT_OPTIMIZER 1 /* disable */
+#define DEFAULT_KSQO 1 /* on */
+#define DEFAULT_UNIQUEINDEX 1 /* dont recognize */
+#define DEFAULT_COMMLOG 0 /* dont log */
+#define DEFAULT_DEBUG 0
+#define DEFAULT_UNKNOWNSIZES UNKNOWNS_AS_MAX
+
+
+#define DEFAULT_FAKEOIDINDEX 0
+#define DEFAULT_SHOWOIDCOLUMN 0
+#define DEFAULT_ROWVERSIONING 0
+#define DEFAULT_SHOWSYSTEMTABLES 0 /* dont show system tables */
+#define DEFAULT_LIE 0
+#define DEFAULT_PARSE 0
+
+#define DEFAULT_CANCELASFREESTMT 0
+
+#define DEFAULT_EXTRASYSTABLEPREFIXES "dd_;"
+
+/* prototypes */
+void getCommonDefaults(const char *section, const char *filename, ConnInfo *ci);
+
+#ifdef WIN32
+void SetDlgStuff(HWND hdlg, const ConnInfo *ci);
+void GetDlgStuff(HWND hdlg, ConnInfo *ci);
+
+int CALLBACK driver_optionsProc(HWND hdlg,
+ WORD wMsg,
+ WPARAM wParam,
+ LPARAM lParam);
+int CALLBACK ds_optionsProc(HWND hdlg,
+ WORD wMsg,
+ WPARAM wParam,
+ LPARAM lParam);
+#endif /* WIN32 */
+
+void updateGlobals(void);
+void writeDSNinfo(const ConnInfo *ci);
+void getDSNdefaults(ConnInfo *ci);
+void getDSNinfo(ConnInfo *ci, char overwrite);
+void makeConnectString(char *connect_string, const ConnInfo *ci, UWORD);
+void copyAttributes(ConnInfo *ci, const char *attribute, const char *value);
+void copyCommonAttributes(ConnInfo *ci, const char *attribute, const char *value);
+
+#endif
--- /dev/null
+/*-------
+ Module: drvconn.c
+ *
+ * Description: This module contains only routines related to
+ * implementing SQLDriverConnect.
+ *
+ * Classes: n/a
+ *
+ * API functions: SQLDriverConnect
+ *
+ * Comments: See "notice.txt" for copyright and license information.
+ *-------
+ */
+
+#include "psqlodbc.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "connection.h"
+
+#ifndef WIN32
+#include <sys/types.h>
+#include <sys/socket.h>
+#define NEAR
+#else
+#include <winsock.h>
+#endif
+
+#include <string.h>
+
+#ifdef WIN32
+#include <windowsx.h>
+#include "resource.h"
+#endif
+#include "pgapifunc.h"
+
+#ifndef TRUE
+#define TRUE (BOOL)1
+#endif
+#ifndef FALSE
+#define FALSE (BOOL)0
+#endif
+
+#include "dlg_specific.h"
+
+/* prototypes */
+void dconn_get_connect_attributes(const UCHAR FAR * connect_string, ConnInfo *ci);
+static void dconn_get_common_attributes(const UCHAR FAR * connect_string, ConnInfo *ci);
+
+#ifdef WIN32
+BOOL FAR PASCAL dconn_FDriverConnectProc(HWND hdlg, UINT wMsg, WPARAM wParam, LPARAM lParam);
+RETCODE dconn_DoDialog(HWND hwnd, ConnInfo *ci);
+
+extern HINSTANCE NEAR s_hModule; /* Saved module handle. */
+#endif
+
+
+RETCODE SQL_API
+PGAPI_DriverConnect(
+ HDBC hdbc,
+ HWND hwnd,
+ UCHAR FAR * szConnStrIn,
+ SWORD cbConnStrIn,
+ UCHAR FAR * szConnStrOut,
+ SWORD cbConnStrOutMax,
+ SWORD FAR * pcbConnStrOut,
+ UWORD fDriverCompletion)
+{
+ static char *func = "PGAPI_DriverConnect";
+ ConnectionClass *conn = (ConnectionClass *) hdbc;
+ ConnInfo *ci;
+
+#ifdef WIN32
+ RETCODE dialog_result;
+#endif
+ RETCODE result;
+ char connStrIn[MAX_CONNECT_STRING];
+ char connStrOut[MAX_CONNECT_STRING];
+ int retval;
+ char password_required = FALSE;
+ int len = 0;
+ SWORD lenStrout;
+
+
+ mylog("%s: entering...\n", func);
+
+ if (!conn)
+ {
+ CC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+
+ make_string(szConnStrIn, cbConnStrIn, connStrIn);
+
+ mylog("**** PGAPI_DriverConnect: fDriverCompletion=%d, connStrIn='%s'\n", fDriverCompletion, connStrIn);
+ qlog("conn=%u, PGAPI_DriverConnect( in)='%s', fDriverCompletion=%d\n", conn, connStrIn, fDriverCompletion);
+
+ ci = &(conn->connInfo);
+
+ /* Parse the connect string and fill in conninfo for this hdbc. */
+ dconn_get_connect_attributes(connStrIn, ci);
+
+ /*
+ * If the ConnInfo in the hdbc is missing anything, this function will
+ * fill them in from the registry (assuming of course there is a DSN
+ * given -- if not, it does nothing!)
+ */
+ getDSNinfo(ci, CONN_DONT_OVERWRITE);
+ dconn_get_common_attributes(connStrIn, ci);
+ logs_on_off(1, ci->drivers.debug, ci->drivers.commlog);
+
+ /* Fill in any default parameters if they are not there. */
+ getDSNdefaults(ci);
+ /* initialize pg_version */
+ CC_initialize_pg_version(conn);
+
+#ifdef WIN32
+dialog:
+#endif
+ ci->focus_password = password_required;
+
+ switch (fDriverCompletion)
+ {
+#ifdef WIN32
+ case SQL_DRIVER_PROMPT:
+ dialog_result = dconn_DoDialog(hwnd, ci);
+ if (dialog_result != SQL_SUCCESS)
+ return dialog_result;
+ break;
+
+ case SQL_DRIVER_COMPLETE_REQUIRED:
+
+ /* Fall through */
+
+ case SQL_DRIVER_COMPLETE:
+
+ /* Password is not a required parameter. */
+ if (ci->username[0] == '\0' ||
+ ci->server[0] == '\0' ||
+ ci->database[0] == '\0' ||
+ ci->port[0] == '\0' ||
+ password_required)
+ {
+ dialog_result = dconn_DoDialog(hwnd, ci);
+ if (dialog_result != SQL_SUCCESS)
+ return dialog_result;
+ }
+ break;
+#else
+ case SQL_DRIVER_PROMPT:
+ case SQL_DRIVER_COMPLETE:
+ case SQL_DRIVER_COMPLETE_REQUIRED:
+#endif
+ case SQL_DRIVER_NOPROMPT:
+ break;
+ }
+
+ /*
+ * Password is not a required parameter unless authentication asks for
+ * it. For now, I think it's better to just let the application ask
+ * over and over until a password is entered (the user can always hit
+ * Cancel to get out)
+ */
+ if (ci->username[0] == '\0' ||
+ ci->server[0] == '\0' ||
+ ci->database[0] == '\0' ||
+ ci->port[0] == '\0')
+ {
+ /* (password_required && ci->password[0] == '\0')) */
+
+ return SQL_NO_DATA_FOUND;
+ }
+
+ /* do the actual connect */
+ retval = CC_connect(conn, password_required);
+ if (retval < 0)
+ { /* need a password */
+ if (fDriverCompletion == SQL_DRIVER_NOPROMPT)
+ {
+ CC_log_error(func, "Need password but Driver_NoPrompt", conn);
+ return SQL_ERROR; /* need a password but not allowed to
+ * prompt so error */
+ }
+ else
+ {
+#ifdef WIN32
+ password_required = TRUE;
+ goto dialog;
+#else
+ return SQL_ERROR; /* until a better solution is found. */
+#endif
+ }
+ }
+ else if (retval == 0)
+ {
+ /* error msg filled in above */
+ CC_log_error(func, "Error from CC_Connect", conn);
+ return SQL_ERROR;
+ }
+
+ /*
+ * Create the Output Connection String
+ */
+ result = SQL_SUCCESS;
+
+ lenStrout = cbConnStrOutMax;
+ if (conn->ms_jet && lenStrout > 255)
+ lenStrout = 255;
+ makeConnectString(connStrOut, ci, lenStrout);
+ len = strlen(connStrOut);
+
+ if (szConnStrOut)
+ {
+ /*
+ * Return the completed string to the caller. The correct method
+ * is to only construct the connect string if a dialog was put up,
+ * otherwise, it should just copy the connection input string to
+ * the output. However, it seems ok to just always construct an
+ * output string. There are possible bad side effects on working
+ * applications (Access) by implementing the correct behavior,
+ * anyway.
+ */
+ strncpy_null(szConnStrOut, connStrOut, cbConnStrOutMax);
+
+ if (len >= cbConnStrOutMax)
+ {
+ int clen;
+
+ for (clen = strlen(szConnStrOut) - 1; clen >= 0 && szConnStrOut[clen] != ';'; clen--)
+ szConnStrOut[clen] = '\0';
+ result = SQL_SUCCESS_WITH_INFO;
+ conn->errornumber = CONN_TRUNCATED;
+ conn->errormsg = "The buffer was too small for the ConnStrOut.";
+ }
+ }
+
+ if (pcbConnStrOut)
+ *pcbConnStrOut = len;
+
+ mylog("szConnStrOut = '%s' len=%d,%d\n", szConnStrOut, len, cbConnStrOutMax);
+ qlog("conn=%u, PGAPI_DriverConnect(out)='%s'\n", conn, szConnStrOut);
+
+
+ mylog("PGAPI_DRiverConnect: returning %d\n", result);
+ return result;
+}
+
+
+#ifdef WIN32
+RETCODE
+dconn_DoDialog(HWND hwnd, ConnInfo *ci)
+{
+ int dialog_result;
+
+ mylog("dconn_DoDialog: ci = %u\n", ci);
+
+ if (hwnd)
+ {
+ dialog_result = DialogBoxParam(s_hModule, MAKEINTRESOURCE(DLG_CONFIG),
+ hwnd, dconn_FDriverConnectProc, (LPARAM) ci);
+ if (!dialog_result || (dialog_result == -1))
+ return SQL_NO_DATA_FOUND;
+ else
+ return SQL_SUCCESS;
+ }
+
+ return SQL_ERROR;
+}
+
+
+BOOL FAR PASCAL
+dconn_FDriverConnectProc(
+ HWND hdlg,
+ UINT wMsg,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ ConnInfo *ci;
+
+ switch (wMsg)
+ {
+ case WM_INITDIALOG:
+ ci = (ConnInfo *) lParam;
+
+ /* Change the caption for the setup dialog */
+ SetWindowText(hdlg, "PostgreSQL Connection");
+
+ SetWindowText(GetDlgItem(hdlg, IDC_DATASOURCE), "Connection");
+
+ /* Hide the DSN and description fields */
+ ShowWindow(GetDlgItem(hdlg, IDC_DSNAMETEXT), SW_HIDE);
+ ShowWindow(GetDlgItem(hdlg, IDC_DSNAME), SW_HIDE);
+ ShowWindow(GetDlgItem(hdlg, IDC_DESCTEXT), SW_HIDE);
+ ShowWindow(GetDlgItem(hdlg, IDC_DESC), SW_HIDE);
+
+ SetWindowLong(hdlg, DWL_USER, lParam); /* Save the ConnInfo for
+ * the "OK" */
+ SetDlgStuff(hdlg, ci);
+
+ if (ci->database[0] == '\0')
+ ; /* default focus */
+ else if (ci->server[0] == '\0')
+ SetFocus(GetDlgItem(hdlg, IDC_SERVER));
+ else if (ci->port[0] == '\0')
+ SetFocus(GetDlgItem(hdlg, IDC_PORT));
+ else if (ci->username[0] == '\0')
+ SetFocus(GetDlgItem(hdlg, IDC_USER));
+ else if (ci->focus_password)
+ SetFocus(GetDlgItem(hdlg, IDC_PASSWORD));
+ break;
+
+ case WM_COMMAND:
+ switch (GET_WM_COMMAND_ID(wParam, lParam))
+ {
+ case IDOK:
+ ci = (ConnInfo *) GetWindowLong(hdlg, DWL_USER);
+
+ GetDlgStuff(hdlg, ci);
+
+ case IDCANCEL:
+ EndDialog(hdlg, GET_WM_COMMAND_ID(wParam, lParam) == IDOK);
+ return TRUE;
+
+ case IDC_DRIVER:
+ ci = (ConnInfo *) GetWindowLong(hdlg, DWL_USER);
+ DialogBoxParam(s_hModule, MAKEINTRESOURCE(DLG_OPTIONS_DRV),
+ hdlg, driver_optionsProc, (LPARAM) ci);
+ break;
+
+ case IDC_DATASOURCE:
+ ci = (ConnInfo *) GetWindowLong(hdlg, DWL_USER);
+ DialogBoxParam(s_hModule, MAKEINTRESOURCE(DLG_OPTIONS_DS),
+ hdlg, ds_optionsProc, (LPARAM) ci);
+ break;
+ }
+ }
+
+ return FALSE;
+}
+#endif /* WIN32 */
+
+
+void
+dconn_get_connect_attributes(const UCHAR FAR * connect_string, ConnInfo *ci)
+{
+ char *our_connect_string;
+ char *pair,
+ *attribute,
+ *value,
+ *equals;
+ char *strtok_arg;
+
+ memset(ci, 0, sizeof(ConnInfo));
+#ifdef DRIVER_CURSOR_IMPLEMENT
+ ci->updatable_cursors = 1;
+#endif /* DRIVER_CURSOR_IMPLEMENT */
+
+ our_connect_string = strdup(connect_string);
+ strtok_arg = our_connect_string;
+
+ mylog("our_connect_string = '%s'\n", our_connect_string);
+
+ while (1)
+ {
+ pair = strtok(strtok_arg, ";");
+ if (strtok_arg)
+ strtok_arg = 0;
+ if (!pair)
+ break;
+
+ equals = strchr(pair, '=');
+ if (!equals)
+ continue;
+
+ *equals = '\0';
+ attribute = pair; /* ex. DSN */
+ value = equals + 1; /* ex. 'CEO co1' */
+
+ mylog("attribute = '%s', value = '%s'\n", attribute, value);
+
+ if (!attribute || !value)
+ continue;
+
+ /* Copy the appropriate value to the conninfo */
+ copyAttributes(ci, attribute, value);
+
+ }
+
+ free(our_connect_string);
+}
+
+static void
+dconn_get_common_attributes(const UCHAR FAR * connect_string, ConnInfo *ci)
+{
+ char *our_connect_string;
+ char *pair,
+ *attribute,
+ *value,
+ *equals;
+ char *strtok_arg;
+
+ our_connect_string = strdup(connect_string);
+ strtok_arg = our_connect_string;
+
+ mylog("our_connect_string = '%s'\n", our_connect_string);
+
+ while (1)
+ {
+ pair = strtok(strtok_arg, ";");
+ if (strtok_arg)
+ strtok_arg = 0;
+ if (!pair)
+ break;
+
+ equals = strchr(pair, '=');
+ if (!equals)
+ continue;
+
+ *equals = '\0';
+ attribute = pair; /* ex. DSN */
+ value = equals + 1; /* ex. 'CEO co1' */
+
+ mylog("attribute = '%s', value = '%s'\n", attribute, value);
+
+ if (!attribute || !value)
+ continue;
+
+ /* Copy the appropriate value to the conninfo */
+ copyCommonAttributes(ci, attribute, value);
+
+ }
+
+ free(our_connect_string);
+}
--- /dev/null
+/*-------
+ * Module: environ.c
+ *
+ * Description: This module contains routines related to
+ * the environment, such as storing connection handles,
+ * and returning errors.
+ *
+ * Classes: EnvironmentClass (Functions prefix: "EN_")
+ *
+ * API functions: SQLAllocEnv, SQLFreeEnv, SQLError
+ *
+ * Comments: See "notice.txt" for copyright and license information.
+ *-------
+ */
+
+#include "environ.h"
+
+#include "connection.h"
+#include "dlg_specific.h"
+#include "statement.h"
+#include <stdlib.h>
+#include <string.h>
+#include "pgapifunc.h"
+
+extern GLOBAL_VALUES globals;
+
+/* The one instance of the handles */
+ConnectionClass *conns[MAX_CONNECTIONS];
+
+
+RETCODE SQL_API
+PGAPI_AllocEnv(HENV FAR * phenv)
+{
+ static char *func = "PGAPI_AllocEnv";
+
+ mylog("**** in PGAPI_AllocEnv ** \n");
+
+ /*
+ * Hack for systems on which none of the constructor-making techniques
+ * in psqlodbc.c work: if globals appears not to have been
+ * initialized, then cause it to be initialized. Since this should be
+ * the first function called in this shared library, doing it here
+ * should work.
+ */
+ if (globals.socket_buffersize <= 0)
+ getCommonDefaults(DBMS_NAME, ODBCINST_INI, NULL);
+
+ *phenv = (HENV) EN_Constructor();
+ if (!*phenv)
+ {
+ *phenv = SQL_NULL_HENV;
+ EN_log_error(func, "Error allocating environment", NULL);
+ return SQL_ERROR;
+ }
+
+ mylog("** exit PGAPI_AllocEnv: phenv = %u **\n", *phenv);
+ return SQL_SUCCESS;
+}
+
+
+RETCODE SQL_API
+PGAPI_FreeEnv(HENV henv)
+{
+ static char *func = "PGAPI_FreeEnv";
+ EnvironmentClass *env = (EnvironmentClass *) henv;
+
+ mylog("**** in PGAPI_FreeEnv: env = %u ** \n", env);
+
+ if (env && EN_Destructor(env))
+ {
+ mylog(" ok\n");
+ return SQL_SUCCESS;
+ }
+
+ mylog(" error\n");
+ EN_log_error(func, "Error freeing environment", env);
+ return SQL_ERROR;
+}
+
+
+/* Returns the next SQL error information. */
+RETCODE SQL_API
+PGAPI_Error(
+ HENV henv,
+ HDBC hdbc,
+ HSTMT hstmt,
+ UCHAR FAR * szSqlState,
+ SDWORD FAR * pfNativeError,
+ UCHAR FAR * szErrorMsg,
+ SWORD cbErrorMsgMax,
+ SWORD FAR * pcbErrorMsg)
+{
+ char *msg;
+ int status;
+ BOOL once_again = FALSE;
+ SWORD msglen;
+
+ mylog("**** PGAPI_Error: henv=%u, hdbc=%u, hstmt=%u <%d>\n", henv, hdbc, hstmt, cbErrorMsgMax);
+
+ if (cbErrorMsgMax < 0)
+ return SQL_ERROR;
+ if (SQL_NULL_HSTMT != hstmt)
+ {
+ /* CC: return an error of a hstmt */
+ StatementClass *stmt = (StatementClass *) hstmt;
+
+ if (SC_get_error(stmt, &status, &msg))
+ {
+ mylog("SC_get_error: status = %d, msg = #%s#\n", status, msg);
+ if (NULL == msg)
+ {
+ if (NULL != szSqlState)
+ strcpy(szSqlState, "00000");
+ if (NULL != pcbErrorMsg)
+ *pcbErrorMsg = 0;
+ if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
+ szErrorMsg[0] = '\0';
+
+ return SQL_NO_DATA_FOUND;
+ }
+ msglen = (SWORD) strlen(msg);
+ if (NULL != pcbErrorMsg)
+ {
+ *pcbErrorMsg = msglen;
+ if (cbErrorMsgMax == 0)
+ once_again = TRUE;
+ else if (msglen >= cbErrorMsgMax)
+ {
+ once_again = TRUE;
+ *pcbErrorMsg = cbErrorMsgMax - 1;
+ }
+ }
+
+ if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
+ strncpy_null(szErrorMsg, msg, cbErrorMsgMax);
+
+ if (NULL != pfNativeError)
+ *pfNativeError = status;
+
+ if (NULL != szSqlState)
+
+ switch (status)
+ {
+ /* now determine the SQLSTATE to be returned */
+ case STMT_ROW_VERSION_CHANGED:
+ strcpy(szSqlState, "01001");
+ /* data truncated */
+ break;
+ case STMT_TRUNCATED:
+ strcpy(szSqlState, "01004");
+ /* data truncated */
+ break;
+ case STMT_INFO_ONLY:
+ strcpy(szSqlState, "00000");
+ /* just information that is returned, no error */
+ break;
+ case STMT_BAD_ERROR:
+ strcpy(szSqlState, "08S01");
+ /* communication link failure */
+ break;
+ case STMT_CREATE_TABLE_ERROR:
+ strcpy(szSqlState, "S0001");
+ /* table already exists */
+ break;
+ case STMT_STATUS_ERROR:
+ case STMT_SEQUENCE_ERROR:
+ strcpy(szSqlState, "S1010");
+ /* Function sequence error */
+ break;
+ case STMT_NO_MEMORY_ERROR:
+ strcpy(szSqlState, "S1001");
+ /* memory allocation failure */
+ break;
+ case STMT_COLNUM_ERROR:
+ strcpy(szSqlState, "S1002");
+ /* invalid column number */
+ break;
+ case STMT_NO_STMTSTRING:
+ strcpy(szSqlState, "S1001");
+ /* having no stmtstring is also a malloc problem */
+ break;
+ case STMT_ERROR_TAKEN_FROM_BACKEND:
+ strcpy(szSqlState, "S1000");
+ /* general error */
+ break;
+ case STMT_INTERNAL_ERROR:
+ strcpy(szSqlState, "S1000");
+ /* general error */
+ break;
+ case STMT_ROW_OUT_OF_RANGE:
+ strcpy(szSqlState, "S1107");
+ break;
+
+ case STMT_OPERATION_CANCELLED:
+ strcpy(szSqlState, "S1008");
+ break;
+
+ case STMT_NOT_IMPLEMENTED_ERROR:
+ strcpy(szSqlState, "S1C00"); /* == 'driver not
+ * capable' */
+ break;
+ case STMT_OPTION_OUT_OF_RANGE_ERROR:
+ strcpy(szSqlState, "S1092");
+ break;
+ case STMT_BAD_PARAMETER_NUMBER_ERROR:
+ strcpy(szSqlState, "S1093");
+ break;
+ case STMT_INVALID_COLUMN_NUMBER_ERROR:
+ strcpy(szSqlState, "S1002");
+ break;
+ case STMT_RESTRICTED_DATA_TYPE_ERROR:
+ strcpy(szSqlState, "07006");
+ break;
+ case STMT_INVALID_CURSOR_STATE_ERROR:
+ strcpy(szSqlState, "24000");
+ break;
+ case STMT_OPTION_VALUE_CHANGED:
+ strcpy(szSqlState, "01S02");
+ break;
+ case STMT_POS_BEFORE_RECORDSET:
+ strcpy(szSqlState, "01S06");
+ break;
+ case STMT_INVALID_CURSOR_NAME:
+ strcpy(szSqlState, "34000");
+ break;
+ case STMT_NO_CURSOR_NAME:
+ strcpy(szSqlState, "S1015");
+ break;
+ case STMT_INVALID_ARGUMENT_NO:
+ strcpy(szSqlState, "S1009");
+ /* invalid argument value */
+ break;
+ case STMT_INVALID_CURSOR_POSITION:
+ strcpy(szSqlState, "S1109");
+ break;
+ case STMT_VALUE_OUT_OF_RANGE:
+ strcpy(szSqlState, "22003");
+ break;
+ case STMT_OPERATION_INVALID:
+ strcpy(szSqlState, "S1011");
+ break;
+ case STMT_INVALID_OPTION_IDENTIFIER:
+ strcpy(szSqlState, "HY092");
+ break;
+ case STMT_EXEC_ERROR:
+ default:
+ strcpy(szSqlState, "S1000");
+ /* also a general error */
+ break;
+ }
+ mylog(" szSqlState = '%s', szError='%s'\n", szSqlState, szErrorMsg);
+ }
+ else
+ {
+ if (NULL != szSqlState)
+ strcpy(szSqlState, "00000");
+ if (NULL != pcbErrorMsg)
+ *pcbErrorMsg = 0;
+ if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
+ szErrorMsg[0] = '\0';
+
+ mylog(" returning NO_DATA_FOUND\n");
+
+ return SQL_NO_DATA_FOUND;
+ }
+
+ if (once_again)
+ {
+ int outlen;
+
+ stmt->errornumber = status;
+ if (cbErrorMsgMax > 0)
+ outlen = *pcbErrorMsg;
+ else
+ outlen = 0;
+ if (!stmt->errormsg_malloced || !stmt->errormsg)
+ {
+ stmt->errormsg = malloc(msglen - outlen + 1);
+ stmt->errormsg_malloced = TRUE;
+ }
+ memmove(stmt->errormsg, msg + outlen, msglen - outlen + 1);
+ }
+ else if (stmt->errormsg_malloced)
+ SC_clear_error(stmt);
+ if (cbErrorMsgMax == 0)
+ return SQL_SUCCESS_WITH_INFO;
+ else
+ return SQL_SUCCESS;
+ }
+ else if (SQL_NULL_HDBC != hdbc)
+ {
+ ConnectionClass *conn = (ConnectionClass *) hdbc;
+
+ mylog("calling CC_get_error\n");
+ if (CC_get_error(conn, &status, &msg))
+ {
+ mylog("CC_get_error: status = %d, msg = #%s#\n", status, msg);
+ if (NULL == msg)
+ {
+ if (NULL != szSqlState)
+ strcpy(szSqlState, "00000");
+ if (NULL != pcbErrorMsg)
+ *pcbErrorMsg = 0;
+ if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
+ szErrorMsg[0] = '\0';
+
+ return SQL_NO_DATA_FOUND;
+ }
+
+ msglen = strlen(msg);
+ if (NULL != pcbErrorMsg)
+ {
+ *pcbErrorMsg = msglen;
+ if (cbErrorMsgMax == 0)
+ once_again = TRUE;
+ else if (msglen >= cbErrorMsgMax)
+ *pcbErrorMsg = cbErrorMsgMax - 1;
+ }
+ if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
+ strncpy_null(szErrorMsg, msg, cbErrorMsgMax);
+ if (NULL != pfNativeError)
+ *pfNativeError = status;
+
+ if (NULL != szSqlState)
+ switch (status)
+ {
+ case STMT_OPTION_VALUE_CHANGED:
+ case CONN_OPTION_VALUE_CHANGED:
+ strcpy(szSqlState, "01S02");
+ break;
+ case STMT_TRUNCATED:
+ case CONN_TRUNCATED:
+ strcpy(szSqlState, "01004");
+ /* data truncated */
+ break;
+ case CONN_INIREAD_ERROR:
+ strcpy(szSqlState, "IM002");
+ /* data source not found */
+ break;
+ case CONN_OPENDB_ERROR:
+ strcpy(szSqlState, "08001");
+ /* unable to connect to data source */
+ break;
+ case CONN_INVALID_AUTHENTICATION:
+ case CONN_AUTH_TYPE_UNSUPPORTED:
+ strcpy(szSqlState, "28000");
+ break;
+ case CONN_STMT_ALLOC_ERROR:
+ strcpy(szSqlState, "S1001");
+ /* memory allocation failure */
+ break;
+ case CONN_IN_USE:
+ strcpy(szSqlState, "S1000");
+ /* general error */
+ break;
+ case CONN_UNSUPPORTED_OPTION:
+ strcpy(szSqlState, "IM001");
+ /* driver does not support this function */
+ case CONN_INVALID_ARGUMENT_NO:
+ strcpy(szSqlState, "S1009");
+ /* invalid argument value */
+ break;
+ case CONN_TRANSACT_IN_PROGRES:
+ strcpy(szSqlState, "S1010");
+
+ /*
+ * when the user tries to switch commit mode in a
+ * transaction
+ */
+ /* -> function sequence error */
+ break;
+ case CONN_NO_MEMORY_ERROR:
+ strcpy(szSqlState, "S1001");
+ break;
+ case CONN_NOT_IMPLEMENTED_ERROR:
+ case STMT_NOT_IMPLEMENTED_ERROR:
+ strcpy(szSqlState, "S1C00");
+ break;
+ case CONN_VALUE_OUT_OF_RANGE:
+ case STMT_VALUE_OUT_OF_RANGE:
+ strcpy(szSqlState, "22003");
+ break;
+ default:
+ strcpy(szSqlState, "S1000");
+ /* general error */
+ break;
+ }
+ }
+ else
+ {
+ mylog("CC_Get_error returned nothing.\n");
+ if (NULL != szSqlState)
+ strcpy(szSqlState, "00000");
+ if (NULL != pcbErrorMsg)
+ *pcbErrorMsg = 0;
+ if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
+ szErrorMsg[0] = '\0';
+
+ return SQL_NO_DATA_FOUND;
+ }
+
+ if (once_again)
+ {
+ conn->errornumber = status;
+ return SQL_SUCCESS_WITH_INFO;
+ }
+ else
+ return SQL_SUCCESS;
+ }
+ else if (SQL_NULL_HENV != henv)
+ {
+ EnvironmentClass *env = (EnvironmentClass *) henv;
+
+ if (EN_get_error(env, &status, &msg))
+ {
+ mylog("EN_get_error: status = %d, msg = #%s#\n", status, msg);
+ if (NULL == msg)
+ {
+ if (NULL != szSqlState)
+ strcpy(szSqlState, "00000");
+ if (NULL != pcbErrorMsg)
+ *pcbErrorMsg = 0;
+ if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
+ szErrorMsg[0] = '\0';
+
+ return SQL_NO_DATA_FOUND;
+ }
+
+ if (NULL != pcbErrorMsg)
+ *pcbErrorMsg = (SWORD) strlen(msg);
+ if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
+ strncpy_null(szErrorMsg, msg, cbErrorMsgMax);
+ if (NULL != pfNativeError)
+ *pfNativeError = status;
+
+ if (szSqlState)
+ {
+ switch (status)
+ {
+ case ENV_ALLOC_ERROR:
+ /* memory allocation failure */
+ strcpy(szSqlState, "S1001");
+ break;
+ default:
+ strcpy(szSqlState, "S1000");
+ /* general error */
+ break;
+ }
+ }
+ }
+ else
+ {
+ if (NULL != szSqlState)
+ strcpy(szSqlState, "00000");
+ if (NULL != pcbErrorMsg)
+ *pcbErrorMsg = 0;
+ if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
+ szErrorMsg[0] = '\0';
+
+ return SQL_NO_DATA_FOUND;
+ }
+
+ return SQL_SUCCESS;
+ }
+
+ if (NULL != szSqlState)
+ strcpy(szSqlState, "00000");
+ if (NULL != pcbErrorMsg)
+ *pcbErrorMsg = 0;
+ if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
+ szErrorMsg[0] = '\0';
+
+ return SQL_NO_DATA_FOUND;
+}
+
+
+/*
+ * EnvironmentClass implementation
+ */
+EnvironmentClass *
+EN_Constructor(void)
+{
+ EnvironmentClass *rv;
+
+ rv = (EnvironmentClass *) malloc(sizeof(EnvironmentClass));
+ if (rv)
+ {
+ rv->errormsg = 0;
+ rv->errornumber = 0;
+ }
+
+ return rv;
+}
+
+
+char
+EN_Destructor(EnvironmentClass *self)
+{
+ int lf;
+ char rv = 1;
+
+ mylog("in EN_Destructor, self=%u\n", self);
+
+ /*
+ * the error messages are static strings distributed throughout the
+ * source--they should not be freed
+ */
+
+ /* Free any connections belonging to this environment */
+ for (lf = 0; lf < MAX_CONNECTIONS; lf++)
+ {
+ if (conns[lf] && conns[lf]->henv == self)
+ rv = rv && CC_Destructor(conns[lf]);
+ }
+ free(self);
+
+ mylog("exit EN_Destructor: rv = %d\n", rv);
+#ifdef _MEMORY_DEBUG_
+ debug_memory_inouecheck();
+#endif /* _MEMORY_DEBUG_ */
+ return rv;
+}
+
+
+char
+EN_get_error(EnvironmentClass *self, int *number, char **message)
+{
+ if (self && self->errormsg && self->errornumber)
+ {
+ *message = self->errormsg;
+ *number = self->errornumber;
+ self->errormsg = 0;
+ self->errornumber = 0;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+
+char
+EN_add_connection(EnvironmentClass *self, ConnectionClass *conn)
+{
+ int i;
+
+ mylog("EN_add_connection: self = %u, conn = %u\n", self, conn);
+
+ for (i = 0; i < MAX_CONNECTIONS; i++)
+ {
+ if (!conns[i])
+ {
+ conn->henv = self;
+ conns[i] = conn;
+
+ mylog(" added at i =%d, conn->henv = %u, conns[i]->henv = %u\n", i, conn->henv, conns[i]->henv);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+char
+EN_remove_connection(EnvironmentClass *self, ConnectionClass *conn)
+{
+ int i;
+
+ for (i = 0; i < MAX_CONNECTIONS; i++)
+ if (conns[i] == conn && conns[i]->status != CONN_EXECUTING)
+ {
+ conns[i] = NULL;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+void
+EN_log_error(char *func, char *desc, EnvironmentClass *self)
+{
+ if (self)
+ qlog("ENVIRON ERROR: func=%s, desc='%s', errnum=%d, errmsg='%s'\n", func, desc, self->errornumber, self->errormsg);
+ else
+ qlog("INVALID ENVIRON HANDLE ERROR: func=%s, desc='%s'\n", func, desc);
+}
--- /dev/null
+/* File: environ.h
+ *
+ * Description: See "environ.c"
+ *
+ * Comments: See "notice.txt" for copyright and license information.
+ *
+ */
+
+#ifndef __ENVIRON_H__
+#define __ENVIRON_H__
+
+#include "psqlodbc.h"
+
+#define ENV_ALLOC_ERROR 1
+
+/********** Environment Handle *************/
+struct EnvironmentClass_
+{
+ char *errormsg;
+ int errornumber;
+};
+
+/* Environment prototypes */
+EnvironmentClass *EN_Constructor(void);
+char EN_Destructor(EnvironmentClass *self);
+char EN_get_error(EnvironmentClass *self, int *number, char **message);
+char EN_add_connection(EnvironmentClass *self, ConnectionClass *conn);
+char EN_remove_connection(EnvironmentClass *self, ConnectionClass *conn);
+void EN_log_error(char *func, char *desc, EnvironmentClass *self);
+
+#endif
--- /dev/null
+/*-------
+ * Module: execute.c
+ *
+ * Description: This module contains routines related to
+ * preparing and executing an SQL statement.
+ *
+ * Classes: n/a
+ *
+ * API functions: SQLPrepare, SQLExecute, SQLExecDirect, SQLTransact,
+ * SQLCancel, SQLNativeSql, SQLParamData, SQLPutData
+ *
+ * Comments: See "notice.txt" for copyright and license information.
+ *-------
+ */
+
+#include "psqlodbc.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "connection.h"
+#include "statement.h"
+#include "qresult.h"
+#include "convert.h"
+#include "bind.h"
+#include "pgtypes.h"
+#include "lobj.h"
+#include "pgapifunc.h"
+
+/*extern GLOBAL_VALUES globals;*/
+
+
+/* Perform a Prepare on the SQL statement */
+RETCODE SQL_API
+PGAPI_Prepare(HSTMT hstmt,
+ UCHAR FAR * szSqlStr,
+ SDWORD cbSqlStr)
+{
+ static char *func = "PGAPI_Prepare";
+ StatementClass *self = (StatementClass *) hstmt;
+
+ mylog("%s: entering...\n", func);
+
+ if (!self)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+
+ /*
+ * According to the ODBC specs it is valid to call SQLPrepare mulitple
+ * times. In that case, the bound SQL statement is replaced by the new
+ * one
+ */
+
+ switch (self->status)
+ {
+ case STMT_PREMATURE:
+ mylog("**** PGAPI_Prepare: STMT_PREMATURE, recycle\n");
+ SC_recycle_statement(self); /* recycle the statement, but do
+ * not remove parameter bindings */
+ break;
+
+ case STMT_FINISHED:
+ mylog("**** PGAPI_Prepare: STMT_FINISHED, recycle\n");
+ SC_recycle_statement(self); /* recycle the statement, but do
+ * not remove parameter bindings */
+ break;
+
+ case STMT_ALLOCATED:
+ mylog("**** PGAPI_Prepare: STMT_ALLOCATED, copy\n");
+ self->status = STMT_READY;
+ break;
+
+ case STMT_READY:
+ mylog("**** PGAPI_Prepare: STMT_READY, change SQL\n");
+ break;
+
+ case STMT_EXECUTING:
+ mylog("**** PGAPI_Prepare: STMT_EXECUTING, error!\n");
+
+ self->errornumber = STMT_SEQUENCE_ERROR;
+ self->errormsg = "PGAPI_Prepare(): The handle does not point to a statement that is ready to be executed";
+ SC_log_error(func, "", self);
+
+ return SQL_ERROR;
+
+ default:
+ self->errornumber = STMT_INTERNAL_ERROR;
+ self->errormsg = "An Internal Error has occured -- Unknown statement status.";
+ SC_log_error(func, "", self);
+ return SQL_ERROR;
+ }
+
+ if (self->statement)
+ free(self->statement);
+
+ self->statement = make_string(szSqlStr, cbSqlStr, NULL);
+ if (!self->statement)
+ {
+ self->errornumber = STMT_NO_MEMORY_ERROR;
+ self->errormsg = "No memory available to store statement";
+ SC_log_error(func, "", self);
+ return SQL_ERROR;
+ }
+
+ self->prepare = TRUE;
+ self->statement_type = statement_type(self->statement);
+
+ /* Check if connection is onlyread (only selects are allowed) */
+ if (CC_is_onlyread(self->hdbc) && STMT_UPDATE(self))
+ {
+ self->errornumber = STMT_EXEC_ERROR;
+ self->errormsg = "Connection is readonly, only select statements are allowed.";
+ SC_log_error(func, "", self);
+ return SQL_ERROR;
+ }
+
+ return SQL_SUCCESS;
+}
+
+
+/* Performs the equivalent of SQLPrepare, followed by SQLExecute. */
+RETCODE SQL_API
+PGAPI_ExecDirect(
+ HSTMT hstmt,
+ UCHAR FAR * szSqlStr,
+ SDWORD cbSqlStr)
+{
+ StatementClass *stmt = (StatementClass *) hstmt;
+ RETCODE result;
+ static char *func = "PGAPI_ExecDirect";
+
+ mylog("%s: entering...\n", func);
+
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+
+ if (stmt->statement)
+ free(stmt->statement);
+
+ /*
+ * keep a copy of the un-parametrized statement, in case they try to
+ * execute this statement again
+ */
+ stmt->statement = make_string(szSqlStr, cbSqlStr, NULL);
+ if (!stmt->statement)
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "No memory available to store statement";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ mylog("**** %s: hstmt=%u, statement='%s'\n", func, hstmt, stmt->statement);
+
+ stmt->prepare = FALSE;
+
+ /*
+ * If an SQLPrepare was performed prior to this, but was left in the
+ * premature state because an error occurred prior to SQLExecute then
+ * set the statement to finished so it can be recycled.
+ */
+ if (stmt->status == STMT_PREMATURE)
+ stmt->status = STMT_FINISHED;
+
+ stmt->statement_type = statement_type(stmt->statement);
+
+ /* Check if connection is onlyread (only selects are allowed) */
+ if (CC_is_onlyread(stmt->hdbc) && STMT_UPDATE(stmt))
+ {
+ stmt->errornumber = STMT_EXEC_ERROR;
+ stmt->errormsg = "Connection is readonly, only select statements are allowed.";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ mylog("%s: calling PGAPI_Execute...\n", func);
+
+ result = PGAPI_Execute(hstmt);
+
+ mylog("%s: returned %hd from PGAPI_Execute\n", func, result);
+ return result;
+}
+
+
+/* Execute a prepared SQL statement */
+RETCODE SQL_API
+PGAPI_Execute(
+ HSTMT hstmt)
+{
+ static char *func = "PGAPI_Execute";
+ StatementClass *stmt = (StatementClass *) hstmt;
+ ConnectionClass *conn;
+ int i,
+ retval;
+
+ mylog("%s: entering...\n", func);
+
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ mylog("%s: NULL statement so return SQL_INVALID_HANDLE\n", func);
+ return SQL_INVALID_HANDLE;
+ }
+
+ /*
+ * If the statement is premature, it means we already executed it from
+ * an SQLPrepare/SQLDescribeCol type of scenario. So just return
+ * success.
+ */
+ if (stmt->prepare && stmt->status == STMT_PREMATURE)
+ {
+ if (stmt->inaccurate_result)
+ SC_recycle_statement(stmt);
+ else
+ {
+ stmt->status = STMT_FINISHED;
+ if (stmt->errormsg == NULL)
+ {
+ mylog("%s: premature statement but return SQL_SUCCESS\n", func);
+ return SQL_SUCCESS;
+ }
+ else
+ {
+ SC_log_error(func, "", stmt);
+ mylog("%s: premature statement so return SQL_ERROR\n", func);
+ return SQL_ERROR;
+ }
+ }
+ }
+
+ mylog("%s: clear errors...\n", func);
+
+ SC_clear_error(stmt);
+
+ conn = SC_get_conn(stmt);
+ if (conn->status == CONN_EXECUTING)
+ {
+ stmt->errormsg = "Connection is already in use.";
+ stmt->errornumber = STMT_SEQUENCE_ERROR;
+ SC_log_error(func, "", stmt);
+ mylog("%s: problem with connection\n", func);
+ return SQL_ERROR;
+ }
+
+ if (!stmt->statement)
+ {
+ stmt->errornumber = STMT_NO_STMTSTRING;
+ stmt->errormsg = "This handle does not have a SQL statement stored in it";
+ SC_log_error(func, "", stmt);
+ mylog("%s: problem with handle\n", func);
+ return SQL_ERROR;
+ }
+
+ /*
+ * If SQLExecute is being called again, recycle the statement. Note
+ * this should have been done by the application in a call to
+ * SQLFreeStmt(SQL_CLOSE) or SQLCancel.
+ */
+ if (stmt->status == STMT_FINISHED)
+ {
+ mylog("%s: recycling statement (should have been done by app)...\n", func);
+ SC_recycle_statement(stmt);
+ }
+
+ /* Check if the statement is in the correct state */
+ if ((stmt->prepare && stmt->status != STMT_READY) ||
+ (stmt->status != STMT_ALLOCATED && stmt->status != STMT_READY))
+ {
+ stmt->errornumber = STMT_STATUS_ERROR;
+ stmt->errormsg = "The handle does not point to a statement that is ready to be executed";
+ SC_log_error(func, "", stmt);
+ mylog("%s: problem with statement\n", func);
+ return SQL_ERROR;
+ }
+
+ /*
+ * Check if statement has any data-at-execute parameters when it is
+ * not in SC_pre_execute.
+ */
+ if (!stmt->pre_executing)
+ {
+ /*
+ * The bound parameters could have possibly changed since the last
+ * execute of this statement? Therefore check for params and
+ * re-copy.
+ */
+ stmt->data_at_exec = -1;
+ for (i = 0; i < stmt->parameters_allocated; i++)
+ {
+ Int4 *pcVal = stmt->parameters[i].used;
+
+ if (pcVal && (*pcVal == SQL_DATA_AT_EXEC || *pcVal <= SQL_LEN_DATA_AT_EXEC_OFFSET))
+ stmt->parameters[i].data_at_exec = TRUE;
+ else
+ stmt->parameters[i].data_at_exec = FALSE;
+ /* Check for data at execution parameters */
+ if (stmt->parameters[i].data_at_exec == TRUE)
+ {
+ if (stmt->data_at_exec < 0)
+ stmt->data_at_exec = 1;
+ else
+ stmt->data_at_exec++;
+ }
+ }
+
+ /*
+ * If there are some data at execution parameters, return need
+ * data
+ */
+
+ /*
+ * SQLParamData and SQLPutData will be used to send params and
+ * execute the statement.
+ */
+ if (stmt->data_at_exec > 0)
+ return SQL_NEED_DATA;
+
+ }
+
+
+ mylog("%s: copying statement params: trans_status=%d, len=%d, stmt='%s'\n", func, conn->transact_status, strlen(stmt->statement), stmt->statement);
+
+ /* Create the statement with parameters substituted. */
+ retval = copy_statement_with_parameters(stmt);
+ if (retval != SQL_SUCCESS)
+ /* error msg passed from above */
+ return retval;
+
+ mylog(" stmt_with_params = '%s'\n", stmt->stmt_with_params);
+
+ /*
+ * Get the field info for the prepared query using dummy backward
+ * fetch.
+ */
+ if (stmt->inaccurate_result && conn->connInfo.disallow_premature)
+ {
+ if (SC_is_pre_executable(stmt))
+ {
+ BOOL in_trans = CC_is_in_trans(conn);
+ BOOL issued_begin = FALSE,
+ begin_included = FALSE;
+ QResultClass *res;
+
+ if (strnicmp(stmt->stmt_with_params, "BEGIN;", 6) == 0)
+ begin_included = TRUE;
+ else if (!in_trans)
+ {
+ res = CC_send_query(conn, "BEGIN", NULL);
+ if (res && !QR_aborted(res))
+ issued_begin = TRUE;
+ if (res)
+ QR_Destructor(res);
+ if (!issued_begin)
+ {
+ stmt->errornumber = STMT_EXEC_ERROR;
+ stmt->errormsg = "Handle prepare error";
+ return SQL_ERROR;
+ }
+ }
+ /* we are now in a transaction */
+ CC_set_in_trans(conn);
+ stmt->result = res = CC_send_query(conn, stmt->stmt_with_params, NULL);
+ if (!res || QR_aborted(res))
+ {
+ CC_abort(conn);
+ stmt->errornumber = STMT_EXEC_ERROR;
+ stmt->errormsg = "Handle prepare error";
+ return SQL_ERROR;
+ }
+ else
+ {
+ if (CC_is_in_autocommit(conn))
+ {
+ if (issued_begin)
+ {
+ res = CC_send_query(conn, "COMMIT", NULL);
+ CC_set_no_trans(conn);
+ if (res)
+ QR_Destructor(res);
+ }
+ else if (!in_trans && begin_included)
+ CC_set_no_trans(conn);
+ }
+ stmt->status = STMT_FINISHED;
+ return SQL_SUCCESS;
+ }
+ }
+ else
+ return SQL_SUCCESS;
+ }
+
+ return SC_execute(stmt);
+}
+
+
+RETCODE SQL_API
+PGAPI_Transact(
+ HENV henv,
+ HDBC hdbc,
+ UWORD fType)
+{
+ static char *func = "PGAPI_Transact";
+ extern ConnectionClass *conns[];
+ ConnectionClass *conn;
+ QResultClass *res;
+ char ok,
+ *stmt_string;
+ int lf;
+
+ mylog("entering %s: hdbc=%u, henv=%u\n", func, hdbc, henv);
+
+ if (hdbc == SQL_NULL_HDBC && henv == SQL_NULL_HENV)
+ {
+ CC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+
+ /*
+ * If hdbc is null and henv is valid, it means transact all
+ * connections on that henv.
+ */
+ if (hdbc == SQL_NULL_HDBC && henv != SQL_NULL_HENV)
+ {
+ for (lf = 0; lf < MAX_CONNECTIONS; lf++)
+ {
+ conn = conns[lf];
+
+ if (conn && conn->henv == henv)
+ if (PGAPI_Transact(henv, (HDBC) conn, fType) != SQL_SUCCESS)
+ return SQL_ERROR;
+ }
+ return SQL_SUCCESS;
+ }
+
+ conn = (ConnectionClass *) hdbc;
+
+ if (fType == SQL_COMMIT)
+ stmt_string = "COMMIT";
+ else if (fType == SQL_ROLLBACK)
+ stmt_string = "ROLLBACK";
+ else
+ {
+ conn->errornumber = CONN_INVALID_ARGUMENT_NO;
+ conn->errormsg = "PGAPI_Transact can only be called with SQL_COMMIT or SQL_ROLLBACK as parameter";
+ CC_log_error(func, "", conn);
+ return SQL_ERROR;
+ }
+
+ /* If manual commit and in transaction, then proceed. */
+ if (!CC_is_in_autocommit(conn) && CC_is_in_trans(conn))
+ {
+ mylog("PGAPI_Transact: sending on conn %d '%s'\n", conn, stmt_string);
+
+ res = CC_send_query(conn, stmt_string, NULL);
+ CC_set_no_trans(conn);
+
+ if (!res)
+ {
+ /* error msg will be in the connection */
+ CC_log_error(func, "", conn);
+ return SQL_ERROR;
+ }
+
+ ok = QR_command_successful(res);
+ QR_Destructor(res);
+
+ if (!ok)
+ {
+ CC_log_error(func, "", conn);
+ return SQL_ERROR;
+ }
+ }
+ return SQL_SUCCESS;
+}
+
+
+RETCODE SQL_API
+PGAPI_Cancel(
+ HSTMT hstmt) /* Statement to cancel. */
+{
+ static char *func = "PGAPI_Cancel";
+ StatementClass *stmt = (StatementClass *) hstmt;
+ RETCODE result;
+ ConnInfo *ci;
+
+#ifdef WIN32
+ HMODULE hmodule;
+ FARPROC addr;
+#endif
+
+ mylog("%s: entering...\n", func);
+
+ /* Check if this can handle canceling in the middle of a SQLPutData? */
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+ ci = &(SC_get_conn(stmt)->connInfo);
+
+ /*
+ * Not in the middle of SQLParamData/SQLPutData so cancel like a
+ * close.
+ */
+ if (stmt->data_at_exec < 0)
+ {
+ /*
+ * MAJOR HACK for Windows to reset the driver manager's cursor
+ * state: Because of what seems like a bug in the Odbc driver
+ * manager, SQLCancel does not act like a SQLFreeStmt(CLOSE), as
+ * many applications depend on this behavior. So, this brute
+ * force method calls the driver manager's function on behalf of
+ * the application.
+ */
+
+#ifdef WIN32
+ if (ci->drivers.cancel_as_freestmt)
+ {
+ hmodule = GetModuleHandle("ODBC32");
+ addr = GetProcAddress(hmodule, "SQLFreeStmt");
+ result = addr((char *) (stmt->phstmt) - 96, SQL_CLOSE);
+ }
+ else
+ result = PGAPI_FreeStmt(hstmt, SQL_CLOSE);
+#else
+ result = PGAPI_FreeStmt(hstmt, SQL_CLOSE);
+#endif
+
+ mylog("PGAPI_Cancel: PGAPI_FreeStmt returned %d\n", result);
+
+ SC_clear_error(hstmt);
+ return SQL_SUCCESS;
+ }
+
+ /* In the middle of SQLParamData/SQLPutData, so cancel that. */
+
+ /*
+ * Note, any previous data-at-exec buffers will be freed in the
+ * recycle
+ */
+ /* if they call SQLExecDirect or SQLExecute again. */
+
+ stmt->data_at_exec = -1;
+ stmt->current_exec_param = -1;
+ stmt->put_data = FALSE;
+
+ return SQL_SUCCESS;
+}
+
+
+/*
+ * Returns the SQL string as modified by the driver.
+ * Currently, just copy the input string without modification
+ * observing buffer limits and truncation.
+ */
+RETCODE SQL_API
+PGAPI_NativeSql(
+ HDBC hdbc,
+ UCHAR FAR * szSqlStrIn,
+ SDWORD cbSqlStrIn,
+ UCHAR FAR * szSqlStr,
+ SDWORD cbSqlStrMax,
+ SDWORD FAR * pcbSqlStr)
+{
+ static char *func = "PGAPI_NativeSql";
+ int len = 0;
+ char *ptr;
+ ConnectionClass *conn = (ConnectionClass *) hdbc;
+ RETCODE result;
+
+ mylog("%s: entering...cbSqlStrIn=%d\n", func, cbSqlStrIn);
+
+ ptr = (cbSqlStrIn == 0) ? "" : make_string(szSqlStrIn, cbSqlStrIn, NULL);
+ if (!ptr)
+ {
+ conn->errornumber = CONN_NO_MEMORY_ERROR;
+ conn->errormsg = "No memory available to store native sql string";
+ CC_log_error(func, "", conn);
+ return SQL_ERROR;
+ }
+
+ result = SQL_SUCCESS;
+ len = strlen(ptr);
+
+ if (szSqlStr)
+ {
+ strncpy_null(szSqlStr, ptr, cbSqlStrMax);
+
+ if (len >= cbSqlStrMax)
+ {
+ result = SQL_SUCCESS_WITH_INFO;
+ conn->errornumber = STMT_TRUNCATED;
+ conn->errormsg = "The buffer was too small for the NativeSQL.";
+ }
+ }
+
+ if (pcbSqlStr)
+ *pcbSqlStr = len;
+
+ if (cbSqlStrIn)
+ free(ptr);
+
+ return result;
+}
+
+
+/*
+ * Supplies parameter data at execution time.
+ * Used in conjuction with SQLPutData.
+ */
+RETCODE SQL_API
+PGAPI_ParamData(
+ HSTMT hstmt,
+ PTR FAR * prgbValue)
+{
+ static char *func = "PGAPI_ParamData";
+ StatementClass *stmt = (StatementClass *) hstmt;
+ int i,
+ retval;
+ ConnInfo *ci;
+
+ mylog("%s: entering...\n", func);
+
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+ ci = &(SC_get_conn(stmt)->connInfo);
+
+ mylog("%s: data_at_exec=%d, params_alloc=%d\n", func, stmt->data_at_exec, stmt->parameters_allocated);
+
+ if (stmt->data_at_exec < 0)
+ {
+ stmt->errornumber = STMT_SEQUENCE_ERROR;
+ stmt->errormsg = "No execution-time parameters for this statement";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ if (stmt->data_at_exec > stmt->parameters_allocated)
+ {
+ stmt->errornumber = STMT_SEQUENCE_ERROR;
+ stmt->errormsg = "Too many execution-time parameters were present";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ /* close the large object */
+ if (stmt->lobj_fd >= 0)
+ {
+ lo_close(stmt->hdbc, stmt->lobj_fd);
+
+ /* commit transaction if needed */
+ if (!ci->drivers.use_declarefetch && CC_is_in_autocommit(stmt->hdbc))
+ {
+ QResultClass *res;
+ char ok;
+
+ res = CC_send_query(stmt->hdbc, "COMMIT", NULL);
+ if (!res)
+ {
+ stmt->errormsg = "Could not commit (in-line) a transaction";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+ ok = QR_command_successful(res);
+ CC_set_no_trans(stmt->hdbc);
+ QR_Destructor(res);
+ if (!ok)
+ {
+ stmt->errormsg = "Could not commit (in-line) a transaction";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+ }
+ stmt->lobj_fd = -1;
+ }
+
+ /* Done, now copy the params and then execute the statement */
+ if (stmt->data_at_exec == 0)
+ {
+ retval = copy_statement_with_parameters(stmt);
+ if (retval != SQL_SUCCESS)
+ return retval;
+
+ stmt->current_exec_param = -1;
+
+ return SC_execute(stmt);
+ }
+
+ /*
+ * Set beginning param; if first time SQLParamData is called , start
+ * at 0. Otherwise, start at the last parameter + 1.
+ */
+ i = stmt->current_exec_param >= 0 ? stmt->current_exec_param + 1 : 0;
+
+ /* At least 1 data at execution parameter, so Fill in the token value */
+ for (; i < stmt->parameters_allocated; i++)
+ {
+ if (stmt->parameters[i].data_at_exec == TRUE)
+ {
+ stmt->data_at_exec--;
+ stmt->current_exec_param = i;
+ stmt->put_data = FALSE;
+ *prgbValue = stmt->parameters[i].buffer; /* token */
+ break;
+ }
+ }
+
+ return SQL_NEED_DATA;
+}
+
+
+/*
+ * Supplies parameter data at execution time.
+ * Used in conjunction with SQLParamData.
+ */
+RETCODE SQL_API
+PGAPI_PutData(
+ HSTMT hstmt,
+ PTR rgbValue,
+ SDWORD cbValue)
+{
+ static char *func = "PGAPI_PutData";
+ StatementClass *stmt = (StatementClass *) hstmt;
+ int old_pos,
+ retval;
+ ParameterInfoClass *current_param;
+ char *buffer;
+
+ mylog("%s: entering...\n", func);
+
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+
+ if (stmt->current_exec_param < 0)
+ {
+ stmt->errornumber = STMT_SEQUENCE_ERROR;
+ stmt->errormsg = "Previous call was not SQLPutData or SQLParamData";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ current_param = &(stmt->parameters[stmt->current_exec_param]);
+
+ if (!stmt->put_data)
+ { /* first call */
+ mylog("PGAPI_PutData: (1) cbValue = %d\n", cbValue);
+
+ stmt->put_data = TRUE;
+
+ current_param->EXEC_used = (SDWORD *) malloc(sizeof(SDWORD));
+ if (!current_param->EXEC_used)
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Out of memory in PGAPI_PutData (1)";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ *current_param->EXEC_used = cbValue;
+
+ if (cbValue == SQL_NULL_DATA)
+ return SQL_SUCCESS;
+
+ /* Handle Long Var Binary with Large Objects */
+ if (current_param->SQLType == SQL_LONGVARBINARY)
+ {
+ /* begin transaction if needed */
+ if (!CC_is_in_trans(stmt->hdbc))
+ {
+ QResultClass *res;
+ char ok;
+
+ res = CC_send_query(stmt->hdbc, "BEGIN", NULL);
+ if (!res)
+ {
+ stmt->errormsg = "Could not begin (in-line) a transaction";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+ ok = QR_command_successful(res);
+ QR_Destructor(res);
+ if (!ok)
+ {
+ stmt->errormsg = "Could not begin (in-line) a transaction";
+ stmt->errornumber = STMT_EXEC_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ CC_set_in_trans(stmt->hdbc);
+ }
+
+ /* store the oid */
+ current_param->lobj_oid = lo_creat(stmt->hdbc, INV_READ | INV_WRITE);
+ if (current_param->lobj_oid == 0)
+ {
+ stmt->errornumber = STMT_EXEC_ERROR;
+ stmt->errormsg = "Couldnt create large object.";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ /*
+ * major hack -- to allow convert to see somethings there have
+ * to modify convert to handle this better
+ */
+ current_param->EXEC_buffer = (char *) ¤t_param->lobj_oid;
+
+ /* store the fd */
+ stmt->lobj_fd = lo_open(stmt->hdbc, current_param->lobj_oid, INV_WRITE);
+ if (stmt->lobj_fd < 0)
+ {
+ stmt->errornumber = STMT_EXEC_ERROR;
+ stmt->errormsg = "Couldnt open large object for writing.";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ retval = lo_write(stmt->hdbc, stmt->lobj_fd, rgbValue, cbValue);
+ mylog("lo_write: cbValue=%d, wrote %d bytes\n", cbValue, retval);
+ }
+ else
+ {
+ /* for handling fields */
+ if (cbValue == SQL_NTS)
+ {
+ current_param->EXEC_buffer = strdup(rgbValue);
+ if (!current_param->EXEC_buffer)
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Out of memory in PGAPI_PutData (2)";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+ }
+ else
+ {
+ Int2 ctype = current_param->CType;
+
+ if (ctype == SQL_C_DEFAULT)
+ ctype = sqltype_to_default_ctype(current_param->SQLType);
+ if (ctype == SQL_C_CHAR || ctype == SQL_C_BINARY)
+ {
+ current_param->EXEC_buffer = malloc(cbValue + 1);
+ if (!current_param->EXEC_buffer)
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Out of memory in PGAPI_PutData (2)";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+ memcpy(current_param->EXEC_buffer, rgbValue, cbValue);
+ current_param->EXEC_buffer[cbValue] = '\0';
+ }
+ else
+ {
+ Int4 used = ctype_length(ctype);
+
+ current_param->EXEC_buffer = malloc(used);
+ if (!current_param->EXEC_buffer)
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Out of memory in PGAPI_PutData (2)";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+ memcpy(current_param->EXEC_buffer, rgbValue, used);
+ }
+ }
+ }
+ }
+ else
+ {
+ /* calling SQLPutData more than once */
+ mylog("PGAPI_PutData: (>1) cbValue = %d\n", cbValue);
+
+ if (current_param->SQLType == SQL_LONGVARBINARY)
+ {
+ /* the large object fd is in EXEC_buffer */
+ retval = lo_write(stmt->hdbc, stmt->lobj_fd, rgbValue, cbValue);
+ mylog("lo_write(2): cbValue = %d, wrote %d bytes\n", cbValue, retval);
+
+ *current_param->EXEC_used += cbValue;
+ }
+ else
+ {
+ buffer = current_param->EXEC_buffer;
+
+ if (cbValue == SQL_NTS)
+ {
+ buffer = realloc(buffer, strlen(buffer) + strlen(rgbValue) + 1);
+ if (!buffer)
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Out of memory in PGAPI_PutData (3)";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+ strcat(buffer, rgbValue);
+
+ mylog(" cbValue = SQL_NTS: strlen(buffer) = %d\n", strlen(buffer));
+
+ *current_param->EXEC_used = cbValue;
+
+ /* reassign buffer incase realloc moved it */
+ current_param->EXEC_buffer = buffer;
+ }
+ else if (cbValue > 0)
+ {
+ old_pos = *current_param->EXEC_used;
+
+ *current_param->EXEC_used += cbValue;
+
+ mylog(" cbValue = %d, old_pos = %d, *used = %d\n", cbValue, old_pos, *current_param->EXEC_used);
+
+ /* dont lose the old pointer in case out of memory */
+ buffer = realloc(current_param->EXEC_buffer, *current_param->EXEC_used + 1);
+ if (!buffer)
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Out of memory in PGAPI_PutData (3)";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ memcpy(&buffer[old_pos], rgbValue, cbValue);
+ buffer[*current_param->EXEC_used] = '\0';
+
+ /* reassign buffer incase realloc moved it */
+ current_param->EXEC_buffer = buffer;
+ }
+ else
+ {
+ SC_log_error(func, "bad cbValue", stmt);
+ return SQL_ERROR;
+ }
+ }
+ }
+
+ return SQL_SUCCESS;
+}
--- /dev/null
+/*--------
+ * Module: info.c
+ *
+ * Description: This module contains routines related to
+ * ODBC informational functions.
+ *
+ * Classes: n/a
+ *
+ * API functions: SQLGetInfo, SQLGetTypeInfo, SQLGetFunctions,
+ * SQLTables, SQLColumns, SQLStatistics, SQLSpecialColumns,
+ * SQLPrimaryKeys, SQLForeignKeys,
+ * SQLProcedureColumns(NI), SQLProcedures(NI),
+ * SQLTablePrivileges(NI), SQLColumnPrivileges(NI)
+ *
+ * Comments: See "notice.txt" for copyright and license information.
+ *--------
+ */
+
+#include "psqlodbc.h"
+
+#include <string.h>
+#include <stdio.h>
+
+#ifndef WIN32
+#include <ctype.h>
+#endif
+
+#include "tuple.h"
+#include "pgtypes.h"
+
+#include "environ.h"
+#include "connection.h"
+#include "statement.h"
+#include "qresult.h"
+#include "bind.h"
+#include "misc.h"
+#include "pgtypes.h"
+#include "pgapifunc.h"
+
+
+/* Trigger related stuff for SQLForeign Keys */
+#define TRIGGER_SHIFT 3
+#define TRIGGER_MASK 0x03
+#define TRIGGER_DELETE 0x01
+#define TRIGGER_UPDATE 0x02
+
+
+/* extern GLOBAL_VALUES globals; */
+
+
+
+RETCODE SQL_API
+PGAPI_GetInfo(
+ HDBC hdbc,
+ UWORD fInfoType,
+ PTR rgbInfoValue,
+ SWORD cbInfoValueMax,
+ SWORD FAR * pcbInfoValue)
+{
+ static char *func = "PGAPI_GetInfo";
+ ConnectionClass *conn = (ConnectionClass *) hdbc;
+ ConnInfo *ci;
+ char *p = NULL,
+ tmp[MAX_INFO_STRING];
+ int len = 0,
+ value = 0;
+ RETCODE result;
+
+ mylog("%s: entering...fInfoType=%d\n", func, fInfoType);
+
+ if (!conn)
+ {
+ CC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+
+ ci = &(conn->connInfo);
+
+ switch (fInfoType)
+ {
+ case SQL_ACCESSIBLE_PROCEDURES: /* ODBC 1.0 */
+ p = "N";
+ break;
+
+ case SQL_ACCESSIBLE_TABLES: /* ODBC 1.0 */
+ p = "N";
+ break;
+
+ case SQL_ACTIVE_CONNECTIONS: /* ODBC 1.0 */
+ len = 2;
+ value = MAX_CONNECTIONS;
+ break;
+
+ case SQL_ACTIVE_STATEMENTS: /* ODBC 1.0 */
+ len = 2;
+ value = 0;
+ break;
+
+ case SQL_ALTER_TABLE: /* ODBC 2.0 */
+ len = 4;
+ value = SQL_AT_ADD_COLUMN;
+ break;
+
+ case SQL_BOOKMARK_PERSISTENCE: /* ODBC 2.0 */
+ /* very simple bookmark support */
+ len = 4;
+ value = ci->drivers.use_declarefetch ? 0 : (SQL_BP_SCROLL);
+ break;
+
+ case SQL_COLUMN_ALIAS: /* ODBC 2.0 */
+ p = "N";
+ break;
+
+ case SQL_CONCAT_NULL_BEHAVIOR: /* ODBC 1.0 */
+ len = 2;
+ value = SQL_CB_NON_NULL;
+ break;
+
+ case SQL_CONVERT_BIGINT:
+ case SQL_CONVERT_BINARY:
+ case SQL_CONVERT_BIT:
+ case SQL_CONVERT_CHAR:
+ case SQL_CONVERT_DATE:
+ case SQL_CONVERT_DECIMAL:
+ case SQL_CONVERT_DOUBLE:
+ case SQL_CONVERT_FLOAT:
+ case SQL_CONVERT_INTEGER:
+ case SQL_CONVERT_LONGVARBINARY:
+ case SQL_CONVERT_LONGVARCHAR:
+ case SQL_CONVERT_NUMERIC:
+ case SQL_CONVERT_REAL:
+ case SQL_CONVERT_SMALLINT:
+ case SQL_CONVERT_TIME:
+ case SQL_CONVERT_TIMESTAMP:
+ case SQL_CONVERT_TINYINT:
+ case SQL_CONVERT_VARBINARY:
+ case SQL_CONVERT_VARCHAR: /* ODBC 1.0 */
+ len = 4;
+ value = fInfoType;
+ break;
+
+ case SQL_CONVERT_FUNCTIONS: /* ODBC 1.0 */
+ len = 4;
+ value = 0;
+ break;
+
+ case SQL_CORRELATION_NAME: /* ODBC 1.0 */
+
+ /*
+ * Saying no correlation name makes Query not work right.
+ * value = SQL_CN_NONE;
+ */
+ len = 2;
+ value = SQL_CN_ANY;
+ break;
+
+ case SQL_CURSOR_COMMIT_BEHAVIOR: /* ODBC 1.0 */
+ len = 2;
+ value = SQL_CB_CLOSE;
+ if (ci->updatable_cursors)
+ if (!ci->drivers.use_declarefetch)
+ value = SQL_CB_PRESERVE;
+ break;
+
+ case SQL_CURSOR_ROLLBACK_BEHAVIOR: /* ODBC 1.0 */
+ len = 2;
+ value = SQL_CB_CLOSE;
+ if (ci->updatable_cursors)
+ if (!ci->drivers.use_declarefetch)
+ value = SQL_CB_PRESERVE;
+ break;
+
+ case SQL_DATA_SOURCE_NAME: /* ODBC 1.0 */
+ p = CC_get_DSN(conn);
+ break;
+
+ case SQL_DATA_SOURCE_READ_ONLY: /* ODBC 1.0 */
+ p = CC_is_onlyread(conn) ? "Y" : "N";
+ break;
+
+ case SQL_DATABASE_NAME: /* Support for old ODBC 1.0 Apps */
+
+ /*
+ * Returning the database name causes problems in MS Query. It
+ * generates query like: "SELECT DISTINCT a FROM byronnbad3
+ * bad3"
+ *
+ * p = CC_get_database(conn);
+ */
+ p = "";
+ break;
+
+ case SQL_DBMS_NAME: /* ODBC 1.0 */
+ p = DBMS_NAME;
+ break;
+
+ case SQL_DBMS_VER: /* ODBC 1.0 */
+
+ /*
+ * The ODBC spec wants ##.##.#### ...whatever... so prepend
+ * the driver
+ */
+ /* version number to the dbms version string */
+ sprintf(tmp, "%s %s", POSTGRESDRIVERVERSION, conn->pg_version);
+ p = tmp;
+ break;
+
+ case SQL_DEFAULT_TXN_ISOLATION: /* ODBC 1.0 */
+ len = 4;
+ value = SQL_TXN_READ_COMMITTED; /* SQL_TXN_SERIALIZABLE; */
+ break;
+
+ case SQL_DRIVER_NAME: /* ODBC 1.0 */
+ p = DRIVER_FILE_NAME;
+ break;
+
+ case SQL_DRIVER_ODBC_VER:
+ p = DRIVER_ODBC_VER;
+ break;
+
+ case SQL_DRIVER_VER: /* ODBC 1.0 */
+ p = POSTGRESDRIVERVERSION;
+ break;
+
+ case SQL_EXPRESSIONS_IN_ORDERBY: /* ODBC 1.0 */
+ p = "N";
+ break;
+
+ case SQL_FETCH_DIRECTION: /* ODBC 1.0 */
+ len = 4;
+ value = ci->drivers.use_declarefetch ? (SQL_FD_FETCH_NEXT) : (SQL_FD_FETCH_NEXT |
+ SQL_FD_FETCH_FIRST |
+ SQL_FD_FETCH_LAST |
+ SQL_FD_FETCH_PRIOR |
+ SQL_FD_FETCH_ABSOLUTE |
+ SQL_FD_FETCH_RELATIVE |
+ SQL_FD_FETCH_BOOKMARK);
+ break;
+
+ case SQL_FILE_USAGE: /* ODBC 2.0 */
+ len = 2;
+ value = SQL_FILE_NOT_SUPPORTED;
+ break;
+
+ case SQL_GETDATA_EXTENSIONS: /* ODBC 2.0 */
+ len = 4;
+ value = (SQL_GD_ANY_COLUMN | SQL_GD_ANY_ORDER | SQL_GD_BOUND | SQL_GD_BLOCK);
+ break;
+
+ case SQL_GROUP_BY: /* ODBC 2.0 */
+ len = 2;
+ value = SQL_GB_GROUP_BY_EQUALS_SELECT;
+ break;
+
+ case SQL_IDENTIFIER_CASE: /* ODBC 1.0 */
+
+ /*
+ * are identifiers case-sensitive (yes, but only when quoted.
+ * If not quoted, they default to lowercase)
+ */
+ len = 2;
+ value = SQL_IC_LOWER;
+ break;
+
+ case SQL_IDENTIFIER_QUOTE_CHAR: /* ODBC 1.0 */
+ /* the character used to quote "identifiers" */
+ p = PG_VERSION_LE(conn, 6.2) ? " " : "\"";
+ break;
+
+ case SQL_KEYWORDS: /* ODBC 2.0 */
+ p = "";
+ break;
+
+ case SQL_LIKE_ESCAPE_CLAUSE: /* ODBC 2.0 */
+
+ /*
+ * is there a character that escapes '%' and '_' in a LIKE
+ * clause? not as far as I can tell
+ */
+ p = "N";
+ break;
+
+ case SQL_LOCK_TYPES: /* ODBC 2.0 */
+ len = 4;
+ value = ci->drivers.lie ? (SQL_LCK_NO_CHANGE | SQL_LCK_EXCLUSIVE | SQL_LCK_UNLOCK) : SQL_LCK_NO_CHANGE;
+ break;
+
+ case SQL_MAX_BINARY_LITERAL_LEN: /* ODBC 2.0 */
+ len = 4;
+ value = 0;
+ break;
+
+ case SQL_MAX_CHAR_LITERAL_LEN: /* ODBC 2.0 */
+ len = 4;
+ value = 0;
+ break;
+
+ case SQL_MAX_COLUMN_NAME_LEN: /* ODBC 1.0 */
+ len = 2;
+ value = MAX_COLUMN_LEN;
+ break;
+
+ case SQL_MAX_COLUMNS_IN_GROUP_BY: /* ODBC 2.0 */
+ len = 2;
+ value = 0;
+ break;
+
+ case SQL_MAX_COLUMNS_IN_INDEX: /* ODBC 2.0 */
+ len = 2;
+ value = 0;
+ break;
+
+ case SQL_MAX_COLUMNS_IN_ORDER_BY: /* ODBC 2.0 */
+ len = 2;
+ value = 0;
+ break;
+
+ case SQL_MAX_COLUMNS_IN_SELECT: /* ODBC 2.0 */
+ len = 2;
+ value = 0;
+ break;
+
+ case SQL_MAX_COLUMNS_IN_TABLE: /* ODBC 2.0 */
+ len = 2;
+ value = 0;
+ break;
+
+ case SQL_MAX_CURSOR_NAME_LEN: /* ODBC 1.0 */
+ len = 2;
+ value = MAX_CURSOR_LEN;
+ break;
+
+ case SQL_MAX_INDEX_SIZE: /* ODBC 2.0 */
+ len = 4;
+ value = 0;
+ break;
+
+ case SQL_MAX_OWNER_NAME_LEN: /* ODBC 1.0 */
+ len = 2;
+ value = 0;
+ break;
+
+ case SQL_MAX_PROCEDURE_NAME_LEN: /* ODBC 1.0 */
+ len = 2;
+ value = 0;
+ break;
+
+ case SQL_MAX_QUALIFIER_NAME_LEN: /* ODBC 1.0 */
+ len = 2;
+ value = 0;
+ break;
+
+ case SQL_MAX_ROW_SIZE: /* ODBC 2.0 */
+ len = 4;
+ if (PG_VERSION_GE(conn, 7.1))
+ {
+ /* Large Rowa in 7.1+ */
+ value = MAX_ROW_SIZE;
+ }
+ else
+ {
+ /* Without the Toaster we're limited to the blocksize */
+ value = BLCKSZ;
+ }
+ break;
+
+ case SQL_MAX_ROW_SIZE_INCLUDES_LONG: /* ODBC 2.0 */
+
+ /*
+ * does the preceding value include LONGVARCHAR and
+ * LONGVARBINARY fields? Well, it does include longvarchar,
+ * but not longvarbinary.
+ */
+ p = "Y";
+ break;
+
+ case SQL_MAX_STATEMENT_LEN: /* ODBC 2.0 */
+ /* maybe this should be 0? */
+ len = 4;
+ value = CC_get_max_query_len(conn);
+ break;
+
+ case SQL_MAX_TABLE_NAME_LEN: /* ODBC 1.0 */
+ len = 2;
+ value = MAX_TABLE_LEN;
+ break;
+
+ case SQL_MAX_TABLES_IN_SELECT: /* ODBC 2.0 */
+ len = 2;
+ value = 0;
+ break;
+
+ case SQL_MAX_USER_NAME_LEN:
+ len = 2;
+ value = 0;
+ break;
+
+ case SQL_MULT_RESULT_SETS: /* ODBC 1.0 */
+ /* Don't support multiple result sets but say yes anyway? */
+ p = "Y";
+ break;
+
+ case SQL_MULTIPLE_ACTIVE_TXN: /* ODBC 1.0 */
+ p = "Y";
+ break;
+
+ case SQL_NEED_LONG_DATA_LEN: /* ODBC 2.0 */
+
+ /*
+ * Don't need the length, SQLPutData can handle any size and
+ * multiple calls
+ */
+ p = "N";
+ break;
+
+ case SQL_NON_NULLABLE_COLUMNS: /* ODBC 1.0 */
+ len = 2;
+ value = SQL_NNC_NON_NULL;
+ break;
+
+ case SQL_NULL_COLLATION: /* ODBC 2.0 */
+ /* where are nulls sorted? */
+ len = 2;
+ value = SQL_NC_END;
+ break;
+
+ case SQL_NUMERIC_FUNCTIONS: /* ODBC 1.0 */
+ len = 4;
+ value = 0;
+ break;
+
+ case SQL_ODBC_API_CONFORMANCE: /* ODBC 1.0 */
+ len = 2;
+ value = SQL_OAC_LEVEL1;
+ break;
+
+ case SQL_ODBC_SAG_CLI_CONFORMANCE: /* ODBC 1.0 */
+ len = 2;
+ value = SQL_OSCC_NOT_COMPLIANT;
+ break;
+
+ case SQL_ODBC_SQL_CONFORMANCE: /* ODBC 1.0 */
+ len = 2;
+ value = SQL_OSC_CORE;
+ break;
+
+ case SQL_ODBC_SQL_OPT_IEF: /* ODBC 1.0 */
+ p = "N";
+ break;
+
+ case SQL_OJ_CAPABILITIES: /* ODBC 2.01 */
+ len = 4;
+ if (PG_VERSION_GE(conn, 7.1))
+ {
+ /* OJs in 7.1+ */
+ value = (SQL_OJ_LEFT |
+ SQL_OJ_RIGHT |
+ SQL_OJ_FULL |
+ SQL_OJ_NESTED |
+ SQL_OJ_NOT_ORDERED |
+ SQL_OJ_INNER |
+ SQL_OJ_ALL_COMPARISON_OPS);
+ }
+ else
+ /* OJs not in <7.1 */
+ value = 0;
+ break;
+
+ case SQL_ORDER_BY_COLUMNS_IN_SELECT: /* ODBC 2.0 */
+ p = (PG_VERSION_LE(conn, 6.3)) ? "Y" : "N";
+ break;
+
+ case SQL_OUTER_JOINS: /* ODBC 1.0 */
+ if (PG_VERSION_GE(conn, 7.1))
+ /* OJs in 7.1+ */
+ p = "Y";
+ else
+ /* OJs not in <7.1 */
+ p = "N";
+ break;
+
+ case SQL_OWNER_TERM: /* ODBC 1.0 */
+ p = "owner";
+ break;
+
+ case SQL_OWNER_USAGE: /* ODBC 2.0 */
+ len = 4;
+ value = 0;
+ break;
+
+ case SQL_POS_OPERATIONS: /* ODBC 2.0 */
+ len = 4;
+ value = ci->drivers.lie ? (SQL_POS_POSITION | SQL_POS_REFRESH | SQL_POS_UPDATE | SQL_POS_DELETE | SQL_POS_ADD) : (SQL_POS_POSITION | SQL_POS_REFRESH);
+ if (ci->updatable_cursors)
+ value |= (SQL_POS_UPDATE | SQL_POS_DELETE | SQL_POS_ADD);
+ break;
+
+ case SQL_POSITIONED_STATEMENTS: /* ODBC 2.0 */
+ len = 4;
+ value = ci->drivers.lie ? (SQL_PS_POSITIONED_DELETE |
+ SQL_PS_POSITIONED_UPDATE |
+ SQL_PS_SELECT_FOR_UPDATE) : 0;
+ break;
+
+ case SQL_PROCEDURE_TERM: /* ODBC 1.0 */
+ p = "procedure";
+ break;
+
+ case SQL_PROCEDURES: /* ODBC 1.0 */
+ p = "Y";
+ break;
+
+ case SQL_QUALIFIER_LOCATION: /* ODBC 2.0 */
+ len = 2;
+ value = SQL_QL_START;
+ break;
+
+ case SQL_QUALIFIER_NAME_SEPARATOR: /* ODBC 1.0 */
+ p = "";
+ break;
+
+ case SQL_QUALIFIER_TERM: /* ODBC 1.0 */
+ p = "";
+ break;
+
+ case SQL_QUALIFIER_USAGE: /* ODBC 2.0 */
+ len = 4;
+ value = 0;
+ break;
+
+ case SQL_QUOTED_IDENTIFIER_CASE: /* ODBC 2.0 */
+ /* are "quoted" identifiers case-sensitive? YES! */
+ len = 2;
+ value = SQL_IC_SENSITIVE;
+ break;
+
+ case SQL_ROW_UPDATES: /* ODBC 1.0 */
+
+ /*
+ * Driver doesn't support keyset-driven or mixed cursors, so
+ * not much point in saying row updates are supported
+ */
+ p = (ci->drivers.lie || ci->updatable_cursors) ? "Y" : "N";
+ break;
+
+ case SQL_SCROLL_CONCURRENCY: /* ODBC 1.0 */
+ len = 4;
+ value = ci->drivers.lie ? (SQL_SCCO_READ_ONLY |
+ SQL_SCCO_LOCK |
+ SQL_SCCO_OPT_ROWVER |
+ SQL_SCCO_OPT_VALUES) :
+ (SQL_SCCO_READ_ONLY);
+ if (ci->updatable_cursors)
+ value |= SQL_SCCO_OPT_ROWVER;
+ break;
+
+ case SQL_SCROLL_OPTIONS: /* ODBC 1.0 */
+ len = 4;
+ value = ci->drivers.lie ? (SQL_SO_FORWARD_ONLY |
+ SQL_SO_STATIC |
+ SQL_SO_KEYSET_DRIVEN |
+ SQL_SO_DYNAMIC |
+ SQL_SO_MIXED)
+ : (ci->drivers.use_declarefetch ? SQL_SO_FORWARD_ONLY : (SQL_SO_FORWARD_ONLY | SQL_SO_STATIC));
+ if (ci->updatable_cursors)
+ value |= 0; /* SQL_SO_KEYSET_DRIVEN in the furure */
+ break;
+
+ case SQL_SEARCH_PATTERN_ESCAPE: /* ODBC 1.0 */
+ p = "";
+ break;
+
+ case SQL_SERVER_NAME: /* ODBC 1.0 */
+ p = CC_get_server(conn);
+ break;
+
+ case SQL_SPECIAL_CHARACTERS: /* ODBC 2.0 */
+ p = "_";
+ break;
+
+ case SQL_STATIC_SENSITIVITY: /* ODBC 2.0 */
+ len = 4;
+ value = ci->drivers.lie ? (SQL_SS_ADDITIONS | SQL_SS_DELETIONS | SQL_SS_UPDATES) : 0;
+ if (ci->updatable_cursors)
+ value |= (SQL_SS_ADDITIONS | SQL_SS_DELETIONS | SQL_SS_UPDATES);
+ break;
+
+ case SQL_STRING_FUNCTIONS: /* ODBC 1.0 */
+ len = 4;
+ value = (SQL_FN_STR_CONCAT |
+ SQL_FN_STR_LCASE |
+ SQL_FN_STR_LENGTH |
+ SQL_FN_STR_LOCATE |
+ SQL_FN_STR_LTRIM |
+ SQL_FN_STR_RTRIM |
+ SQL_FN_STR_SUBSTRING |
+ SQL_FN_STR_UCASE);
+ break;
+
+ case SQL_SUBQUERIES: /* ODBC 2.0 */
+ /* postgres 6.3 supports subqueries */
+ len = 4;
+ value = (SQL_SQ_QUANTIFIED |
+ SQL_SQ_IN |
+ SQL_SQ_EXISTS |
+ SQL_SQ_COMPARISON);
+ break;
+
+ case SQL_SYSTEM_FUNCTIONS: /* ODBC 1.0 */
+ len = 4;
+ value = 0;
+ break;
+
+ case SQL_TABLE_TERM: /* ODBC 1.0 */
+ p = "table";
+ break;
+
+ case SQL_TIMEDATE_ADD_INTERVALS: /* ODBC 2.0 */
+ len = 4;
+ value = 0;
+ break;
+
+ case SQL_TIMEDATE_DIFF_INTERVALS: /* ODBC 2.0 */
+ len = 4;
+ value = 0;
+ break;
+
+ case SQL_TIMEDATE_FUNCTIONS: /* ODBC 1.0 */
+ len = 4;
+ value = (SQL_FN_TD_NOW);
+ break;
+
+ case SQL_TXN_CAPABLE: /* ODBC 1.0 */
+
+ /*
+ * Postgres can deal with create or drop table statements in a
+ * transaction
+ */
+ len = 2;
+ value = SQL_TC_ALL;
+ break;
+
+ case SQL_TXN_ISOLATION_OPTION: /* ODBC 1.0 */
+ len = 4;
+ value = SQL_TXN_READ_COMMITTED; /* SQL_TXN_SERIALIZABLE; */
+ break;
+
+ case SQL_UNION: /* ODBC 2.0 */
+ /* unions with all supported in postgres 6.3 */
+ len = 4;
+ value = (SQL_U_UNION | SQL_U_UNION_ALL);
+ break;
+
+ case SQL_USER_NAME: /* ODBC 1.0 */
+ p = CC_get_username(conn);
+ break;
+
+ default:
+ /* unrecognized key */
+ conn->errormsg = "Unrecognized key passed to PGAPI_GetInfo.";
+ conn->errornumber = CONN_NOT_IMPLEMENTED_ERROR;
+ CC_log_error(func, "", conn);
+ return SQL_ERROR;
+ }
+
+ result = SQL_SUCCESS;
+
+ mylog("%s: p='%s', len=%d, value=%d, cbMax=%d\n", func, p ? p : "<NULL>", len, value, cbInfoValueMax);
+
+ /*
+ * NOTE, that if rgbInfoValue is NULL, then no warnings or errors
+ * should result and just pcbInfoValue is returned, which indicates
+ * what length would be required if a real buffer had been passed in.
+ */
+ if (p)
+ {
+ /* char/binary data */
+ len = strlen(p);
+
+ if (rgbInfoValue)
+ {
+ strncpy_null((char *) rgbInfoValue, p, (size_t) cbInfoValueMax);
+
+ if (len >= cbInfoValueMax)
+ {
+ result = SQL_SUCCESS_WITH_INFO;
+ conn->errornumber = STMT_TRUNCATED;
+ conn->errormsg = "The buffer was too small for tthe InfoValue.";
+ }
+ }
+ }
+ else
+ {
+ /* numeric data */
+ if (rgbInfoValue)
+ {
+ if (len == 2)
+ *((WORD *) rgbInfoValue) = (WORD) value;
+ else if (len == 4)
+ *((DWORD *) rgbInfoValue) = (DWORD) value;
+ }
+ }
+
+ if (pcbInfoValue)
+ *pcbInfoValue = len;
+
+ return result;
+}
+
+
+RETCODE SQL_API
+PGAPI_GetTypeInfo(
+ HSTMT hstmt,
+ SWORD fSqlType)
+{
+ static char *func = "PGAPI_GetTypeInfo";
+ StatementClass *stmt = (StatementClass *) hstmt;
+ TupleNode *row;
+ int i;
+
+ /* Int4 type; */
+ Int4 pgType;
+ Int2 sqlType;
+
+ mylog("%s: entering...fSqlType = %d\n", func, fSqlType);
+
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+
+ stmt->manual_result = TRUE;
+ stmt->result = QR_Constructor();
+ if (!stmt->result)
+ {
+ SC_log_error(func, "Error creating result.", stmt);
+ return SQL_ERROR;
+ }
+
+ extend_bindings(stmt, 15);
+
+ QR_set_num_fields(stmt->result, 15);
+ QR_set_field_info(stmt->result, 0, "TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 1, "DATA_TYPE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 2, "PRECISION", PG_TYPE_INT4, 4);
+ QR_set_field_info(stmt->result, 3, "LITERAL_PREFIX", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 4, "LITERAL_SUFFIX", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 5, "CREATE_PARAMS", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 6, "NULLABLE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 7, "CASE_SENSITIVE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 8, "SEARCHABLE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 9, "UNSIGNED_ATTRIBUTE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 10, "MONEY", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 11, "AUTO_INCREMENT", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 12, "LOCAL_TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 13, "MINIMUM_SCALE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 14, "MAXIMUM_SCALE", PG_TYPE_INT2, 2);
+
+ for (i = 0, sqlType = sqlTypes[0]; sqlType; sqlType = sqlTypes[++i])
+ {
+ pgType = sqltype_to_pgtype(stmt, sqlType);
+
+ if (fSqlType == SQL_ALL_TYPES || fSqlType == sqlType)
+ {
+ row = (TupleNode *) malloc(sizeof(TupleNode) + (15 - 1) *sizeof(TupleField));
+
+ /* These values can't be NULL */
+ set_tuplefield_string(&row->tuple[0], pgtype_to_name(stmt, pgType));
+ set_tuplefield_int2(&row->tuple[1], (Int2) sqlType);
+ set_tuplefield_int2(&row->tuple[6], pgtype_nullable(stmt, pgType));
+ set_tuplefield_int2(&row->tuple[7], pgtype_case_sensitive(stmt, pgType));
+ set_tuplefield_int2(&row->tuple[8], pgtype_searchable(stmt, pgType));
+ set_tuplefield_int2(&row->tuple[10], pgtype_money(stmt, pgType));
+
+ /*
+ * Localized data-source dependent data type name (always
+ * NULL)
+ */
+ set_tuplefield_null(&row->tuple[12]);
+
+ /* These values can be NULL */
+ set_nullfield_int4(&row->tuple[2], pgtype_precision(stmt, pgType, PG_STATIC, PG_STATIC));
+ set_nullfield_string(&row->tuple[3], pgtype_literal_prefix(stmt, pgType));
+ set_nullfield_string(&row->tuple[4], pgtype_literal_suffix(stmt, pgType));
+ set_nullfield_string(&row->tuple[5], pgtype_create_params(stmt, pgType));
+ set_nullfield_int2(&row->tuple[9], pgtype_unsigned(stmt, pgType));
+ set_nullfield_int2(&row->tuple[11], pgtype_auto_increment(stmt, pgType));
+ set_nullfield_int2(&row->tuple[13], pgtype_scale(stmt, pgType, PG_STATIC));
+ set_nullfield_int2(&row->tuple[14], pgtype_scale(stmt, pgType, PG_STATIC));
+
+ QR_add_tuple(stmt->result, row);
+ }
+ }
+
+ stmt->status = STMT_FINISHED;
+ stmt->currTuple = -1;
+ stmt->rowset_start = -1;
+ stmt->current_col = -1;
+
+ return SQL_SUCCESS;
+}
+
+
+RETCODE SQL_API
+PGAPI_GetFunctions(
+ HDBC hdbc,
+ UWORD fFunction,
+ UWORD FAR * pfExists)
+{
+ static char *func = "PGAPI_GetFunctions";
+ ConnectionClass *conn = (ConnectionClass *) hdbc;
+ ConnInfo *ci = &(conn->connInfo);
+
+ mylog("%s: entering...%u\n", func, fFunction);
+
+ if (fFunction == SQL_API_ALL_FUNCTIONS)
+ {
+#if (ODBCVER < 0x0300)
+ if (ci->drivers.lie)
+ {
+ int i;
+
+ memset(pfExists, 0, sizeof(UWORD) * 100);
+
+ pfExists[SQL_API_SQLALLOCENV] = TRUE;
+ pfExists[SQL_API_SQLFREEENV] = TRUE;
+ for (i = SQL_API_SQLALLOCCONNECT; i <= SQL_NUM_FUNCTIONS; i++)
+ pfExists[i] = TRUE;
+ for (i = SQL_EXT_API_START; i <= SQL_EXT_API_LAST; i++)
+ pfExists[i] = TRUE;
+ }
+ else
+#endif
+ {
+ memset(pfExists, 0, sizeof(UWORD) * 100);
+
+ /* ODBC core functions */
+ pfExists[SQL_API_SQLALLOCCONNECT] = TRUE;
+ pfExists[SQL_API_SQLALLOCENV] = TRUE;
+ pfExists[SQL_API_SQLALLOCSTMT] = TRUE;
+ pfExists[SQL_API_SQLBINDCOL] = TRUE;
+ pfExists[SQL_API_SQLCANCEL] = TRUE;
+ pfExists[SQL_API_SQLCOLATTRIBUTES] = TRUE;
+ pfExists[SQL_API_SQLCONNECT] = TRUE;
+ pfExists[SQL_API_SQLDESCRIBECOL] = TRUE; /* partial */
+ pfExists[SQL_API_SQLDISCONNECT] = TRUE;
+ pfExists[SQL_API_SQLERROR] = TRUE;
+ pfExists[SQL_API_SQLEXECDIRECT] = TRUE;
+ pfExists[SQL_API_SQLEXECUTE] = TRUE;
+ pfExists[SQL_API_SQLFETCH] = TRUE;
+ pfExists[SQL_API_SQLFREECONNECT] = TRUE;
+ pfExists[SQL_API_SQLFREEENV] = TRUE;
+ pfExists[SQL_API_SQLFREESTMT] = TRUE;
+ pfExists[SQL_API_SQLGETCURSORNAME] = TRUE;
+ pfExists[SQL_API_SQLNUMRESULTCOLS] = TRUE;
+ pfExists[SQL_API_SQLPREPARE] = TRUE; /* complete? */
+ pfExists[SQL_API_SQLROWCOUNT] = TRUE;
+ pfExists[SQL_API_SQLSETCURSORNAME] = TRUE;
+ pfExists[SQL_API_SQLSETPARAM] = FALSE; /* odbc 1.0 */
+ pfExists[SQL_API_SQLTRANSACT] = TRUE;
+
+ /* ODBC level 1 functions */
+ pfExists[SQL_API_SQLBINDPARAMETER] = TRUE;
+ pfExists[SQL_API_SQLCOLUMNS] = TRUE;
+ pfExists[SQL_API_SQLDRIVERCONNECT] = TRUE;
+ pfExists[SQL_API_SQLGETCONNECTOPTION] = TRUE; /* partial */
+ pfExists[SQL_API_SQLGETDATA] = TRUE;
+ pfExists[SQL_API_SQLGETFUNCTIONS] = TRUE;
+ pfExists[SQL_API_SQLGETINFO] = TRUE;
+ pfExists[SQL_API_SQLGETSTMTOPTION] = TRUE; /* partial */
+ pfExists[SQL_API_SQLGETTYPEINFO] = TRUE;
+ pfExists[SQL_API_SQLPARAMDATA] = TRUE;
+ pfExists[SQL_API_SQLPUTDATA] = TRUE;
+ pfExists[SQL_API_SQLSETCONNECTOPTION] = TRUE; /* partial */
+ pfExists[SQL_API_SQLSETSTMTOPTION] = TRUE;
+ pfExists[SQL_API_SQLSPECIALCOLUMNS] = TRUE;
+ pfExists[SQL_API_SQLSTATISTICS] = TRUE;
+ pfExists[SQL_API_SQLTABLES] = TRUE;
+
+ /* ODBC level 2 functions */
+ pfExists[SQL_API_SQLBROWSECONNECT] = FALSE;
+ pfExists[SQL_API_SQLCOLUMNPRIVILEGES] = FALSE;
+ pfExists[SQL_API_SQLDATASOURCES] = FALSE; /* only implemented by
+ * DM */
+ pfExists[SQL_API_SQLDESCRIBEPARAM] = FALSE; /* not properly
+ * implemented */
+ pfExists[SQL_API_SQLDRIVERS] = FALSE; /* only implemented by
+ * DM */
+ pfExists[SQL_API_SQLEXTENDEDFETCH] = TRUE;
+ pfExists[SQL_API_SQLFOREIGNKEYS] = TRUE;
+ pfExists[SQL_API_SQLMORERESULTS] = TRUE;
+ pfExists[SQL_API_SQLNATIVESQL] = TRUE;
+ pfExists[SQL_API_SQLNUMPARAMS] = TRUE;
+ pfExists[SQL_API_SQLPARAMOPTIONS] = FALSE;
+ pfExists[SQL_API_SQLPRIMARYKEYS] = TRUE;
+ pfExists[SQL_API_SQLPROCEDURECOLUMNS] = FALSE;
+ if (PG_VERSION_LT(conn, 6.5))
+ pfExists[SQL_API_SQLPROCEDURES] = FALSE;
+ else
+ pfExists[SQL_API_SQLPROCEDURES] = TRUE;
+ pfExists[SQL_API_SQLSETPOS] = TRUE;
+ pfExists[SQL_API_SQLSETSCROLLOPTIONS] = TRUE; /* odbc 1.0 */
+ pfExists[SQL_API_SQLTABLEPRIVILEGES] = FALSE;
+ }
+ }
+ else
+ {
+ if (ci->drivers.lie)
+ *pfExists = TRUE;
+ else
+ {
+ switch (fFunction)
+ {
+ case SQL_API_SQLALLOCCONNECT:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLALLOCENV:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLALLOCSTMT:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLBINDCOL:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLCANCEL:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLCOLATTRIBUTES:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLCONNECT:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLDESCRIBECOL:
+ *pfExists = TRUE;
+ break; /* partial */
+ case SQL_API_SQLDISCONNECT:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLERROR:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLEXECDIRECT:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLEXECUTE:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLFETCH:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLFREECONNECT:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLFREEENV:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLFREESTMT:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLGETCURSORNAME:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLNUMRESULTCOLS:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLPREPARE:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLROWCOUNT:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLSETCURSORNAME:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLSETPARAM:
+ *pfExists = FALSE;
+ break; /* odbc 1.0 */
+ case SQL_API_SQLTRANSACT:
+ *pfExists = TRUE;
+ break;
+
+ /* ODBC level 1 functions */
+ case SQL_API_SQLBINDPARAMETER:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLCOLUMNS:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLDRIVERCONNECT:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLGETCONNECTOPTION:
+ *pfExists = TRUE;
+ break; /* partial */
+ case SQL_API_SQLGETDATA:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLGETFUNCTIONS:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLGETINFO:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLGETSTMTOPTION:
+ *pfExists = TRUE;
+ break; /* partial */
+ case SQL_API_SQLGETTYPEINFO:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLPARAMDATA:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLPUTDATA:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLSETCONNECTOPTION:
+ *pfExists = TRUE;
+ break; /* partial */
+ case SQL_API_SQLSETSTMTOPTION:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLSPECIALCOLUMNS:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLSTATISTICS:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLTABLES:
+ *pfExists = TRUE;
+ break;
+
+ /* ODBC level 2 functions */
+ case SQL_API_SQLBROWSECONNECT:
+ *pfExists = FALSE;
+ break;
+ case SQL_API_SQLCOLUMNPRIVILEGES:
+ *pfExists = FALSE;
+ break;
+ case SQL_API_SQLDATASOURCES:
+ *pfExists = FALSE;
+ break; /* only implemented by DM */
+ case SQL_API_SQLDESCRIBEPARAM:
+ *pfExists = FALSE;
+ break; /* not properly implemented */
+ case SQL_API_SQLDRIVERS:
+ *pfExists = FALSE;
+ break; /* only implemented by DM */
+ case SQL_API_SQLEXTENDEDFETCH:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLFOREIGNKEYS:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLMORERESULTS:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLNATIVESQL:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLNUMPARAMS:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLPARAMOPTIONS:
+ *pfExists = FALSE;
+ break;
+ case SQL_API_SQLPRIMARYKEYS:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLPROCEDURECOLUMNS:
+ *pfExists = FALSE;
+ break;
+ case SQL_API_SQLPROCEDURES:
+ if (PG_VERSION_LT(conn, 6.5))
+ *pfExists = FALSE;
+ else
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLSETPOS:
+ *pfExists = TRUE;
+ break;
+ case SQL_API_SQLSETSCROLLOPTIONS:
+ *pfExists = TRUE;
+ break; /* odbc 1.0 */
+ case SQL_API_SQLTABLEPRIVILEGES:
+ *pfExists = FALSE;
+ break;
+ }
+ }
+ }
+ return SQL_SUCCESS;
+}
+
+
+RETCODE SQL_API
+PGAPI_Tables(
+ HSTMT hstmt,
+ UCHAR FAR * szTableQualifier,
+ SWORD cbTableQualifier,
+ UCHAR FAR * szTableOwner,
+ SWORD cbTableOwner,
+ UCHAR FAR * szTableName,
+ SWORD cbTableName,
+ UCHAR FAR * szTableType,
+ SWORD cbTableType)
+{
+ static char *func = "PGAPI_Tables";
+ StatementClass *stmt = (StatementClass *) hstmt;
+ StatementClass *tbl_stmt;
+ TupleNode *row;
+ HSTMT htbl_stmt;
+ RETCODE result;
+ char *tableType;
+ char tables_query[INFO_INQUIRY_LEN];
+ char table_name[MAX_INFO_STRING],
+ table_owner[MAX_INFO_STRING],
+ relkind_or_hasrules[MAX_INFO_STRING];
+ ConnectionClass *conn;
+ ConnInfo *ci;
+ char *prefix[32],
+ prefixes[MEDIUM_REGISTRY_LEN];
+ char *table_type[32],
+ table_types[MAX_INFO_STRING];
+ char show_system_tables,
+ show_regular_tables,
+ show_views;
+ char regular_table,
+ view,
+ systable;
+ int i;
+
+ mylog("%s: entering...stmt=%u\n", func, stmt);
+
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+
+ stmt->manual_result = TRUE;
+ stmt->errormsg_created = TRUE;
+
+ conn = SC_get_conn(stmt);
+ ci = &(conn->connInfo);
+
+ result = PGAPI_AllocStmt(stmt->hdbc, &htbl_stmt);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Couldn't allocate statement for PGAPI_Tables result.";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+ tbl_stmt = (StatementClass *) htbl_stmt;
+
+ /*
+ * Create the query to find out the tables
+ */
+ if (PG_VERSION_GE(conn, 7.1))
+ {
+ /* view is represented by its relkind since 7.1 */
+ strcpy(tables_query, "select relname, usename, relkind from pg_class, pg_user");
+ strcat(tables_query, " where relkind in ('r', 'v')");
+ }
+ else
+ {
+ strcpy(tables_query, "select relname, usename, relhasrules from pg_class, pg_user");
+ strcat(tables_query, " where relkind = 'r'");
+ }
+
+ my_strcat(tables_query, " and usename like '%.*s'", szTableOwner, cbTableOwner);
+ my_strcat(tables_query, " and relname like '%.*s'", szTableName, cbTableName);
+
+ /* Parse the extra systable prefix */
+ strcpy(prefixes, ci->drivers.extra_systable_prefixes);
+ i = 0;
+ prefix[i] = strtok(prefixes, ";");
+ while (prefix[i] && i < 32)
+ prefix[++i] = strtok(NULL, ";");
+
+ /* Parse the desired table types to return */
+ show_system_tables = FALSE;
+ show_regular_tables = FALSE;
+ show_views = FALSE;
+
+ /* make_string mallocs memory */
+ tableType = make_string(szTableType, cbTableType, NULL);
+ if (tableType)
+ {
+ strcpy(table_types, tableType);
+ free(tableType);
+ i = 0;
+ table_type[i] = strtok(table_types, ",");
+ while (table_type[i] && i < 32)
+ table_type[++i] = strtok(NULL, ",");
+
+ /* Check for desired table types to return */
+ i = 0;
+ while (table_type[i])
+ {
+ if (strstr(table_type[i], "SYSTEM TABLE"))
+ show_system_tables = TRUE;
+ else if (strstr(table_type[i], "TABLE"))
+ show_regular_tables = TRUE;
+ else if (strstr(table_type[i], "VIEW"))
+ show_views = TRUE;
+ i++;
+ }
+ }
+ else
+ {
+ show_regular_tables = TRUE;
+ show_views = TRUE;
+ }
+
+ /*
+ * If not interested in SYSTEM TABLES then filter them out to save
+ * some time on the query. If treating system tables as regular
+ * tables, then dont filter either.
+ */
+ if (!atoi(ci->show_system_tables) && !show_system_tables)
+ {
+ strcat(tables_query, " and relname !~ '^" POSTGRES_SYS_PREFIX);
+
+ /* Also filter out user-defined system table types */
+ i = 0;
+ while (prefix[i])
+ {
+ strcat(tables_query, "|^");
+ strcat(tables_query, prefix[i]);
+ i++;
+ }
+ strcat(tables_query, "'");
+ }
+
+ /* match users */
+ if (PG_VERSION_LT(conn, 7.1))
+ /* filter out large objects in older versions */
+ strcat(tables_query, " and relname !~ '^xinv[0-9]+'");
+
+ strcat(tables_query, " and usesysid = relowner");
+ strcat(tables_query, " order by relname");
+
+ result = PGAPI_ExecDirect(htbl_stmt, tables_query, strlen(tables_query));
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = SC_create_errormsg(htbl_stmt);
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(htbl_stmt, 1, SQL_C_CHAR,
+ table_name, MAX_INFO_STRING, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(htbl_stmt, 2, SQL_C_CHAR,
+ table_owner, MAX_INFO_STRING, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+ result = PGAPI_BindCol(htbl_stmt, 3, SQL_C_CHAR,
+ relkind_or_hasrules, MAX_INFO_STRING, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ stmt->result = QR_Constructor();
+ if (!stmt->result)
+ {
+ stmt->errormsg = "Couldn't allocate memory for PGAPI_Tables result.";
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ /* the binding structure for a statement is not set up until */
+
+ /*
+ * a statement is actually executed, so we'll have to do this
+ * ourselves.
+ */
+ extend_bindings(stmt, 5);
+
+ /* set the field names */
+ QR_set_num_fields(stmt->result, 5);
+ QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 3, "TABLE_TYPE", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 4, "REMARKS", PG_TYPE_TEXT, 254);
+
+ /* add the tuples */
+ result = PGAPI_Fetch(htbl_stmt);
+ while ((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO))
+ {
+ /*
+ * Determine if this table name is a system table. If treating
+ * system tables as regular tables, then no need to do this test.
+ */
+ systable = FALSE;
+ if (!atoi(ci->show_system_tables))
+ {
+ if (strncmp(table_name, POSTGRES_SYS_PREFIX, strlen(POSTGRES_SYS_PREFIX)) == 0)
+ systable = TRUE;
+
+ else
+ {
+ /* Check extra system table prefixes */
+ i = 0;
+ while (prefix[i])
+ {
+ mylog("table_name='%s', prefix[%d]='%s'\n", table_name, i, prefix[i]);
+ if (strncmp(table_name, prefix[i], strlen(prefix[i])) == 0)
+ {
+ systable = TRUE;
+ break;
+ }
+ i++;
+ }
+ }
+ }
+
+ /* Determine if the table name is a view */
+ if (PG_VERSION_GE(conn, 7.1))
+ /* view is represented by its relkind since 7.1 */
+ view = (relkind_or_hasrules[0] == 'v');
+ else
+ view = (relkind_or_hasrules[0] == '1');
+
+ /* It must be a regular table */
+ regular_table = (!systable && !view);
+
+
+ /* Include the row in the result set if meets all criteria */
+
+ /*
+ * NOTE: Unsupported table types (i.e., LOCAL TEMPORARY, ALIAS,
+ * etc) will return nothing
+ */
+ if ((systable && show_system_tables) ||
+ (view && show_views) ||
+ (regular_table && show_regular_tables))
+ {
+ row = (TupleNode *) malloc(sizeof(TupleNode) + (5 - 1) *sizeof(TupleField));
+
+ set_tuplefield_string(&row->tuple[0], "");
+
+ /*
+ * I have to hide the table owner from Access, otherwise it
+ * insists on referring to the table as 'owner.table'. (this
+ * is valid according to the ODBC SQL grammar, but Postgres
+ * won't support it.)
+ *
+ * set_tuplefield_string(&row->tuple[1], table_owner);
+ */
+
+ mylog("%s: table_name = '%s'\n", func, table_name);
+
+ set_tuplefield_string(&row->tuple[1], "");
+ set_tuplefield_string(&row->tuple[2], table_name);
+ set_tuplefield_string(&row->tuple[3], systable ? "SYSTEM TABLE" : (view ? "VIEW" : "TABLE"));
+ set_tuplefield_string(&row->tuple[4], "");
+
+ QR_add_tuple(stmt->result, row);
+ }
+ result = PGAPI_Fetch(htbl_stmt);
+ }
+ if (result != SQL_NO_DATA_FOUND)
+ {
+ stmt->errormsg = SC_create_errormsg(htbl_stmt);
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ /*
+ * also, things need to think that this statement is finished so the
+ * results can be retrieved.
+ */
+ stmt->status = STMT_FINISHED;
+
+ /* set up the current tuple pointer for SQLFetch */
+ stmt->currTuple = -1;
+ stmt->rowset_start = -1;
+ stmt->current_col = -1;
+
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ mylog("%s: EXIT, stmt=%u\n", func, stmt);
+ return SQL_SUCCESS;
+}
+
+
+RETCODE SQL_API
+PGAPI_Columns(
+ HSTMT hstmt,
+ UCHAR FAR * szTableQualifier,
+ SWORD cbTableQualifier,
+ UCHAR FAR * szTableOwner,
+ SWORD cbTableOwner,
+ UCHAR FAR * szTableName,
+ SWORD cbTableName,
+ UCHAR FAR * szColumnName,
+ SWORD cbColumnName)
+{
+ static char *func = "PGAPI_Columns";
+ StatementClass *stmt = (StatementClass *) hstmt;
+ TupleNode *row;
+ HSTMT hcol_stmt;
+ StatementClass *col_stmt;
+ char columns_query[INFO_INQUIRY_LEN];
+ RETCODE result;
+ char table_owner[MAX_INFO_STRING],
+ table_name[MAX_INFO_STRING],
+ field_name[MAX_INFO_STRING],
+ field_type_name[MAX_INFO_STRING];
+ Int2 field_number,
+ result_cols,
+ scale;
+ Int4 field_type,
+ the_type,
+ field_length,
+ mod_length,
+ precision;
+ char useStaticPrecision;
+ char not_null[MAX_INFO_STRING],
+ relhasrules[MAX_INFO_STRING];
+ ConnInfo *ci;
+ ConnectionClass *conn;
+
+
+ mylog("%s: entering...stmt=%u\n", func, stmt);
+
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+
+ stmt->manual_result = TRUE;
+ stmt->errormsg_created = TRUE;
+
+ conn = SC_get_conn(stmt);
+ ci = &(conn->connInfo);
+
+ /*
+ * Create the query to find out the columns (Note: pre 6.3 did not
+ * have the atttypmod field)
+ */
+ sprintf(columns_query, "select u.usename, c.relname, a.attname, a.atttypid"
+ ", t.typname, a.attnum, a.attlen, %s, a.attnotnull, c.relhasrules"
+ " from pg_user u, pg_class c, pg_attribute a, pg_type t"
+ " where u.usesysid = c.relowner"
+ " and c.oid= a.attrelid and a.atttypid = t.oid and (a.attnum > 0)",
+ PG_VERSION_LE(conn, 6.2) ? "a.attlen" : "a.atttypmod");
+
+ my_strcat(columns_query, " and c.relname like '%.*s'", szTableName, cbTableName);
+ my_strcat(columns_query, " and u.usename like '%.*s'", szTableOwner, cbTableOwner);
+ my_strcat(columns_query, " and a.attname like '%.*s'", szColumnName, cbColumnName);
+
+ /*
+ * give the output in the order the columns were defined when the
+ * table was created
+ */
+ strcat(columns_query, " order by attnum");
+
+ result = PGAPI_AllocStmt(stmt->hdbc, &hcol_stmt);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Couldn't allocate statement for PGAPI_Columns result.";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+ col_stmt = (StatementClass *) hcol_stmt;
+
+ mylog("%s: hcol_stmt = %u, col_stmt = %u\n", func, hcol_stmt, col_stmt);
+
+ result = PGAPI_ExecDirect(hcol_stmt, columns_query,
+ strlen(columns_query));
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = SC_create_errormsg(hcol_stmt);
+ stmt->errornumber = col_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(hcol_stmt, 1, SQL_C_CHAR,
+ table_owner, MAX_INFO_STRING, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(hcol_stmt, 2, SQL_C_CHAR,
+ table_name, MAX_INFO_STRING, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(hcol_stmt, 3, SQL_C_CHAR,
+ field_name, MAX_INFO_STRING, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(hcol_stmt, 4, SQL_C_LONG,
+ &field_type, 4, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(hcol_stmt, 5, SQL_C_CHAR,
+ field_type_name, MAX_INFO_STRING, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(hcol_stmt, 6, SQL_C_SHORT,
+ &field_number, MAX_INFO_STRING, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(hcol_stmt, 7, SQL_C_LONG,
+ &field_length, MAX_INFO_STRING, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(hcol_stmt, 8, SQL_C_LONG,
+ &mod_length, MAX_INFO_STRING, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(hcol_stmt, 9, SQL_C_CHAR,
+ not_null, MAX_INFO_STRING, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(hcol_stmt, 10, SQL_C_CHAR,
+ relhasrules, MAX_INFO_STRING, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ stmt->result = QR_Constructor();
+ if (!stmt->result)
+ {
+ stmt->errormsg = "Couldn't allocate memory for PGAPI_Columns result.";
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ /* the binding structure for a statement is not set up until */
+
+ /*
+ * a statement is actually executed, so we'll have to do this
+ * ourselves.
+ */
+ result_cols = 14;
+ extend_bindings(stmt, result_cols);
+
+ /* set the field names */
+ QR_set_num_fields(stmt->result, result_cols);
+ QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 3, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 4, "DATA_TYPE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 5, "TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 6, "PRECISION", PG_TYPE_INT4, 4);
+ QR_set_field_info(stmt->result, 7, "LENGTH", PG_TYPE_INT4, 4);
+ QR_set_field_info(stmt->result, 8, "SCALE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 9, "RADIX", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 10, "NULLABLE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 11, "REMARKS", PG_TYPE_TEXT, 254);
+
+ /* User defined fields */
+ QR_set_field_info(stmt->result, 12, "DISPLAY_SIZE", PG_TYPE_INT4, 4);
+ QR_set_field_info(stmt->result, 13, "FIELD_TYPE", PG_TYPE_INT4, 4);
+
+ result = PGAPI_Fetch(hcol_stmt);
+
+ /*
+ * Only show oid if option AND there are other columns AND it's not
+ * being called by SQLStatistics . Always show OID if it's a system
+ * table
+ */
+
+ if (result != SQL_ERROR && !stmt->internal)
+ {
+ if (relhasrules[0] != '1' &&
+ (atoi(ci->show_oid_column) ||
+ strncmp(table_name, POSTGRES_SYS_PREFIX, strlen(POSTGRES_SYS_PREFIX)) == 0))
+ {
+ /* For OID fields */
+ the_type = PG_TYPE_OID;
+ row = (TupleNode *) malloc(sizeof(TupleNode) +
+ (result_cols - 1) *sizeof(TupleField));
+
+ set_tuplefield_string(&row->tuple[0], "");
+ /* see note in SQLTables() */
+ /* set_tuplefield_string(&row->tuple[1], table_owner); */
+ set_tuplefield_string(&row->tuple[1], "");
+ set_tuplefield_string(&row->tuple[2], table_name);
+ set_tuplefield_string(&row->tuple[3], "oid");
+ set_tuplefield_int2(&row->tuple[4], pgtype_to_sqltype(stmt, the_type));
+ set_tuplefield_string(&row->tuple[5], "OID");
+
+ set_tuplefield_int4(&row->tuple[7], pgtype_length(stmt, the_type, PG_STATIC, PG_STATIC));
+ set_tuplefield_int4(&row->tuple[6], pgtype_precision(stmt, the_type, PG_STATIC, PG_STATIC));
+
+ set_nullfield_int2(&row->tuple[8], pgtype_scale(stmt, the_type, PG_STATIC));
+ set_nullfield_int2(&row->tuple[9], pgtype_radix(stmt, the_type));
+ set_tuplefield_int2(&row->tuple[10], SQL_NO_NULLS);
+ set_tuplefield_string(&row->tuple[11], "");
+
+ set_tuplefield_int4(&row->tuple[12], pgtype_display_size(stmt, the_type, PG_STATIC, PG_STATIC));
+ set_tuplefield_int4(&row->tuple[13], the_type);
+
+ QR_add_tuple(stmt->result, row);
+ }
+ }
+
+ while ((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO))
+ {
+ row = (TupleNode *) malloc(sizeof(TupleNode) +
+ (result_cols - 1) *sizeof(TupleField));
+
+
+ set_tuplefield_string(&row->tuple[0], "");
+ /* see note in SQLTables() */
+ /* set_tuplefield_string(&row->tuple[1], table_owner); */
+ set_tuplefield_string(&row->tuple[1], "");
+ set_tuplefield_string(&row->tuple[2], table_name);
+ set_tuplefield_string(&row->tuple[3], field_name);
+ set_tuplefield_int2(&row->tuple[4], pgtype_to_sqltype(stmt, field_type));
+ set_tuplefield_string(&row->tuple[5], field_type_name);
+
+
+ /*----------
+ * Some Notes about Postgres Data Types:
+ *
+ * VARCHAR - the length is stored in the pg_attribute.atttypmod field
+ * BPCHAR - the length is also stored as varchar is
+ *
+ * NUMERIC - the scale is stored in atttypmod as follows:
+ *
+ * precision =((atttypmod - VARHDRSZ) >> 16) & 0xffff
+ * scale = (atttypmod - VARHDRSZ) & 0xffff
+ *
+ *----------
+ */
+ qlog("PGAPI_Columns: table='%s',field_name='%s',type=%d,sqltype=%d,name='%s'\n",
+ table_name, field_name, field_type, pgtype_to_sqltype, field_type_name);
+
+ useStaticPrecision = TRUE;
+
+ if (field_type == PG_TYPE_NUMERIC)
+ {
+ if (mod_length >= 4)
+ mod_length -= 4; /* the length is in atttypmod - 4 */
+
+ if (mod_length >= 0)
+ {
+ useStaticPrecision = FALSE;
+
+ precision = (mod_length >> 16) & 0xffff;
+ scale = mod_length & 0xffff;
+
+ mylog("%s: field type is NUMERIC: field_type = %d, mod_length=%d, precision=%d, scale=%d\n", func, field_type, mod_length, precision, scale);
+
+ set_tuplefield_int4(&row->tuple[7], precision + 2); /* sign+dec.point */
+ set_tuplefield_int4(&row->tuple[6], precision);
+ set_tuplefield_int4(&row->tuple[12], precision + 2); /* sign+dec.point */
+ set_nullfield_int2(&row->tuple[8], scale);
+ }
+ }
+
+ if ((field_type == PG_TYPE_VARCHAR) ||
+ (field_type == PG_TYPE_BPCHAR))
+ {
+ useStaticPrecision = FALSE;
+
+ if (mod_length >= 4)
+ mod_length -= 4; /* the length is in atttypmod - 4 */
+
+ if (mod_length > ci->drivers.max_varchar_size || mod_length <= 0)
+ mod_length = ci->drivers.max_varchar_size;
+
+ mylog("%s: field type is VARCHAR,BPCHAR: field_type = %d, mod_length = %d\n", func, field_type, mod_length);
+
+ set_tuplefield_int4(&row->tuple[7], mod_length);
+ set_tuplefield_int4(&row->tuple[6], mod_length);
+ set_tuplefield_int4(&row->tuple[12], mod_length);
+ set_nullfield_int2(&row->tuple[8], pgtype_scale(stmt, field_type, PG_STATIC));
+ }
+
+ if (useStaticPrecision)
+ {
+ mylog("%s: field type is OTHER: field_type = %d, pgtype_length = %d\n", func, field_type, pgtype_length(stmt, field_type, PG_STATIC, PG_STATIC));
+
+ set_tuplefield_int4(&row->tuple[7], pgtype_length(stmt, field_type, PG_STATIC, PG_STATIC));
+ set_tuplefield_int4(&row->tuple[6], pgtype_precision(stmt, field_type, PG_STATIC, PG_STATIC));
+ set_tuplefield_int4(&row->tuple[12], pgtype_display_size(stmt, field_type, PG_STATIC, PG_STATIC));
+ set_nullfield_int2(&row->tuple[8], pgtype_scale(stmt, field_type, PG_STATIC));
+ }
+
+ set_nullfield_int2(&row->tuple[9], pgtype_radix(stmt, field_type));
+ set_tuplefield_int2(&row->tuple[10], (Int2) (not_null[0] == '1' ? SQL_NO_NULLS : pgtype_nullable(stmt, field_type)));
+ set_tuplefield_string(&row->tuple[11], "");
+ set_tuplefield_int4(&row->tuple[13], field_type);
+
+ QR_add_tuple(stmt->result, row);
+
+
+ result = PGAPI_Fetch(hcol_stmt);
+
+ }
+ if (result != SQL_NO_DATA_FOUND)
+ {
+ stmt->errormsg = SC_create_errormsg(hcol_stmt);
+ stmt->errornumber = col_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ /*
+ * Put the row version column at the end so it might not be mistaken
+ * for a key field.
+ */
+ if (relhasrules[0] != '1' && !stmt->internal && atoi(ci->row_versioning))
+ {
+ /* For Row Versioning fields */
+ the_type = PG_TYPE_INT4;
+
+ row = (TupleNode *) malloc(sizeof(TupleNode) +
+ (result_cols - 1) *sizeof(TupleField));
+
+ set_tuplefield_string(&row->tuple[0], "");
+ set_tuplefield_string(&row->tuple[1], "");
+ set_tuplefield_string(&row->tuple[2], table_name);
+ set_tuplefield_string(&row->tuple[3], "xmin");
+ set_tuplefield_int2(&row->tuple[4], pgtype_to_sqltype(stmt, the_type));
+ set_tuplefield_string(&row->tuple[5], pgtype_to_name(stmt, the_type));
+ set_tuplefield_int4(&row->tuple[6], pgtype_precision(stmt, the_type, PG_STATIC, PG_STATIC));
+ set_tuplefield_int4(&row->tuple[7], pgtype_length(stmt, the_type, PG_STATIC, PG_STATIC));
+ set_nullfield_int2(&row->tuple[8], pgtype_scale(stmt, the_type, PG_STATIC));
+ set_nullfield_int2(&row->tuple[9], pgtype_radix(stmt, the_type));
+ set_tuplefield_int2(&row->tuple[10], SQL_NO_NULLS);
+ set_tuplefield_string(&row->tuple[11], "");
+ set_tuplefield_int4(&row->tuple[12], pgtype_display_size(stmt, the_type, PG_STATIC, PG_STATIC));
+ set_tuplefield_int4(&row->tuple[13], the_type);
+
+ QR_add_tuple(stmt->result, row);
+ }
+
+ /*
+ * also, things need to think that this statement is finished so the
+ * results can be retrieved.
+ */
+ stmt->status = STMT_FINISHED;
+
+ /* set up the current tuple pointer for SQLFetch */
+ stmt->currTuple = -1;
+ stmt->rowset_start = -1;
+ stmt->current_col = -1;
+
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ mylog("%s: EXIT, stmt=%u\n", func, stmt);
+ return SQL_SUCCESS;
+}
+
+
+RETCODE SQL_API
+PGAPI_SpecialColumns(
+ HSTMT hstmt,
+ UWORD fColType,
+ UCHAR FAR * szTableQualifier,
+ SWORD cbTableQualifier,
+ UCHAR FAR * szTableOwner,
+ SWORD cbTableOwner,
+ UCHAR FAR * szTableName,
+ SWORD cbTableName,
+ UWORD fScope,
+ UWORD fNullable)
+{
+ static char *func = "PGAPI_SpecialColumns";
+ TupleNode *row;
+ StatementClass *stmt = (StatementClass *) hstmt;
+ ConnInfo *ci;
+ HSTMT hcol_stmt;
+ StatementClass *col_stmt;
+ char columns_query[INFO_INQUIRY_LEN];
+ RETCODE result;
+ char relhasrules[MAX_INFO_STRING];
+
+ mylog("%s: entering...stmt=%u\n", func, stmt);
+
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+ ci = &(SC_get_conn(stmt)->connInfo);
+
+ stmt->manual_result = TRUE;
+
+ /*
+ * Create the query to find out if this is a view or not...
+ */
+ sprintf(columns_query, "select c.relhasrules "
+ "from pg_user u, pg_class c where "
+ "u.usesysid = c.relowner");
+
+ my_strcat(columns_query, " and c.relname like '%.*s'", szTableName, cbTableName);
+ my_strcat(columns_query, " and u.usename like '%.*s'", szTableOwner, cbTableOwner);
+
+
+ result = PGAPI_AllocStmt(stmt->hdbc, &hcol_stmt);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Couldn't allocate statement for SQLSpecialColumns result.";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+ col_stmt = (StatementClass *) hcol_stmt;
+
+ mylog("%s: hcol_stmt = %u, col_stmt = %u\n", func, hcol_stmt, col_stmt);
+
+ result = PGAPI_ExecDirect(hcol_stmt, columns_query,
+ strlen(columns_query));
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = SC_create_errormsg(hcol_stmt);
+ stmt->errornumber = col_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(hcol_stmt, 1, SQL_C_CHAR,
+ relhasrules, MAX_INFO_STRING, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_Fetch(hcol_stmt);
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+
+ stmt->result = QR_Constructor();
+ extend_bindings(stmt, 8);
+
+ QR_set_num_fields(stmt->result, 8);
+ QR_set_field_info(stmt->result, 0, "SCOPE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 1, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 2, "DATA_TYPE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 3, "TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 4, "PRECISION", PG_TYPE_INT4, 4);
+ QR_set_field_info(stmt->result, 5, "LENGTH", PG_TYPE_INT4, 4);
+ QR_set_field_info(stmt->result, 6, "SCALE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 7, "PSEUDO_COLUMN", PG_TYPE_INT2, 2);
+
+ if (relhasrules[0] != '1')
+ {
+ /* use the oid value for the rowid */
+ if (fColType == SQL_BEST_ROWID)
+ {
+ row = (TupleNode *) malloc(sizeof(TupleNode) + (8 - 1) *sizeof(TupleField));
+
+ set_tuplefield_int2(&row->tuple[0], SQL_SCOPE_SESSION);
+ set_tuplefield_string(&row->tuple[1], "oid");
+ set_tuplefield_int2(&row->tuple[2], pgtype_to_sqltype(stmt, PG_TYPE_OID));
+ set_tuplefield_string(&row->tuple[3], "OID");
+ set_tuplefield_int4(&row->tuple[4], pgtype_precision(stmt, PG_TYPE_OID, PG_STATIC, PG_STATIC));
+ set_tuplefield_int4(&row->tuple[5], pgtype_length(stmt, PG_TYPE_OID, PG_STATIC, PG_STATIC));
+ set_tuplefield_int2(&row->tuple[6], pgtype_scale(stmt, PG_TYPE_OID, PG_STATIC));
+ set_tuplefield_int2(&row->tuple[7], SQL_PC_PSEUDO);
+
+ QR_add_tuple(stmt->result, row);
+
+ }
+ else if (fColType == SQL_ROWVER)
+ {
+ Int2 the_type = PG_TYPE_INT4;
+
+ if (atoi(ci->row_versioning))
+ {
+ row = (TupleNode *) malloc(sizeof(TupleNode) + (8 - 1) *sizeof(TupleField));
+
+ set_tuplefield_null(&row->tuple[0]);
+ set_tuplefield_string(&row->tuple[1], "xmin");
+ set_tuplefield_int2(&row->tuple[2], pgtype_to_sqltype(stmt, the_type));
+ set_tuplefield_string(&row->tuple[3], pgtype_to_name(stmt, the_type));
+ set_tuplefield_int4(&row->tuple[4], pgtype_precision(stmt, the_type, PG_STATIC, PG_STATIC));
+ set_tuplefield_int4(&row->tuple[5], pgtype_length(stmt, the_type, PG_STATIC, PG_STATIC));
+ set_tuplefield_int2(&row->tuple[6], pgtype_scale(stmt, the_type, PG_STATIC));
+ set_tuplefield_int2(&row->tuple[7], SQL_PC_PSEUDO);
+
+ QR_add_tuple(stmt->result, row);
+ }
+ }
+ }
+
+ stmt->status = STMT_FINISHED;
+ stmt->currTuple = -1;
+ stmt->rowset_start = -1;
+ stmt->current_col = -1;
+
+ mylog("%s: EXIT, stmt=%u\n", func, stmt);
+ return SQL_SUCCESS;
+}
+
+
+RETCODE SQL_API
+PGAPI_Statistics(
+ HSTMT hstmt,
+ UCHAR FAR * szTableQualifier,
+ SWORD cbTableQualifier,
+ UCHAR FAR * szTableOwner,
+ SWORD cbTableOwner,
+ UCHAR FAR * szTableName,
+ SWORD cbTableName,
+ UWORD fUnique,
+ UWORD fAccuracy)
+{
+ static char *func = "PGAPI_Statistics";
+ StatementClass *stmt = (StatementClass *) hstmt;
+ char index_query[INFO_INQUIRY_LEN];
+ HSTMT hindx_stmt;
+ RETCODE result;
+ char *table_name;
+ char index_name[MAX_INFO_STRING];
+ short fields_vector[16];
+ char isunique[10],
+ isclustered[10],
+ ishash[MAX_INFO_STRING];
+ SDWORD index_name_len,
+ fields_vector_len;
+ TupleNode *row;
+ int i;
+ HSTMT hcol_stmt;
+ StatementClass *col_stmt,
+ *indx_stmt;
+ char column_name[MAX_INFO_STRING],
+ relhasrules[MAX_INFO_STRING];
+ char **column_names = 0;
+ SQLINTEGER column_name_len;
+ int total_columns = 0;
+ char error = TRUE;
+ ConnInfo *ci;
+ char buf[256];
+
+ mylog("%s: entering...stmt=%u\n", func, stmt);
+
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+
+ stmt->manual_result = TRUE;
+ stmt->errormsg_created = TRUE;
+
+ ci = &(SC_get_conn(stmt)->connInfo);
+
+ stmt->result = QR_Constructor();
+ if (!stmt->result)
+ {
+ stmt->errormsg = "Couldn't allocate memory for PGAPI_Statistics result.";
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ /* the binding structure for a statement is not set up until */
+
+ /*
+ * a statement is actually executed, so we'll have to do this
+ * ourselves.
+ */
+ extend_bindings(stmt, 13);
+
+ /* set the field names */
+ QR_set_num_fields(stmt->result, 13);
+ QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 3, "NON_UNIQUE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 4, "INDEX_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 5, "INDEX_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 6, "TYPE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 7, "SEQ_IN_INDEX", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 8, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 9, "COLLATION", PG_TYPE_CHAR, 1);
+ QR_set_field_info(stmt->result, 10, "CARDINALITY", PG_TYPE_INT4, 4);
+ QR_set_field_info(stmt->result, 11, "PAGES", PG_TYPE_INT4, 4);
+ QR_set_field_info(stmt->result, 12, "FILTER_CONDITION", PG_TYPE_TEXT, MAX_INFO_STRING);
+
+ /*
+ * only use the table name... the owner should be redundant, and we
+ * never use qualifiers.
+ */
+ table_name = make_string(szTableName, cbTableName, NULL);
+ if (!table_name)
+ {
+ stmt->errormsg = "No table name passed to PGAPI_Statistics.";
+ stmt->errornumber = STMT_INTERNAL_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ /*
+ * we need to get a list of the field names first, so we can return
+ * them later.
+ */
+ result = PGAPI_AllocStmt(stmt->hdbc, &hcol_stmt);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = "PGAPI_AllocStmt failed in PGAPI_Statistics for columns.";
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ goto SEEYA;
+ }
+
+ col_stmt = (StatementClass *) hcol_stmt;
+
+ /*
+ * "internal" prevents SQLColumns from returning the oid if it is
+ * being shown. This would throw everything off.
+ */
+ col_stmt->internal = TRUE;
+ result = PGAPI_Columns(hcol_stmt, "", 0, "", 0,
+ table_name, (SWORD) strlen(table_name), "", 0);
+ col_stmt->internal = FALSE;
+
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = col_stmt->errormsg; /* "SQLColumns failed in
+ * SQLStatistics."; */
+ stmt->errornumber = col_stmt->errornumber; /* STMT_EXEC_ERROR; */
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ goto SEEYA;
+ }
+ result = PGAPI_BindCol(hcol_stmt, 4, SQL_C_CHAR,
+ column_name, MAX_INFO_STRING, &column_name_len);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ goto SEEYA;
+
+ }
+
+ result = PGAPI_Fetch(hcol_stmt);
+ while ((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO))
+ {
+ total_columns++;
+
+ column_names =
+ (char **) realloc(column_names,
+ total_columns * sizeof(char *));
+ column_names[total_columns - 1] =
+ (char *) malloc(strlen(column_name) + 1);
+ strcpy(column_names[total_columns - 1], column_name);
+
+ mylog("%s: column_name = '%s'\n", func, column_name);
+
+ result = PGAPI_Fetch(hcol_stmt);
+ }
+
+ if (result != SQL_NO_DATA_FOUND || total_columns == 0)
+ {
+ stmt->errormsg = SC_create_errormsg(hcol_stmt); /* "Couldn't get column
+ * names in
+ * SQLStatistics."; */
+ stmt->errornumber = col_stmt->errornumber;
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+ goto SEEYA;
+
+ }
+
+ PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
+
+ /* get a list of indexes on this table */
+ result = PGAPI_AllocStmt(stmt->hdbc, &hindx_stmt);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = "PGAPI_AllocStmt failed in SQLStatistics for indices.";
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ goto SEEYA;
+
+ }
+ indx_stmt = (StatementClass *) hindx_stmt;
+
+ sprintf(index_query, "select c.relname, i.indkey, i.indisunique"
+ ", i.indisclustered, a.amname, c.relhasrules"
+ " from pg_index i, pg_class c, pg_class d, pg_am a"
+ " where d.relname = '%s'"
+ " and d.oid = i.indrelid"
+ " and i.indexrelid = c.oid"
+ " and c.relam = a.oid"
+ ,table_name);
+ if (PG_VERSION_GT(SC_get_conn(stmt), 6.4))
+ strcat(index_query, " order by i.indisprimary desc");
+
+ result = PGAPI_ExecDirect(hindx_stmt, index_query, strlen(index_query));
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ /*
+ * "Couldn't execute index query (w/SQLExecDirect) in
+ * SQLStatistics.";
+ */
+ stmt->errormsg = SC_create_errormsg(hindx_stmt);
+
+ stmt->errornumber = indx_stmt->errornumber;
+ PGAPI_FreeStmt(hindx_stmt, SQL_DROP);
+ goto SEEYA;
+ }
+
+ /* bind the index name column */
+ result = PGAPI_BindCol(hindx_stmt, 1, SQL_C_CHAR,
+ index_name, MAX_INFO_STRING, &index_name_len);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = indx_stmt->errormsg; /* "Couldn't bind column
+ * in SQLStatistics."; */
+ stmt->errornumber = indx_stmt->errornumber;
+ PGAPI_FreeStmt(hindx_stmt, SQL_DROP);
+ goto SEEYA;
+
+ }
+ /* bind the vector column */
+ result = PGAPI_BindCol(hindx_stmt, 2, SQL_C_DEFAULT,
+ fields_vector, 32, &fields_vector_len);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = indx_stmt->errormsg; /* "Couldn't bind column
+ * in SQLStatistics."; */
+ stmt->errornumber = indx_stmt->errornumber;
+ PGAPI_FreeStmt(hindx_stmt, SQL_DROP);
+ goto SEEYA;
+
+ }
+ /* bind the "is unique" column */
+ result = PGAPI_BindCol(hindx_stmt, 3, SQL_C_CHAR,
+ isunique, sizeof(isunique), NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = indx_stmt->errormsg; /* "Couldn't bind column
+ * in SQLStatistics."; */
+ stmt->errornumber = indx_stmt->errornumber;
+ PGAPI_FreeStmt(hindx_stmt, SQL_DROP);
+ goto SEEYA;
+ }
+
+ /* bind the "is clustered" column */
+ result = PGAPI_BindCol(hindx_stmt, 4, SQL_C_CHAR,
+ isclustered, sizeof(isclustered), NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = indx_stmt->errormsg; /* "Couldn't bind column
+ * in SQLStatistics."; */
+ stmt->errornumber = indx_stmt->errornumber;
+ PGAPI_FreeStmt(hindx_stmt, SQL_DROP);
+ goto SEEYA;
+
+ }
+
+ /* bind the "is hash" column */
+ result = PGAPI_BindCol(hindx_stmt, 5, SQL_C_CHAR,
+ ishash, sizeof(ishash), NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = indx_stmt->errormsg; /* "Couldn't bind column
+ * in SQLStatistics."; */
+ stmt->errornumber = indx_stmt->errornumber;
+ PGAPI_FreeStmt(hindx_stmt, SQL_DROP);
+ goto SEEYA;
+
+ }
+
+ result = PGAPI_BindCol(hindx_stmt, 6, SQL_C_CHAR,
+ relhasrules, MAX_INFO_STRING, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = indx_stmt->errormsg;
+ stmt->errornumber = indx_stmt->errornumber;
+ PGAPI_FreeStmt(hindx_stmt, SQL_DROP);
+ goto SEEYA;
+ }
+
+ /* fake index of OID */
+ if (relhasrules[0] != '1' && atoi(ci->show_oid_column) && atoi(ci->fake_oid_index))
+ {
+ row = (TupleNode *) malloc(sizeof(TupleNode) +
+ (13 - 1) *sizeof(TupleField));
+
+ /* no table qualifier */
+ set_tuplefield_string(&row->tuple[0], "");
+ /* don't set the table owner, else Access tries to use it */
+ set_tuplefield_string(&row->tuple[1], "");
+ set_tuplefield_string(&row->tuple[2], table_name);
+
+ /* non-unique index? */
+ set_tuplefield_int2(&row->tuple[3], (Int2) (ci->drivers.unique_index ? FALSE : TRUE));
+
+ /* no index qualifier */
+ set_tuplefield_string(&row->tuple[4], "");
+
+ sprintf(buf, "%s_idx_fake_oid", table_name);
+ set_tuplefield_string(&row->tuple[5], buf);
+
+ /*
+ * Clustered/HASH index?
+ */
+ set_tuplefield_int2(&row->tuple[6], (Int2) SQL_INDEX_OTHER);
+ set_tuplefield_int2(&row->tuple[7], (Int2) 1);
+
+ set_tuplefield_string(&row->tuple[8], "oid");
+ set_tuplefield_string(&row->tuple[9], "A");
+ set_tuplefield_null(&row->tuple[10]);
+ set_tuplefield_null(&row->tuple[11]);
+ set_tuplefield_null(&row->tuple[12]);
+
+ QR_add_tuple(stmt->result, row);
+ }
+
+ result = PGAPI_Fetch(hindx_stmt);
+ while ((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO))
+ {
+ /* If only requesting unique indexs, then just return those. */
+ if (fUnique == SQL_INDEX_ALL ||
+ (fUnique == SQL_INDEX_UNIQUE && atoi(isunique)))
+ {
+ i = 0;
+ /* add a row in this table for each field in the index */
+ while (i < 16 && fields_vector[i] != 0)
+ {
+ row = (TupleNode *) malloc(sizeof(TupleNode) +
+ (13 - 1) *sizeof(TupleField));
+
+ /* no table qualifier */
+ set_tuplefield_string(&row->tuple[0], "");
+ /* don't set the table owner, else Access tries to use it */
+ set_tuplefield_string(&row->tuple[1], "");
+ set_tuplefield_string(&row->tuple[2], table_name);
+
+ /* non-unique index? */
+ if (ci->drivers.unique_index)
+ set_tuplefield_int2(&row->tuple[3], (Int2) (atoi(isunique) ? FALSE : TRUE));
+ else
+ set_tuplefield_int2(&row->tuple[3], TRUE);
+
+ /* no index qualifier */
+ set_tuplefield_string(&row->tuple[4], "");
+ set_tuplefield_string(&row->tuple[5], index_name);
+
+ /*
+ * Clustered/HASH index?
+ */
+ set_tuplefield_int2(&row->tuple[6], (Int2)
+ (atoi(isclustered) ? SQL_INDEX_CLUSTERED :
+ (!strncmp(ishash, "hash", 4)) ? SQL_INDEX_HASHED : SQL_INDEX_OTHER));
+ set_tuplefield_int2(&row->tuple[7], (Int2) (i + 1));
+
+ if (fields_vector[i] == OID_ATTNUM)
+ {
+ set_tuplefield_string(&row->tuple[8], "oid");
+ mylog("%s: column name = oid\n", func);
+ }
+ else if (fields_vector[i] < 0 || fields_vector[i] > total_columns)
+ {
+ set_tuplefield_string(&row->tuple[8], "UNKNOWN");
+ mylog("%s: column name = UNKNOWN\n", func);
+ }
+ else
+ {
+ set_tuplefield_string(&row->tuple[8], column_names[fields_vector[i] - 1]);
+ mylog("%s: column name = '%s'\n", func, column_names[fields_vector[i] - 1]);
+ }
+
+ set_tuplefield_string(&row->tuple[9], "A");
+ set_tuplefield_null(&row->tuple[10]);
+ set_tuplefield_null(&row->tuple[11]);
+ set_tuplefield_null(&row->tuple[12]);
+
+ QR_add_tuple(stmt->result, row);
+ i++;
+ }
+ }
+
+ result = PGAPI_Fetch(hindx_stmt);
+ }
+ if (result != SQL_NO_DATA_FOUND)
+ {
+ /* "SQLFetch failed in SQLStatistics."; */
+ stmt->errormsg = SC_create_errormsg(hindx_stmt);
+ stmt->errornumber = indx_stmt->errornumber;
+ PGAPI_FreeStmt(hindx_stmt, SQL_DROP);
+ goto SEEYA;
+ }
+
+ PGAPI_FreeStmt(hindx_stmt, SQL_DROP);
+
+ /*
+ * also, things need to think that this statement is finished so the
+ * results can be retrieved.
+ */
+ stmt->status = STMT_FINISHED;
+
+ /* set up the current tuple pointer for SQLFetch */
+ stmt->currTuple = -1;
+ stmt->rowset_start = -1;
+ stmt->current_col = -1;
+
+ error = FALSE;
+
+SEEYA:
+ /* These things should be freed on any error ALSO! */
+ free(table_name);
+ for (i = 0; i < total_columns; i++)
+ free(column_names[i]);
+ free(column_names);
+
+ mylog("%s: EXIT, %s, stmt=%u\n", func, error ? "error" : "success", stmt);
+
+ if (error)
+ {
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+ else
+ return SQL_SUCCESS;
+}
+
+
+RETCODE SQL_API
+PGAPI_ColumnPrivileges(
+ HSTMT hstmt,
+ UCHAR FAR * szTableQualifier,
+ SWORD cbTableQualifier,
+ UCHAR FAR * szTableOwner,
+ SWORD cbTableOwner,
+ UCHAR FAR * szTableName,
+ SWORD cbTableName,
+ UCHAR FAR * szColumnName,
+ SWORD cbColumnName)
+{
+ static char *func = "PGAPI_ColumnPrivileges";
+
+ mylog("%s: entering...\n", func);
+
+ /* Neither Access or Borland care about this. */
+
+ SC_log_error(func, "Function not implemented", (StatementClass *) hstmt);
+ return SQL_ERROR;
+}
+
+
+/*
+ * SQLPrimaryKeys()
+ *
+ * Retrieve the primary key columns for the specified table.
+ */
+RETCODE SQL_API
+PGAPI_PrimaryKeys(
+ HSTMT hstmt,
+ UCHAR FAR * szTableQualifier,
+ SWORD cbTableQualifier,
+ UCHAR FAR * szTableOwner,
+ SWORD cbTableOwner,
+ UCHAR FAR * szTableName,
+ SWORD cbTableName)
+{
+ static char *func = "PGAPI_PrimaryKeys";
+ StatementClass *stmt = (StatementClass *) hstmt;
+ ConnectionClass *conn;
+ TupleNode *row;
+ RETCODE result;
+ int seq = 0;
+ HSTMT htbl_stmt;
+ StatementClass *tbl_stmt;
+ char tables_query[INFO_INQUIRY_LEN];
+ char attname[MAX_INFO_STRING];
+ SDWORD attname_len;
+ char pktab[MAX_TABLE_LEN + 1];
+ Int2 result_cols;
+ int qno,
+ qstart,
+ qend;
+
+ mylog("%s: entering...stmt=%u\n", func, stmt);
+
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+ stmt->manual_result = TRUE;
+ stmt->errormsg_created = TRUE;
+
+ stmt->result = QR_Constructor();
+ if (!stmt->result)
+ {
+ stmt->errormsg = "Couldn't allocate memory for PGAPI_PrimaryKeys result.";
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ /* the binding structure for a statement is not set up until */
+
+ /*
+ * a statement is actually executed, so we'll have to do this
+ * ourselves.
+ */
+ result_cols = 6;
+ extend_bindings(stmt, result_cols);
+
+ /* set the field names */
+ QR_set_num_fields(stmt->result, result_cols);
+ QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 3, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 4, "KEY_SEQ", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 5, "PK_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+
+
+ result = PGAPI_AllocStmt(stmt->hdbc, &htbl_stmt);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Couldn't allocate statement for Primary Key result.";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+ tbl_stmt = (StatementClass *) htbl_stmt;
+
+ pktab[0] = '\0';
+ make_string(szTableName, cbTableName, pktab);
+ if (pktab[0] == '\0')
+ {
+ stmt->errormsg = "No Table specified to PGAPI_PrimaryKeys.";
+ stmt->errornumber = STMT_INTERNAL_ERROR;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(htbl_stmt, 1, SQL_C_CHAR,
+ attname, MAX_INFO_STRING, &attname_len);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ conn = SC_get_conn(stmt);
+ if (PG_VERSION_LE(conn, 6.4))
+ qstart = 2;
+ else
+ qstart = 1;
+ qend = 2;
+ for (qno = qstart; qno <= qend; qno++)
+ {
+ switch (qno)
+ {
+ case 1:
+
+ /*
+ * Simplified query to remove assumptions about number of
+ * possible index columns. Courtesy of Tom Lane - thomas
+ * 2000-03-21
+ */
+ sprintf(tables_query, "select ta.attname, ia.attnum"
+ " from pg_attribute ta, pg_attribute ia, pg_class c, pg_index i"
+ " where c.relname = '%s'"
+ " AND c.oid = i.indrelid"
+ " AND i.indisprimary = 't'"
+ " AND ia.attrelid = i.indexrelid"
+ " AND ta.attrelid = i.indrelid"
+ " AND ta.attnum = i.indkey[ia.attnum-1]"
+ " order by ia.attnum", pktab);
+ break;
+ case 2:
+
+ /*
+ * Simplified query to search old fashoned primary key
+ */
+ sprintf(tables_query, "select ta.attname, ia.attnum"
+ " from pg_attribute ta, pg_attribute ia, pg_class c, pg_index i"
+ " where c.relname = '%s_pkey'"
+ " AND c.oid = i.indexrelid"
+ " AND ia.attrelid = i.indexrelid"
+ " AND ta.attrelid = i.indrelid"
+ " AND ta.attnum = i.indkey[ia.attnum-1]"
+ " order by ia.attnum", pktab);
+ break;
+ }
+ mylog("%s: tables_query='%s'\n", func, tables_query);
+
+ result = PGAPI_ExecDirect(htbl_stmt, tables_query, strlen(tables_query));
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = SC_create_errormsg(htbl_stmt);
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_Fetch(htbl_stmt);
+ if (result != SQL_NO_DATA_FOUND)
+ break;
+ }
+
+ while ((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO))
+ {
+ row = (TupleNode *) malloc(sizeof(TupleNode) + (result_cols - 1) *sizeof(TupleField));
+
+ set_tuplefield_null(&row->tuple[0]);
+
+ /*
+ * I have to hide the table owner from Access, otherwise it
+ * insists on referring to the table as 'owner.table'. (this is
+ * valid according to the ODBC SQL grammar, but Postgres won't
+ * support it.)
+ */
+ set_tuplefield_string(&row->tuple[1], "");
+ set_tuplefield_string(&row->tuple[2], pktab);
+ set_tuplefield_string(&row->tuple[3], attname);
+ set_tuplefield_int2(&row->tuple[4], (Int2) (++seq));
+ set_tuplefield_null(&row->tuple[5]);
+
+ QR_add_tuple(stmt->result, row);
+
+ mylog(">> primaryKeys: pktab = '%s', attname = '%s', seq = %d\n", pktab, attname, seq);
+
+ result = PGAPI_Fetch(htbl_stmt);
+ }
+
+ if (result != SQL_NO_DATA_FOUND)
+ {
+ stmt->errormsg = SC_create_errormsg(htbl_stmt);
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+
+
+ /*
+ * also, things need to think that this statement is finished so the
+ * results can be retrieved.
+ */
+ stmt->status = STMT_FINISHED;
+
+ /* set up the current tuple pointer for SQLFetch */
+ stmt->currTuple = -1;
+ stmt->rowset_start = -1;
+ stmt->current_col = -1;
+
+ mylog("%s: EXIT, stmt=%u\n", func, stmt);
+ return SQL_SUCCESS;
+}
+
+
+#ifdef MULTIBYTE
+/*
+ * Multibyte support stuff for SQLForeignKeys().
+ * There may be much more effective way in the
+ * future version. The way is very forcible currently.
+ */
+static BOOL
+isMultibyte(const unsigned char *str)
+{
+ for (; *str; str++)
+ {
+ if (*str >= 0x80)
+ return TRUE;
+ }
+ return FALSE;
+}
+static char *
+getClientTableName(ConnectionClass *conn, char *serverTableName, BOOL *nameAlloced)
+{
+ char query[1024],
+ saveoid[24],
+ *ret = serverTableName;
+ BOOL continueExec = TRUE,
+ bError = FALSE;
+ QResultClass *res;
+
+ *nameAlloced = FALSE;
+ if (!conn->client_encoding || !isMultibyte(serverTableName))
+ return ret;
+ if (!conn->server_encoding)
+ {
+ if (res = CC_send_query(conn, "select getdatabaseencoding()", NULL), res)
+ {
+ if (QR_get_num_tuples(res) > 0)
+ conn->server_encoding = strdup(QR_get_value_backend_row(res, 0, 0));
+ QR_Destructor(res);
+ }
+ }
+ if (!conn->server_encoding)
+ return ret;
+ sprintf(query, "SET CLIENT_ENCODING TO '%s'", conn->server_encoding);
+ if (res = CC_send_query(conn, query, NULL), res)
+ {
+ bError = QR_get_aborted(res);
+ QR_Destructor(res);
+ }
+ else
+ bError = TRUE;
+ if (!bError && continueExec)
+ {
+ sprintf(query, "select OID from pg_class where relname = '%s'", serverTableName);
+ if (res = CC_send_query(conn, query, NULL), res)
+ {
+ if (QR_get_num_tuples(res) > 0)
+ strcpy(saveoid, QR_get_value_backend_row(res, 0, 0));
+ else
+ {
+ continueExec = FALSE;
+ bError = QR_get_aborted(res);
+ }
+ QR_Destructor(res);
+ }
+ else
+ bError = TRUE;
+ }
+ continueExec = (continueExec && !bError);
+ if (bError && CC_is_in_trans(conn))
+ {
+ if (res = CC_send_query(conn, "abort", NULL), res)
+ QR_Destructor(res);
+ CC_set_no_trans(conn);
+ bError = FALSE;
+ }
+ /* restore the client encoding */
+ sprintf(query, "SET CLIENT_ENCODING TO '%s'", conn->client_encoding);
+ if (res = CC_send_query(conn, query, NULL), res)
+ {
+ bError = QR_get_aborted(res);
+ QR_Destructor(res);
+ }
+ else
+ bError = TRUE;
+ if (bError || !continueExec)
+ return ret;
+ sprintf(query, "select relname from pg_class where OID = %s", saveoid);
+ if (res = CC_send_query(conn, query, NULL), res)
+ {
+ if (QR_get_num_tuples(res) > 0)
+ {
+ ret = strdup(QR_get_value_backend_row(res, 0, 0));
+ *nameAlloced = TRUE;
+ }
+ QR_Destructor(res);
+ }
+ return ret;
+}
+static char *
+getClientColumnName(ConnectionClass *conn, const char *serverTableName, char *serverColumnName, BOOL *nameAlloced)
+{
+ char query[1024],
+ saveattrelid[24],
+ saveattnum[16],
+ *ret = serverColumnName;
+ BOOL continueExec = TRUE,
+ bError = FALSE;
+ QResultClass *res;
+
+ *nameAlloced = FALSE;
+ if (!conn->client_encoding || !isMultibyte(serverColumnName))
+ return ret;
+ if (!conn->server_encoding)
+ {
+ if (res = CC_send_query(conn, "select getdatabaseencoding()", NULL), res)
+ {
+ if (QR_get_num_tuples(res) > 0)
+ conn->server_encoding = strdup(QR_get_value_backend_row(res, 0, 0));
+ QR_Destructor(res);
+ }
+ }
+ if (!conn->server_encoding)
+ return ret;
+ sprintf(query, "SET CLIENT_ENCODING TO '%s'", conn->server_encoding);
+ if (res = CC_send_query(conn, query, NULL), res)
+ {
+ bError = QR_get_aborted(res);
+ QR_Destructor(res);
+ }
+ else
+ bError = TRUE;
+ if (!bError && continueExec)
+ {
+ sprintf(query, "select attrelid, attnum from pg_class, pg_attribute "
+ "where relname = '%s' and attrelid = pg_class.oid "
+ "and attname = '%s'", serverTableName, serverColumnName);
+ if (res = CC_send_query(conn, query, NULL), res)
+ {
+ if (QR_get_num_tuples(res) > 0)
+ {
+ strcpy(saveattrelid, QR_get_value_backend_row(res, 0, 0));
+ strcpy(saveattnum, QR_get_value_backend_row(res, 0, 1));
+ }
+ else
+ {
+ continueExec = FALSE;
+ bError = QR_get_aborted(res);
+ }
+ QR_Destructor(res);
+ }
+ else
+ bError = TRUE;
+ }
+ continueExec = (continueExec && !bError);
+ if (bError && CC_is_in_trans(conn))
+ {
+ if (res = CC_send_query(conn, "abort", NULL), res)
+ QR_Destructor(res);
+ CC_set_no_trans(conn);
+ bError = FALSE;
+ }
+ /* restore the cleint encoding */
+ sprintf(query, "SET CLIENT_ENCODING TO '%s'", conn->client_encoding);
+ if (res = CC_send_query(conn, query, NULL), res)
+ {
+ bError = QR_get_aborted(res);
+ QR_Destructor(res);
+ }
+ else
+ bError = TRUE;
+ if (bError || !continueExec)
+ return ret;
+ sprintf(query, "select attname from pg_attribute where attrelid = %s and attnum = %s", saveattrelid, saveattnum);
+ if (res = CC_send_query(conn, query, NULL), res)
+ {
+ if (QR_get_num_tuples(res) > 0)
+ {
+ ret = strdup(QR_get_value_backend_row(res, 0, 0));
+ *nameAlloced = TRUE;
+ }
+ QR_Destructor(res);
+ }
+ return ret;
+}
+#endif /* MULTIBYTE */
+
+RETCODE SQL_API
+PGAPI_ForeignKeys(
+ HSTMT hstmt,
+ UCHAR FAR * szPkTableQualifier,
+ SWORD cbPkTableQualifier,
+ UCHAR FAR * szPkTableOwner,
+ SWORD cbPkTableOwner,
+ UCHAR FAR * szPkTableName,
+ SWORD cbPkTableName,
+ UCHAR FAR * szFkTableQualifier,
+ SWORD cbFkTableQualifier,
+ UCHAR FAR * szFkTableOwner,
+ SWORD cbFkTableOwner,
+ UCHAR FAR * szFkTableName,
+ SWORD cbFkTableName)
+{
+ static char *func = "PGAPI_ForeignKeys";
+ StatementClass *stmt = (StatementClass *) hstmt;
+ TupleNode *row;
+ HSTMT htbl_stmt,
+ hpkey_stmt;
+ StatementClass *tbl_stmt;
+ RETCODE result,
+ keyresult;
+ char tables_query[INFO_INQUIRY_LEN];
+ char trig_deferrable[2];
+ char trig_initdeferred[2];
+ char trig_args[1024];
+ char upd_rule[MAX_TABLE_LEN],
+ del_rule[MAX_TABLE_LEN];
+ char pk_table_needed[MAX_TABLE_LEN + 1];
+ char fk_table_needed[MAX_TABLE_LEN + 1];
+ char *pkey_ptr,
+ *pkey_text,
+ *fkey_ptr,
+ *fkey_text,
+ *pk_table,
+ *pkt_text,
+ *fk_table,
+ *fkt_text;
+
+#ifdef MULTIBYTE
+ BOOL pkey_alloced,
+ fkey_alloced,
+ pkt_alloced,
+ fkt_alloced;
+ ConnectionClass *conn;
+#endif /* MULTIBYTE */
+ int i,
+ j,
+ k,
+ num_keys;
+ SWORD trig_nargs,
+ upd_rule_type = 0,
+ del_rule_type = 0;
+
+#if (ODBCVER >= 0x0300)
+ SWORD defer_type;
+#endif
+ char pkey[MAX_INFO_STRING];
+ Int2 result_cols;
+
+ mylog("%s: entering...stmt=%u\n", func, stmt);
+
+ if (!stmt)
+ {
+ SC_log_error(func, "", NULL);
+ return SQL_INVALID_HANDLE;
+ }
+
+ stmt->manual_result = TRUE;
+ stmt->errormsg_created = TRUE;
+
+ stmt->result = QR_Constructor();
+ if (!stmt->result)
+ {
+ stmt->errormsg = "Couldn't allocate memory for PGAPI_ForeignKeys result.";
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ /* the binding structure for a statement is not set up until */
+
+ /*
+ * a statement is actually executed, so we'll have to do this
+ * ourselves.
+ */
+ result_cols = 14;
+ extend_bindings(stmt, result_cols);
+
+ /* set the field names */
+ QR_set_num_fields(stmt->result, result_cols);
+ QR_set_field_info(stmt->result, 0, "PKTABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 1, "PKTABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 2, "PKTABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 3, "PKCOLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 4, "FKTABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 5, "FKTABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 6, "FKTABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 7, "FKCOLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 8, "KEY_SEQ", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 9, "UPDATE_RULE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 10, "DELETE_RULE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 11, "FK_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 12, "PK_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 13, "TRIGGER_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+#if (ODBCVER >= 0x0300)
+ QR_set_field_info(stmt->result, 14, "DEFERRABILITY", PG_TYPE_INT2, 2);
+#endif /* ODBCVER >= 0x0300 */
+
+ /*
+ * also, things need to think that this statement is finished so the
+ * results can be retrieved.
+ */
+ stmt->status = STMT_FINISHED;
+
+ /* set up the current tuple pointer for SQLFetch */
+ stmt->currTuple = -1;
+ stmt->rowset_start = -1;
+ stmt->current_col = -1;
+
+
+ result = PGAPI_AllocStmt(stmt->hdbc, &htbl_stmt);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Couldn't allocate statement for PGAPI_ForeignKeys result.";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ tbl_stmt = (StatementClass *) htbl_stmt;
+
+ pk_table_needed[0] = '\0';
+ fk_table_needed[0] = '\0';
+
+ make_string(szPkTableName, cbPkTableName, pk_table_needed);
+ make_string(szFkTableName, cbFkTableName, fk_table_needed);
+
+#ifdef MULTIBYTE
+ pkey_text = fkey_text = pkt_text = fkt_text = NULL;
+ pkey_alloced = fkey_alloced = pkt_alloced = fkt_alloced = FALSE;
+ conn = SC_get_conn(stmt);
+#endif /* MULTIBYTE */
+
+ /*
+ * Case #2 -- Get the foreign keys in the specified table (fktab) that
+ * refer to the primary keys of other table(s).
+ */
+ if (fk_table_needed[0] != '\0')
+ {
+ mylog("%s: entering Foreign Key Case #2", func);
+ sprintf(tables_query, "SELECT pt.tgargs, "
+ " pt.tgnargs, "
+ " pt.tgdeferrable, "
+ " pt.tginitdeferred, "
+ " pg_proc.proname, "
+ " pg_proc_1.proname "
+ "FROM pg_class pc, "
+ " pg_proc pg_proc, "
+ " pg_proc pg_proc_1, "
+ " pg_trigger pg_trigger, "
+ " pg_trigger pg_trigger_1, "
+ " pg_proc pp, "
+ " pg_trigger pt "
+ "WHERE pt.tgrelid = pc.oid "
+ "AND pp.oid = pt.tgfoid "
+ "AND pg_trigger.tgconstrrelid = pc.oid "
+ "AND pg_proc.oid = pg_trigger.tgfoid "
+ "AND pg_trigger_1.tgfoid = pg_proc_1.oid "
+ "AND pg_trigger_1.tgconstrrelid = pc.oid "
+ "AND ((pc.relname='%s') "
+ "AND (pp.proname LIKE '%%ins') "
+ "AND (pg_proc.proname LIKE '%%upd') "
+ "AND (pg_proc_1.proname LIKE '%%del') "
+ "AND (pg_trigger.tgrelid=pt.tgconstrrelid) "
+ "AND (pg_trigger.tgconstrname=pt.tgconstrname) "
+ "AND (pg_trigger_1.tgrelid=pt.tgconstrrelid) "
+ "AND (pg_trigger_1.tgconstrname=pt.tgconstrname))",
+ fk_table_needed);
+
+ result = PGAPI_ExecDirect(htbl_stmt, tables_query, strlen(tables_query));
+
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = SC_create_errormsg(htbl_stmt);
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(htbl_stmt, 1, SQL_C_BINARY,
+ trig_args, sizeof(trig_args), NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(htbl_stmt, 2, SQL_C_SHORT,
+ &trig_nargs, 0, NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(htbl_stmt, 3, SQL_C_CHAR,
+ trig_deferrable, sizeof(trig_deferrable), NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(htbl_stmt, 4, SQL_C_CHAR,
+ trig_initdeferred, sizeof(trig_initdeferred), NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(htbl_stmt, 5, SQL_C_CHAR,
+ upd_rule, sizeof(upd_rule), NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_BindCol(htbl_stmt, 6, SQL_C_CHAR,
+ del_rule, sizeof(del_rule), NULL);
+ if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = PGAPI_Fetch(htbl_stmt);
+ if (result == SQL_NO_DATA_FOUND)
+ return SQL_SUCCESS;
+
+ if (result != SQL_SUCCESS)
+ {
+ stmt->errormsg = SC_create_errormsg(htbl_stmt);
+ stmt->errornumber = tbl_stmt->errornumber;
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ keyresult = PGAPI_AllocStmt(stmt->hdbc, &hpkey_stmt);
+ if ((keyresult != SQL_SUCCESS) && (keyresult != SQL_SUCCESS_WITH_INFO))
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Couldn't allocate statement for PGAPI_ForeignKeys (pkeys) result.";
+ SC_log_error(func, "", stmt);
+ return SQL_ERROR;
+ }
+
+ keyresult = PGAPI_BindCol(hpkey_stmt, 4, SQL_C_CHAR,
+ pkey, sizeof(pkey), NULL);
+ if (keyresult != SQL_SUCCESS)
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Couldn't bindcol for primary keys for PGAPI_ForeignKeys result.";
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hpkey_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ while (result == SQL_SUCCESS)
+ {
+ /* Compute the number of keyparts. */
+ num_keys = (trig_nargs - 4) / 2;
+
+ mylog("Foreign Key Case#2: trig_nargs = %d, num_keys = %d\n", trig_nargs, num_keys);
+
+ pk_table = trig_args;
+
+ /* Get to the PK Table Name */
+ for (k = 0; k < 2; k++)
+ pk_table += strlen(pk_table) + 1;
+
+#ifdef MULTIBYTE
+ fk_table = trig_args + strlen(trig_args) + 1;
+ pkt_text = getClientTableName(conn, pk_table, &pkt_alloced);
+#else
+ pkt_text = pk_table;
+#endif /* MULTIBYTE */
+ /* If there is a pk table specified, then check it. */
+ if (pk_table_needed[0] != '\0')
+ {
+ /* If it doesn't match, then continue */
+ if (strcmp(pkt_text, pk_table_needed))
+ {
+ result = PGAPI_Fetch(htbl_stmt);
+ continue;
+ }
+ }
+
+ keyresult = PGAPI_PrimaryKeys(hpkey_stmt, NULL, 0, NULL, 0, pkt_text, SQL_NTS);
+ if (keyresult != SQL_SUCCESS)
+ {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Couldn't get primary keys for PGAPI_ForeignKeys result.";
+ SC_log_error(func, "", stmt);
+ PGAPI_FreeStmt(hpkey_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+
+ /* Get to first primary key */
+ pkey_ptr = trig_args;
+ for (i = 0; i < 5; i++)
+ pkey_ptr += strlen(pkey_ptr) + 1;
+
+ for (k = 0; k < num_keys; k++)
+ {
+ /* Check that the key listed is the primary key */
+ keyresult = PGAPI_Fetch(hpkey_stmt);
+ if (keyresult != SQL_SUCCESS)
+ {
+ num_keys = 0;
+ break;
+ }
+#ifdef MULTIBYTE
+ pkey_text = getClientColumnName(conn, pk_table, pkey_ptr, &pkey_alloced);
+#else
+ pkey_text = pkey_ptr;
+#endif /* MULTIBYTE */
+ mylog("%s: pkey_ptr='%s', pkey='%s'\n", func, pkey_text, pkey);
+ if (strcmp(pkey_text, pkey))
+ {
+ num_keys = 0;
+ break;
+ }
+#ifdef MULTIBYTE
+ if (pkey_alloced)
+ free(pkey_text);
+#endif /* MULTIBYTE */
+ /* Get to next primary key */
+ for (k = 0; k < 2; k++)
+ pkey_ptr += strlen(pkey_ptr) + 1;
+
+ }
+
+ /* Set to first fk column */
+ fkey_ptr = trig_args;
+ for (k = 0; k < 4; k++)
+ fkey_ptr += strlen(fkey_ptr) + 1;
+
+ /* Set update and delete actions for foreign keys */
+ if (!strcmp(upd_rule, "RI_FKey_cascade_upd"))
+ upd_rule_type = SQL_CASCADE;
+ else if (!strcmp(upd_rule, "RI_FKey_noaction_upd"))
+ upd_rule_type = SQL_NO_ACTION;
+ else if (!strcmp(upd_rule, "RI_FKey_restrict_upd"))
+ upd_rule_type = SQL_NO_ACTION;
+ else if (!strcmp(upd_rule, "RI_FKey_setdefault_upd"))
+ upd_rule_type = SQL_SET_DEFAULT;
+ else if (!strcmp(upd_rule, "RI_FKey_setnull_upd"))
+ upd_rule_type = SQL_SET_NULL;
+
+ if (!strcmp(upd_rule, "RI_FKey_cascade_del"))
+ del_rule_type = SQL_CASCADE;
+ else if (!strcmp(upd_rule, "RI_FKey_noaction_del"))
+ del_rule_type = SQL_NO_ACTION;
+ else if (!strcmp(upd_rule, "RI_FKey_restrict_del"))
+ del_rule_type = SQL_NO_ACTION;
+ else if (!strcmp(upd_rule, "RI_FKey_setdefault_del"))
+ del_rule_type = SQL_SET_DEFAULT;
+ else if (!strcmp(upd_rule, "RI_FKey_setnull_del"))
+ del_rule_type = SQL_SET_NULL;
+
+#if (ODBCVER >= 0x0300)
+ /* Set deferrability type */
+ if (!strcmp(trig_initdeferred, "y"))
+ defer_type = SQL_INITIALLY_DEFERRED;
+ else if (!strcmp(trig_deferrable, "y"))
+ defer_type = SQL_INITIALLY_IMMEDIATE;
+ else
+ defer_type = SQL_NOT_DEFERRABLE;
+#endif /* ODBCVER >= 0x0300 */
+
+ /* Get to first primary key */
+ pkey_ptr = trig_args;
+ for (i = 0; i < 5; i++)
+ pkey_ptr += strlen(pkey_ptr) + 1;
+
+ for (k = 0; k < num_keys; k++)
+ {
+ row = (TupleNode *) malloc(sizeof(TupleNode) + (result_cols - 1) *sizeof(TupleField));
+
+#ifdef MULTIBYTE
+ pkey_text = getClientColumnName(conn, pk_table, pkey_ptr, &pkey_alloced);
+ fkey_text = getClientColumnName(conn, fk_table, fkey_ptr, &fkey_alloced);
+#else
+ pkey_text = pkey_ptr;
+ fkey_text = fkey_ptr;
+#endif /* MULTIBYTE */
+ mylog("%s: pk_table = '%s', pkey_ptr = '%s'\n", func, pkt_text, pkey_text);
+ set_tuplefield_null(&row->tuple[0]);
+ set_tuplefield_string(&row->tuple[1], "");
+ set_tuplefield_string(&row->tuple[2], pkt_text);
+ set_tuplefield_string(&row->tuple[3], pkey_text);
+
+ mylog("%s: fk_table_needed = '%s', fkey_ptr = '%s'\n", func, fk_table_needed, fkey_text);
+ set_tuplefield_null(&row->tuple[4]);
+ set_tuplefield_string(&row->tuple[5], "");
+ set_tuplefield_string(&row->tuple[6], fk_table_needed);
+ set_tuplefield_string(&row->tuple[7], fkey_text);
+
+ mylog("%s: upd_rule_type = '%i', del_rule_type = '%i'\n, trig_name = '%s'", func, upd_rule_type, del_rule_type, trig_args);
+ set_tuplefield_int2(&row->tuple[8], (Int2) (k + 1));
+ set_tuplefield_int2(&row->tuple[9], (Int2) upd_rule_type);
+ set_tuplefield_int2(&row->tuple[10], (Int2) del_rule_type);
+ &nb