/*-------
 * 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>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#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"	  }, built_in */
	{"CHAR", "chr($*)" },
	{"CONCAT", "textcat($*)" },
/*	{ "DIFFERENCE", "difference" }, how to ? */
	{"INSERT", "substring($1 from 1 for $2 - 1) || $4 || substring($1 from $2 + $3)" },
	{"LCASE", "lower($*)" },
	{"LEFT", "ltrunc($*)" },
	{"%2LOCATE", "strpos($2,  $1)" },	/* 2 parameters */
	{"%3LOCATE", "strpos(substring($2 from $3), $1) + $3 - 1" },	/* 3 parameters */
	{"LENGTH", "char_length($*)"},
/*	{ "LTRIM",		 "ltrim"	  }, built_in */
	{"RIGHT", "rtrunc($*)" },
	{"SPACE", "repeat('' '', $1)" },
/*	{ "REPEAT",		 "repeat"	  }, built_in */
/*	{ "REPLACE", "replace" }, ??? */
/*	{ "RTRIM",		 "rtrim"	  }, built_in */
/*	{ "SOUNDEX", "soundex" }, how to ? */
	{"SUBSTRING", "substr($*)" },
	{"UCASE", "upper($*)" },

/*	{ "ABS",		 "abs"		  }, built_in */
/*	{ "ACOS",		 "acos"		  }, built_in */
/*	{ "ASIN",		 "asin"		  }, built_in */
/*	{ "ATAN",		 "atan"		  }, built_in */
/*	{ "ATAN2",		 "atan2"	  }, bui;t_in */
	{"CEILING", "ceil($*)" },
/*	{ "COS",		 "cos" 		  }, built_in */
/*	{ "COT",		 "cot" 		  }, built_in */
/*	{ "DEGREES",		 "degrees" 	  }, built_in */
/*	{ "EXP",		 "exp" 		  }, built_in */
/*	{ "FLOOR",		 "floor" 	  }, built_in */
	{"LOG", "ln($*)" },
	{"LOG10", "log($*)" },
/*	{ "MOD",		 "mod" 		  }, built_in */
/*	{ "PI",			 "pi" 		  }, built_in */
	{"POWER", "pow($*)" },
/*	{ "RADIANS",		 "radians"	  }, built_in */
	{"%0RAND", "random()" },	/* 0 parameters */
	{"%1RAND", "(setseed($1) * .0 + random())" },	/* 1 parameters */
/*	{ "ROUND",		 "round"	  }, built_in */
/*	{ "SIGN",		 "sign"		  }, built_in */
/*	{ "SIN",		 "sin"		  }, built_in */
/*	{ "SQRT",		 "sqrt"		  }, built_in */
/*	{ "TAN",		 "tan"		  }, built_in */
	{"TRUNCATE", "trunc($*)" },

	{"CURRENT_DATE", "current_date" },
	{"CURRENT_TIME", "current_time" },
	{"CURRENT_TIMESTAMP", "current_timestamp" },
	{"CURRENT_USER", "cast(current_user as text)" },
	{"SESSION_USER", "cast(session_user as text)" },
	{"CURDATE",	 "current_date" },
	{"CURTIME",	 "current_time" },
	{"DAYNAME",	 "to_char($1, 'Day')" },
	{"DAYOFMONTH",  "cast(extract(day from $1) as integer)" },
	{"DAYOFWEEK",	 "(cast(extract(dow from $1) as integer) + 1)" },
	{"DAYOFYEAR",	 "cast(extract(doy from $1) as integer)" }, 
	{"HOUR",	 "cast(extract(hour from $1) as integer)" },
	{"MINUTE",	"cast(extract(minute from $1) as integer)" },
	{"MONTH",	"cast(extract(month from $1) as integer)" },
	{"MONTHNAME",	 " to_char($1, 'Month')" },
/*	{ "NOW",		 "now"		  }, built_in */
	{"QUARTER",	 "cast(extract(quarter from $1) as integer)" },
	{"SECOND",	"cast(extract(second from $1) as integer)" },
	{"WEEK",	"cast(extract(week from $1) as integer)" },
	{"YEAR",	"cast(extract(year from $1) as integer)" },

/*	{ "DATABASE",	 "database"   }, */
	{"IFNULL", "coalesce($*)" },
	{"USER", "cast(current_user as text)" },
	{0, 0}
};

static const char *mapFunction(const char *func, int param_count);
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;
#if defined(WIN32) || defined(HAVE_INT_TIMEZONE)
	long		timediff;
#endif
	BOOL		withZone = *bZone;

	*bZone = FALSE;
	*zone = 0;
	st->fr = 0;
	st->infinity = 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 (st->infinity > 0)
	{
		strcpy(str, "Infinity");
		return TRUE;
	}
	else if (st->infinity < 0)
	{
		strcpy(str, "-Infinity");
		return TRUE;
	}
	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)
{
	ARDFields *opts = SC_get_ARD(stmt);
	BindInfoClass *bic = &(opts->bindings[col]);
	UInt4	offset = opts->row_offset_ptr ? *opts->row_offset_ptr : 0;

	return copy_and_convert_field(stmt, field_type, value, (Int2) bic->returntype, (PTR) (bic->buffer + offset),
							 (SDWORD) bic->buflen, (SDWORD *) (bic->used + (offset >> 2)));
}


/*	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)
{
	static char *func = "copy_and_convert_field";
	ARDFields	*opts = SC_get_ARD(stmt);
	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 = opts->bind_size;
	int			result = COPY_OK;
#ifdef HAVE_LOCALE_H
	char saved_locale[256];
#endif /* HAVE_LOCALE_H */
	BOOL		changed, true_is_minus1 = FALSE;
	const char *neut_str = value;
	char		midtemp[2][32];
	int			mtemp_cnt = 0;
	static BindInfoClass sbic;
	BindInfoClass *pbic;
#ifdef	UNICODE_SUPPORT
	BOOL	wchanged =   FALSE;
#endif /* UNICODE_SUPPORT */

	if (stmt->current_col >= 0)
	{
		pbic = &opts->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;
		}
		else
		{
			stmt->errornumber = STMT_RETURN_NULL_WITHOUT_INDICATOR;
			stmt->errormsg = "StrLen_or_IndPtr was a null pointer and NULL data was retrieved";	
			SC_log_error(func, "", stmt);
			return	SQL_ERROR;
		}
	}

	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_NO_TMZONE:
		case PG_TYPE_TIMESTAMP:
			st.fr = 0;
			st.infinity = 0;
			if (strnicmp(value, "infinity", 8) == 0)
			{
				st.infinity = 1;
				st.m = 12;
				st.d = 31;
				st.y = 9999;
				st.hh = 23;
				st.mm = 59;
				st.ss = 59;
			}
			if (strnicmp(value, "-infinity", 9) == 0)
			{
				st.infinity = -1;
				st.m = 0;
				st.d = 0;
				st.y = 0;
				st.hh = 0;
				st.mm = 0;
				st.ss = 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);
