/*-------
 * Module:			bind.c
 *
 * Description:		This module contains routines related to binding
 *					columns and parameters.
 *
 * Classes:			BindInfoClass, ParameterInfoClass
 *
 * API functions:	SQLBindParameter, SQLBindCol, SQLDescribeParam, SQLNumParams,
 *					SQLParamOptions
 *
 * Comments:		See "readme.txt" for copyright and license information.
 *-------
 */

#include <string.h>
#include <ctype.h>
#include "bind.h"
#include "misc.h"

#include "environ.h"
#include "statement.h"
#include "descriptor.h"
#include "qresult.h"
#include "pgtypes.h"
#include "multibyte.h"

#include "pgapifunc.h"


/*		Bind parameters on a statement handle */
RETCODE		SQL_API
PGAPI_BindParameter(HSTMT hstmt,
					SQLUSMALLINT ipar,
					SQLSMALLINT fParamType,
					SQLSMALLINT fCType,
					SQLSMALLINT fSqlType,
					SQLULEN cbColDef,
					SQLSMALLINT ibScale,
					PTR rgbValue,
					SQLLEN cbValueMax,
					SQLLEN * pcbValue)
{
	StatementClass *stmt = (StatementClass *) hstmt;
	CSTR func = "PGAPI_BindParameter";
	APDFields	*apdopts;
	IPDFields	*ipdopts;
	PutDataInfo	*pdata_info;

	MYLOG(0, "entering...\n");

	if (!stmt)
	{
		SC_log_error(func, "", NULL);
		return SQL_INVALID_HANDLE;
	}
	SC_clear_error(stmt);

	apdopts = SC_get_APDF(stmt);
	if (apdopts->allocated < ipar)
		extend_parameter_bindings(apdopts, ipar);
	ipdopts = SC_get_IPDF(stmt);
	if (ipdopts->allocated < ipar)
		extend_iparameter_bindings(ipdopts, ipar);
	pdata_info = SC_get_PDTI(stmt);
	if (pdata_info->allocated < ipar)
		extend_putdata_info(pdata_info, ipar, FALSE);

	/* use zero based column numbers for the below part */
	ipar--;

	/* store the given info */
	apdopts->parameters[ipar].buflen = cbValueMax;
	apdopts->parameters[ipar].buffer = rgbValue;
	apdopts->parameters[ipar].used =
	apdopts->parameters[ipar].indicator = pcbValue;
	apdopts->parameters[ipar].CType = fCType;
	ipdopts->parameters[ipar].SQLType = fSqlType;
	ipdopts->parameters[ipar].paramType = fParamType;
	ipdopts->parameters[ipar].column_size = cbColDef;
	ipdopts->parameters[ipar].decimal_digits = ibScale;
	ipdopts->parameters[ipar].precision = 0;
	ipdopts->parameters[ipar].scale = 0;
	switch (fCType)
	{
		case SQL_C_NUMERIC:
			if (cbColDef > 0)
				ipdopts->parameters[ipar].precision = (UInt2) cbColDef;
			if (ibScale > 0)
				ipdopts->parameters[ipar].scale = ibScale;
			break;
		case SQL_C_TYPE_TIMESTAMP:
			if (ibScale > 0)
				ipdopts->parameters[ipar].precision = ibScale;
			break;
		case SQL_C_INTERVAL_DAY_TO_SECOND:
		case SQL_C_INTERVAL_HOUR_TO_SECOND:
		case SQL_C_INTERVAL_MINUTE_TO_SECOND:
		case SQL_C_INTERVAL_SECOND:
				ipdopts->parameters[ipar].precision = 6;
			break;
	}
	apdopts->parameters[ipar].precision = ipdopts->parameters[ipar].precision;
	apdopts->parameters[ipar].scale = ipdopts->parameters[ipar].scale;

	/*
	 * If rebinding a parameter that had data-at-exec stuff in it, then
	 * free that stuff
	 */
	if (pdata_info->pdata[ipar].EXEC_used)
	{
		free(pdata_info->pdata[ipar].EXEC_used);
		pdata_info->pdata[ipar].EXEC_used = NULL;
	}

	if (pdata_info->pdata[ipar].EXEC_buffer)
	{
		free(pdata_info->pdata[ipar].EXEC_buffer);
		pdata_info->pdata[ipar].EXEC_buffer = NULL;
	}

	if (pcbValue && apdopts->param_offset_ptr)
		pcbValue = LENADDR_SHIFT(pcbValue, *apdopts->param_offset_ptr);
#ifdef	NOT_USED /* evaluation of pcbValue here is dangerous */
	/* Data at exec macro only valid for C char/binary data */
	if (pcbValue && (*pcbValue == SQL_DATA_AT_EXEC ||
					 *pcbValue <= SQL_LEN_DATA_AT_EXEC_OFFSET))
		apdopts->parameters[ipar].data_at_exec = TRUE;
	else
		apdopts->parameters[ipar].data_at_exec = FALSE;
#endif /* NOT_USED */

	/* Clear premature result */
	if (stmt->status == STMT_DESCRIBED)
		SC_recycle_statement(stmt);

	MYLOG(0, "ipar=%d, paramType=%d, fCType=%d, fSqlType=%d, cbColDef=" FORMAT_ULEN ", ibScale=%d,", ipar, fParamType, fCType, fSqlType, cbColDef, ibScale);
	MYPRINTF(0, "rgbValue=%p(" FORMAT_LEN "), pcbValue=%p\n", rgbValue, cbValueMax, pcbValue);

	return SQL_SUCCESS;
}


