
/* Module:          results.c
 *
 * Description:     This module contains functions related to 
 *                  retrieving result information through the ODBC API.
 *
 * Classes:         n/a
 *
 * API functions:   SQLRowCount, SQLNumResultCols, SQLDescribeCol, SQLColAttributes,
 *                  SQLGetData, SQLFetch, SQLExtendedFetch, 
 *                  SQLMoreResults(NI), SQLSetPos(NI), SQLSetScrollOptions(NI),
 *                  SQLSetCursorName, SQLGetCursorName
 *
 * Comments:        See "notice.txt" for copyright and license information.
 *
 */

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

#include <string.h>
#include "psqlodbc.h"
#include "dlg_specific.h"
#include "environ.h"
#include "connection.h"
#include "statement.h"
#include "bind.h"
#include "qresult.h"
#include "convert.h"
#include "pgtypes.h" 

#include <stdio.h>

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

extern GLOBAL_VALUES globals;



RETCODE SQL_API SQLRowCount(
        HSTMT      hstmt,
        SDWORD FAR *pcrow)
{
static char *func="SQLRowCount";
StatementClass *stmt = (StatementClass *) hstmt;
QResultClass *res;
char *msg, *ptr;

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

	if(stmt->statement_type == STMT_TYPE_SELECT) {
		if (stmt->status == STMT_FINISHED) {
			res = SC_get_Result(stmt);

			if(res && pcrow) {
				*pcrow = globals.use_declarefetch ? 0 : QR_get_num_tuples(res);
				return SQL_SUCCESS;
			}
		}
	} else {

		res = SC_get_Result(stmt);
		if (res && pcrow) {
			msg = QR_get_command(res);
			mylog("*** msg = '%s'\n", msg);
			trim(msg);	//	get rid of trailing spaces
			ptr = strrchr(msg, ' ');
			if (ptr) {
				*pcrow = atoi(ptr+1);
				mylog("**** SQLRowCount(): THE ROWS: *pcrow = %d\n", *pcrow);
			}
			else {
				*pcrow = -1;

				mylog("**** SQLRowCount(): NO ROWS: *pcrow = %d\n", *pcrow);
			}

		return SQL_SUCCESS;
		}
	}

	SC_log_error(func, "Bad return value", stmt);
	return SQL_ERROR;     
}


//      This returns the number of columns associated with the database
//      attached to "hstmt".


RETCODE SQL_API SQLNumResultCols(
        HSTMT     hstmt,
        SWORD FAR *pccol)
{       
static char *func="SQLNumResultCols";
StatementClass *stmt = (StatementClass *) hstmt;
QResultClass *result;
char parse_ok;

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

	SC_clear_error(stmt);    

	parse_ok = FALSE;
	if (globals.parse && stmt->statement_type == STMT_TYPE_SELECT) {

		if (stmt->parse_status == STMT_PARSE_NONE) {
			mylog("SQLNumResultCols: calling parse_statement on stmt=%u\n", stmt);
			parse_statement(stmt);
		}

		if (stmt->parse_status != STMT_PARSE_FATAL) {
			parse_ok = TRUE;
			*pccol = stmt->nfld;
			mylog("PARSE: SQLNumResultCols: *pccol = %d\n", *pccol);
		}
	}

	if ( ! parse_ok) {

		SC_pre_execute(stmt);       
		result = SC_get_Result(stmt);

		mylog("SQLNumResultCols: result = %u, status = %d, numcols = %d\n", result, stmt->status, result != NULL ? QR_NumResultCols(result) : -1);
		if (( ! result) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE)) ) {
			/* no query has been executed on this statement */
			stmt->errornumber = STMT_SEQUENCE_ERROR;
			stmt->errormsg = "No query has been executed with that handle";
			SC_log_error(func, "", stmt);
			return SQL_ERROR;
		}

		*pccol = QR_NumResultCols(result);
	}

	return SQL_SUCCESS;
}


//      -       -       -       -       -       -       -       -       -