inolog("2stime fr=%d\n", st.fr);
			}
			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];
				switch (((char *)value)[0])
				{
					case 'f':
					case 'F':
					case 'n':
					case 'N':
					case '0':
						strcpy(s, "0");
						break;
					default:
						if (true_is_minus1)
							strcpy(s, "-1");
						else
							strcpy(s, "1");
				}
				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;

#ifdef	UNICODE_SUPPORT
	if (fCType == SQL_C_CHAR || fCType == SQL_C_WCHAR)
#else
	if (fCType == SQL_C_CHAR)
#endif /* UNICODE_SUPPORT */
	{
		/* 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_NO_TMZONE:
			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 = strlen(neut_str);
				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 = &opts->bindings[stmt->current_col];
				if (pbic->data_left < 0)
				{
					BOOL lf_conv = SC_get_conn(stmt)->connInfo.lf_conversion;
#ifdef	UNICODE_SUPPORT
					if (fCType == SQL_C_WCHAR)
					{
						len = utf8_to_ucs2(neut_str, -1, NULL, 0);
						len *= 2;
						wchanged = changed = TRUE;
					}
					else
#endif /* UNICODE_SUPPORT */
					/* convert linefeeds to carriage-return/linefeed */
					len = convert_linefeeds(neut_str, NULL, 0, lf_conv, &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;
						}
#ifdef	UNICODE_SUPPORT
						if (fCType == SQL_C_WCHAR)
						{
							utf8_to_ucs2(neut_str, -1, (SQLWCHAR *) pbic->ttlbuf, len / 2);
						}
						else
#endif /* UNICODE_SUPPORT */
						convert_linefeeds(neut_str, pbic->ttlbuf, pbic->ttlbuflen, lf_conv, &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;

#ifdef HAVE_LOCALE_H
					switch (field_type)
					{
						case PG_TYPE_FLOAT4:
						case PG_TYPE_FLOAT8:
						case PG_TYPE_NUMERIC:
						{
							struct lconv	*lc;
							char		*new_string;
							int		i, j;

							new_string = malloc( cbValueMax );
							lc = localeconv();
							for (i = 0, j = 0; ptr[i]; i++)
								if (ptr[i] == '.')
								{
									strncpy(&new_string[j], lc->decimal_point, strlen(lc->decimal_point));
									j += strlen(lc->decimal_point);
								}
								else
									new_string[j++] = ptr[i];
							new_string[j] = '\0';
 							strncpy_null(rgbValueBindRow, new_string, copy_len + 1);
							free(new_string);
 							break;
						}
						default:
						/*      Copy the data */
						strncpy_null(rgbValueBindRow, ptr, copy_len + 1);
 					}
#else /* HAVE_LOCALE_H */
					/* Copy the data */
					memcpy(rgbValueBindRow, ptr, copy_len);
					rgbValueBindRow[copy_len] = '\0';
#endif /* HAVE_LOCALE_H */

					/* 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;
		}
#ifdef	UNICODE_SUPPORT
		if (SQL_C_WCHAR == fCType && ! wchanged)
		{
			if (cbValueMax > 2 * len)
			{
				char *str = strdup(rgbValueBindRow);
				UInt4	ucount = utf8_to_ucs2(str, len, (SQLWCHAR *) rgbValueBindRow, cbValueMax / 2);
				if (cbValueMax < 2 * (SDWORD) ucount)
					result = COPY_RESULT_TRUNCATED;
				len = ucount * 2;
				free(str); 
			}
			else
			{
				len *= 2;
				result = COPY_RESULT_TRUNCATED;
			}
		}
#endif /* UNICODE_SUPPORT */

	}
	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:
#ifdef HAVE_LOCALE_H
				strcpy(saved_locale, setlocale(LC_ALL, NULL));
				setlocale(LC_ALL, "C");
#endif /* HAVE_LOCALE_H */
				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);
#ifdef HAVE_LOCALE_H
				setlocale(LC_ALL, saved_locale);
#endif /* HAVE_LOCALE_H */
				break;

			case SQL_C_DOUBLE:
#ifdef HAVE_LOCALE_H
				strcpy(saved_locale, setlocale(LC_ALL, NULL));
				setlocale(LC_ALL, "C");
#endif /* HAVE_LOCALE_H */
				len = 8;
				if (bind_size > 0)
					*(SDOUBLE *) ((char *) rgbValue + (bind_row * bind_size)) = atof(neut_str);
				else
					*((SDOUBLE *) rgbValue + bind_row) = atof(neut_str);
#ifdef HAVE_LOCALE_H
				setlocale(LC_ALL, saved_locale);
#endif /* HAVE_LOCALE_H */
				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;
inolog("rgb=%x + %d, pcb=%x, set %s\n", rgbValue, bind_row * bind_size, pcbValue, neut_str);
				if (bind_size > 0)
					*(UDWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atol(neut_str);
				else
					*((UDWORD *) rgbValue + bind_row) = atol(neut_str);
				break;

#if (ODBCVER >= 0x0300) && defined(ODBCINT64)
#ifdef WIN32
			case SQL_C_SBIGINT:
				len = 8;
				if (bind_size > 0)
					*(SQLBIGINT *) ((char *) rgbValue + (bind_row * bind_size)) = _atoi64(neut_str);
				else
					*((SQLBIGINT *) rgbValue + bind_row) = _atoi64(neut_str);
				break;

			case SQL_C_UBIGINT:
				len = 8;
				if (bind_size > 0)
					*(SQLUBIGINT *) ((char *) rgbValue + (bind_row * bind_size)) = _atoi64(neut_str);
				else
					*((SQLUBIGINT *) rgbValue + bind_row) = _atoi64(neut_str);
				break;

#endif /* WIN32 */
#endif /* ODBCINT64 */
			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 = &opts->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)
		opts->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	FLGP_PREPARE_DUMMY_CURSOR	1L
#define	FLGP_CURSOR_CHECK_OK	(1L << 1)
#define	FLGP_SELECT_INTO		(1L << 2)
#define	FLGP_SELECT_FOR_UPDATE	(1L << 3)
typedef struct _QueryParse {
	const char	*statement;
	int		statement_type;
	UInt4		opos;
	int		from_pos;
	int		where_pos;
	UInt4		stmt_len;
	BOOL		in_quote, in_dquote, in_escape;
	char		token_save[64];
	int		token_len;
	BOOL		prev_token_end;
	BOOL		proc_no_param;
	unsigned	int declare_pos;
	UInt4		flags;
#ifdef MULTIBYTE
	encoded_str	encstr;
#endif   /* MULTIBYTE */
}	QueryParse;