/*	Associate a user-supplied buffer with a database column. */
RETCODE		SQL_API
PGAPI_BindCol(HSTMT hstmt,
			  SQLUSMALLINT icol,
			  SQLSMALLINT fCType,
			  PTR rgbValue,
			  SQLLEN cbValueMax,
			  SQLLEN * pcbValue)
{
	StatementClass *stmt = (StatementClass *) hstmt;
	CSTR func = "PGAPI_BindCol";
	ARDFields	*opts;
	GetDataInfo	*gdata_info;
	BindInfoClass	*bookmark;
	RETCODE		ret = SQL_SUCCESS;

	MYLOG(0, "entering...\n");

	MYLOG(0, "**** : stmt = %p, icol = %d\n", stmt, icol);
	MYLOG(0, "**** : fCType=%d rgb=%p valusMax=" FORMAT_LEN " pcb=%p\n", fCType, rgbValue, cbValueMax, pcbValue);

	if (!stmt)
	{
		SC_log_error(func, "", NULL);
		return SQL_INVALID_HANDLE;
	}

	opts = SC_get_ARDF(stmt);
	if (stmt->status == STMT_EXECUTING)
	{
		SC_set_error(stmt, STMT_SEQUENCE_ERROR, "Can't bind columns while statement is still executing.", func);
		return SQL_ERROR;
	}

#define	return	DONT_CALL_RETURN_FROM_HERE ???
	SC_clear_error(stmt);
	/* If the bookmark column is being bound, then just save it */
	if (icol == 0)
	{
		bookmark = opts->bookmark;
		if (rgbValue == NULL)
		{
			if (bookmark)
			{
				bookmark->buffer = NULL;
				bookmark->used =
				bookmark->indicator = NULL;
			}
		}
		else
		{
			/* Make sure it is the bookmark data type */
			switch (fCType)
			{
				case SQL_C_BOOKMARK:
				case SQL_C_VARBOOKMARK:
					break;
				default:
					SC_set_error(stmt, STMT_PROGRAM_TYPE_OUT_OF_RANGE, "Bind column 0 is not of type SQL_C_BOOKMARK", func);
MYLOG(DETAIL_LOG_LEVEL, "Bind column 0 is type %d not of type SQL_C_BOOKMARK\n", fCType);
					ret = SQL_ERROR;
					goto cleanup;
			}

			bookmark = ARD_AllocBookmark(opts);
			bookmark->buffer = rgbValue;
			bookmark->used =
			bookmark->indicator = pcbValue;
			bookmark->buflen = cbValueMax;
			bookmark->returntype = fCType;
		}
		goto cleanup;
	}

	/*
	 * Allocate enough bindings if not already done. Most likely,
	 * execution of a statement would have setup the necessary bindings.
	 * But some apps call BindCol before any statement is executed.
	 */
	if (icol > opts->allocated)
		extend_column_bindings(opts, icol);
	gdata_info = SC_get_GDTI(stmt);
	if (icol > gdata_info->allocated)
		extend_getdata_info(gdata_info, icol, FALSE);

	/* check to see if the bindings were allocated */
	if (!opts->bindings || !gdata_info->gdata)
	{
		SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "Could not allocate memory for bindings.", func);
		ret = SQL_ERROR;
		goto cleanup;
	}

	/* use zero based col numbers from here out */
	icol--;

	/* Reset for SQLGetData */
	GETDATA_RESET(gdata_info->gdata[icol]);

	if (rgbValue == NULL)
	{
		/* we have to unbind the column */
		opts->bindings[icol].buflen = 0;
		opts->bindings[icol].buffer = NULL;
		opts->bindings[icol].used =
		opts->bindings[icol].indicator = NULL;
		opts->bindings[icol].returntype = SQL_C_CHAR;
		opts->bindings[icol].precision = 0;
		opts->bindings[icol].scale = 0;
		if (gdata_info->gdata[icol].ttlbuf)
			free(gdata_info->gdata[icol].ttlbuf);
		gdata_info->gdata[icol].ttlbuf = NULL;
		gdata_info->gdata[icol].ttlbuflen = 0;
		gdata_info->gdata[icol].ttlbufused = 0;
	}
	else
	{
		/* ok, bind that column */
		opts->bindings[icol].buflen = cbValueMax;
		opts->bindings[icol].buffer = rgbValue;
		opts->bindings[icol].used =
		opts->bindings[icol].indicator = pcbValue;
		opts->bindings[icol].returntype = fCType;
		opts->bindings[icol].precision = 0;
		switch (fCType)
		{
			case SQL_C_NUMERIC:
				opts->bindings[icol].precision = 32;
				break;
			case SQL_C_TIMESTAMP:
			case SQL_C_INTERVAL_DAY_TO_SECOND:
			case SQL_C_INTERVAL_HOUR_TO_SECOND:
			case SQL_C_INTERVAL_MINUTE_TO_SECOND:
			case SQL_C_INTERVAL_SECOND:
				opts->bindings[icol].precision = 6;
				break;
		}
		opts->bindings[icol].scale = 0;

		MYLOG(0, "       bound buffer[%d] = %p\n", icol, opts->bindings[icol].buffer);
	}

