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

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

#include "bind.h"
#include "environ.h"
#include "statement.h"
#include "qresult.h"
#include "pgtypes.h"
#include <stdlib.h>
#include <malloc.h>

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

//      Bind parameters on a statement handle

RETCODE SQL_API SQLBindParameter(
        HSTMT      hstmt,
        UWORD      ipar,
        SWORD      fParamType,
        SWORD      fCType,
        SWORD      fSqlType,
        UDWORD     cbColDef,
        SWORD      ibScale,
        PTR        rgbValue,
        SDWORD     cbValueMax,
        SDWORD FAR *pcbValue)
{
StatementClass *stmt = (StatementClass *) hstmt;
static char *func="SQLBindParameter";

	mylog( "%s: entering...\n", func);

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

	if(stmt->parameters_allocated < ipar) {
		ParameterInfoClass *old_parameters;
		int i, old_parameters_allocated;

		old_parameters = stmt->parameters;
		old_parameters_allocated = stmt->parameters_allocated;

		stmt->parameters = (ParameterInfoClass *) malloc(sizeof(ParameterInfoClass)*(ipar));
		if ( ! stmt->parameters) {
			stmt->errornumber = STMT_NO_MEMORY_ERROR;
			stmt->errormsg = "Could not allocate memory for statement parameters";
			SC_log_error(func, "", stmt);
			return SQL_ERROR;
		}

		stmt->parameters_allocated = ipar;

		// copy the old parameters over
		for(i = 0; i < old_parameters_allocated; i++) {
		// a structure copy should work
			stmt->parameters[i] = old_parameters[i];
		}

		// get rid of the old parameters, if there were any
		if(old_parameters)
			free(old_parameters);

		// zero out the newly allocated parameters (in case they skipped some,
		// so we don't accidentally try to use them later)
		for(; i < stmt->parameters_allocated; i++) {
			stmt->parameters[i].buflen = 0;
			stmt->parameters[i].buffer = 0;
			stmt->parameters[i].used = 0;
			stmt->parameters[i].paramType = 0;
			stmt->parameters[i].CType = 0;
			stmt->parameters[i].SQLType = 0;
			stmt->parameters[i].precision = 0;
			stmt->parameters[i].scale = 0;
			stmt->parameters[i].data_at_exec = FALSE;
			stmt->parameters[i].lobj_oid = 0;
			stmt->parameters[i].EXEC_used = NULL;
			stmt->parameters[i].EXEC_buffer = NULL;
		}
	}

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

	// store the given info
	stmt->parameters[ipar].buflen = cbValueMax;
	stmt->parameters[ipar].buffer = rgbValue;
	stmt->parameters[ipar].used = pcbValue;
	stmt->parameters[ipar].paramType = fParamType;
	stmt->parameters[ipar].CType = fCType;
	stmt->parameters[ipar].SQLType = fSqlType;
	stmt->parameters[ipar].precision = cbColDef;
	stmt->parameters[ipar].scale = ibScale;

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

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

	/*	Data at exec macro only valid for C char/binary data */
	if ((fSqlType == SQL_LONGVARBINARY || fSqlType == SQL_LONGVARCHAR) && pcbValue && *pcbValue <= SQL_LEN_DATA_AT_EXEC_OFFSET)
		stmt->parameters[ipar].data_at_exec = TRUE;
	else
		stmt->parameters[ipar].data_at_exec = FALSE;

	mylog("SQLBindParamater: ipar=%d, paramType=%d, fCType=%d, fSqlType=%d, cbColDef=%d, ibScale=%d, rgbValue=%d, *pcbValue = %d, data_at_exec = %d\n", ipar, fParamType, fCType, fSqlType, cbColDef, ibScale, rgbValue, pcbValue ? *pcbValue: -777, stmt->parameters[ipar].data_at_exec);

	return SQL_SUCCESS;
}

//      -       -       -       -       -       -       -       -       -

//      Associate a user-supplied buffer with a database column.
RETCODE SQL_API SQLBindCol(
        HSTMT      hstmt,
        UWORD      icol,
        SWORD      fCType,
        PTR        rgbValue,
        SDWORD     cbValueMax,
        SDWORD FAR *pcbValue)
{
StatementClass *stmt = (StatementClass *) hstmt;
static char *func="SQLBindCol";

	mylog( "%s: entering...\n", func);
    
mylog("**** SQLBindCol: stmt = %u, icol = %d\n", stmt, icol);

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


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

	/*	If the bookmark column is being bound, then just save it */
	if (icol == 0) {

		if (rgbValue == NULL) {
			stmt->bookmark.buffer = NULL;
			stmt->bookmark.used = NULL;
		}
		else {
			/*	Make sure it is the bookmark data type */
			if ( fCType != SQL_C_BOOKMARK) {
				stmt->errormsg = "Column 0 is not of type SQL_C_BOOKMARK";
				stmt->errornumber = STMT_PROGRAM_TYPE_OUT_OF_RANGE;
				SC_log_error(func, "", stmt);
				return SQL_ERROR;
			}

			stmt->bookmark.buffer = rgbValue;
			stmt->bookmark.used = pcbValue;
		}
		return SQL_SUCCESS;
	}

	//	allocate enough bindings if not already done
	//	Most likely, execution of a statement would have setup the 
	//	necessary bindings. But some apps call BindCol before any
	//	statement is executed.
	if ( icol > stmt->bindings_allocated)
		extend_bindings(stmt, icol);

	//	check to see if the bindings were allocated
	if ( ! stmt->bindings) {
		stmt->errormsg = "Could not allocate memory for bindings.";
		stmt->errornumber = STMT_NO_MEMORY_ERROR;
		SC_log_error(func, "", stmt);
		return SQL_ERROR;
	}

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

	/*	Reset for SQLGetData */
	stmt->bindings[icol].data_left = -1;

	if (rgbValue == NULL) {
		/* we have to unbind the column */
		stmt->bindings[icol].buflen = 0;
		stmt->bindings[icol].buffer = NULL;
		stmt->bindings[icol].used =   NULL;
		stmt->bindings[icol].returntype = SQL_C_CHAR;
	} else {
		/* ok, bind that column */
		stmt->bindings[icol].buflen     = cbValueMax;
		stmt->bindings[icol].buffer     = rgbValue;
		stmt->bindings[icol].used       = pcbValue;
		stmt->bindings[icol].returntype = fCType;

		mylog("       bound buffer[%d] = %u\n", icol, stmt->bindings[icol].buffer);
	}

	return SQL_SUCCESS;
}