static int
QP_initialize(QueryParse *q, const StatementClass *stmt)
{
	q->statement = stmt->statement;
	q->statement_type = stmt->statement_type;
	q->opos = 0;
	q->from_pos = -1;
	q->where_pos = -1;
	q->stmt_len = (q->statement) ? strlen(q->statement) : -1;
	q->in_quote = q->in_dquote = q->in_escape = FALSE;
	q->token_save[0] = '\0';
	q->token_len = 0;
	q->prev_token_end = TRUE;
	q->proc_no_param = TRUE;
	q->declare_pos = 0;
	q->flags = 0;
#ifdef MULTIBYTE
	make_encoded_str(&q->encstr, SC_get_conn(stmt), q->statement);
#endif   /* MULTIBYTE */

	return q->stmt_len;
}

#define	FLGB_PRE_EXECUTING	1L
#define	FLGB_INACCURATE_RESULT	(1L << 1)
typedef struct _QueryBuild {
	char	*query_statement;
	UInt4	str_size_limit;
	UInt4	str_alsize;
	UInt4	npos;
	int	current_row;
	int	param_number;
	APDFields *apdopts;
	UInt4	load_stmt_len;
	UInt4	flags;
	BOOL	lf_conv;
	int	ccsc;
	int	errornumber;
	const char *errormsg;

	ConnectionClass	*conn; /* mainly needed for LO handling */
	StatementClass	*stmt; /* needed to set error info in ENLARGE_.. */ 
}	QueryBuild;

#define INIT_MIN_ALLOC	4096
static int
QB_initialize(QueryBuild *qb, UInt4 size, StatementClass *stmt, ConnectionClass *conn)
{
	UInt4	newsize = 0;

	qb->flags = 0;
	qb->load_stmt_len = 0;
	qb->stmt = stmt;
	qb->apdopts = NULL;
	if (conn)
		qb->conn = conn;
	else if (stmt)
	{
		qb->apdopts = SC_get_APD(stmt);
		qb->conn = SC_get_conn(stmt);
		if (stmt->pre_executing)
			qb->flags |= FLGB_PRE_EXECUTING;
	}
	else
	{
		qb->conn = NULL;
		return -1;
	}
	qb->lf_conv = qb->conn->connInfo.lf_conversion;
	qb->ccsc = qb->conn->ccsc;
		
	if (stmt)
		qb->str_size_limit = stmt->stmt_size_limit;
	else
		qb->str_size_limit = -1;
	if (qb->str_size_limit > 0)
	{
		if (size > qb->str_size_limit)
			return -1;
		newsize = qb->str_size_limit;
	}
	else 
	{
		newsize = INIT_MIN_ALLOC;
		while (newsize <= size)
			newsize *= 2;
	}
	if ((qb->query_statement = malloc(newsize)) == NULL)
	{
		qb->str_alsize = 0;
		return -1;
	}	
	qb->query_statement[0] = '\0';
	qb->str_alsize = newsize;
	qb->npos = 0;
	qb->current_row = stmt->exec_current_row < 0 ? 0 : stmt->exec_current_row;
	qb->param_number = -1;
	qb->errornumber = 0;
	qb->errormsg = NULL;

	return newsize;
}

static int
QB_initialize_copy(QueryBuild *qb_to, const QueryBuild *qb_from, UInt4 size)
{
	memcpy(qb_to, qb_from, sizeof(QueryBuild));

	if (qb_to->str_size_limit > 0)
	{
		if (size > qb_to->str_size_limit)
			return -1;
	}
	if ((qb_to->query_statement = malloc(size)) == NULL)
	{
		qb_to->str_alsize = 0;
		return -1;
	}	
	qb_to->query_statement[0] = '\0';
	qb_to->str_alsize = size;
	qb_to->npos = 0;

	return size;
}

static void
QB_Destructor(QueryBuild *qb)
{
	if (qb->query_statement)
	{
		free(qb->query_statement);
		qb->query_statement = NULL;
		qb->str_alsize = 0;
	}
}

/*
 * New macros (Aceto)
 *--------------------
 */

#define F_OldChar(qp) \
qp->statement[qp->opos]

#define F_OldPtr(qp) \
(qp->statement + qp->opos)

#define F_OldNext(qp) \
(++qp->opos)

#define F_OldPrior(qp) \
(--qp->opos)

#define F_OldPos(qp) \
qp->opos

#define F_ExtractOldTo(qp, buf, ch, maxsize) \
do { \
	unsigned int	c = 0; \
	while (qp->statement[qp->opos] != '\0' && qp->statement[qp->opos] != ch) \
	{ \
	    buf[c++] = qp->statement[qp->opos++]; \
		if (c >= maxsize) \
			break; \
	} \
	if (qp->statement[qp->opos] == '\0') \
		return SQL_ERROR; \
	buf[c] = '\0'; \
} while (0)

#define F_NewChar(qb) \
qb->query_statement[qb->npos]

#define F_NewPtr(qb) \
(qb->query_statement + qb->npos)

#define F_NewNext(qb) \
(++qb->npos)

#define F_NewPos(qb) \
(qb->npos)


static int
convert_escape(QueryParse *qp, QueryBuild *qb);
static int
inner_process_tokens(QueryParse *qp, QueryBuild *qb);
static int
ResolveOneParam(QueryBuild *qb);
static int
processParameters(QueryParse *qp, QueryBuild *qb,
UInt4 *output_count, Int4 param_pos[][2]);

static int
enlarge_query_statement(QueryBuild *qb, unsigned int newsize)
{
	unsigned int newalsize = INIT_MIN_ALLOC;
	static char *func = "enlarge_statement";

	if (qb->str_size_limit > 0 && qb->str_size_limit < (int) newsize)
	{
		free(qb->query_statement);
		qb->query_statement = NULL;
		qb->str_alsize = 0;
		if (qb->stmt)
		{
			qb->stmt->errormsg = "Query buffer overflow in copy_statement_with_parameters";
			qb->stmt->errornumber = STMT_EXEC_ERROR;
			SC_log_error(func, "", qb->stmt);
		}
		else
		{
			qb->errormsg = "Query buffer overflow in copy_statement_with_parameters";
			qb->errornumber = STMT_EXEC_ERROR;
		}
		return -1;
	}
	while (newalsize <= newsize)
		newalsize *= 2;
	if (!(qb->query_statement = realloc(qb->query_statement, newalsize)))
	{
		qb->str_alsize = 0;
		if (qb->stmt)
		{
			qb->stmt->errormsg = "Query buffer allocate error in copy_statement_with_parameters";
			qb->stmt->errornumber = STMT_EXEC_ERROR;
		}
		else
		{
			qb->errormsg = "Query buffer allocate error in copy_statement_with_parameters";
			qb->errornumber = STMT_EXEC_ERROR;
		}
		return 0;
	}
	qb->str_alsize = newalsize;
	return newalsize;
}