cleanup:
#undef	return
	return ret;
}


/*
 *	Returns the description of a parameter marker.
 *	This function is listed as not being supported by SQLGetFunctions() because it is
 *	used to describe "parameter markers" (not bound parameters), in which case,
 *	the dbms should return info on the markers.  Since Postgres doesn't support that,
 *	it is best to say this function is not supported and let the application assume a
 *	data type (most likely varchar).
 */
RETCODE		SQL_API
PGAPI_DescribeParam(HSTMT hstmt,
					SQLUSMALLINT ipar,
					SQLSMALLINT * pfSqlType,
					SQLULEN * pcbParamDef,
					SQLSMALLINT * pibScale,
					SQLSMALLINT * pfNullable)
{
	StatementClass *stmt = (StatementClass *) hstmt;
	CSTR func = "PGAPI_DescribeParam";
	IPDFields	*ipdopts;
	RETCODE		ret = SQL_SUCCESS;
	int		num_params;
	OID		pgtype;
	ConnectionClass	*conn;

	MYLOG(0, "entering...%d\n", ipar);

	if (!stmt)
	{
		SC_log_error(func, "", NULL);
		return SQL_INVALID_HANDLE;
	}
	conn = SC_get_conn(stmt);
	SC_clear_error(stmt);

	ipdopts = SC_get_IPDF(stmt);
	/*if ((ipar < 1) || (ipar > ipdopts->allocated))*/
	num_params = stmt->num_params;
	if (num_params < 0)
	{
		SQLSMALLINT	num_p;

		PGAPI_NumParams(stmt, &num_p);
		num_params = num_p;
	}
	if ((ipar < 1) || (ipar > num_params))
	{
MYLOG(DETAIL_LOG_LEVEL, "num_params=%d\n", stmt->num_params);
		SC_set_error(stmt, STMT_BAD_PARAMETER_NUMBER_ERROR, "Invalid parameter number for PGAPI_DescribeParam.", func);
		return SQL_ERROR;
	}
	extend_iparameter_bindings(ipdopts, stmt->num_params);

#define	return	DONT_CALL_RETURN_FROM_HERE???
	if (NOT_YET_PREPARED == stmt->prepared)
	{
		decideHowToPrepare(stmt, FALSE);
MYLOG(DETAIL_LOG_LEVEL, "howTo=%d\n", SC_get_prepare_method(stmt));
		switch (SC_get_prepare_method(stmt))
		{
			case NAMED_PARSE_REQUEST:
			case PARSE_TO_EXEC_ONCE:
			case PARSE_REQ_FOR_INFO:
				if (ret = prepareParameters(stmt, FALSE), SQL_ERROR == ret)
					goto cleanup;
		}
	}

	ipar--;
	pgtype = PIC_get_pgtype(ipdopts->parameters[ipar]);
	/*
	 * This implementation is not very good, since it is supposed to
	 * describe
	 */
	/* parameter markers, not bound parameters.  */
	if (pfSqlType)
	{
MYLOG(DETAIL_LOG_LEVEL, "[%d].SQLType=%d .PGType=%d\n", ipar, ipdopts->parameters[ipar].SQLType, pgtype);
		if (ipdopts->parameters[ipar].SQLType)
			*pfSqlType = ipdopts->parameters[ipar].SQLType;
		else if (pgtype)
			*pfSqlType = pgtype_attr_to_concise_type(conn, pgtype, PG_ATP_UNSET, PG_ADT_UNSET, PG_UNKNOWNS_UNSET);
		else
		{
			ret = SQL_ERROR;
			SC_set_error(stmt, STMT_EXEC_ERROR, "Unfortunatley couldn't get this paramater's info", func);
			goto cleanup;
		}
	}

	if (pcbParamDef)
	{
		*pcbParamDef = 0;
		if (ipdopts->parameters[ipar].SQLType)
			*pcbParamDef = ipdopts->parameters[ipar].column_size;
		if (0 == *pcbParamDef && pgtype)
			*pcbParamDef = pgtype_attr_column_size(conn, pgtype, PG_ATP_UNSET, PG_ADT_UNSET, PG_UNKNOWNS_UNSET);
	}

	if (pibScale)
	{
		*pibScale = 0;
		if (ipdopts->parameters[ipar].SQLType)
			*pibScale = ipdopts->parameters[ipar].decimal_digits;
		else if (pgtype)
			*pibScale = pgtype_scale(stmt, pgtype, -1);
	}

	if (pfNullable)
		*pfNullable = pgtype_nullable(SC_get_conn(stmt), ipdopts->parameters[ipar].paramType);
cleanup:
#undef	return
	return ret;
}


