/*-------
 * 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	*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "psqlodbc.h"

#ifdef MULTIBYTE
#include "multibyte.h"
#endif

#ifndef WIN32
#include "iodbc.h"
#include "isql.h"
#include "isqlext.h"
#else
#include <windows.h>
#include <sql.h>
#include <sqlext.h>
#endif

#include <time.h>
#include <math.h>
#include <stdlib.h>
#include "convert.h"
#include "statement.h"
#include "qresult.h"
#include "bind.h"
#include "pgtypes.h"
#include "lobj.h"
#include "connection.h"

#ifndef WIN32
#ifndef HAVE_STRICMP
#define stricmp(s1,s2) strcasecmp(s1,s2)
#define strnicmp(s1,s2,n) strncasecmp(s1,s2,n)
#endif
#ifndef SCHAR
typedef signed char SCHAR;

#endif
#endif

extern GLOBAL_VALUES globals;

/*
 *	How to map ODBC scalar functions {fn func(args)} to Postgres.
 *	This is just a simple substitution.  List augmented from:
 *	http://www.merant.com/datadirect/download/docs/odbc16/Odbcref/rappc.htm
 *	- thomas 2000-04-03
 */
char	   *mapFuncs[][2] = {
/*	{ "ASCII",		 "ascii"	  }, */
	{"CHAR", "chr"},
	{"CONCAT", "textcat"},
/*	{ "DIFFERENCE",  "difference" }, */
/*	{ "INSERT",		 "insert"	  }, */
	{"LCASE", "lower"},
	{"LEFT", "ltrunc"},
	{"LOCATE", "strpos"},
	{"LENGTH", "char_length"},
/*	{ "LTRIM",		 "ltrim"	  }, */
	{"RIGHT", "rtrunc"},
/*	{ "REPEAT",		 "repeat"	  }, */
/*	{ "REPLACE",	 "replace"	  }, */
/*	{ "RTRIM",		 "rtrim"	  }, */
/*	{ "SOUNDEX",	 "soundex"	  }, */
	{"SUBSTRING", "substr"},
	{"UCASE", "upper"},

/*	{ "ABS",		 "abs"		  }, */
/*	{ "ACOS",		 "acos"		  }, */
/*	{ "ASIN",		 "asin"		  }, */
/*	{ "ATAN",		 "atan"		  }, */
/*	{ "ATAN2",		 "atan2"	  }, */
	{"CEILING", "ceil"},
/*	{ "COS",		 "cos"		  }, */
/*	{ "COT",		 "cot"		  }, */
/*	{ "DEGREES",	 "degrees"	  }, */
/*	{ "EXP",		 "exp"		  }, */
/*	{ "FLOOR",		 "floor"	  }, */
	{"LOG", "ln"},
	{"LOG10", "log"},
/*	{ "MOD",		 "mod"		  }, */
/*	{ "PI",			 "pi"		  }, */
	{"POWER", "pow"},
/*	{ "RADIANS",	 "radians"	  }, */
	{"RAND", "random"},
/*	{ "ROUND",		 "round"	  }, */
/*	{ "SIGN",		 "sign"		  }, */
/*	{ "SIN",		 "sin"		  }, */
/*	{ "SQRT",		 "sqrt"		  }, */
/*	{ "TAN",		 "tan"		  }, */
	{"TRUNCATE", "trunc"},

/*	{ "CURDATE",	 "curdate"	  }, */
/*	{ "CURTIME",	 "curtime"	  }, */
/*	{ "DAYNAME",	 "dayname"	  }, */
/*	{ "DAYOFMONTH",  "dayofmonth" }, */
/*	{ "DAYOFWEEK",	 "dayofweek"  }, */
/*	{ "DAYOFYEAR",	 "dayofyear"  }, */
/*	{ "HOUR",		 "hour"		  }, */
/*	{ "MINUTE",		 "minute"	  }, */
/*	{ "MONTH",		 "month"	  }, */
/*	{ "MONTHNAME",	 "monthname"  }, */
/*	{ "NOW",		 "now"		  }, */
/*	{ "QUARTER",	 "quarter"	  }, */
/*	{ "SECOND",		 "second"	  }, */
/*	{ "WEEK",		 "week"		  }, */
/*	{ "YEAR",		 "year"		  }, */

/*	{ "DATABASE",	 "database"   }, */
	{"IFNULL", "coalesce"},
	{"USER", "odbc_user"},
	{0, 0}
};

char	   *mapFunction(char *func);
unsigned int conv_from_octal(unsigned char *s);
unsigned int conv_from_hex(unsigned char *s);
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
 *---------
 */