/*----------
 *	Enlarge stmt_with_params if necessary.
 *----------
 */
#define ENLARGE_NEWSTATEMENT(qb, newpos) \
	if (newpos >= qb->str_alsize) \
	{ \
		if (enlarge_query_statement(qb, newpos) <= 0) \
			return SQL_ERROR; \
	}

/*----------
 *	Terminate the stmt_with_params string with NULL.
 *----------
 */
#define CVT_TERMINATE(qb) \
do { \
	qb->query_statement[qb->npos] = '\0'; \
} while (0)

/*----------
 *	Append a data.
 *----------
 */
#define CVT_APPEND_DATA(qb, s, len) \
do { \
	unsigned int	newpos = qb->npos + len; \
	ENLARGE_NEWSTATEMENT(qb, newpos) \
	memcpy(&qb->query_statement[qb->npos], s, len); \
	qb->npos = newpos; \
	qb->query_statement[newpos] = '\0'; \
} while (0)

/*----------
 *	Append a string.
 *----------
 */
#define CVT_APPEND_STR(qb, s) \
do { \
	unsigned int len = strlen(s); \
	CVT_APPEND_DATA(qb, s, len); \
} while (0)

/*----------
 *	Append a char.
 *----------
 */
#define CVT_APPEND_CHAR(qb, c) \
do { \
	ENLARGE_NEWSTATEMENT(qb, qb->npos + 1); \
	qb->query_statement[qb->npos++] = c; \
} while (0)

/*----------
 *	Append a binary data.
 *	Newly reqeuired size may be overestimated currently.
 *----------
 */
#define CVT_APPEND_BINARY(qb, buf, used) \
do { \
	unsigned int	newlimit = qb->npos + 5 * used; \
	ENLARGE_NEWSTATEMENT(qb, newlimit); \
	qb->npos += convert_to_pgbinary(buf, &qb->query_statement[qb->npos], used); \
} while (0)

/*----------
 *
 *----------
 */
#define CVT_SPECIAL_CHARS(qb, buf, used) \
do { \
	int cnvlen = convert_special_chars(buf, NULL, used, qb->lf_conv, qb->ccsc); \
	unsigned int	newlimit = qb->npos + cnvlen; \
\
	ENLARGE_NEWSTATEMENT(qb, newlimit); \
	convert_special_chars(buf, &qb->query_statement[qb->npos], used, qb->lf_conv, qb->ccsc); \
	qb->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]);
}

#ifdef MULTIBYTE
#define		my_strchr(conn, s1,c1) pg_mbschr(conn->ccsc, s1,c1)
#else
#define		my_strchr(conn, s1,c1) strchr(s1,c1)
#endif
/*
 *	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";
	RETCODE		retval;
	QueryParse	query_org, *qp;
	QueryBuild	query_crt, *qb;

	char	   *new_statement;

	BOOL	begin_first = FALSE, prepare_dummy_cursor = FALSE;
	ConnectionClass *conn = SC_get_conn(stmt);
	ConnInfo   *ci = &(conn->connInfo);
	int		current_row;

	if (!stmt->statement)
	{
		SC_log_error(func, "No statement string", stmt);
		return SQL_ERROR;
	}

	current_row = stmt->exec_current_row < 0 ? 0 : stmt->exec_current_row;
	qp = &query_org;
	QP_initialize(qp, stmt);

	if (ci->disallow_premature)
		prepare_dummy_cursor = stmt->pre_executing;
	if (prepare_dummy_cursor);
		qp->flags |= FLGP_PREPARE_DUMMY_CURSOR;


#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->updatable)
			stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
		else
		{
			qp->from_pos = stmt->from_pos;
			qp->where_pos = stmt->where_pos;
		}
	}
#else
	stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
	if (stmt->options.cursor_type == SQL_CURSOR_KEYSET_DRIVEN)
		stmt->options.cursor_type = SQL_CURSOR_FORWARD_ONLY;
#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);
	if (stmt->stmt_with_params)
	{
		free(stmt->stmt_with_params);
		stmt->stmt_with_params = NULL;
	}
	qb = &query_crt;
	if (QB_initialize(qb, qp->stmt_len, stmt, NULL) < 0)
		return SQL_ERROR;
	new_statement = qb->query_statement;

	stmt->miscinfo = 0;
	/* 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);
			qb->npos = strlen(new_statement);
			qp->flags |= FLGP_CURSOR_CHECK_OK;
			qp->declare_pos = qb->npos;
		}
	}

	for (qp->opos = 0; qp->opos < qp->stmt_len; qp->opos++)
	{
		retval = inner_process_tokens(qp, qb);
		if (SQL_ERROR == retval)
		{
			if (0 == stmt->errornumber)
			{
				stmt->errornumber = qb->errornumber;
				stmt->errormsg = qb->errormsg;
			}
			SC_log_error(func, "", stmt);
			QB_Destructor(qb);
			return retval;
		}
	}
	/* make sure new_statement is always null-terminated */
	CVT_TERMINATE(qb);

	new_statement = qb->query_statement;
	stmt->statement_type = qp->statement_type;
	stmt->inaccurate_result = (0 != (qb->flags & FLGB_INACCURATE_RESULT));
	if (0 != (qp->flags & FLGP_SELECT_INTO))
	{
		SC_no_pre_executable(stmt);
		SC_no_fetchcursor(stmt);
		stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
	}
	if (0 != (qp->flags & FLGP_SELECT_FOR_UPDATE))
	{
		SC_no_fetchcursor(stmt);
		stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
	}

	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 (!stmt->load_statement && qp->from_pos >= 0)
	{
		UInt4	npos = qb->load_stmt_len;

		if (0 == npos)
			npos = qb->npos;
		stmt->load_statement = malloc(npos + 1);
		memcpy(stmt->load_statement, new_statement, npos);
		if (stmt->load_statement[npos - 1] == ';')
			stmt->load_statement[npos - 1] = '\0';
		else
			stmt->load_statement[npos] = '\0';
	}
#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(qb, fetchstr);
		stmt->inaccurate_result = TRUE;
	}

	stmt->stmt_with_params = qb->query_statement;
	return SQL_SUCCESS;
}