//      -       -       -       -       -       -       -       -       -

//  Returns the description of a parameter marker.
//	This function is listed as not being supported by SQLGetFunctions() because it is 
//	used to describe "parameter markers" (not bound parameters), in which case, 
//	the dbms should return info on the markers.  Since Postgres doesn't support that,
//	it is best to say this function is not supported and let the application assume a 
//	data type (most likely varchar).

RETCODE SQL_API SQLDescribeParam(
        HSTMT      hstmt,
        UWORD      ipar,
        SWORD  FAR *pfSqlType,
        UDWORD FAR *pcbColDef,
        SWORD  FAR *pibScale,
        SWORD  FAR *pfNullable)
{
StatementClass *stmt = (StatementClass *) hstmt;
static char *func = "SQLDescribeParam";

	mylog( "%s: entering...\n", func);

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

	if( (ipar < 1) || (ipar > stmt->parameters_allocated) ) {
		stmt->errormsg = "Invalid parameter number for SQLDescribeParam.";
		stmt->errornumber = STMT_BAD_PARAMETER_NUMBER_ERROR;
		SC_log_error(func, "", stmt);
		return SQL_ERROR;
	}

	ipar--;

	//	This implementation is not very good, since it is supposed to describe
	//	parameter markers, not bound parameters. 
	if(pfSqlType)
		*pfSqlType = stmt->parameters[ipar].SQLType;

	if(pcbColDef)
		*pcbColDef = stmt->parameters[ipar].precision;

	if(pibScale)
		*pibScale = stmt->parameters[ipar].scale;

	if(pfNullable)
		*pfNullable = pgtype_nullable(stmt, stmt->parameters[ipar].paramType);

	return SQL_SUCCESS;
}

//      -       -       -       -       -       -       -       -       -

//      Sets multiple values (arrays) for the set of parameter markers.

RETCODE SQL_API SQLParamOptions(
        HSTMT      hstmt,
        UDWORD     crow,
        UDWORD FAR *pirow)
{
static char *func = "SQLParamOptions";

	mylog( "%s: entering...\n", func);

	SC_log_error(func, "Function not implemented", (StatementClass *) hstmt);
	return SQL_ERROR;
}

//      -       -       -       -       -       -       -       -       -

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

	mylog( "%s: entering...\n", func);

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

	if (pcpar)
		*pcpar = 0;
	else {
		SC_log_error(func, "pcpar was null", stmt);
		return SQL_ERROR;
	}


	if(!stmt->statement) {
		// no statement has been allocated
		stmt->errormsg = "SQLNumParams called with no statement ready.";
		stmt->errornumber = STMT_SEQUENCE_ERROR;
		SC_log_error(func, "", stmt);
		return SQL_ERROR;
	} else {

		for(i=0; i < strlen(stmt->statement); i++) {

			if(stmt->statement[i] == '?' && !in_quote)
				(*pcpar)++;
			else {
				if (stmt->statement[i] == '\'')
					in_quote = (in_quote ? FALSE : TRUE);
			}
		}

		return SQL_SUCCESS;
	}
}

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

	new_bindings = (BindInfoClass *)malloc(num_columns * sizeof(BindInfoClass));
	if(!new_bindings) {
		return 0;
	}

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

	return new_bindings;
}

void
extend_bindings(StatementClass *stmt, int num_columns)
{
static char *func="extend_bindings";
BindInfoClass *new_bindings;
int i;

mylog("%s: entering ... stmt=%u, bindings_allocated=%d, num_columns=%d\n", func, stmt, stmt->bindings_allocated, num_columns);

	/* if we have too few, allocate room for more, and copy the old */
	/* entries into the new structure */
	if(stmt->bindings_allocated < num_columns) {

		new_bindings = create_empty_bindings(num_columns);
		if ( ! new_bindings) {
           mylog("%s: unable to create %d new bindings from %d old bindings\n", func, num_columns, stmt->bindings_allocated);

			if (stmt->bindings) {
				free(stmt->bindings);
				stmt->bindings = NULL;
			}
			stmt->bindings_allocated = 0;
			return;
		}

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

			free(stmt->bindings);
		}

		stmt->bindings = new_bindings;
		stmt->bindings_allocated = num_columns;

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

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

	mylog("exit extend_bindings\n");
}
