
/* 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.
 *
 */
#include "bind.h"
#include "environ.h"
#include "statement.h"
#include "qresult.h"
#include "pgtypes.h"
#include <stdlib.h>
#include <malloc.h>
#include <sql.h>
#include <sqlext.h>

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

	if( ! stmt)
		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";
			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;
	}

	if (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, *pcbValue = %d, data_at_exec = %d\n", 
		ipar, 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;
Int2 numcols;
    
mylog("**** SQLBindCol: stmt = %u, icol = %d\n", stmt, icol);

	if ( ! stmt)
		return SQL_INVALID_HANDLE;

	if (icol < 1) {
		/* currently we do not support bookmarks */
		stmt->errormsg = "Bookmarks are not currently supported.";
		stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR;
		return SQL_ERROR;
	}

	icol--;		/* use zero based col numbers */

	SC_clear_error(stmt);
    
	if( ! stmt->result) {
		stmt->errormsg = "Can't bind columns with a NULL query result structure.";
		stmt->errornumber = STMT_SEQUENCE_ERROR;
		return SQL_ERROR;
	}

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

	numcols = QR_NumResultCols(stmt->result);

	mylog("SQLBindCol: numcols = %d\n", numcols);

	if (icol >= numcols) {
		stmt->errornumber = STMT_COLNUM_ERROR;
		stmt->errormsg = "Column number too big";
		return SQL_ERROR;
	}

	if ( ! stmt->bindings) {
		stmt->errormsg = "Bindings were not allocated properly.";
		stmt->errornumber = STMT_SEQUENCE_ERROR;
		return SQL_ERROR;
	}

	if ((cbValueMax == 0) || (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;

	if( ! stmt)
		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;
		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)
{
	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;


	if(!stmt)
		return SQL_INVALID_HANDLE;

	if (pcpar)
		*pcpar = 0;
	else
		return SQL_ERROR;


	if(!stmt->statement) {
		// no statement has been allocated
		stmt->errormsg = "SQLNumParams called with no statement ready.";
		stmt->errornumber = STMT_SEQUENCE_ERROR;
		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;
	}

	return new_bindings;
}

void
extend_bindings(StatementClass *stmt, int num_columns)
{
BindInfoClass *new_bindings;
int i;

mylog("in extend_bindings: stmt=%u, bindings_allocated=%d, num_columns=%d\n", 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(stmt->bindings) {
			for(i=0; i<stmt->bindings_allocated; i++)
				new_bindings[i] = stmt->bindings[i];

			free(stmt->bindings);
		}

		stmt->bindings = new_bindings;		// null indicates error
		stmt->bindings_allocated = num_columns;

    } else {
	/* if we have too many, make sure the extra ones are emptied out */
	/* so we don't accidentally try to use them for anything */
		for(i = num_columns; i < stmt->bindings_allocated; i++) {
			stmt->bindings[i].buflen = 0;
			stmt->bindings[i].buffer = NULL;
			stmt->bindings[i].used = NULL;
		}
	}

	mylog("exit extend_bindings\n");
}