static int
inner_process_tokens(QueryParse *qp, QueryBuild *qb)
{
	static char *func = "inner_process_tokens";
	BOOL	lf_conv = qb->lf_conv;

	RETCODE	retval;
	char	   oldchar;

	if (qp->from_pos == (Int4) qp->opos)
	{
		CVT_APPEND_STR(qb, ", CTID, OID ");
	}
	else if (qp->where_pos == (Int4) qp->opos)
		qb->load_stmt_len = qb->npos;
#ifdef MULTIBYTE
	oldchar = encoded_byte_check(&qp->encstr, qp->opos);
	if (ENCODE_STATUS(qp->encstr) != 0)
	{
		CVT_APPEND_CHAR(qb, oldchar);
		return SQL_SUCCESS;
	}

	/*
	 * From here we are guaranteed to handle a 1-byte character.
	 */
#else
	oldchar = qp->statement[qp->opos];
#endif

	if (qp->in_escape)			/* escape check */
	{
		qp->in_escape = FALSE;
		CVT_APPEND_CHAR(qb, oldchar);
		return SQL_SUCCESS;
	}
	else if (qp->in_quote || qp->in_dquote) /* quote/double quote check */
	{
		if (oldchar == '\\')
			qp->in_escape = TRUE;
		else if (oldchar == '\'' && qp->in_quote)
			qp->in_quote = FALSE;
		else if (oldchar == '\"' && qp->in_dquote)
			qp->in_dquote = FALSE;
		CVT_APPEND_CHAR(qb, oldchar);
		return SQL_SUCCESS;
	}

	/*
	 * 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 (lf_conv && oldchar == '\r' && qp->opos + 1 < qp->stmt_len &&
			qp->statement[qp->opos + 1] == '\n')
		return SQL_SUCCESS;

	/*
	 * Handle literals (date, time, timestamp) and ODBC scalar
	 * functions
	 */
	else if (oldchar == '{')
	{
		if (SQL_ERROR == convert_escape(qp, qb))
		{
			if (0 == qb->errornumber)
			{
				qb->errornumber = STMT_EXEC_ERROR;
				qb->errormsg = "ODBC escape convert error";
			}
			mylog("%s convert_escape error\n", func);
			return SQL_ERROR;
		}
		if (isalnum(F_OldPtr(qp)[1]))
			CVT_APPEND_CHAR(qb, ' ');
		return SQL_SUCCESS;
	}
	/* End of an escape sequence */
	else if (oldchar == '}')
	{
		if (qp->statement_type == STMT_TYPE_PROCCALL)
		{
			if (qp->proc_no_param)
				CVT_APPEND_STR(qb, "()");
		}
		else if (!isspace(F_OldPtr(qp)[1]))
			CVT_APPEND_CHAR(qb, ' ');
		return SQL_SUCCESS;
	}

	/*
	 * 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 != '?')
	{
		if (oldchar == '\'')
			qp->in_quote = TRUE;
		else if (oldchar == '\\')
			qp->in_escape = TRUE;
		else if (oldchar == '\"')
			qp->in_dquote = TRUE;
		else
		{
			if (isspace((unsigned char) oldchar))
			{
				if (!qp->prev_token_end)
				{
					qp->prev_token_end = TRUE;
					qp->token_save[qp->token_len] = '\0';
					if (qp->token_len == 4)
					{
						if (0 != (qp->flags & FLGP_CURSOR_CHECK_OK) &&
							into_table_from(&qp->statement[qp->opos - qp->token_len]))
						{
							qp->flags |= FLGP_SELECT_INTO;
							qp->flags &= ~FLGP_CURSOR_CHECK_OK;
							qp->statement_type = STMT_TYPE_CREATE;
							memmove(qb->query_statement, qb->query_statement + qp->declare_pos, qb->npos - qp->declare_pos);
							qb->npos -= qp->declare_pos;
						}
					}
					if (qp->token_len == 3)
					{
						int			endpos;

						if (0 != (qp->flags & FLGP_CURSOR_CHECK_OK) &&
							strnicmp(qp->token_save, "for", 3) == 0 &&
							table_for_update(&qp->statement[qp->opos], &endpos))
						{
							qp->flags |= FLGP_SELECT_FOR_UPDATE;
							qp->flags &= ~FLGP_CURSOR_CHECK_OK;
							if (qp->flags & FLGP_PREPARE_DUMMY_CURSOR)
							{
								qb->npos -= 4;
								qp->opos += endpos;
							}
							else
							{
								memmove(qb->query_statement, qb->query_statement + qp->declare_pos, qb->npos - qp->declare_pos);
								qb->npos -= qp->declare_pos;
							}
						}
					}
				}
			}
			else if (qp->prev_token_end)
			{
				qp->prev_token_end = FALSE;
				qp->token_save[0] = oldchar;
				qp->token_len = 1;
			}
			else if (qp->token_len + 1 < sizeof(qp->token_save))
				qp->token_save[qp->token_len++] = oldchar;
		}
		CVT_APPEND_CHAR(qb, oldchar);
		return SQL_SUCCESS;
	}

	/*
	 * Its a '?' parameter alright
	 */
	if (retval = ResolveOneParam(qb), retval < 0)
		return retval;

	return SQL_SUCCESS;
}