/*
 *	This function should really talk to the dbms to determine the number of
 *	"parameter markers" (not bound parameters) in the statement.  But, since
 *	Postgres doesn't support that, the driver should just count the number of markers
 *	and return that.  The reason the driver just can't say this function is unsupported
 *	like it does for SQLDescribeParam is that some applications don't care and try
 *	to call it anyway.
 *	If the statement does not have parameters, it should just return 0.
 */
RETCODE		SQL_API
PGAPI_NumParams(HSTMT hstmt,
				SQLSMALLINT * pcpar)
{
	StatementClass *stmt = (StatementClass *) hstmt;
	CSTR func = "PGAPI_NumParams";

	MYLOG(0, "entering...\n");

	if (!stmt)
	{
		SC_log_error(func, "", NULL);
		return SQL_INVALID_HANDLE;
	}

	if (pcpar)
		*pcpar = 0;
	else
	{
		SC_set_error(stmt, STMT_EXEC_ERROR, "parameter count address is null", func);
		return SQL_ERROR;
	}
MYLOG(DETAIL_LOG_LEVEL, "num_params=%d,%d\n", stmt->num_params, stmt->proc_return);
	if (stmt->num_params >= 0)
		*pcpar = stmt->num_params;
	else if (!stmt->statement)
	{
		/* no statement has been allocated */
		SC_set_error(stmt, STMT_SEQUENCE_ERROR, "PGAPI_NumParams called with no statement ready.", func);
		return SQL_ERROR;
	}
	else
	{
		po_ind_t multi = FALSE, proc_return = 0;

		stmt->proc_return = 0;
		SC_scanQueryAndCountParams(stmt->statement, SC_get_conn(stmt), NULL, pcpar, &multi, &proc_return);
		stmt->num_params = *pcpar;
		stmt->proc_return = proc_return;
		stmt->multi_statement = multi;
	}
MYLOG(DETAIL_LOG_LEVEL, "num_params=%d,%d\n", stmt->num_params, stmt->proc_return);
	return SQL_SUCCESS;
}


