From c0b27c4feb286729613401302e58974f20e05f8f Mon Sep 17 00:00:00 2001
From: Hiroshi Inoue <inoue@tpf.co.jp>
Date: Mon, 5 Nov 2001 09:46:17 +0000
Subject: [PATCH] 1) Fix a few bugs about SQLGetData()    reported by Mika
 Mantyla. 2) Timestamp precision. 3) Separate ODBC3.0 files.

---
 src/interfaces/odbc/bind.c       |   2 +
 src/interfaces/odbc/bind.h       |   2 +
 src/interfaces/odbc/columninfo.c |  12 +-
 src/interfaces/odbc/convert.c    | 374 +++++++++++++++++++++++++------
 src/interfaces/odbc/convert.h    |   3 +-
 src/interfaces/odbc/info.c       |  30 +--
 src/interfaces/odbc/odbcapi30.c  |   8 +-
 src/interfaces/odbc/pgtypes.c    |  67 +++++-
 src/interfaces/odbc/pgtypes.h    |   4 +-
 src/interfaces/odbc/psqlodbc.h   |  21 +-
 src/interfaces/odbc/psqlodbc.rc  |   8 +-
 src/interfaces/odbc/statement.c  |   8 +
 src/interfaces/odbc/win32.mak    |   9 -
 13 files changed, 428 insertions(+), 120 deletions(-)

diff --git a/src/interfaces/odbc/bind.c b/src/interfaces/odbc/bind.c
index d176acee6b1..e9f5cc34827 100644
--- a/src/interfaces/odbc/bind.c
+++ b/src/interfaces/odbc/bind.c
@@ -424,6 +424,8 @@ create_empty_bindings(int num_columns)
 		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;
diff --git a/src/interfaces/odbc/bind.h b/src/interfaces/odbc/bind.h
index 444d30f2d22..363abb059bf 100644
--- a/src/interfaces/odbc/bind.h
+++ b/src/interfaces/odbc/bind.h
@@ -22,6 +22,8 @@ struct BindInfoClass_
 	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...) */
diff --git a/src/interfaces/odbc/columninfo.c b/src/interfaces/odbc/columninfo.c
index d9a22a163a1..7fe72d3f6d1 100644
--- a/src/interfaces/odbc/columninfo.c
+++ b/src/interfaces/odbc/columninfo.c
@@ -12,6 +12,7 @@
  *-------
  */
 
+#include "pgtypes.h"
 #include "columninfo.h"
 
 #include "connection.h"
@@ -95,7 +96,16 @@ CI_read_fields(ColumnInfoClass *self, ConnectionClass *conn)
 			new_atttypmod = (Int4) SOCK_get_int(sock, 4);
 
 			/* Subtract the header length */
-			new_atttypmod -= 4;
+			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;
 
diff --git a/src/interfaces/odbc/convert.c b/src/interfaces/odbc/convert.c
index 871a9c37410..f284b16df91 100644
--- a/src/interfaces/odbc/convert.c
+++ b/src/interfaces/odbc/convert.c
@@ -38,6 +38,11 @@
 #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.
@@ -136,6 +141,156 @@ static char *conv_to_octal(unsigned char val);
 
 
 
+/*	
+ *	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(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)
@@ -165,14 +320,29 @@ copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2
 	int			bind_size = stmt->options.bind_size;
 	int			result = COPY_OK;
 	BOOL		changed;
-	static char *tempBuf = NULL;
-	static unsigned int tempBuflen = 0;
 	const char *neut_str = value;
 	char		midtemp[2][32];
 	int			mtemp_cnt = 0;
+	static	BindInfoClass	sbic;
+	BindInfoClass		*pbic;
 
-	if (!tempBuf)
-		tempBuflen = 0;
+	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
@@ -243,8 +413,15 @@ copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2
 		case PG_TYPE_ABSTIME:
 		case PG_TYPE_DATETIME:
 		case PG_TYPE_TIMESTAMP:
+			st.fr = 0;
 			if (strnicmp(value, "invalid", 7) != 0)
-				sscanf(value, "%4d-%2d-%2d %2d:%2d:%2d", &st.y, &st.m, &st.d, &st.hh, &st.mm, &st.ss);
+			{
+				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
 			{
 				/*
@@ -421,10 +598,14 @@ copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2
 				break;
 
 			default:
-				if (stmt->current_col >= 0 && stmt->bindings[stmt->current_col].data_left == -2)
-					stmt->bindings[stmt->current_col].data_left = (cbValueMax > 0) ? 0 : -1;	/* This seems to be
-																								 * needed for ADO ? */
-				if (stmt->current_col < 0 || stmt->bindings[stmt->current_col].data_left < 0)
+				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);
@@ -434,51 +615,42 @@ copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2
 						result = COPY_RESULT_TRUNCATED;
 						break;
 					}