/*	This is called by SQLFetch() */
int
copy_and_convert_field_bindinfo(StatementClass *stmt, Int4 field_type, void *value, int col)
{
	BindInfoClass *bic = &(stmt->bindings[col]);

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


/*	This is called by SQLGetData() */
int
copy_and_convert_field(StatementClass *stmt, Int4 field_type, void *value, Int2 fCType,
					   PTR rgbValue, SDWORD cbValueMax, SDWORD *pcbValue)
{
	Int4		len = 0,
				copy_len = 0;
	SIMPLE_TIME st;
	time_t		t = time(NULL);
	struct tm  *tim;
	int			pcbValueOffset,
				rgbValueOffset;
	char	   *rgbValueBindRow,
			   *ptr;
	int			bind_row = stmt->bind_row;
	int			bind_size = stmt->options.bind_size;
	int			result = COPY_OK;
	char		tempBuf[TEXT_FIELD_SIZE + 5];

	/*---------
	 *	rgbValueOffset is *ONLY* for character and binary data.
	 *	pcbValueOffset is for computing any pcbValue location
	 *---------
	 */

	if (bind_size > 0)
		pcbValueOffset = rgbValueOffset = (bind_size * bind_row);
	else
	{
		pcbValueOffset = bind_row * sizeof(SDWORD);
		rgbValueOffset = bind_row * cbValueMax;

	}

	memset(&st, 0, sizeof(SIMPLE_TIME));

	/* Initialize current date */
	tim = localtime(&t);
	st.m = tim->tm_mon + 1;
	st.d = tim->tm_mday;
	st.y = tim->tm_year + 1900;

	mylog("copy_and_convert: field_type = %d, fctype = %d, value = '%s', cbValueMax=%d\n", field_type, fCType, (value == NULL) ? "<NULL>" : value, cbValueMax);

	if (!value)
	{

		/*
		 * handle a null just by returning SQL_NULL_DATA in pcbValue, and
		 * doing nothing to the buffer.
		 */
		if (pcbValue)
			*(SDWORD *) ((char *) pcbValue + pcbValueOffset) = SQL_NULL_DATA;
		return COPY_OK;
	}

	if (stmt->hdbc->DataSourceToDriver != NULL)
	{
		int			length = strlen(value);

		stmt->hdbc->DataSourceToDriver(stmt->hdbc->translation_option,
									   SQL_CHAR,
									   value, length,
									   value, length, NULL,
									   NULL, 0, NULL);
	}

	/*
	 * First convert any specific postgres types into more useable data.
	 *
	 * NOTE: Conversions from PG char/varchar of a date/time/timestamp value
	 * to SQL_C_DATE,SQL_C_TIME, SQL_C_TIMESTAMP not supported
	 */
	switch (field_type)
	{

			/*
			 * $$$ need to add parsing for date/time/timestamp strings in
			 * PG_TYPE_CHAR,VARCHAR $$$
			 */
		case PG_TYPE_DATE:
			sscanf(value, "%4d-%2d-%2d", &st.y, &st.m, &st.d);
			break;

		case PG_TYPE_TIME:
			sscanf(value, "%2d:%2d:%2d", &st.hh, &st.mm, &st.ss);
			break;

		case PG_TYPE_ABSTIME:
		case PG_TYPE_DATETIME:
		case PG_TYPE_TIMESTAMP:
			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);
			else
			{

				/*
				 * The timestamp is invalid so set something conspicuous,
				 * like the epoch
				 */
				t = 0;
				tim = localtime(&t);
				st.m = tim->tm_mon + 1;
				st.d = tim->tm_mday;
				st.y = tim->tm_year + 1900;
				st.hh = tim->tm_hour;
				st.mm = tim->tm_min;
				st.ss = tim->tm_sec;
			}
			break;

		case PG_TYPE_BOOL:
			{					/* change T/F to 1/0 */
				char	   *s = (char *) value;

				if (s[0] == 'T' || s[0] == 't')
					s[0] = '1';
				else
					s[0] = '0';
			}
			break;

			/* This is for internal use by SQLStatistics() */
		case PG_TYPE_INT2VECTOR:
			{
				int			nval,
							i;
				char	   *vp;

				/* this is an array of eight integers */
				short	   *short_array = (short *) ((char *) rgbValue + rgbValueOffset);

				len = 32;
				vp = value;
				nval = 0;
				mylog("index=(");
				for (i = 0; i < 16; i++)
				{
					if (sscanf(vp, "%hd", &short_array[i]) != 1)
						break;

					mylog(" %d", short_array[i]);
					nval++;

					/* skip the current token */
					while ((*vp != '\0') && (!isspace((unsigned char) *vp)))
						vp++;
					/* and skip the space to the next token */
					while ((*vp != '\0') && (isspace((unsigned char) *vp)))
						vp++;
					if (*vp == '\0')
						break;
				}
				mylog(") nval = %d\n", nval);

				for (i = nval; i < 16; i++)
					short_array[i] = 0;

#if 0
				sscanf(value, "%hd %hd %hd %hd %hd %hd %hd %hd",
					   &short_array[0],
					   &short_array[1],
					   &short_array[2],
					   &short_array[3],
					   &short_array[4],
					   &short_array[5],
					   &short_array[6],
					   &short_array[7]);
#endif

				/* There is no corresponding fCType for this. */
				if (pcbValue)
					*(SDWORD *) ((char *) pcbValue + pcbValueOffset) = len;

				return COPY_OK; /* dont go any further or the data will be
								 * trashed */
			}

			/*
			 * This is a large object OID, which is used to store
			 * LONGVARBINARY objects.
			 */
		case PG_TYPE_LO:

			return convert_lo(stmt, value, fCType, ((char *) rgbValue + rgbValueOffset), cbValueMax, (SDWORD *) ((char *) pcbValue + pcbValueOffset));

		default:

			if (field_type == stmt->hdbc->lobj_type)	/* hack until permanent
														 * type available */
				return convert_lo(stmt, value, fCType, ((char *) rgbValue + rgbValueOffset), cbValueMax, (SDWORD *) ((char *) pcbValue + pcbValueOffset));
	}

	/* Change default into something useable */
	if (fCType == SQL_C_DEFAULT)
	{
		fCType = pgtype_to_ctype(stmt, field_type);

		mylog("copy_and_convert, SQL_C_DEFAULT: fCType = %d\n", fCType);
	}

	rgbValueBindRow = (char *) rgbValue + rgbValueOffset;

	if (fCType == SQL_C_CHAR)
	{
		/* Special character formatting as required */

		/*
		 * These really should return error if cbValueMax is not big
		 * enough.
		 */
		switch (field_type)
		{
			case PG_TYPE_DATE:
				len = 10;
				if (cbValueMax > len)
					sprintf(rgbValueBindRow, "%.4d-%.2d-%.2d", st.y, st.m, st.d);
				break;

			case PG_TYPE_TIME:
				len = 8;
				if (cbValueMax > len)
					sprintf(rgbValueBindRow, "%.2d:%.2d:%.2d", st.hh, st.mm, st.ss);
				break;

			case PG_TYPE_ABSTIME:
			case PG_TYPE_DATETIME:
			case PG_TYPE_TIMESTAMP:
				len = 19;
				if (cbValueMax > len)
					sprintf(rgbValueBindRow, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d",
							st.y, st.m, st.d, st.hh, st.mm, st.ss);
				break;

			case PG_TYPE_BOOL:
				len = 1;
				if (cbValueMax > len)
				{
					strcpy(rgbValueBindRow, value);
					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(value, rgbValueBindRow, cbValueMax);

				/***** THIS IS NOT PROPERLY IMPLEMENTED *****/
				break;

			default:
				/* convert linefeeds to carriage-return/linefeed */
				len = convert_linefeeds(value, tempBuf, sizeof(tempBuf));
				ptr = tempBuf;

				mylog("DEFAULT: len = %d, ptr = '%s'\n", len, ptr);

				if (stmt->current_col >= 0)
				{
					if (stmt->bindings[stmt->current_col].data_left == 0)
						return COPY_NO_DATA_FOUND;
					else if (stmt->bindings[stmt->current_col].data_left > 0)
					{
						ptr += len - stmt->bindings[stmt->current_col].data_left;
						len = stmt->bindings[stmt->current_col].data_left;
					}
					else
						stmt->bindings[stmt->current_col].data_left = strlen(ptr);
				}

				if (cbValueMax > 0)
				{
					copy_len = (len >= cbValueMax) ? cbValueMax - 1 : len;

					/* Copy the data */
					strncpy_null(rgbValueBindRow, ptr, copy_len + 1);

					/* Adjust data_left for next time */
					if (stmt->current_col >= 0)
						stmt->bindings[stmt->current_col].data_left -= copy_len;
				}

				/*
				 * Finally, check for truncation so that proper status can
				 * be returned
				 */
				if (len >= cbValueMax)
					result = COPY_RESULT_TRUNCATED;


				mylog("    SQL_C_CHAR, default: len = %d, cbValueMax = %d, rgbValueBindRow = '%s'\n", len, cbValueMax, rgbValueBindRow);
				break;
		}


	}
	else
	{

		/*
		 * for SQL_C_CHAR, it's probably ok to leave currency symbols in.
		 * But to convert to numeric types, it is necessary to get rid of
		 * those.
		 */
		if (field_type == PG_TYPE_MONEY)
			convert_money(value);

		switch (fCType)
		{
			case SQL_C_DATE:
				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:
				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:
				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 = 0;
				}
				break;

			case SQL_C_BIT:
				len = 1;
				if (bind_size > 0)
					*(UCHAR *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(value);
				else
					*((UCHAR *) rgbValue + bind_row) = atoi(value);

				/*
				 * mylog("SQL_C_BIT: val = %d, cb = %d, rgb=%d\n",
				 * atoi(value), cbValueMax, *((UCHAR *)rgbValue));
				 */
				break;

			case SQL_C_STINYINT:
			case SQL_C_TINYINT:
				len = 1;
				if (bind_size > 0)
					*(SCHAR *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(value);
				else
					*((SCHAR *) rgbValue + bind_row) = atoi(value);
				break;

			case SQL_C_UTINYINT:
				len = 1;
				if (bind_size > 0)
					*(UCHAR *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(value);
				else
					*((UCHAR *) rgbValue + bind_row) = atoi(value);
				break;

			case SQL_C_FLOAT:
				len = 4;
				if (bind_size > 0)
					*(SFLOAT *) ((char *) rgbValue + (bind_row * bind_size)) = (float) atof(value);
				else
					*((SFLOAT *) rgbValue + bind_row) = (float) atof(value);
				break;

			case SQL_C_DOUBLE:
				len = 8;
				if (bind_size > 0)
					*(SDOUBLE *) ((char *) rgbValue + (bind_row * bind_size)) = atof(value);
				else
					*((SDOUBLE *) rgbValue + bind_row) = atof(value);
				break;

			case SQL_C_SSHORT:
			case SQL_C_SHORT:
				len = 2;
				if (bind_size > 0)
					*(SWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(value);
				else
					*((SWORD *) rgbValue + bind_row) = atoi(value);
				break;

			case SQL_C_USHORT:
				len = 2;
				if (bind_size > 0)
					*(UWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atoi(value);
				else
					*((UWORD *) rgbValue + bind_row) = atoi(value);
				break;

			case SQL_C_SLONG:
			case SQL_C_LONG:
				len = 4;
				if (bind_size > 0)
					*(SDWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atol(value);
				else
					*((SDWORD *) rgbValue + bind_row) = atol(value);
				break;

			case SQL_C_ULONG:
				len = 4;
				if (bind_size > 0)
					*(UDWORD *) ((char *) rgbValue + (bind_row * bind_size)) = atol(value);
				else
					*((UDWORD *) rgbValue + bind_row) = atol(value);
				break;

			case SQL_C_BINARY:

				/* truncate if necessary */
				/* convert octal escapes to bytes */

				len = convert_from_pgbinary(value, tempBuf, sizeof(tempBuf));
				ptr = tempBuf;

				if (stmt->current_col >= 0)
				{
					/* No more data left for this column */
					if (stmt->bindings[stmt->current_col].data_left == 0)
						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)
					{
						ptr += len - stmt->bindings[stmt->current_col].data_left;
						len = stmt->bindings[stmt->current_col].data_left;
					}

					/* First call to SQLGetData so initialize data_left */
					else
						stmt->bindings[stmt->current_col].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)
						stmt->bindings[stmt->current_col].data_left -= copy_len;
				}

				/*
				 * Finally, check for truncation so that proper status can
				 * be returned
				 */
				if (len > cbValueMax)
					result = COPY_RESULT_TRUNCATED;

				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;

	return result;

}


/*
 *	This function inserts parameters into an SQL statements.
 *	It will also modify a SELECT statement for use with declare/fetch cursors.
 *	This function no longer does any dynamic memory allocation!
 */
int
copy_statement_with_parameters(StatementClass *stmt)
{
	static char *func = "copy_statement_with_parameters";
	unsigned int opos,
				npos,
				oldstmtlen;
	char		param_string[128],
				tmp[256],
				cbuf[TEXT_FIELD_SIZE + 5];
	int			param_number;
	Int2		param_ctype,
				param_sqltype;
	char	   *old_statement = stmt->statement;
	char	   *new_statement = stmt->stmt_with_params;
	SIMPLE_TIME st;
	time_t		t = time(NULL);
	struct tm  *tim;
	SDWORD		used;
	char	   *buffer,
			   *buf;
	char		in_quote = FALSE;
	Oid			lobj_oid;
	int			lobj_fd,
				retval;


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

	memset(&st, 0, sizeof(SIMPLE_TIME));

	/* Initialize current date */
	tim = localtime(&t);
	st.m = tim->tm_mon + 1;
	st.d = tim->tm_mday;
	st.y = tim->tm_year + 1900;

	/* 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);

	/* For selects, prepend a declare cursor to the statement */
	if (stmt->statement_type == STMT_TYPE_SELECT && globals.use_declarefetch)
	{
		sprintf(new_statement, "declare %s cursor for ", stmt->cursor_name);
		npos = strlen(new_statement);
	}
	else
	{
		new_statement[0] = '0';
		npos = 0;
	}

	param_number = -1;

	oldstmtlen = strlen(old_statement);
#ifdef MULTIBYTE
	multibyte_init();
#endif

	for (opos = 0; opos < oldstmtlen; opos++)
	{
		/* Squeeze carriage-return/linefeed pairs to linefeed only */
		if (old_statement[opos] == '\r' && opos + 1 < oldstmtlen &&
			old_statement[opos + 1] == '\n')
			continue;

		/*
		 * Handle literals (date, time, timestamp) and ODBC scalar
		 * functions
		 */
#ifdef MULTIBYTE
		else if (multibyte_char_check(old_statement[opos]) == 0 && old_statement[opos] == '{')
		{
#else
		else if (old_statement[opos] == '{')
		{
#endif
			char	   *esc;
			char	   *begin = &old_statement[opos + 1];

#ifdef MULTIBYTE
			char	   *end = multibyte_strchr(begin, '}');

#else
			char	   *end = strchr(begin, '}');

#endif

			if (!end)
				continue;

			*end = '\0';

			esc = convert_escape(begin);
			if (esc)
			{
				memcpy(&new_statement[npos], esc, strlen(esc));
				npos += strlen(esc);
			}
			else
			{					/* it's not a valid literal so just copy */
				*end = '}';
				new_statement[npos++] = old_statement[opos];
				continue;
			}

			opos += end - begin + 1;
			*end = '}';
			continue;
		}

		/*
		 * Can you have parameter markers inside of quotes?  I dont think
		 * so. All the queries I've seen expect the driver to put quotes
		 * if needed.
		 */
		else if (old_statement[opos] == '?' && !in_quote)
			;					/* ok */
		else
		{
			if (old_statement[opos] == '\'')
				in_quote = (in_quote ? FALSE : TRUE);

			new_statement[npos++] = old_statement[opos];
			continue;
		}

		/*
		 * Its a '?' parameter alright
		 */
		param_number++;

		if (param_number >= stmt->parameters_allocated)
		{
			if (stmt->pre_executing)
			{
				strcpy(&new_statement[npos], "NULL");
				npos += 4;
				stmt->inaccurate_result = TRUE;
				continue;
			}
			else
			{
				new_statement[npos++] = '?';
				continue;
			}
		}

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

		/* Handle NULL parameter data */
		if (used == SQL_NULL_DATA)
		{
			strcpy(&new_statement[npos], "NULL");
			npos += 4;
			continue;
		}

		/*
		 * If no buffer, and it's not null, then what the hell is it? Just
		 * leave it alone then.
		 */
		if (!buffer)
		{
			if (stmt->pre_executing)
			{
				strcpy(&new_statement[npos], "NULL");
				npos += 4;
				stmt->inaccurate_result = TRUE;
				continue;
			}
			else
			{
				new_statement[npos++] = '?';
				continue;
			}
		}

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

		mylog("copy_statement_with_params: from(fcType)=%d, to(fSqlType)=%d\n", param_ctype, param_sqltype);

		/* replace DEFAULT with something we can use */
		if (param_ctype == SQL_C_DEFAULT)
			param_ctype = sqltype_to_default_ctype(param_sqltype);

		buf = NULL;
		param_string[0] = '\0';
		cbuf[0] = '\0';

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

			case SQL_C_DOUBLE:
				sprintf(param_string, "%.15g",
						*((SDOUBLE *) buffer));
				break;

			case SQL_C_FLOAT:
				sprintf(param_string, "%.6g",
						*((SFLOAT *) buffer));
				break;

			case SQL_C_SLONG:
			case SQL_C_LONG:
				sprintf(param_string, "%ld",
						*((SDWORD *) buffer));
				break;

			case SQL_C_SSHORT:
			case SQL_C_SHORT:
				sprintf(param_string, "%d",
						*((SWORD *) buffer));
				break;

			case SQL_C_STINYINT:
			case SQL_C_TINYINT:
				sprintf(param_string, "%d",
						*((SCHAR *) buffer));
				break;

			case SQL_C_ULONG:
				sprintf(param_string, "%lu",
						*((UDWORD *) buffer));
				break;

			case SQL_C_USHORT:
				sprintf(param_string, "%u",
						*((UWORD *) buffer));
				break;

			case SQL_C_UTINYINT:
				sprintf(param_string, "%u",
						*((UCHAR *) buffer));
				break;

			case SQL_C_BIT:
				{
					int			i = *((UCHAR *) buffer);

					sprintf(param_string, "%d", i ? 1 : 0);
					break;
				}

			case SQL_C_DATE:
				{
					DATE_STRUCT *ds = (DATE_STRUCT *) buffer;

					st.m = ds->month;
					st.d = ds->day;
					st.y = ds->year;

					break;
				}

			case SQL_C_TIME:
				{
					TIME_STRUCT *ts = (TIME_STRUCT *) buffer;

					st.hh = ts->hour;
					st.mm = ts->minute;
					st.ss = ts->second;

					break;
				}

			case SQL_C_TIMESTAMP:
				{
					TIMESTAMP_STRUCT *tss = (TIMESTAMP_STRUCT *) buffer;

					st.m = tss->month;
					st.d = tss->day;
					st.y = tss->year;
					st.hh = tss->hour;
					st.mm = tss->minute;
					st.ss = tss->second;

					mylog("m=%d,d=%d,y=%d,hh=%d,mm=%d,ss=%d\n", st.m, st.d, st.y, st.hh, st.mm, st.ss);

					break;

				}
			default:
				/* error */
				stmt->errormsg = "Unrecognized C_parameter type in copy_statement_with_parameters";
				stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR;
				new_statement[npos] = '\0';		/* just in case */
				SC_log_error(func, "", stmt);
				return SQL_ERROR;
		}

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

		switch (param_sqltype)
		{
			case SQL_CHAR:
			case SQL_VARCHAR:
			case SQL_LONGVARCHAR:

				new_statement[npos++] = '\'';	/* Open Quote */

				/* it was a SQL_C_CHAR */
				if (buf)
				{
					convert_special_chars(buf, &new_statement[npos], used);
					npos += strlen(&new_statement[npos]);
				}

				/* it was a numeric type */
				else if (param_string[0] != '\0')
				{
					strcpy(&new_statement[npos], param_string);
					npos += strlen(param_string);
				}

				/* it was date,time,timestamp -- use m,d,y,hh,mm,ss */
				else
				{
					sprintf(tmp, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d",
							st.y, st.m, st.d, st.hh, st.mm, st.ss);

					strcpy(&new_statement[npos], tmp);
					npos += strlen(tmp);
				}

				new_statement[npos++] = '\'';	/* Close Quote */

				break;

			case SQL_DATE:
				if (buf)
				{				/* copy char data to time */
					my_strcpy(cbuf, sizeof(cbuf), buf, used);
					parse_datetime(cbuf, &st);
				}

				sprintf(tmp, "'%.4d-%.2d-%.2d'", st.y, st.m, st.d);

				strcpy(&new_statement[npos], tmp);
				npos += strlen(tmp);
				break;

			case SQL_TIME:
				if (buf)
				{				/* copy char data to time */
					my_strcpy(cbuf, sizeof(cbuf), buf, used);
					parse_datetime(cbuf, &st);
				}

				sprintf(tmp, "'%.2d:%.2d:%.2d'", st.hh, st.mm, st.ss);

				strcpy(&new_statement[npos], tmp);
				npos += strlen(tmp);
				break;

			case SQL_TIMESTAMP:

				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);

				strcpy(&new_statement[npos], tmp);
				npos += strlen(tmp);

				break;

			case SQL_BINARY:
			case SQL_VARBINARY:/* non-ascii characters should be
								 * converted to octal */
				new_statement[npos++] = '\'';	/* Open Quote */

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

				npos += convert_to_pgbinary(buf, &new_statement[npos], used);

				new_statement[npos++] = '\'';	/* Close Quote */

				break;

			case SQL_LONGVARBINARY:

				if (stmt->parameters[param_number].data_at_exec)
					lobj_oid = stmt->parameters[param_number].lobj_oid;
				else
				{
					/* begin transaction if needed */
					if (!CC_is_in_trans(stmt->hdbc))
					{
						QResultClass *res;
						char		ok;

						res = CC_send_query(stmt->hdbc, "BEGIN", NULL);
						if (!res)
						{
							stmt->errormsg = "Could not begin (in-line) a transaction";
							stmt->errornumber = STMT_EXEC_ERROR;
							SC_log_error(func, "", stmt);
							return SQL_ERROR;
						}
						ok = QR_command_successful(res);
						QR_Destructor(res);
						if (!ok)
						{
							stmt->errormsg = "Could not begin (in-line) a transaction";
							stmt->errornumber = STMT_EXEC_ERROR;
							SC_log_error(func, "", stmt);
							return SQL_ERROR;
						}

						CC_set_in_trans(stmt->hdbc);
					}

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

					/* store the fd */
					lobj_fd = lo_open(stmt->hdbc, lobj_oid, INV_WRITE);
					if (lobj_fd < 0)
					{
						stmt->errornumber = STMT_EXEC_ERROR;
						stmt->errormsg = "Couldnt open (in-line) large object for writing.";
						SC_log_error(func, "", stmt);
						return SQL_ERROR;
					}

					retval = lo_write(stmt->hdbc, lobj_fd, buffer, used);

					lo_close(stmt->hdbc, lobj_fd);

					/* commit transaction if needed */
					if (!globals.use_declarefetch && CC_is_in_autocommit(stmt->hdbc))
					{
						QResultClass *res;
						char		ok;

						res = CC_send_query(stmt->hdbc, "COMMIT", NULL);
						if (!res)
						{
							stmt->errormsg = "Could not commit (in-line) a transaction";
							stmt->errornumber = STMT_EXEC_ERROR;
							SC_log_error(func, "", stmt);
							return SQL_ERROR;
						}
						ok = QR_command_successful(res);
						QR_Destructor(res);
						if (!ok)
						{
							stmt->errormsg = "Could not commit (in-line) a transaction";
							stmt->errornumber = STMT_EXEC_ERROR;
							SC_log_error(func, "", stmt);
							return SQL_ERROR;
						}

						CC_set_no_trans(stmt->hdbc);
					}
				}

				/*
				 * 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);
				strcpy(&new_statement[npos], param_string);
				npos += strlen(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);
				strcpy(&new_statement[npos], tmp);
				npos += strlen(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);
				strcpy(&new_statement[npos], tmp);
				npos += strlen(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);
				my_strcpy(&new_statement[npos], sizeof(stmt->stmt_with_params) - npos - 1, cbuf, strlen(cbuf));
				npos += strlen(&new_statement[npos]);
				break;
			default:			/* a numeric type or SQL_BIT */
				if (param_sqltype == SQL_BIT)
					new_statement[npos++] = '\'';		/* Open Quote */

				if (buf)
				{
					my_strcpy(&new_statement[npos], sizeof(stmt->stmt_with_params) - npos, buf, used);
					npos += strlen(&new_statement[npos]);
				}
				else
				{
					strcpy(&new_statement[npos], param_string);
					npos += strlen(param_string);
				}

				if (param_sqltype == SQL_BIT)
					new_statement[npos++] = '\'';		/* Close Quote */

				break;
		}
	}							/* end, for */

	/* make sure new_statement is always null-terminated */
	new_statement[npos] = '\0';

	if (stmt->hdbc->DriverToDataSource != NULL)
	{
		int			length = strlen(new_statement);

		stmt->hdbc->DriverToDataSource(stmt->hdbc->translation_option,
									   SQL_CHAR,
									   new_statement, length,
									   new_statement, length, NULL,
									   NULL, 0, NULL);
	}

	return SQL_SUCCESS;
}


char *
mapFunction(char *func)
{
	int			i;

	for (i = 0; mapFuncs[i][0]; i++)
		if (!stricmp(mapFuncs[i][0], func))
			return mapFuncs[i][1];

	return NULL;
}


/*
 * convert_escape()
 *
 * This function returns a pointer to static memory!
 */
char *
convert_escape(char *value)
{
	static char escape[1024];
	char		key[33];

	/* Separate off the key, skipping leading and trailing whitespace */
	while ((*value != '\0') && isspace((unsigned char) *value))
		value++;
	sscanf(value, "%32s", key);
	while ((*value != '\0') && (!isspace((unsigned char) *value)))
		value++;
	while ((*value != '\0') && isspace((unsigned char) *value))
		value++;

	mylog("convert_escape: key='%s', val='%s'\n", key, value);

	if ((strcmp(key, "d") == 0) ||
		(strcmp(key, "t") == 0) ||
		(strcmp(key, "oj") == 0) ||		/* {oj syntax support for 7.1
										 * servers */
		(strcmp(key, "ts") == 0))
	{
		/* Literal; return the escape part as-is */
		strncpy(escape, value, sizeof(escape) - 1);
	}
	else if (strcmp(key, "fn") == 0)
	{

		/*
		 * Function invocation Separate off the func name, skipping
		 * trailing whitespace.
		 */
		char	   *funcEnd = value;
		char		svchar;
		char	   *mapFunc;

		while ((*funcEnd != '\0') && (*funcEnd != '(') &&
			   (!isspace((unsigned char) *funcEnd)))
			funcEnd++;
		svchar = *funcEnd;
		*funcEnd = '\0';
		sscanf(value, "%32s", key);
		*funcEnd = svchar;
		while ((*funcEnd != '\0') && isspace((unsigned char) *funcEnd))
			funcEnd++;

		/*
		 * We expect left parenthesis here, else return fn body as-is
		 * since it is one of those "function constants".
		 */
		if (*funcEnd != '(')
		{
			strncpy(escape, value, sizeof(escape) - 1);
			return escape;
		}
		mapFunc = mapFunction(key);

		/*
		 * We could have mapFunction() return key if not in table... -
		 * thomas 2000-04-03
		 */
		if (mapFunc == NULL)
		{
			/* If unrecognized function name, return fn body as-is */
			strncpy(escape, value, sizeof(escape) - 1);
			return escape;
		}
		/* copy mapped name and remaining input string */
		strcpy(escape, mapFunc);
		strncat(escape, funcEnd, sizeof(escape) - 1 - strlen(mapFunc));
	}
	else
	{
		/* Bogus key, leave untranslated */
		return NULL;
	}

	return escape;
}


char *
convert_money(char *s)
{
	size_t		i = 0,
				out = 0;

	for (i = 0; i < strlen(s); i++)
	{
		if (s[i] == '$' || s[i] == ',' || s[i] == ')')
			;					/* skip these characters */
		else if (s[i] == '(')
			s[out++] = '-';
		else
			s[out++] = s[i];
	}
	s[out] = '\0';
	return s;
}


/*
 *	This function parses a character string for date/time info and fills in SIMPLE_TIME
 *	It does not zero out SIMPLE_TIME in case it is desired to initialize it with a value
 */
char
parse_datetime(char *buf, SIMPLE_TIME *st)
{
	int			y,
				m,
				d,
				hh,
				mm,
				ss;
	int			nf;

	y = m = d = hh = mm = ss = 0;

	if (buf[4] == '-')			/* year first */
		nf = sscanf(buf, "%4d-%2d-%2d %2d:%2d:%2d", &y, &m, &d, &hh, &mm, &ss);
	else
		nf = sscanf(buf, "%2d-%2d-%4d %2d:%2d:%2d", &m, &d, &y, &hh, &mm, &ss);

	if (nf == 5 || nf == 6)
	{
		st->y = y;
		st->m = m;
		st->d = d;
		st->hh = hh;
		st->mm = mm;
		st->ss = ss;

		return TRUE;
	}

	if (buf[4] == '-')			/* year first */
		nf = sscanf(buf, "%4d-%2d-%2d", &y, &m, &d);
	else
		nf = sscanf(buf, "%2d-%2d-%4d", &m, &d, &y);

	if (nf == 3)
	{
		st->y = y;
		st->m = m;
		st->d = d;

		return TRUE;
	}

	nf = sscanf(buf, "%2d:%2d:%2d", &hh, &mm, &ss);
	if (nf == 2 || nf == 3)
	{
		st->hh = hh;
		st->mm = mm;
		st->ss = ss;

		return TRUE;
	}

	return FALSE;
}


/*	Change linefeed to carriage-return/linefeed */
int
convert_linefeeds(char *si, char *dst, size_t max)
{
	size_t		i = 0,
				out = 0;

	for (i = 0; i < strlen(si) && out < max - 1; i++)
	{
		if (si[i] == '\n')
		{
			/* Only add the carriage-return if needed */
			if (i > 0 && si[i - 1] == '\r')
			{
				dst[out++] = si[i];
				continue;
			}

			dst[out++] = '\r';
			dst[out++] = '\n';
		}
		else
			dst[out++] = si[i];
	}
	dst[out] = '\0';
	return out;
}


/*
 *	Change carriage-return/linefeed to just linefeed
 *	Plus, escape any special characters.
 */
char *
convert_special_chars(char *si, char *dst, int used)
{
	size_t		i = 0,
				out = 0,
				max;
	static char sout[TEXT_FIELD_SIZE + 5];
	char	   *p;

	if (dst)
		p = dst;
	else
		p = sout;

	p[0] = '\0';

	if (used == SQL_NTS)
		max = strlen(si);
	else
		max = used;
#ifdef MULTIBYTE
	multibyte_init();
#endif

	for (i = 0; i < max; i++)
	{
		if (si[i] == '\r' && i + 1 < strlen(si) && si[i + 1] == '\n')
			continue;
#ifdef MULTIBYTE
		else if (multibyte_char_check(si[i]) == 0 && (si[i] == '\'' || si[i] == '\\'))
#else
		else if (si[i] == '\'' || si[i] == '\\')
#endif
			p[out++] = '\\';

		p[out++] = si[i];
	}
	p[out] = '\0';
	return p;
}


/*	!!! Need to implement this function !!!  */
int
convert_pgbinary_to_char(char *value, char *rgbValue, int cbValueMax)
{
	mylog("convert_pgbinary_to_char: value = '%s'\n", value);

	strncpy_null(rgbValue, value, cbValueMax);
	return 0;
}


unsigned int
conv_from_octal(unsigned char *s)
{
	int			i,
				y = 0;

	for (i = 1; i <= 3; i++)
		y += (s[i] - 48) * (int) pow(8, 3 - i);

	return y;

}


unsigned int
conv_from_hex(unsigned char *s)
{
	int			i,
				y = 0,
				val;

	for (i = 1; i <= 2; i++)
	{
		if (s[i] >= 'a' && s[i] <= 'f')
			val = s[i] - 'a' + 10;
		else if (s[i] >= 'A' && s[i] <= 'F')
			val = s[i] - 'A' + 10;
		else
			val = s[i] - '0';

		y += val * (int) pow(16, 2 - i);
	}

	return y;
}


/*	convert octal escapes to bytes */
int
convert_from_pgbinary(unsigned char *value, unsigned char *rgbValue, int cbValueMax)
{
	size_t		i;
	int			o = 0;


	for (i = 0; i < strlen(value);)
	{
		if (value[i] == '\\')
		{
			rgbValue[o] = conv_from_octal(&value[i]);
			i += 4;
		}
		else
			rgbValue[o] = value[i++];
		mylog("convert_from_pgbinary: i=%d, rgbValue[%d] = %d, %c\n", i, o, rgbValue[o], rgbValue[o]);
		o++;
	}

	rgbValue[o] = '\0';			/* extra protection */

	return o;
}


char *
conv_to_octal(unsigned char val)
{
	int			i;
	static char x[6];

	x[0] = '\\';
	x[1] = '\\';
	x[5] = '\0';

	for (i = 4; i > 1; i--)
	{
		x[i] = (val & 7) + 48;
		val >>= 3;
	}

	return x;
}


/*	convert non-ascii bytes to octal escape sequences */
int
convert_to_pgbinary(unsigned char *in, char *out, int len)
{
	int			i,
				o = 0;

	for (i = 0; i < len; i++)
	{
		mylog("convert_to_pgbinary: in[%d] = %d, %c\n", i, in[i], in[i]);
		if (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(char *in, char *out)
{
	unsigned int i,
				o = 0;

	for (i = 0; i < strlen(in); 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(char *in, char *out)
{
	unsigned int i,
				o = 0;

	for (i = 0; i < strlen(in); 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';
}


/*-------
 *	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, void *value, Int2 fCType, PTR rgbValue,
		   SDWORD cbValueMax, SDWORD *pcbValue)
{
	Oid			oid;
	int			retval,
				result,
				left = -1;
	BindInfoClass *bindInfo = NULL;


	/* If using SQLGetData, then current_col will be set */
	if (stmt->current_col >= 0)
	{
		bindInfo = &stmt->bindings[stmt->current_col];
		left = bindInfo->data_left;
	}

	/*
	 * if this is the first call for this column, open the large object
	 * for reading
	 */

	if (!bindInfo || bindInfo->data_left == -1)
	{
		/* begin transaction if needed */
		if (!CC_is_in_trans(stmt->hdbc))
		{
			QResultClass *res;
			char		ok;

			res = CC_send_query(stmt->hdbc, "BEGIN", NULL);
			if (!res)
			{
				stmt->errormsg = "Could not begin (in-line) a transaction";
				stmt->errornumber = STMT_EXEC_ERROR;
				return COPY_GENERAL_ERROR;
			}
			ok = QR_command_successful(res);
			QR_Destructor(res);
			if (!ok)
			{
				stmt->errormsg = "Could not begin (in-line) a transaction";
				stmt->errornumber = STMT_EXEC_ERROR;
				return COPY_GENERAL_ERROR;
			}

			CC_set_in_trans(stmt->hdbc);
		}

		oid = atoi(value);
		stmt->lobj_fd = lo_open(stmt->hdbc, 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(stmt->hdbc, stmt->lobj_fd, 0L, SEEK_END);
		if (retval >= 0)
		{
			left = lo_tell(stmt->hdbc, stmt->lobj_fd);
			if (bindInfo)
				bindInfo->data_left = left;

			/* return to beginning */
			lo_lseek(stmt->hdbc, stmt->lobj_fd, 0L, SEEK_SET);
		}
	}

	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(stmt->hdbc, stmt->lobj_fd, (char *) rgbValue, cbValueMax);
	if (retval < 0)
	{
		lo_close(stmt->hdbc, stmt->lobj_fd);

		/* commit transaction if needed */
		if (!globals.use_declarefetch && CC_is_in_autocommit(stmt->hdbc))
		{
			QResultClass *res;
			char		ok;

			res = CC_send_query(stmt->hdbc, "COMMIT", NULL);
			if (!res)
			{
				stmt->errormsg = "Could not commit (in-line) a transaction";
				stmt->errornumber = STMT_EXEC_ERROR;
				return COPY_GENERAL_ERROR;
			}
			ok = QR_command_successful(res);
			QR_Destructor(res);
			if (!ok)
			{
				stmt->errormsg = "Could not commit (in-line) a transaction";
				stmt->errornumber = STMT_EXEC_ERROR;
				return COPY_GENERAL_ERROR;
			}

			CC_set_no_trans(stmt->hdbc);
		}

		stmt->lobj_fd = -1;

		stmt->errornumber = STMT_EXEC_ERROR;
		stmt->errormsg = "Error reading from large object.";
		return COPY_GENERAL_ERROR;
	}

	if (retval < left)
		result = COPY_RESULT_TRUNCATED;
	else
		result = COPY_OK;

	if (pcbValue)
		*pcbValue = left < 0 ? SQL_NO_TOTAL : left;

	if (bindInfo && bindInfo->data_left > 0)
		bindInfo->data_left -= retval;

	if (!bindInfo || bindInfo->data_left == 0)
	{
		lo_close(stmt->hdbc, stmt->lobj_fd);

		/* commit transaction if needed */
		if (!globals.use_declarefetch && CC_is_in_autocommit(stmt->hdbc))
		{
			QResultClass *res;
			char		ok;

			res = CC_send_query(stmt->hdbc, "COMMIT", NULL);
			if (!res)
			{
				stmt->errormsg = "Could not commit (in-line) a transaction";
				stmt->errornumber = STMT_EXEC_ERROR;
				return COPY_GENERAL_ERROR;
			}
			ok = QR_command_successful(res);
			QR_Destructor(res);
			if (!ok)
			{
				stmt->errormsg = "Could not commit (in-line) a transaction";
				stmt->errornumber = STMT_EXEC_ERROR;
				return COPY_GENERAL_ERROR;
			}

			CC_set_no_trans(stmt->hdbc);
		}

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

	return result;
}