/*
 *	 Bindings Implementation
 */
static BindInfoClass *
create_empty_bindings(int num_columns)
{
	BindInfoClass *new_bindings;
	int			i;

	new_bindings = (BindInfoClass *) malloc(num_columns * sizeof(BindInfoClass));
	if (!new_bindings)
		return NULL;

	for (i = 0; i < num_columns; i++)
	{
		new_bindings[i].buflen = 0;
		new_bindings[i].buffer = NULL;
		new_bindings[i].used =
		new_bindings[i].indicator = NULL;
	}

	return new_bindings;
}

void
extend_parameter_bindings(APDFields *self, int num_params)
{
	ParameterInfoClass *new_bindings;

	MYLOG(0, "entering ... self=%p, parameters_allocated=%d, num_params=%d,%p\n", self, self->allocated, num_params, self->parameters);

	/*
	 * if we have too few, allocate room for more, and copy the old
	 * entries into the new structure
	 */
	if (self->allocated < num_params)
	{
		new_bindings = (ParameterInfoClass *) realloc(self->parameters, sizeof(ParameterInfoClass) * num_params);
		if (!new_bindings)
		{
			MYLOG(0, "unable to create %d new bindings from %d old bindings\n", num_params, self->allocated);

			if (self->parameters)
				free(self->parameters);
			self->parameters = NULL;
			self->allocated = 0;
			return;
		}
		memset(&new_bindings[self->allocated], 0, sizeof(ParameterInfoClass) * (num_params - self->allocated));

		self->parameters = new_bindings;
		self->allocated = num_params;
	}

	MYLOG(0, "leaving %p\n", self->parameters);
}

void
extend_iparameter_bindings(IPDFields *self, int num_params)
{
	ParameterImplClass *new_bindings;

	MYLOG(0, "entering ... self=%p, parameters_allocated=%d, num_params=%d\n", self, self->allocated, num_params);

	/*
	 * if we have too few, allocate room for more, and copy the old
	 * entries into the new structure
	 */
	if (self->allocated < num_params)
	{
		new_bindings = (ParameterImplClass *) realloc(self->parameters, sizeof(ParameterImplClass) * num_params);
		if (!new_bindings)
		{
			MYLOG(0, "unable to create %d new bindings from %d old bindings\n", num_params, self->allocated);

			if (self->parameters)
				free(self->parameters);
			self->parameters = NULL;
			self->allocated = 0;
			return;
		}
		memset(&new_bindings[self->allocated], 0,
			sizeof(ParameterImplClass) * (num_params - self->allocated));

		self->parameters = new_bindings;
		self->allocated = num_params;
	}

	MYLOG(0, "leaving %p\n", self->parameters);
}

void
reset_a_parameter_binding(APDFields *self, int ipar)
{

	MYLOG(0, "entering ... self=%p, parameters_allocated=%d, ipar=%d\n", self, self->allocated, ipar);

	if (ipar < 1 || ipar > self->allocated)
		return;

	ipar--;
	self->parameters[ipar].buflen = 0;
	self->parameters[ipar].buffer = NULL;
	self->parameters[ipar].used =
	self->parameters[ipar].indicator = NULL;
	self->parameters[ipar].CType = 0;
	self->parameters[ipar].data_at_exec = FALSE;
	self->parameters[ipar].precision = 0;
	self->parameters[ipar].scale = 0;
}

void
reset_a_iparameter_binding(IPDFields *self, int ipar)
{
	MYLOG(0, "entering ... self=%p, parameters_allocated=%d, ipar=%d\n", self, self->allocated, ipar);

	if (ipar < 1 || ipar > self->allocated)
		return;

	ipar--;
	NULL_THE_NAME(self->parameters[ipar].paramName);
	self->parameters[ipar].paramType = 0;
	self->parameters[ipar].SQLType = 0;
	self->parameters[ipar].column_size = 0;
	self->parameters[ipar].decimal_digits = 0;
	self->parameters[ipar].precision = 0;
	self->parameters[ipar].scale = 0;
	PIC_set_pgtype(self->parameters[ipar], 0);
}