+					if (!pbic->ttlbuf)
+						pbic->ttlbuflen = 0;
 					if (changed || len >= cbValueMax)
 					{
-						if (len >= (int) tempBuflen)
+						if (len >= (int) pbic->ttlbuflen)
 						{
-							tempBuf = realloc(tempBuf, len + 1);
-							tempBuflen = len + 1;
+							pbic->ttlbuf = realloc(pbic->ttlbuf, len + 1);
+							pbic->ttlbuflen = len + 1;
 						}
-						convert_linefeeds(neut_str, tempBuf, tempBuflen, &changed);
-						ptr = tempBuf;
+						convert_linefeeds(neut_str, pbic->ttlbuf, pbic->ttlbuflen, &changed);
+						ptr = pbic->ttlbuf;
 					}
 					else
 					{
-						if (tempBuf)
+						if (pbic->ttlbuf)
 						{
-							free(tempBuf);
-							tempBuf = NULL;
+							free(pbic->ttlbuf);
+							pbic->ttlbuf = NULL;
 						}
 						ptr = neut_str;
 					}
 				}
 				else
-					ptr = tempBuf;
+					ptr = pbic->ttlbuf;
 
 				mylog("DEFAULT: len = %d, ptr = '%s'\n", len, ptr);
 
 				if (stmt->current_col >= 0)
 				{
-					if (stmt->bindings[stmt->current_col].data_left == 0)
+					if (pbic->data_left > 0)
 					{
-						if (tempBuf)
-						{
-							free(tempBuf);
-							tempBuf = NULL;
-						}
-						/* The following seems to be needed for ADO ? */
-						stmt->bindings[stmt->current_col].data_left = -2;
-						return COPY_NO_DATA_FOUND;
-					}
-					else if (stmt->bindings[stmt->current_col].data_left > 0)
-					{
-						ptr += strlen(ptr) - stmt->bindings[stmt->current_col].data_left;
-						len = stmt->bindings[stmt->current_col].data_left;
+						ptr += strlen(ptr) - pbic->data_left;
+						len = pbic->data_left;
 					}
 					else
-						stmt->bindings[stmt->current_col].data_left = len;
+						pbic->data_left = len;
 				}
 
 				if (cbValueMax > 0)
@@ -491,7 +663,7 @@ copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2
 
 					/* Adjust data_left for next time */
 					if (stmt->current_col >= 0)