//      Return information about the database column the user wants
//      information about.
RETCODE SQL_API SQLDescribeCol(
        HSTMT      hstmt,
        UWORD      icol,
        UCHAR  FAR *szColName,
        SWORD      cbColNameMax,
        SWORD  FAR *pcbColName,
        SWORD  FAR *pfSqlType,
        UDWORD FAR *pcbColDef,
        SWORD  FAR *pibScale,
        SWORD  FAR *pfNullable)
{
static char *func="SQLDescribeCol";
    /* gets all the information about a specific column */
StatementClass *stmt = (StatementClass *) hstmt;
QResultClass *result;
char *col_name = NULL;
Int4 fieldtype = 0;
int precision = 0;
ConnInfo *ci;
char parse_ok;

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

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

	ci = &(stmt->hdbc->connInfo);

    SC_clear_error(stmt);

	/*	Dont check for bookmark column.  This is the responsibility
		of the driver manager.  
	*/

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


	parse_ok = FALSE;
	if (globals.parse && stmt->statement_type == STMT_TYPE_SELECT) {

		if (stmt->parse_status == STMT_PARSE_NONE) {
			mylog("SQLDescribeCol: calling parse_statement on stmt=%u\n", stmt);
			parse_statement(stmt);
		}


		mylog("PARSE: DescribeCol: icol=%d, stmt=%u, stmt->nfld=%d, stmt->fi=%u\n", icol, stmt, stmt->nfld, stmt->fi);

		if (stmt->parse_status != STMT_PARSE_FATAL && stmt->fi && stmt->fi[icol]) {

			if (icol >= stmt->nfld) {
				stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR;
				stmt->errormsg = "Invalid column number in DescribeCol.";
				SC_log_error(func, "", stmt);
				return SQL_ERROR;
			}
			mylog("DescribeCol: getting info for icol=%d\n", icol);

			fieldtype = stmt->fi[icol]->type;
			col_name = stmt->fi[icol]->name;
			precision = stmt->fi[icol]->precision;

			mylog("PARSE: fieldtype=%d, col_name='%s', precision=%d\n", fieldtype, col_name, precision);
			if (fieldtype > 0)
				parse_ok = TRUE;
		}
	}


	/*	If couldn't parse it OR the field being described was not parsed (i.e., because
		it was a function or expression, etc, then do it the old fashioned way.
	*/
	if ( ! parse_ok) {
		SC_pre_execute(stmt);
	
		result = SC_get_Result(stmt);

		mylog("**** SQLDescribeCol: result = %u, stmt->status = %d, !finished=%d, !premature=%d\n", result, stmt->status, stmt->status != STMT_FINISHED, stmt->status != STMT_PREMATURE);
		if ( (NULL == result) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE))) {
			/* no query has been executed on this statement */
			stmt->errornumber = STMT_SEQUENCE_ERROR;
			stmt->errormsg = "No query has been assigned to this statement.";
			SC_log_error(func, "", stmt);
			return SQL_ERROR;
		}

		if (icol >= QR_NumResultCols(result)) {
			stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR;
			stmt->errormsg = "Invalid column number in DescribeCol.";
			SC_log_error(func, "", stmt);
			return SQL_ERROR;
		}

		col_name = QR_get_fieldname(result, icol);
        fieldtype = QR_get_field_type(result, icol);

		precision = pgtype_precision(stmt, fieldtype, icol, globals.unknown_sizes);  // atoi(ci->unknown_sizes)
	}

	mylog("describeCol: col %d fieldname = '%s'\n", icol, col_name);
	mylog("describeCol: col %d fieldtype = %d\n", icol, fieldtype);
	mylog("describeCol: col %d precision = %d\n", icol, precision);

    if (cbColNameMax >= 1) {
        if (pcbColName)  {
            if (col_name) 
                *pcbColName = strlen(col_name);
            else
                *pcbColName = 0;
        }
        if (szColName) {
            if (col_name) 
                strncpy_null(szColName, col_name, cbColNameMax);
            else
                szColName[0] = '\0';
        }
    }


    if (pfSqlType) {
        *pfSqlType = pgtype_to_sqltype(stmt, fieldtype);

		mylog("describeCol: col %d *pfSqlType = %d\n", icol, *pfSqlType);
	}

    if (pcbColDef) {

		if ( precision < 0)
			precision = 0;		// "I dont know"

		*pcbColDef = precision;

		mylog("describeCol: col %d  *pcbColDef = %d\n", icol, *pcbColDef);
	}

    if (pibScale) {
        Int2 scale;
        scale = pgtype_scale(stmt, fieldtype);
        if(scale == -1) { scale = 0; }
        
        *pibScale = scale;
		mylog("describeCol: col %d  *pibScale = %d\n", icol, *pibScale);
    }

    if (pfNullable) {
		if (parse_ok)
			*pfNullable = stmt->fi[icol]->nullable;
		else
			*pfNullable = pgtype_nullable(stmt, fieldtype);

		mylog("describeCol: col %d  *pfNullable = %d\n", icol, *pfNullable);
    }

    return SQL_SUCCESS;
}