int
CountParameters(const StatementClass *self, Int2 *inputCount, Int2 *ioCount, Int2 *outputCount)
{
	IPDFields	*ipdopts = SC_get_IPDF(self);
	int	i, num_params, valid_count;

	if (inputCount)
		*inputCount = 0;
	if (ioCount)
		*ioCount = 0;
	if (outputCount)
		*outputCount = 0;
	if (!ipdopts)	return -1;
	num_params = self->num_params;
	if (ipdopts->allocated < num_params)
		num_params = ipdopts->allocated;
	for (i = 0, valid_count = 0; i < num_params; i++)
	{
		if (SQL_PARAM_OUTPUT == ipdopts->parameters[i].paramType)
		{
			if (outputCount)
			{
				(*outputCount)++;
				valid_count++;
			}
		}
		else if (SQL_PARAM_INPUT_OUTPUT == ipdopts->parameters[i].paramType)
		{
			if (ioCount)
			{
				(*ioCount)++;
				valid_count++;
			}
		}
		else if (inputCount)
		{
			 (*inputCount)++;
			valid_count++;
		}
	}
	return valid_count;
}

/*
 *	Free parameters and free the memory.
 */
void
APD_free_params(APDFields *apdopts, char option)
{
	MYLOG(0, "entering self=%p\n", apdopts);

	if (!apdopts->parameters)
		return;

	if (option == STMT_FREE_PARAMS_ALL)
	{
		free(apdopts->parameters);
		apdopts->parameters = NULL;
		apdopts->allocated = 0;
	}

	MYLOG(0, "leaving\n");
}

void
PDATA_free_params(PutDataInfo *pdata, char option)
{
	int			i;

	MYLOG(0, "entering self=%p\n", pdata);

	if (!pdata->pdata)
		return;

	for (i = 0; i < pdata->allocated; i++)
	{
		if (pdata->pdata[i].EXEC_used)
		{
			free(pdata->pdata[i].EXEC_used);
			pdata->pdata[i].EXEC_used = NULL;
		}
		if (pdata->pdata[i].EXEC_buffer)
		{
			free(pdata->pdata[i].EXEC_buffer);
			pdata->pdata[i].EXEC_buffer = NULL;
		}
	}

	if (option == STMT_FREE_PARAMS_ALL)
	{
		free(pdata->pdata);
		pdata->pdata = NULL;
		pdata->allocated = 0;
	}

	MYLOG(0, "leaving\n");
}

/*
 *	Free parameters and free the memory.
 */
void
IPD_free_params(IPDFields *ipdopts, char option)
{
	MYLOG(0, "entering self=%p\n", ipdopts);

	if (!ipdopts->parameters)
		return;
	if (option == STMT_FREE_PARAMS_ALL)
	{
		free(ipdopts->parameters);
		ipdopts->parameters = NULL;
		ipdopts->allocated = 0;
	}

	MYLOG(0, "leaving\n");
}

void
extend_column_bindings(ARDFields *self, int num_columns)
{
	BindInfoClass *new_bindings;
	int			i;

	MYLOG(0, "entering ... self=%p, bindings_allocated=%d, num_columns=%d\n", self, self->allocated, num_columns);

	/*
	 * if we have too few, allocate room for more, and copy the old
	 * entries into the new structure
	 */
	if (self->allocated < num_columns)
	{
		new_bindings = create_empty_bindings(num_columns);
		if (!new_bindings)
		{
			MYLOG(0, "unable to create %d new bindings from %d old bindings\n", num_columns, self->allocated);

			if (self->bindings)
			{
				free(self->bindings);
				self->bindings = NULL;
			}
			self->allocated = 0;
			return;
		}

		if (self->bindings)
		{
			for (i = 0; i < self->allocated; i++)
				new_bindings[i] = self->bindings[i];

			free(self->bindings);
		}

		self->bindings = new_bindings;
		self->allocated = num_columns;
	}

	/*
	 * There is no reason to zero out extra bindings if there are more
	 * than needed.  If an app has allocated extra bindings, let it worry
	 * about it by unbinding those columns.
	 */

	/* SQLBindCol(1..) ... SQLBindCol(10...)   # got 10 bindings */
	/* SQLExecDirect(...)  # returns 5 cols */
	/* SQLExecDirect(...)  # returns 10 cols  (now OK) */

	MYLOG(0, "leaving %p\n", self->bindings);
}