-						stmt->bindings[stmt->current_col].data_left -= copy_len;
+						pbic->data_left -= copy_len;
 				}
 
 				/*
@@ -502,10 +674,10 @@ copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2
 					result = COPY_RESULT_TRUNCATED;
 				else
 				{
-					if (tempBuf)
+					if (pbic->ttlbuf != NULL)
 					{
-						free(tempBuf);
-						tempBuf = NULL;
+						free(pbic->ttlbuf);
+						pbic->ttlbuf = NULL;
 					}
 				}
 
@@ -537,6 +709,9 @@ copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2
 		switch (fCType)
 		{
 			case SQL_C_DATE:
+#if (ODBCVER >= 0x0300)
+			case SQL_C_TYPE_DATE: /* 91 */
+#endif
 				len = 6;
 				{
 					DATE_STRUCT *ds;
@@ -552,6 +727,9 @@ copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2
 				break;
 
 			case SQL_C_TIME:
+#if (ODBCVER >= 0x0300)
+			case SQL_C_TYPE_TIME: /* 92 */
+#endif
 				len = 6;
 				{
 					TIME_STRUCT *ts;
@@ -567,6 +745,9 @@ copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2
 				break;
 
 			case SQL_C_TIMESTAMP:
+#if (ODBCVER >= 0x0300)
+			case SQL_C_TYPE_TIMESTAMP: /* 93 */
+#endif
 				len = 16;
 				{
 					TIMESTAMP_STRUCT *ts;
@@ -581,7 +762,7 @@ copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2
 					ts->hour = st.hh;
 					ts->minute = st.mm;
 					ts->second = st.ss;
-					ts->fraction = 0;
+					ts->fraction = st.fr;
 				}
 				break;
 
@@ -671,37 +852,38 @@ copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2
 				/* truncate if necessary */
 				/* convert octal escapes to bytes */
 
-				if (len = strlen(neut_str), len >= (int) tempBuflen)
+				if (stmt->current_col < 0)
 				{
-					tempBuf = realloc(tempBuf, len + 1);
-					tempBuflen = len + 1;
+					pbic = &sbic;
+					pbic->data_left = -1;
 				}
-				len = convert_from_pgbinary(neut_str, tempBuf, tempBuflen);
-				ptr = tempBuf;
+				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)
 				{
-					/* No more data left for this column */
-					if (stmt->bindings[stmt->current_col].data_left == 0)
-					{
-						free(tempBuf);
-						tempBuf = NULL;
-						return COPY_NO_DATA_FOUND;
-					}
-
 					/*
 					 * Second (or more) call to SQLGetData so move the
 					 * pointer
 					 */
-					else if (stmt->bindings[stmt->current_col].data_left > 0)
+					if (pbic->data_left > 0)
 					{
-						ptr += len - stmt->bindings[stmt->current_col].data_left;
-						len = stmt->bindings[stmt->current_col].data_left;
+						ptr += len - pbic->data_left;
+						len = pbic->data_left;
 					}
 
 					/* First call to SQLGetData so initialize data_left */
 					else
-						stmt->bindings[stmt->current_col].data_left = len;
+						pbic->data_left = len;
 
 				}
 
@@ -714,7 +896,7 @@ copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2
 
 					/* Adjust data_left for next time */
 					if (stmt->current_col >= 0)
-						stmt->bindings[stmt->current_col].data_left -= copy_len;
+						pbic->data_left -= copy_len;
 				}
 
 				/*
@@ -724,10 +906,10 @@ copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2
 				if (len > cbValueMax)
 					result = COPY_RESULT_TRUNCATED;
 
-				if (tempBuf)
+				if (pbic->ttlbuf)
 				{
-					free(tempBuf);
-					tempBuf = NULL;
+					free(pbic->ttlbuf);
+					pbic->ttlbuf = NULL;
 				}
 				mylog("SQL_C_BINARY: len = %d, copy_len = %d\n", len, copy_len);
 				break;
@@ -741,6 +923,8 @@ copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2
 	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;
 
 }
@@ -1406,6 +1590,9 @@ copy_statement_with_parameters(StatementClass *stmt)
 				}
 
 			case SQL_C_DATE:
+#if (ODBCVER >= 0x0300)
+			case SQL_C_TYPE_DATE: /* 91 */
+#endif
 				{
 					DATE_STRUCT *ds = (DATE_STRUCT *) buffer;
 
@@ -1417,6 +1604,9 @@ copy_statement_with_parameters(StatementClass *stmt)
 				}
 
 			case SQL_C_TIME:
+#if (ODBCVER >= 0x0300)
+			case SQL_C_TYPE_TIME: /* 92 */
+#endif
 				{
 					TIME_STRUCT *ts = (TIME_STRUCT *) buffer;
 
@@ -1428,6 +1618,9 @@ copy_statement_with_parameters(StatementClass *stmt)
 				}
 
 			case SQL_C_TIMESTAMP:
+#if (ODBCVER >= 0x0300)
+			case SQL_C_TYPE_TIMESTAMP: /* 93 */
+#endif
 				{
 					TIMESTAMP_STRUCT *tss = (TIMESTAMP_STRUCT *) buffer;
 
@@ -1437,6 +1630,7 @@ copy_statement_with_parameters(StatementClass *stmt)
 					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);
 
@@ -1487,6 +1681,9 @@ copy_statement_with_parameters(StatementClass *stmt)
 				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);
@@ -1499,6 +1696,9 @@ copy_statement_with_parameters(StatementClass *stmt)
 				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);
@@ -1511,6 +1711,9 @@ copy_statement_with_parameters(StatementClass *stmt)
 				break;
 
 			case SQL_TIMESTAMP:
+#if (ODBCVER >= 0x0300)
+			case SQL_TYPE_TIMESTAMP: /* 93 */
+#endif
 
 				if (buf)
 				{
@@ -1518,8 +1721,12 @@ copy_statement_with_parameters(StatementClass *stmt)
 					parse_datetime(cbuf, &st);
 				}
 