//      Returns result column descriptor information for a result set.

RETCODE SQL_API SQLColAttributes(
        HSTMT      hstmt,
        UWORD      icol,
        UWORD      fDescType,
        PTR        rgbDesc,
        SWORD      cbDescMax,
        SWORD  FAR *pcbDesc,
        SDWORD FAR *pfDesc)
{
static char *func = "SQLColAttributes";
StatementClass *stmt = (StatementClass *) hstmt;
char *value;
Int4 field_type = 0;
ConnInfo *ci;
int unknown_sizes;
int cols = 0;
char parse_ok;

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

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

	ci = &(stmt->hdbc->connInfo);

	/*	Dont check for bookmark column.  This is the responsibility
		of the driver manager.  For certain types of arguments, the column
		number is ignored anyway, so it may be 0.
	*/

	icol--;

	unknown_sizes = globals.unknown_sizes;          // atoi(ci->unknown_sizes);
	if (unknown_sizes == UNKNOWNS_AS_DONTKNOW)		// not appropriate for SQLColAttributes()
		unknown_sizes = UNKNOWNS_AS_MAX;

	parse_ok = FALSE;
	if (globals.parse && stmt->statement_type == STMT_TYPE_SELECT) {

		if (stmt->parse_status == STMT_PARSE_NONE) {
			mylog("SQLColAttributes: calling parse_statement\n");
			parse_statement(stmt);
		}

		cols = stmt->nfld;

		/*	Column Count is a special case.  The Column number is ignored
			in this case.
		*/
		if (fDescType == SQL_COLUMN_COUNT) {
			if (pfDesc)
				*pfDesc = cols;

			return SQL_SUCCESS;
		}

		if (stmt->parse_status != STMT_PARSE_FATAL && stmt->fi && stmt->fi[icol]) {

			if (icol >= cols) {
				stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR;
				stmt->errormsg = "Invalid column number in DescribeCol.";
				SC_log_error(func, "", stmt);
				return SQL_ERROR;
			}

			field_type = stmt->fi[icol]->type;
			if (field_type > 0)
				parse_ok = TRUE;
		}
	}

	if ( ! parse_ok) {
		SC_pre_execute(stmt);       

		mylog("**** SQLColAtt: result = %u, status = %d, numcols = %d\n", stmt->result, stmt->status, stmt->result != NULL ? QR_NumResultCols(stmt->result) : -1);

		if ( (NULL == stmt->result) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE)) ) {
			stmt->errormsg = "Can't get column attributes: no result found.";
			stmt->errornumber = STMT_SEQUENCE_ERROR;
			SC_log_error(func, "", stmt);
			return SQL_ERROR;
		}

		cols = QR_NumResultCols(stmt->result);

		/*	Column Count is a special case.  The Column number is ignored
			in this case.
		*/
		if (fDescType == SQL_COLUMN_COUNT) {
			if (pfDesc)
				*pfDesc = cols;

			return SQL_SUCCESS;
		}

		if (icol >= cols) {
			stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR;
			stmt->errormsg = "Invalid column number in DescribeCol.";
			SC_log_error(func, "", stmt);
			return SQL_ERROR;
		}

		field_type = QR_get_field_type(stmt->result, icol);
	}

	mylog("colAttr: col %d field_type = %d\n", icol, field_type);

	switch(fDescType) {
	case SQL_COLUMN_AUTO_INCREMENT:
		if (pfDesc) {
			*pfDesc = pgtype_auto_increment(stmt, field_type);
			if (*pfDesc == -1)  /*  non-numeric becomes FALSE (ODBC Doc) */
				*pfDesc = FALSE;
				
		}
		break;

	case SQL_COLUMN_CASE_SENSITIVE:
		if (pfDesc)    
			*pfDesc = pgtype_case_sensitive(stmt, field_type);
		break;

	/* 	This special case is handled above.

	case SQL_COLUMN_COUNT: 
	*/

    case SQL_COLUMN_DISPLAY_SIZE:
		if (pfDesc) {
			if (parse_ok)
				*pfDesc = stmt->fi[icol]->display_size;
			else
				*pfDesc = pgtype_display_size(stmt, field_type, icol, unknown_sizes);
		}

		mylog("SQLColAttributes: col %d, display_size= %d\n", icol, *pfDesc);

        break;

	case SQL_COLUMN_LABEL:
		if (parse_ok && stmt->fi[icol]->alias[0] != '\0') {
			strncpy_null((char *)rgbDesc, stmt->fi[icol]->alias, cbDescMax);
			if (pcbDesc)
				*pcbDesc = strlen(stmt->fi[icol]->alias);

			break;

			mylog("SQLColAttr: COLUMN_LABEL = '%s'\n", rgbDesc);
		}	// otherwise same as column name

	case SQL_COLUMN_NAME:

		if (parse_ok)
			value = stmt->fi[icol]->name;
		else
			value = QR_get_fieldname(stmt->result, icol);

		strncpy_null((char *)rgbDesc, value, cbDescMax);

		if (pcbDesc)
			*pcbDesc = strlen(value);

		mylog("SQLColAttr: COLUMN_NAME = '%s'\n", rgbDesc);
		break;

	case SQL_COLUMN_LENGTH:
		if (pfDesc) {
			if (parse_ok)
				*pfDesc = stmt->fi[icol]->length;
			else
				*pfDesc = pgtype_length(stmt, field_type, icol, unknown_sizes); 
		}
		mylog("SQLColAttributes: col %d, length = %d\n", icol, *pfDesc);
        break;

	case SQL_COLUMN_MONEY:
		if (pfDesc)    
			*pfDesc = pgtype_money(stmt, field_type);
		break;

	case SQL_COLUMN_NULLABLE:
		if (pfDesc) {
			if (parse_ok)
				*pfDesc = stmt->fi[icol]->nullable;
			else
				*pfDesc = pgtype_nullable(stmt, field_type);
		}
		break;

	case SQL_COLUMN_OWNER_NAME:
		strncpy_null((char *)rgbDesc, "", cbDescMax);
		if (pcbDesc)        
			*pcbDesc = 0;
		break;

	case SQL_COLUMN_PRECISION:
		if (pfDesc) {
			if (parse_ok)
				*pfDesc = stmt->fi[icol]->precision;
			else
				*pfDesc = pgtype_precision(stmt, field_type, icol, unknown_sizes);
		}
		mylog("SQLColAttributes: col %d, precision = %d\n", icol, *pfDesc);
        break;

	case SQL_COLUMN_QUALIFIER_NAME:
		strncpy_null((char *)rgbDesc, "", cbDescMax);
		if (pcbDesc)        
			*pcbDesc = 0;
		break;

	case SQL_COLUMN_SCALE:
		if (pfDesc)    
			*pfDesc = pgtype_scale(stmt, field_type);
		break;

	case SQL_COLUMN_SEARCHABLE:
		if (pfDesc)    
			*pfDesc = pgtype_searchable(stmt, field_type);
		break;

    case SQL_COLUMN_TABLE_NAME:
		if (parse_ok && stmt->fi[icol]->ti) {
			strncpy_null((char *)rgbDesc, stmt->fi[icol]->ti->name, cbDescMax);
			if (pcbDesc)        
				*pcbDesc = strlen(stmt->fi[icol]->ti->name);
		}
		else {
			strncpy_null((char *)rgbDesc, "", cbDescMax);
			if (pcbDesc)        
				*pcbDesc = 0;
		}

		mylog("SQLColAttr: TABLE_NAME = '%s'\n", rgbDesc);
        break;

	case SQL_COLUMN_TYPE:
		if (pfDesc) {
			*pfDesc = pgtype_to_sqltype(stmt, field_type);
		}
		break;

	case SQL_COLUMN_TYPE_NAME:
		value = pgtype_to_name(stmt, field_type);
		strncpy_null((char *)rgbDesc, value, cbDescMax);
		if (pcbDesc)        
			*pcbDesc = strlen(value);
		break;

	case SQL_COLUMN_UNSIGNED:
		if (pfDesc) {
			*pfDesc = pgtype_unsigned(stmt, field_type);
			if(*pfDesc == -1)	/* non-numeric becomes TRUE (ODBC Doc) */
				*pfDesc = TRUE;
		}
		break;

	case SQL_COLUMN_UPDATABLE:
		if (pfDesc)    {
			/*	Neither Access or Borland care about this.

			if (field_type == PG_TYPE_OID)
				*pfDesc = SQL_ATTR_READONLY;
			else
			*/

			*pfDesc = SQL_ATTR_WRITE;
			mylog("SQLColAttr: UPDATEABLE = %d\n", *pfDesc);
		}
		break;
    }

    return SQL_SUCCESS;
}