void
reset_a_column_binding(ARDFields *self, int icol)
{
	BindInfoClass	*bookmark;

	MYLOG(0, "entering ... self=%p, bindings_allocated=%d, icol=%d\n", self, self->allocated, icol);

	if (icol > self->allocated)
		return;

	/* use zero based col numbers from here out */
	if (0 == icol)
	{
		if (bookmark = self->bookmark, bookmark != NULL)
		{
			bookmark->buffer = NULL;
			bookmark->used =
			bookmark->indicator = NULL;
		}
	}
	else
	{
		icol--;

		/* we have to unbind the column */
		self->bindings[icol].buflen = 0;
		self->bindings[icol].buffer = NULL;
		self->bindings[icol].used =
		self->bindings[icol].indicator = NULL;
		self->bindings[icol].returntype = SQL_C_CHAR;
	}
}

void	ARD_unbind_cols(ARDFields *self, BOOL freeall)
{
	Int2	lf;

MYLOG(DETAIL_LOG_LEVEL, "freeall=%d allocated=%d bindings=%p\n", freeall, self->allocated, self->bindings);
	for (lf = 1; lf <= self->allocated; lf++)
		reset_a_column_binding(self, lf);
	if (freeall)
	{
		if (self->bindings)
			free(self->bindings);
		self->bindings = NULL;
		self->allocated = 0;
	}
}
void	GDATA_unbind_cols(GetDataInfo *self, BOOL freeall)
{
	Int2	lf;

MYLOG(DETAIL_LOG_LEVEL, "freeall=%d allocated=%d gdata=%p\n", freeall, self->allocated, self->gdata);
	if (self->fdata.ttlbuf)
	{
		free(self->fdata.ttlbuf);
		self->fdata.ttlbuf = NULL;
	}
	self->fdata.ttlbuflen = self->fdata.ttlbufused = 0;
	GETDATA_RESET(self->fdata);
	for (lf = 1; lf <= self->allocated; lf++)
		reset_a_getdata_info(self, lf);
	if (freeall)
	{
		if (self->gdata)
			free(self->gdata);
		self->gdata = NULL;
		self->allocated = 0;
	}
}

void GetDataInfoInitialize(GetDataInfo *gdata_info)
{
	GETDATA_RESET(gdata_info->fdata);
	gdata_info->fdata.ttlbuf = NULL;
	gdata_info->fdata.ttlbuflen = gdata_info->fdata.ttlbufused = 0;
	gdata_info->allocated = 0;
	gdata_info->gdata = NULL;
}
static GetDataClass *
create_empty_gdata(int num_columns)
{
	GetDataClass	*new_gdata;
	int			i;

	new_gdata = (GetDataClass *) malloc(num_columns * sizeof(GetDataClass));
	if (!new_gdata)
		return NULL;
	for (i = 0; i < num_columns; i++)
	{
		GETDATA_RESET(new_gdata[i]);
		new_gdata[i].ttlbuf = NULL;
		new_gdata[i].ttlbuflen = 0;
		new_gdata[i].ttlbufused = 0;
	}

	return new_gdata;
}
void
extend_getdata_info(GetDataInfo *self, int num_columns, BOOL shrink)
{
	GetDataClass	*new_gdata;

	MYLOG(0, "entering ... self=%p, gdata_allocated=%d, num_columns=%d\n", self, self->allocated, num_columns);

	/*
	 * if we have too few, allocate room for more, and copy the old
	 * entries into the new structure
	 */
	if (self->allocated < num_columns)
	{
		new_gdata = create_empty_gdata(num_columns);
		if (!new_gdata)
		{
			MYLOG(0, "unable to create %d new gdata from %d old gdata\n", num_columns, self->allocated);

			if (self->gdata)
			{
				free(self->gdata);
				self->gdata = NULL;
			}
			self->allocated = 0;
			return;
		}
		if (self->gdata)
		{
			size_t	i;

			for (i = 0; i < self->allocated; i++)
				new_gdata[i] = self->gdata[i];
			free(self->gdata);
		}
		self->gdata = new_gdata;
		self->allocated = num_columns;
	}
	else if (shrink && self->allocated > num_columns)
	{
		int	i;

		for (i = self->allocated; i > num_columns; i--)
			reset_a_getdata_info(self, i);
		self->allocated = num_columns;
		if (0 == num_columns)
		{
			free(self->gdata);
			self->gdata = NULL;
		}
	}

	/*
	 * There is no reason to zero out extra gdata if there are more
	 * than needed.  If an app has allocated extra gdata, let it worry
	 * about it by unbinding those columns.
	 */

	MYLOG(0, "leaving %p\n", self->gdata);
}
void	reset_a_getdata_info(GetDataInfo *gdata_info, int icol)
{
	if (icol < 1 || icol > gdata_info->allocated)
		return;
	icol--;
	if (gdata_info->gdata[icol].ttlbuf)
	{
		free(gdata_info->gdata[icol].ttlbuf);
		gdata_info->gdata[icol].ttlbuf = NULL;
	}
	gdata_info->gdata[icol].ttlbuflen =
	gdata_info->gdata[icol].ttlbufused = 0;
	GETDATA_RESET(gdata_info->gdata[icol]);
}