static int
ResolveOneParam(QueryBuild *qb)
{
	const char *func = "ResolveOneParam";

	ConnectionClass *conn = qb->conn;
	ConnInfo   *ci = &(conn->connInfo);
	APDFields *opts = qb->apdopts;

	int		param_number;
	char		param_string[128], tmp[256],
			cbuf[PG_NUMERIC_MAX_PRECISION * 2]; /* seems big enough to handle the data in this function */
	Int2		param_ctype, param_sqltype;
	SIMPLE_TIME	st;
	time_t		t;
	struct tm	*tim;
	SDWORD		used;
	char		*buffer, *buf, *allocbuf;
	Oid		lobj_oid;
	int		lobj_fd, retval;
	UInt4	offset = opts->param_offset_ptr ? *opts->param_offset_ptr : 0;
	UInt4	current_row = qb->current_row;

	/*
	 * Its a '?' parameter alright
	 */
	param_number = ++qb->param_number;

	if (param_number >= opts->allocated)
	{
		if (0 != (qb->flags & FLGB_PRE_EXECUTING))
		{
			CVT_APPEND_STR(qb, "NULL");
			qb->flags |= FLGB_INACCURATE_RESULT;
			return SQL_SUCCESS;
		}
		else
		{
			CVT_APPEND_CHAR(qb, '?');
			return SQL_SUCCESS;
		}
	}

	/* Assign correct buffers based on data at exec param or not */
	if (opts->parameters[param_number].data_at_exec)
	{
		used = opts->parameters[param_number].EXEC_used ? *opts->parameters[param_number].EXEC_used : SQL_NTS;
		buffer = opts->parameters[param_number].EXEC_buffer;
	}
	else
	{
		UInt4	bind_size = opts->param_bind_type;
		UInt4	ctypelen;

		buffer = opts->parameters[param_number].buffer + offset;
		if (current_row > 0)
		{
			if (bind_size > 0)
				buffer += (bind_size * current_row);
			else if (ctypelen = ctype_length(opts->parameters[param_number].CType), ctypelen > 0)
				buffer += current_row * ctypelen;
			else 
				buffer += current_row * opts->parameters[param_number].buflen;
		}
		if (opts->parameters[param_number].used)
		{
			UInt4	p_offset = offset;
			if (bind_size > 0)
				p_offset = offset + bind_size * current_row;
			else
				p_offset = offset + sizeof(SDWORD) * current_row;
			used = *(SDWORD *)((char *)opts->parameters[param_number].used + p_offset);
		}
		else
			used = SQL_NTS;
	}	

	/* Handle NULL parameter data */
	if (used == SQL_NULL_DATA)
	{
		CVT_APPEND_STR(qb, "NULL");
		return SQL_SUCCESS;
	}

	/*
	 * If no buffer, and it's not null, then what the hell is it? Just
	 * leave it alone then.
	 */
	if (!buffer)
	{
		if (0 != (qb->flags & FLGB_PRE_EXECUTING))
		{
			CVT_APPEND_STR(qb, "NULL");
			qb->flags |= FLGB_INACCURATE_RESULT;
			return SQL_SUCCESS;
		}
		else
		{
			CVT_APPEND_CHAR(qb, '?');
			return SQL_SUCCESS;
		}
	}

	param_ctype = opts->parameters[param_number].CType;
	param_sqltype = opts->parameters[param_number].SQLType;

	mylog("%s: from(fcType)=%d, to(fSqlType)=%d\n", func,
				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);

	allocbuf = buf = NULL;
	param_string[0] = '\0';
	cbuf[0] = '\0';
	memset(&st, 0, sizeof(st));
	t = time(NULL);
	tim = localtime(&t);
	st.m = tim->tm_mon + 1;
	st.d = tim->tm_mday;
	st.y = tim->tm_year + 1900;

	/* Convert input C type to a neutral format */
	switch (param_ctype)
	{
		case SQL_C_BINARY:
		case SQL_C_CHAR:
			buf = buffer;
			break;

#ifdef	UNICODE_SUPPORT
		case SQL_C_WCHAR:
			buf = allocbuf = ucs2_to_utf8((SQLWCHAR *) buffer, used / 2, &used);
			used *= 2;
			break;
#endif /* UNICODE_SUPPORT */

		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;

#if (ODBCVER >= 0x0300) && defined(ODBCINT64)
#ifdef WIN32
		case SQL_C_SBIGINT:
			sprintf(param_string, "%I64d",
					*((SQLBIGINT *) buffer));
			break;

		case SQL_C_UBIGINT:
			sprintf(param_string, "%I64u",
					*((SQLUBIGINT *) buffer));
			break;

#endif /* WIN32 */
#endif /* ODBCINT64 */
		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 */
			qb->errormsg = "Unrecognized C_parameter type in copy_statement_with_parameters";
			qb->errornumber = STMT_NOT_IMPLEMENTED_ERROR;
			CVT_TERMINATE(qb);	/* just in case */
			return SQL_ERROR;
	}

	/*
	 * Now that the input data is in a neutral format, convert it to
	 * the desired output format (sqltype)
	 */

	switch (param_sqltype)
	{
		case SQL_CHAR:
		case SQL_VARCHAR:
		case SQL_LONGVARCHAR:
#ifdef	UNICODE_SUPPORT
		case SQL_WCHAR:
		case SQL_WVARCHAR:
		case SQL_WLONGVARCHAR:
#endif /* UNICODE_SUPPORT */

			CVT_APPEND_CHAR(qb, '\'');	/* Open Quote */

			/* it was a SQL_C_CHAR */
			if (buf)
				CVT_SPECIAL_CHARS(qb, buf, used);

			/* it was a numeric type */
			else if (param_string[0] != '\0')
				CVT_APPEND_STR(qb, 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(qb, tmp);
			}

			CVT_APPEND_CHAR(qb, '\'');	/* 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'::date", st.y, st.m, st.d);

			CVT_APPEND_STR(qb, 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'::time", st.hh, st.mm, st.ss);

			CVT_APPEND_STR(qb, 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, USE_ZONE, PG_VERSION_GE(conn, 7.2));
			strcat(tmp, "'::timestamp");

			CVT_APPEND_STR(qb, tmp);

			break;

		case SQL_BINARY:
		case SQL_VARBINARY:/* non-ascii characters should be
							* converted to octal */
			CVT_APPEND_CHAR(qb, '\'');	/* Open Quote */

			mylog("SQL_VARBINARY: about to call convert_to_pgbinary, used = %d\n", used);

			CVT_APPEND_BINARY(qb, buf, used);

			CVT_APPEND_CHAR(qb, '\'');	/* Close Quote */

			break;

		case SQL_LONGVARBINARY:

			if (opts->parameters[param_number].data_at_exec)
				lobj_oid = opts->parameters[param_number].lobj_oid;
			else
			{
				/* begin transaction if needed */
				if (!CC_is_in_trans(conn))
				{
					if (!CC_begin(conn))
					{
						qb->errormsg = "Could not begin (in-line) a transaction";
						qb->errornumber = STMT_EXEC_ERROR;
						return SQL_ERROR;
					}
				}

				/* store the oid */
				lobj_oid = lo_creat(conn, INV_READ | INV_WRITE);
				if (lobj_oid == 0)
				{
					qb->errornumber = STMT_EXEC_ERROR;
					qb->errormsg = "Couldnt create (in-line) large object.";
					return SQL_ERROR;
				}

				/* store the fd */
				lobj_fd = lo_open(conn, lobj_oid, INV_WRITE);
				if (lobj_fd < 0)
				{
					qb->errornumber = STMT_EXEC_ERROR;
					qb->errormsg = "Couldnt open (in-line) large object for writing.";
					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))
				{
					if (!CC_commit(conn))
					{
						qb->errormsg = "Could not commit (in-line) a transaction";
						qb->errornumber = STMT_EXEC_ERROR;
						return SQL_ERROR;
					}
				}
			}

			/*
			 * 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(qb, 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(qb, 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(qb, 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(qb, cbuf);
			break;
			default:			/* a numeric type or SQL_BIT */
			if (param_sqltype == SQL_BIT)
				CVT_APPEND_CHAR(qb, '\'');		/* Open Quote */

			if (buf)
			{
				switch (used)
				{
					case SQL_NULL_DATA:
						break;
					case SQL_NTS:
						CVT_APPEND_STR(qb, buf);
						break;
					default:
						CVT_APPEND_DATA(qb, buf, used);
				}
			}
			else
				CVT_APPEND_STR(qb, param_string);

			if (param_sqltype == SQL_BIT)
				CVT_APPEND_CHAR(qb, '\'');		/* Close Quote */

			break;
	}