//      Returns result data for a single column in the current row.

RETCODE SQL_API SQLGetData(
        HSTMT      hstmt,
        UWORD      icol,
        SWORD      fCType,
        PTR        rgbValue,
        SDWORD     cbValueMax,
        SDWORD FAR *pcbValue)
{
static char *func="SQLGetData";
QResultClass *res;
StatementClass *stmt = (StatementClass *) hstmt;
int num_cols, num_rows;
Int4 field_type;
void *value;
int result;
char multiple;


mylog("SQLGetData: enter, stmt=%u\n", stmt);

    if( ! stmt) {
		SC_log_error(func, "", NULL);
        return SQL_INVALID_HANDLE;
    }
	res = stmt->result;

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

    if (stmt->status != STMT_FINISHED) {
        stmt->errornumber = STMT_STATUS_ERROR;
        stmt->errormsg = "GetData can only be called after the successful execution on a SQL statement";
		SC_log_error(func, "", stmt);
        return SQL_ERROR;
    }

    if (icol == 0) {
        stmt->errormsg = "Bookmarks are not currently supported.";
        stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR;
		SC_log_error(func, "", stmt);
        return SQL_ERROR;
    }

    // use zero-based column numbers
    icol--;

    // make sure the column number is valid
    num_cols = QR_NumResultCols(res);
    if (icol >= num_cols) {
        stmt->errormsg = "Invalid column number.";
        stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR;
		SC_log_error(func, "", stmt);
        return SQL_ERROR;
    }

	if ( stmt->manual_result || ! globals.use_declarefetch) {
		// make sure we're positioned on a valid row
		num_rows = QR_get_num_tuples(res);
		if((stmt->currTuple < 0) ||
		   (stmt->currTuple >= num_rows)) {
			stmt->errormsg = "Not positioned on a valid row for GetData.";
			stmt->errornumber = STMT_INVALID_CURSOR_STATE_ERROR;
			SC_log_error(func, "", stmt);
			return SQL_ERROR;
		}
		mylog("     num_rows = %d\n", num_rows);
		if ( stmt->manual_result) {
			value = QR_get_value_manual(res, stmt->currTuple, icol);
		}
		else {
			value = QR_get_value_backend_row(res, stmt->currTuple, icol);
		}
		mylog("     value = '%s'\n", value);
	}
	else { /* its a SOCKET result (backend data) */
		if (stmt->currTuple == -1 || ! res || QR_end_tuples(res)) {
			stmt->errormsg = "Not positioned on a valid row for GetData.";
			stmt->errornumber = STMT_INVALID_CURSOR_STATE_ERROR;
			SC_log_error(func, "", stmt);
			return SQL_ERROR;
		}

		value = QR_get_value_backend(res, icol);

		mylog("  socket: value = '%s'\n", value);
	}

	field_type = QR_get_field_type(res, icol);

	mylog("**** SQLGetData: icol = %d, fCType = %d, field_type = %d, value = '%s'\n", icol, fCType, field_type, value);

	/*	Is this another call for the same column to retrieve more data? */
	multiple = (icol == stmt->current_col) ? TRUE : FALSE;

    result = copy_and_convert_field(stmt, field_type, value, 
                                    fCType, rgbValue, cbValueMax, pcbValue, multiple);


	stmt->current_col = icol;

	switch(result) {
	case COPY_OK:
		return SQL_SUCCESS;

	case COPY_UNSUPPORTED_TYPE:
		stmt->errormsg = "Received an unsupported type from Postgres.";
		stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR;
		SC_log_error(func, "", stmt);
		return SQL_ERROR;

	case COPY_UNSUPPORTED_CONVERSION:
		stmt->errormsg = "Couldn't handle the necessary data type conversion.";
		stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR;
		SC_log_error(func, "", stmt);
		return SQL_ERROR;

	case COPY_RESULT_TRUNCATED:
		stmt->errornumber = STMT_TRUNCATED;
		stmt->errormsg = "The buffer was too small for the result.";
		return SQL_SUCCESS_WITH_INFO;

	case COPY_GENERAL_ERROR:	/* error msg already filled in */
		SC_log_error(func, "", stmt);
		return SQL_ERROR;

	case COPY_NO_DATA_FOUND:
		SC_log_error(func, "no data found", stmt);
		return SQL_NO_DATA_FOUND;

    default:
        stmt->errormsg = "Unrecognized return value from copy_and_convert_field.";
        stmt->errornumber = STMT_INTERNAL_ERROR;
		SC_log_error(func, "", stmt);
        return SQL_ERROR;
    }
}