-				sprintf(tmp, "'%.4d-%.2d-%.2d %.2d:%.2d:%.2d'",
-						st.y, st.m, st.d, st.hh, st.mm, st.ss);
+				/* 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);
 
@@ -2203,7 +2410,43 @@ decode(const char *in, char *out)
 	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
@@ -2231,6 +2474,7 @@ convert_lo(StatementClass *stmt, const void *value, Int2 fCType, PTR rgbValue,
 	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)
@@ -2304,7 +2548,7 @@ convert_lo(StatementClass *stmt, const void *value, Int2 fCType, PTR rgbValue,
 		return COPY_GENERAL_ERROR;
 	}
 
-	retval = lo_read(conn, stmt->lobj_fd, (char *) rgbValue, cbValueMax);
+	retval = lo_read(conn, stmt->lobj_fd, (char *) rgbValue, factor > 1 ? (cbValueMax - 1) / factor : cbValueMax);
 	if (retval < 0)
 	{
 		lo_close(conn, stmt->lobj_fd);
@@ -2341,13 +2585,15 @@ convert_lo(StatementClass *stmt, const void *value, Int2 fCType, PTR rgbValue,
 		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;
+		*pcbValue = left < 0 ? SQL_NO_TOTAL : left * factor;
 
 	if (bindInfo && bindInfo->data_left > 0)
 		bindInfo->data_left -= retval;
diff --git a/src/interfaces/odbc/convert.h b/src/interfaces/odbc/convert.h
index dfbd32ce247..2257fb27793 100644
--- a/src/interfaces/odbc/convert.h
+++ b/src/interfaces/odbc/convert.h
@@ -27,7 +27,8 @@ typedef struct
 	int			hh;
 	int			mm;
 	int			ss;
-} SIMPLE_TIME;
+	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,
diff --git a/src/interfaces/odbc/info.c b/src/interfaces/odbc/info.c
index 09439b2a5ea..23291b55546 100644
--- a/src/interfaces/odbc/info.c
+++ b/src/interfaces/odbc/info.c
@@ -216,32 +216,6 @@ PGAPI_GetInfo(
 
 		case SQL_DRIVER_ODBC_VER:
 			p = DRIVER_ODBC_VER;
-#ifdef	DRIVER_CURSOR_IMPLEMENT
-			{
-				static char dver[32];
-
-				SQLGetPrivateProfileString(DBMS_NAME,
-				"DriverODBCVer", "", dver, sizeof(dver), "odbcinst.ini");
-				if (dver[0])
-				{
-					int			major,
-								minor;
-
-					mylog("REGISTRY_ODBC_VER = %s\n", dver)
-						;
-					if (sscanf(dver, "%x.%x", &major, &minor) >= 2)
-					{
-						Int2		drv_ver = (major << 8) + minor;
-
-						if (drv_ver > ODBCVER)
-						{
-							conn->driver_version = drv_ver;
-							p = dver;
-						}
-					}
-				}
-			}
-#endif	 /* DRIVER_CURSOR_IMPLEMENT */
 			break;
 
 		case SQL_DRIVER_VER:	/* ODBC 1.0 */