#ifdef	UNICODE_SUPPORT
	if (allocbuf)
		free(allocbuf);
#endif /* UNICODE_SUPPORT */
	return SQL_SUCCESS;
}


static const char *
mapFunction(const char *func, int param_count)
{
	int			i;

	for (i = 0; mapFuncs[i][0]; i++)
	{
		if (mapFuncs[i][0][0] == '%')
		{
			if (mapFuncs[i][0][1] - '0' == param_count &&
			    !stricmp(mapFuncs[i][0] + 2, func))
				return mapFuncs[i][1];
		}
		else if (!stricmp(mapFuncs[i][0], func))
			return mapFuncs[i][1];
	}

	return NULL;
}

/*
 * processParameters()
 * Process function parameters and work with embedded escapes sequences.
 */
static int
processParameters(QueryParse *qp, QueryBuild *qb,
		UInt4 *output_count, Int4 param_pos[][2])
{
	static const char *func = "processParameters";
	int retval, innerParenthesis, param_count;
	BOOL stop;

	/* begin with outer '(' */
	innerParenthesis = 0;
	param_count = 0;
	stop = FALSE;
	for (; F_OldPos(qp) < qp->stmt_len; F_OldNext(qp))
	{
		retval = inner_process_tokens(qp, qb);
		if (retval == SQL_ERROR)
			return retval;
#ifdef MULTIBYTE
		if (ENCODE_STATUS(qp->encstr) != 0)
			continue;
#endif
		if (qp->in_dquote || qp->in_quote || qp->in_escape)
			continue;

		switch (F_OldChar(qp))
		{
			case ',':
				if (1 == innerParenthesis)
				{
					param_pos[param_count][1] = F_NewPos(qb) - 2;
					param_count++;
					param_pos[param_count][0] = F_NewPos(qb);
					param_pos[param_count][1] = -1;
				}
				break;
			case '(':
				if (0 == innerParenthesis)
				{
					param_pos[param_count][0] = F_NewPos(qb);
					param_pos[param_count][1] = -1;
				}
				innerParenthesis++;
				break;
     
			case ')':
				innerParenthesis--;
				if (0 == innerParenthesis)
				{
					param_pos[param_count][1] = F_NewPos(qb) - 2;
					param_count++;
					param_pos[param_count][0] =
					param_pos[param_count][1] = -1;
				}
				if (output_count)
					*output_count = F_NewPos(qb);
				break;

			case '}':
				stop = (0 == innerParenthesis);
				break;

		}
		if (stop) /* returns with the last } position */
			break;
	}
	if (param_pos[param_count][0] >= 0)
	{
		mylog("%s closing ) not found %d\n", func, innerParenthesis);
		qb->errornumber = STMT_EXEC_ERROR;
		qb->errormsg = "processParameters closing ) not found";
		return SQL_ERROR;
	}
	else if (1 == param_count) /* the 1 parameter is really valid ? */
	{
		BOOL	param_exist = FALSE;
		int	i;

		for (i = param_pos[0][0]; i <= param_pos[0][1]; i++)
		{
			if (!isspace(qb->query_statement[i]))
			{
				param_exist = TRUE;
				break;
			}
		}
		if (!param_exist)
		{
			param_pos[0][0] = param_pos[0][1] = -1;
		}
	}

	return SQL_SUCCESS;
}

/*
 * convert_escape()
 * This function doesn't return a pointer to static memory any longer !
 */