//      Returns data for bound columns in the current row ("hstmt->iCursor"),
//      advances the cursor.

RETCODE SQL_API SQLFetch(
        HSTMT   hstmt)
{
static char *func = "SQLFetch";
StatementClass *stmt = (StatementClass *) hstmt;   
QResultClass *res;
int retval;
Int2 num_cols, lf;
Oid type;
char *value;
ColumnInfoClass *ci;
// TupleField *tupleField;

mylog("SQLFetch: stmt = %u, stmt->result= %u\n", stmt, stmt->result);

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

	SC_clear_error(stmt);

	if ( ! (res = stmt->result)) {
		stmt->errormsg = "Null statement result in SQLFetch.";
		stmt->errornumber = STMT_SEQUENCE_ERROR;
		SC_log_error(func, "", stmt);
		return SQL_ERROR;
	}

	ci = QR_get_fields(res);		/* the column info */

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


	if (stmt->status != STMT_FINISHED) {
		stmt->errornumber = STMT_STATUS_ERROR;
		stmt->errormsg = "Fetch can only be called after the successful execution on a SQL statement";
		SC_log_error(func, "", stmt);
		return SQL_ERROR;
	}

	if (stmt->bindings == NULL) {
		// just to avoid a crash if the user insists on calling this
		// function even if SQL_ExecDirect has reported an Error
		stmt->errormsg = "Bindings were not allocated properly.";
		stmt->errornumber = STMT_SEQUENCE_ERROR;
		SC_log_error(func, "", stmt);
		return SQL_ERROR;
	}

	mylog("manual_result = %d, use_declarefetch = %d\n", stmt->manual_result, globals.use_declarefetch);
 
	if ( stmt->manual_result || ! globals.use_declarefetch) {

		if (stmt->currTuple >= QR_get_num_tuples(res) -1 || 
			(stmt->maxRows > 0 && stmt->currTuple == stmt->maxRows - 1)) {

			/*	if at the end of the tuples, return "no data found" 
				and set the cursor past the end of the result set 
			*/
			stmt->currTuple = QR_get_num_tuples(res);	
			return SQL_NO_DATA_FOUND;
		}
 
		mylog("**** SQLFetch: manual_result\n");
		(stmt->currTuple)++;
	}
	else {

		// read from the cache or the physical next tuple
		retval = QR_next_tuple(res);
		if (retval < 0) {
			mylog("**** SQLFetch: end_tuples\n");
			return SQL_NO_DATA_FOUND;
		}
		else if (retval > 0)
			(stmt->currTuple)++;		// all is well

		else {
			mylog("SQLFetch: error\n");
			stmt->errornumber = STMT_EXEC_ERROR;
			stmt->errormsg = "Error fetching next row";
			SC_log_error(func, "", stmt);
			return SQL_ERROR;
		}
	}

	num_cols = QR_NumResultCols(res);

	for (lf=0; lf < num_cols; lf++) {

		mylog("fetch: cols=%d, lf=%d, stmt = %u, stmt->bindings = %u, buffer[] = %u\n", num_cols, lf, stmt, stmt->bindings, stmt->bindings[lf].buffer);

		if (stmt->bindings[lf].buffer != NULL) {
            // this column has a binding

            // type = QR_get_field_type(res, lf);
			type = CI_get_oid(ci, lf);		/* speed things up */

			mylog("type = %d\n", type);

			if (stmt->manual_result) {
				value = QR_get_value_manual(res, stmt->currTuple, lf);
				mylog("manual_result\n");
			}
			else if (globals.use_declarefetch)
				value = QR_get_value_backend(res, lf);
			else {
				value = QR_get_value_backend_row(res, stmt->currTuple, lf);
			}

			mylog("value = '%s'\n", value);

			retval = copy_and_convert_field_bindinfo(stmt, type, value, lf);

			mylog("copy_and_convert: retval = %d\n", retval);


			switch(retval) {
			case COPY_OK:
				break;	/*	OK, do next bound column */

			case COPY_UNSUPPORTED_TYPE:
				stmt->errormsg = "Received an unsupported type from Postgres.";
				stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR;
				SC_log_error(func, "", stmt);
				return SQL_ERROR;

			case COPY_UNSUPPORTED_CONVERSION:
				stmt->errormsg = "Couldn't handle the necessary data type conversion.";
				stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR;
				SC_log_error(func, "", stmt);
				return SQL_ERROR;

			case COPY_RESULT_TRUNCATED:
				stmt->errornumber = STMT_TRUNCATED;
				stmt->errormsg = "The buffer was too small for the result.";
				return SQL_SUCCESS_WITH_INFO;

			case COPY_GENERAL_ERROR:	/* error msg already filled in */
				SC_log_error(func, "", stmt);
				return SQL_ERROR;

			case COPY_NO_DATA_FOUND:
				SC_log_error(func, "no data found", stmt);
				return SQL_NO_DATA_FOUND;

			default:
				stmt->errormsg = "Unrecognized return value from copy_and_convert_field.";
				stmt->errornumber = STMT_INTERNAL_ERROR;
				SC_log_error(func, "", stmt);
				return SQL_ERROR;
			}
		}
	}

	return SQL_SUCCESS;
}