void PutDataInfoInitialize(PutDataInfo *pdata_info)
{
	pdata_info->allocated = 0;
	pdata_info->pdata = NULL;
}
void
extend_putdata_info(PutDataInfo *self, int num_params, BOOL shrink)
{
	PutDataClass	*new_pdata;

	MYLOG(0, "entering ... self=%p, parameters_allocated=%d, num_params=%d\n", self, self->allocated, num_params);

	/*
	 * if we have too few, allocate room for more, and copy the old
	 * entries into the new structure
	 */
	if (self->allocated < num_params)
	{
		if (self->allocated <= 0 && self->pdata)
		{
			MYLOG(0, "??? pdata is not null while allocated == 0\n");
			self->pdata = NULL;
		}
		new_pdata = (PutDataClass *) realloc(self->pdata, sizeof(PutDataClass) * num_params);
		if (!new_pdata)
		{
			MYLOG(0, "unable to create %d new pdata from %d old pdata\n", num_params, self->allocated);

			self->pdata = NULL;
			self->allocated = 0;
			return;
		}
		memset(&new_pdata[self->allocated], 0,
			sizeof(PutDataClass) * (num_params - self->allocated));

		self->pdata = new_pdata;
		self->allocated = num_params;
	}
	else if (shrink && self->allocated > num_params)
	{
		int	i;

		for (i = self->allocated; i > num_params; i--)
			reset_a_putdata_info(self, i);
		self->allocated = num_params;
		if (0 == num_params)
		{
			free(self->pdata);
			self->pdata = NULL;
		}
	}

	MYLOG(0, "leaving %p\n", self->pdata);
}
void	reset_a_putdata_info(PutDataInfo *pdata_info, int ipar)
{
	if (ipar < 1 || ipar > pdata_info->allocated)
		return;
	ipar--;
	if (pdata_info->pdata[ipar].EXEC_used)
	{
		free(pdata_info->pdata[ipar].EXEC_used);
		pdata_info->pdata[ipar].EXEC_used = NULL;
	}
	if (pdata_info->pdata[ipar].EXEC_buffer)
	{
		free(pdata_info->pdata[ipar].EXEC_buffer);
		pdata_info->pdata[ipar].EXEC_buffer = NULL;
	}
	pdata_info->pdata[ipar].lobj_oid = 0;
}

void SC_param_next(const StatementClass *stmt, int *param_number, ParameterInfoClass **apara, ParameterImplClass **ipara)
{
	int	next;
	IPDFields	*ipdopts = SC_get_IPDF(stmt);

	if (*param_number < 0)
		next = stmt->proc_return;
	else
		next = *param_number + 1;
	if (stmt->discard_output_params)
	{
		for (;next < ipdopts->allocated && SQL_PARAM_OUTPUT == ipdopts->parameters[next].paramType; next++) ;
	}
	*param_number = next;
	if (ipara)
	{
		if (next < ipdopts->allocated)
			*ipara = ipdopts->parameters + next;
		else
			*ipara = NULL;
	}
	if (apara)
	{
		APDFields	*apdopts = SC_get_APDF(stmt);
		if (next < apdopts->allocated)
			*apara = apdopts->parameters + next;
		else
			*apara = NULL;
	}
}