static int
convert_escape(QueryParse *qp, QueryBuild *qb)
{
	static const char *func = "convert_escape";
	RETCODE	retval = SQL_SUCCESS;
	char		buf[1024], key[65];
	unsigned char	ucv;
	UInt4		prtlen;
 
	if (F_OldChar(qp) == '{') /* skip the first { */
		F_OldNext(qp);
	/* Separate off the key, skipping leading and trailing whitespace */
	while ((ucv = F_OldChar(qp)) != '\0' && isspace(ucv))
		F_OldNext(qp);
	/*
	 * procedure calls
	 */
	if (qp->statement_type == STMT_TYPE_PROCCALL)
	{
		int	lit_call_len = 4;
		ConnectionClass *conn = qb->conn;

		/* '?=' to accept return values exists ? */
		if (F_OldChar(qp) == '?')
		{
			qb->param_number++;
			while (isspace((unsigned char) qp->statement[++qp->opos]));
			if (F_OldChar(qp) != '=')
			{
				F_OldPrior(qp);
				return SQL_SUCCESS;
			}
			while (isspace((unsigned char) qp->statement[++qp->opos]));
		}
		if (strnicmp(F_OldPtr(qp), "call", lit_call_len) ||
			!isspace((unsigned char) F_OldPtr(qp)[lit_call_len]))
		{
			F_OldPrior(qp);
			return SQL_SUCCESS;
		}
		qp->opos += lit_call_len;
		CVT_APPEND_STR(qb, "SELECT ");
		if (my_strchr(conn, F_OldPtr(qp), '('))
			qp->proc_no_param = FALSE;
		return SQL_SUCCESS;
	}

	sscanf(F_OldPtr(qp), "%32s", key);
	while ((ucv = F_OldChar(qp)) != '\0' && (!isspace(ucv)))
		F_OldNext(qp);
	while ((ucv = F_OldChar(qp)) != '\0' && isspace(ucv))
		F_OldNext(qp);
    
	/* Avoid the concatenation of the function name with the previous word. Aceto */

	if (F_NewPos(qb) > 0 && isalnum(F_NewPtr(qb)[-1]))
		CVT_APPEND_CHAR(qb, ' ');
	
	if (strcmp(key, "d") == 0)
	{
		/* Literal; return the escape part adding type cast */
		F_ExtractOldTo(qp, buf, '}', sizeof(buf));
		prtlen = snprintf(buf, sizeof(buf), "%s::date ", buf);
		CVT_APPEND_DATA(qb, buf, prtlen);
	}
	else if (strcmp(key, "t") == 0)
	{
		/* Literal; return the escape part adding type cast */
		F_ExtractOldTo(qp, buf, '}', sizeof(buf));
		prtlen = snprintf(buf, sizeof(buf), "%s::time", buf);
		CVT_APPEND_DATA(qb, buf, prtlen);
	}
	else if (strcmp(key, "ts") == 0)
	{
		/* Literal; return the escape part adding type cast */
		F_ExtractOldTo(qp, buf, '}', sizeof(buf));
		if (PG_VERSION_LT(qb->conn, 7.1))
			prtlen = snprintf(buf, sizeof(buf), "%s::datetime", buf);
		else
			prtlen = snprintf(buf, sizeof(buf), "%s::timestamp", buf);
		CVT_APPEND_DATA(qb, buf, prtlen);
	}
	else if (strcmp(key, "oj") == 0) /* {oj syntax support for 7.1 * servers */
	{
		F_OldPrior(qp);
		return SQL_SUCCESS; /* Continue at inner_process_tokens loop */
	}
	else if (strcmp(key, "fn") == 0)
	{
		QueryBuild	nqb;
		const char *mapExpr;
		int	i, param_count;
		UInt4	param_consumed;
		Int4	param_pos[16][2];

		/* Separate off the func name, skipping leading and trailing whitespace */
		i = 0;
		while ((ucv = F_OldChar(qp)) != '\0' && ucv != '(' &&
			   (!isspace(ucv)))
		{
			if (i < sizeof(key)-1)
				key[i++] = ucv;
			F_OldNext(qp);
		}
		key[i] = '\0';
		while ((ucv = F_OldChar(qp)) != '\0' && isspace(ucv))
			F_OldNext(qp);

		/*
 		 * We expect left parenthesis here, else return fn body as-is
		 * since it is one of those "function constants".
		 */
		if (F_OldChar(qp) != '(')
		{
			CVT_APPEND_STR(qb, key);
			return SQL_SUCCESS;
		}

		/*
		 * Process parameter list and inner escape
		 * sequences
		 * Aceto 2002-01-29
		 */

		QB_initialize_copy(&nqb, qb, 1024);
		if (retval = processParameters(qp, &nqb, &param_consumed, param_pos), retval == SQL_ERROR)
		{
			qb->errornumber = nqb.errornumber;
			qb->errormsg = nqb.errormsg;
			QB_Destructor(&nqb);
			return retval;
		}

		for (param_count = 0;; param_count++)
		{
			if (param_pos[param_count][0] < 0)
				break;
		}
		if (param_count == 1 &&
		    param_pos[0][1] < param_pos[0][0])
			param_count = 0;

		mapExpr = mapFunction(key, param_count);
		if (mapExpr == NULL)
		{
			CVT_APPEND_STR(qb, key);
			CVT_APPEND_DATA(qb, nqb.query_statement, nqb.npos);
		}
		else
		{
			const char *mapptr;
			int	from, to, pidx, paramlen;

			for (prtlen = 0, mapptr = mapExpr; *mapptr; mapptr++)
			{
				if (*mapptr != '$')
				{
					CVT_APPEND_CHAR(qb, *mapptr);
					continue;
				}
				mapptr++;
				if (*mapptr == '*')
				{
					from = 1;
					to = param_consumed - 2;
				}
				else if (isdigit(*mapptr))
				{
					pidx = *mapptr - '0' - 1;
					if (pidx < 0 ||
					    param_pos[pidx][0] < 0)
					{
						qb->errornumber = STMT_EXEC_ERROR;
						qb->errormsg = "param not found";
						qlog("%s %dth param not found for the expression %s\n", pidx + 1, mapExpr);
						retval = SQL_ERROR;
						break;
					}
					from = param_pos[pidx][0];
					to = param_pos[pidx][1];
				}
				else
				{
					qb->errornumber = STMT_EXEC_ERROR;
					qb->errormsg = "internal expression error";
					qlog("%s internal expression error %s\n", func, mapExpr);
					retval = SQL_ERROR;
					break;
				}
				paramlen = to - from + 1;
				if (paramlen > 0)
					CVT_APPEND_DATA(qb, nqb.query_statement+ from, paramlen);
			}
		}
		if (0 == qb->errornumber)
		{
			qb->errornumber = nqb.errornumber;
			qb->errormsg = nqb.errormsg;
		}
		if (SQL_ERROR != retval)
		{
			qb->param_number = nqb.param_number;
			qb->flags = nqb.flags;
		}
		QB_Destructor(&nqb);
	}
	else
	{
		/* Bogus key, leave untranslated */
		return SQL_ERROR;
	}
 
	return retval;
}

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(const char *buf, SIMPLE_TIME *st)
{
	int			y,
				m,
				d,
				hh,
				mm,
				ss;
	int			nf;

	y = m = d = hh = mm = ss = 0;
	st->fr = 0;
	st->infinity = 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 convlf, 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 (convlf && 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, BOOL convlf, int ccsc)
{
	size_t		i = 0,
				out = 0,
				max;
	char	   *p = NULL;
#ifdef MULTIBYTE
	encoded_str	encstr;
#endif


	if (used == SQL_NTS)
		max = strlen(si);
	else
		max = used;
	if (dst)
	{
		p = dst;
		p[0] = '\0';
	}
#ifdef MULTIBYTE
	encoded_str_constr(&encstr, ccsc, si);
#endif

	for (i = 0; i < max && si[i]; i++)
	{
#ifdef MULTIBYTE
		encoded_nextchar(&encstr);
		if (ENCODE_STATUS(encstr) != 0)
		{
			if (p)
				p[out] = si[i];
			out++;
			continue;
		}
#endif
		if (convlf && 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] - '0') << (3  * (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 << (4 * (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) + '0';
		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);
	ARDFields	*opts = SC_get_ARD(stmt);
	int			factor = (fCType == SQL_C_CHAR ? 2 : 1);

	/* If using SQLGetData, then current_col will be set */
	if (stmt->current_col >= 0)
	{
		bindInfo = &opts->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))
		{
			if (!CC_begin(conn))
			{
				stmt->errormsg = "Could not begin (in-line) a transaction";
				stmt->errornumber = STMT_EXEC_ERROR;
				return COPY_GENERAL_ERROR;
			}
		}

		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))
		{
			if (!CC_commit(conn))
			{
				stmt->errormsg = "Could not commit (in-line) a transaction";
				stmt->errornumber = STMT_EXEC_ERROR;
				return COPY_GENERAL_ERROR;
			}
		}

		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))
		{
			if (!CC_commit(conn))
			{
				stmt->errormsg = "Could not commit (in-line) a transaction";
				stmt->errornumber = STMT_EXEC_ERROR;
				return COPY_GENERAL_ERROR;
			}
		}

		stmt->lobj_fd = -1;		/* prevent further reading */
	}

	return result;
}