//      This fetchs a block of data (rowset).

RETCODE SQL_API SQLExtendedFetch(
        HSTMT      hstmt,
        UWORD      fFetchType,
        SDWORD     irow,
        UDWORD FAR *pcrow,
        UWORD  FAR *rgfRowStatus)
{
static char *func = "SQLExtendedFetch";
StatementClass *stmt = (StatementClass *) hstmt;
int num_tuples;
RETCODE result;


mylog("SQLExtendedFetch: stmt=%u\n", stmt);

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

	if ( globals.use_declarefetch) {
		SC_log_error(func, "SQLExtendedFetch with UseDeclareFetch not yet supported", stmt);
		return SQL_ERROR;
	}

	/*	Initialize to no rows fetched */
	if (rgfRowStatus)
		*rgfRowStatus = SQL_ROW_NOROW;
	if (pcrow)
		*pcrow = 0;

	num_tuples = QR_get_num_tuples(stmt->result);

	switch (fFetchType)  {
	case SQL_FETCH_NEXT:
		mylog("SQL_FETCH_NEXT: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple);
		break;

	case SQL_FETCH_PRIOR:
		mylog("SQL_FETCH_PRIOR: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple);

		/*	If already before result set, return no data found */
		if (stmt->currTuple <= 0)
			return SQL_NO_DATA_FOUND;

		stmt->currTuple -= 2;
		break;

	case SQL_FETCH_FIRST:
		mylog("SQL_FETCH_FIRST: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple);

		stmt->currTuple = -1;
		break;

	case SQL_FETCH_LAST:
		mylog("SQL_FETCH_LAST: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple);
		stmt->currTuple = num_tuples <= 0 ? -1 : (num_tuples - 2);
		break;

	case SQL_FETCH_ABSOLUTE:
		mylog("SQL_FETCH_ABSOLUTE: num_tuples=%d, currtuple=%d, irow=%d\n", num_tuples, stmt->currTuple, irow);

		/*	Position before result set, but dont fetch anything */
		if (irow == 0) {
			stmt->currTuple = -1;
			return SQL_NO_DATA_FOUND;
		}
		/*	Position before the desired row */
		else if (irow > 0) {
			stmt->currTuple = irow-2;
		}
		/*	Position with respect to the end of the result set */
		else {
			stmt->currTuple = num_tuples + irow - 1;
		}    
		break;

	default:
		SC_log_error(func, "Unsupported SQLExtendedFetch Direction", stmt);
		return SQL_ERROR;   

	}           

	mylog("SQLExtendedFetch: new currTuple = %d\n", stmt->currTuple);

	result = SQLFetch(hstmt);

	if (result == SQL_SUCCESS) {
		if (rgfRowStatus)
			*rgfRowStatus = SQL_ROW_SUCCESS;
		if (pcrow)
			*pcrow = 1;
	}

	return result;

}


//      This determines whether there are more results sets available for
//      the "hstmt".

/* CC: return SQL_NO_DATA_FOUND since we do not support multiple result sets */
RETCODE SQL_API SQLMoreResults(
        HSTMT   hstmt)
{
	return SQL_NO_DATA_FOUND;
}

//      This positions the cursor within a block of data.

RETCODE SQL_API SQLSetPos(
        HSTMT   hstmt,
        UWORD   irow,
        UWORD   fOption,
        UWORD   fLock)
{
static char *func = "SQLSetPos";
char buf[128];

	sprintf(buf, "SQLSetPos not implemented: irow=%d, fOption=%d, fLock=%d\n", irow, fOption, fLock);

	SC_log_error(func, buf, (StatementClass *) hstmt);
	return SQL_ERROR;
}

//      Sets options that control the behavior of cursors.

RETCODE SQL_API SQLSetScrollOptions(
        HSTMT      hstmt,
        UWORD      fConcurrency,
        SDWORD  crowKeyset,
        UWORD      crowRowset)
{
static char *func = "SQLSetScrollOptions";

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


//      Set the cursor name on a statement handle

RETCODE SQL_API SQLSetCursorName(
        HSTMT     hstmt,
        UCHAR FAR *szCursor,
        SWORD     cbCursor)
{
static char *func="SQLSetCursorName";
StatementClass *stmt = (StatementClass *) hstmt;
int len;

mylog("SQLSetCursorName: hstmt=%u, szCursor=%u, cbCursorMax=%d\n", hstmt, szCursor, cbCursor);

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

	len = (cbCursor == SQL_NTS) ? strlen(szCursor) : cbCursor;
	mylog("cursor len = %d\n", len);
	if (len <= 0 || len > sizeof(stmt->cursor_name) - 1) {
		stmt->errornumber = STMT_INVALID_CURSOR_NAME;
		stmt->errormsg = "Invalid Cursor Name";
		SC_log_error(func, "", stmt);
		return SQL_ERROR;
	}
	strncpy_null(stmt->cursor_name, szCursor, cbCursor);
	return SQL_SUCCESS;
}

//      Return the cursor name for a statement handle

RETCODE SQL_API SQLGetCursorName(
        HSTMT     hstmt,
        UCHAR FAR *szCursor,
        SWORD     cbCursorMax,
        SWORD FAR *pcbCursor)
{
static char *func="SQLGetCursorName";
StatementClass *stmt = (StatementClass *) hstmt;

mylog("SQLGetCursorName: hstmt=%u, szCursor=%u, cbCursorMax=%d, pcbCursor=%u\n", hstmt, szCursor, cbCursorMax, pcbCursor);

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


	if ( stmt->cursor_name[0] == '\0') {
		stmt->errornumber = STMT_NO_CURSOR_NAME;
		stmt->errormsg = "No Cursor name available";
		SC_log_error(func, "", stmt);
		return SQL_ERROR;
	}

	strncpy_null(szCursor, stmt->cursor_name, cbCursorMax);

	if (pcbCursor)
		*pcbCursor = strlen(szCursor);

	return SQL_SUCCESS;
}