@@ -842,6 +816,7 @@ PGAPI_GetFunctions(
 
 	if (fFunction == SQL_API_ALL_FUNCTIONS)
 	{
+#if (ODBCVER < 0x0300)
 		if (ci->drivers.lie)
 		{
 			int			i;
@@ -856,6 +831,7 @@ PGAPI_GetFunctions(
 				pfExists[i] = TRUE;
 		}
 		else
+#endif
 		{
 			memset(pfExists, 0, sizeof(UWORD) * 100);
 
@@ -2685,7 +2661,7 @@ PGAPI_PrimaryKeys(
 /*
  *	Multibyte support stuff for SQLForeignKeys().
  *	There may be much more effective way in the
- *	future version. The way is very forcive currently.
+ *	future version. The way is very forcible currently.
  */
 static BOOL
 isMultibyte(const unsigned char *str)
diff --git a/src/interfaces/odbc/odbcapi30.c b/src/interfaces/odbc/odbcapi30.c
index 5a63748bf50..9de10a91095 100644
--- a/src/interfaces/odbc/odbcapi30.c
+++ b/src/interfaces/odbc/odbcapi30.c
@@ -18,8 +18,8 @@
  *-------
  */
 
-#ifndef ODBCVER_REP
-#define ODBCVER_REP 0x0300
+#ifndef ODBCVER
+#define ODBCVER 0x0300
 #endif
 #include "psqlodbc.h"
 #include <stdio.h>
@@ -344,7 +344,7 @@ SQLGetStmtAttr(HSTMT StatementHandle,
 			len = 4;
 			break;
 		case SQL_ATTR_AUTO_IPD:	/* 10001 */
-		case SQL_ATTR_ROW_BIND_TYPE:	/* == SQL_BIND_TYPE */
+		/* case SQL_ATTR_ROW_BIND_TYPE:	** == SQL_BIND_TYPE(ODBC2.0) */
 		case SQL_ATTR_PARAMSET_SIZE:	/* 22 */
 		case SQL_ATTR_PARAM_STATUS_PTR: /* 20 */
 		case SQL_ATTR_PARAMS_PROCESSED_PTR:		/* 21 */
@@ -484,7 +484,7 @@ SQLSetStmtAttr(HSTMT StatementHandle,
 		case SQL_ATTR_APP_ROW_DESC:		/* 10010 */
 		case SQL_ATTR_APP_PARAM_DESC:	/* 10011 */
 		case SQL_ATTR_AUTO_IPD:	/* 10001 */
-			/* case SQL_ATTR_ROW_BIND_TYPE: */	/* == SQL_BIND_TYPE */
+		/* case SQL_ATTR_ROW_BIND_TYPE: ** == SQL_BIND_TYPE(ODBC2.0) */
 		case SQL_ATTR_IMP_ROW_DESC:		/* 10012 */
 		case SQL_ATTR_IMP_PARAM_DESC:	/* 10013 */
 		case SQL_ATTR_METADATA_ID:		/* 10014 */
diff --git a/src/interfaces/odbc/pgtypes.c b/src/interfaces/odbc/pgtypes.c
index 1f177a10c4b..0a7bd35b6e0 100644
--- a/src/interfaces/odbc/pgtypes.c
+++ b/src/interfaces/odbc/pgtypes.c
@@ -536,6 +536,67 @@ getCharPrecision(StatementClass *stmt, Int4 type, int col, int handle_unknown_si
 		return p;
 }
 
+static Int2
+getTimestampScale(StatementClass *stmt, Int4 type, int col)
+{
+	ConnectionClass	*conn = SC_get_conn (stmt);
+	Int4		atttypmod;
+	QResultClass *result;
+	ColumnInfoClass *flds;
+
+	mylog("getTimestampScale: type=%d, col=%d\n", type, col);
+
+	if (col < 0)
+		return 0;
+	if (PG_VERSION_LT(conn, 7.2))
+		return 0;
+
+	result = SC_get_Result(stmt);
+
+	/*
+	 * Manual Result Sets -- use assigned column width (i.e., from
+	 * set_tuplefield_string)
+	 */
+	atttypmod = 0;
+	if (stmt->manual_result)
+	{
+		flds = result->fields;
+		if (flds)
+			atttypmod = flds->atttypmod[col];
+mylog("atttypmod1=%d\n", atttypmod);
+	}
+	else 
+		atttypmod = QR_get_atttypmod(result, col);
+mylog("atttypmod2=%d\n", atttypmod);
+	return (atttypmod > -1 ? atttypmod : 0);
+}
+
+
+static Int4
+getTimestampPrecision(StatementClass *stmt, Int4 type, int col)
+{
+	Int4	fixed, scale;
+
+	mylog("getTimestampPrecision: type=%d, col=%d\n", type, col);
+
+	switch (type)
+	{
+		case PG_TYPE_TIME:
+			fixed = 8;
+			break;
+		case PG_TYPE_TIME_WITH_TMZONE:
+			fixed = 11;
+			break;
+		case PG_TYPE_TIMESTAMP_NO_TMZONE:
+			fixed = 19;
+			break;
+		default:
+			fixed = 22;
+			break;
+	}
+	scale = getTimestampScale(stmt, type, col);	
+	return (scale > 0) ? fixed + 1 + scale : fixed;	
+}
 
 /*
  *	For PG_TYPE_VARCHAR, PG_TYPE_BPCHAR, PG_TYPE_NUMERIC, SQLColumns will
@@ -591,7 +652,8 @@ pgtype_precision(StatementClass *stmt, Int4 type, int col, int handle_unknown_si
 		case PG_TYPE_TIMESTAMP:
 			return 22;
 		case PG_TYPE_DATETIME:
-			return 22;
+			/* return 22; */
+			return getTimestampPrecision(stmt, type, col);
 
 		case PG_TYPE_BOOL:
 			return 1;
@@ -728,7 +790,8 @@ pgtype_scale(StatementClass *stmt, Int4 type, int col)
 		case PG_TYPE_TIMESTAMP:
 			return 0;
 		case PG_TYPE_DATETIME:
-			return 0;
+			/* return 0; */
+			return getTimestampScale(stmt, type, col);
 
 		case PG_TYPE_NUMERIC:
 			return getNumericScale(stmt, type, col);
diff --git a/src/interfaces/odbc/pgtypes.h b/src/interfaces/odbc/pgtypes.h
index 7bd33cf7512..0e6a1cc8976 100644
--- a/src/interfaces/odbc/pgtypes.h
+++ b/src/interfaces/odbc/pgtypes.h
@@ -58,8 +58,10 @@
 #define PG_TYPE_VARCHAR			1043
 #define PG_TYPE_DATE			1082
 #define PG_TYPE_TIME			1083
+#define PG_TYPE_TIMESTAMP_NO_TMZONE	1114	/* since 7.2 */
 #define PG_TYPE_DATETIME		1184
-#define PG_TYPE_TIMESTAMP		1296
+#define PG_TYPE_TIME_WITH_TMZONE	1266	/* since 7.1 */
+#define PG_TYPE_TIMESTAMP		1296	/* deprecated since 7.0 */
 #define PG_TYPE_NUMERIC			1700
 
 /* extern Int4 pgtypes_defined[]; */
diff --git a/src/interfaces/odbc/psqlodbc.h b/src/interfaces/odbc/psqlodbc.h
index 540e295d564..62e6b9b23a2 100644
--- a/src/interfaces/odbc/psqlodbc.h
+++ b/src/interfaces/odbc/psqlodbc.h
@@ -5,7 +5,7 @@
  *
  * Comments:		See "notice.txt" for copyright and license information.
  *
- * $Id: psqlodbc.h,v 1.53 2001/10/28 06:26:14 momjian Exp $
+ * $Id: psqlodbc.h,v 1.54 2001/11/05 09:46:17 inoue Exp $
  *
  */
 
@@ -21,9 +21,7 @@
 #include <stdio.h>				/* for FILE* pointers: see GLOBAL_VALUES */
 
 /* Must come before sql.h */
-#ifdef	ODBCVER_REP
-#define ODBCVER						ODBCVER_REP
-#else
+#ifndef	ODBCVER
 #define ODBCVER						0x0250
 #endif	 /* ODBCVER_REP */
 
@@ -77,18 +75,27 @@ typedef UInt4 Oid;
 #endif
 
 /* Driver stuff */
-#define DRIVER_ODBC_VER				"02.50"
 
 #define DRIVERNAME					"PostgreSQL ODBC"
+#if (ODBCVER >= 0x0300)
+#define DRIVER_ODBC_VER				"03.00"
+#define DBMS_NAME					"PostgreSQL30"
+#else
+#define DRIVER_ODBC_VER				"02.50"
 #define DBMS_NAME					"PostgreSQL"
+#endif /* ODBCVER */
 
-#define POSTGRESDRIVERVERSION		"07.01.0008"
+#define POSTGRESDRIVERVERSION		"07.01.0009"
 
 #ifdef WIN32
+#if (ODBCVER >= 0x0300)
+#define DRIVER_FILE_NAME			"PSQLODBC30.DLL"
+#else
 #define DRIVER_FILE_NAME			"PSQLODBC.DLL"
+#endif /* ODBCVER */
 #else
 #define DRIVER_FILE_NAME			"libpsqlodbc.so"
-#endif
+#endif /* WIN32 */
 
 /* Limits */
 #ifdef WIN32
diff --git a/src/interfaces/odbc/psqlodbc.rc b/src/interfaces/odbc/psqlodbc.rc
index a6c9b23bc92..30a7ff7203d 100644
--- a/src/interfaces/odbc/psqlodbc.rc
+++ b/src/interfaces/odbc/psqlodbc.rc
@@ -354,8 +354,8 @@ END
 //
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 7,1,0,8
- PRODUCTVERSION 7,1,0,8
+ FILEVERSION 7,1,0,9
+ PRODUCTVERSION 7,1,0,9
  FILEFLAGSMASK 0x3L
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -377,14 +377,14 @@ BEGIN
             VALUE "CompanyName", "Insight Distribution Systems\0"
 #endif
             VALUE "FileDescription", "PostgreSQL Driver\0"
-            VALUE "FileVersion", " 07.01.0008\0"
+            VALUE "FileVersion", " 07.01.0009\0"
             VALUE "InternalName", "psqlodbc\0"
             VALUE "LegalCopyright", "\0"
             VALUE "LegalTrademarks", "ODBC(TM) is a trademark of Microsoft Corporation.  Microsoft� is a registered trademark of Microsoft Corporation. Windows(TM) is a trademark of Microsoft Corporation.\0"
             VALUE "OriginalFilename", "psqlodbc.dll\0"
             VALUE "PrivateBuild", "\0"
             VALUE "ProductName", "Microsoft Open Database Connectivity\0"
-            VALUE "ProductVersion", " 07.01.0008\0"
+            VALUE "ProductVersion", " 07.01.0009\0"
             VALUE "SpecialBuild", "\0"
         END
     END
diff --git a/src/interfaces/odbc/statement.c b/src/interfaces/odbc/statement.c
index b4e33d05923..b2798506785 100644
--- a/src/interfaces/odbc/statement.c
+++ b/src/interfaces/odbc/statement.c
@@ -320,7 +320,15 @@ SC_Destructor(StatementClass *self)
 	 */
 	/* about that here. */
 	if (self->bindings)
+	{
+		int	lf;
+		for (lf = 0; lf < self->bindings_allocated; lf++)
+		{
+			if (self->bindings[lf].ttlbuf != NULL)
+				free(self->bindings[lf].ttlbuf);
+		}
 		free(self->bindings);
+	}
 
 	/* Free the parsed table information */
 	if (self->ti)
diff --git a/src/interfaces/odbc/win32.mak b/src/interfaces/odbc/win32.mak
index 0158adbfa3a..6d83b1c7aa2 100644
--- a/src/interfaces/odbc/win32.mak
+++ b/src/interfaces/odbc/win32.mak
@@ -84,7 +84,6 @@ CLEAN :
 	-@erase "$(INTDIR)\tuple.obj"
 	-@erase "$(INTDIR)\tuplelist.obj"
 	-@erase "$(INTDIR)\odbcapi.obj"
-	-@erase "$(INTDIR)\odbcapi30.obj"
 	-@erase "$(INTDIR)\vc60.idb"
 	-@erase "$(OUTDIR)\psqlodbc.dll"
 	-@erase "$(OUTDIR)\psqlodbc.exp"
@@ -169,7 +168,6 @@ LINK32_OBJS= \
 	"$(INTDIR)\tuple.obj" \
 	"$(INTDIR)\tuplelist.obj" \
 	"$(INTDIR)\odbcapi.obj" \
-	"$(INTDIR)\odbcapi30.obj" \
 	"$(INTDIR)\psqlodbc.res"
 
 "$(OUTDIR)\psqlodbc.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
@@ -219,7 +217,6 @@ CLEAN :
 	-@erase "$(INTDIR)\tuple.obj"
 	-@erase "$(INTDIR)\tuplelist.obj"
 	-@erase "$(INTDIR)\odbcapi.obj"
-	-@erase "$(INTDIR)\odbcapi30.obj"
 	-@erase "$(INTDIR)\vc60.idb"
 	-@erase "$(INTDIR)\vc60.pdb"
 	-@erase "$(OUTDIR)\psqlodbc.dll"
@@ -307,7 +304,6 @@ LINK32_OBJS= \
 	"$(INTDIR)\tuple.obj" \
 	"$(INTDIR)\tuplelist.obj" \
 	"$(INTDIR)\odbcapi.obj" \
-	"$(INTDIR)\odbcapi30.obj" \
 	"$(INTDIR)\psqlodbc.res"
 
 "$(OUTDIR)\psqlodbc.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
@@ -496,11 +492,6 @@ SOURCE=odbcapi.c
 	$(CPP) $(CPP_PROJ) $(SOURCE)
 
 
-SOURCE=odbcapi30.c
-
-"$(INTDIR)\odbcapi30.obj" : $(SOURCE) "$(INTDIR)"
-	$(CPP) $(CPP_PROJ) $(SOURCE)
-
 
 
 !ENDIF