/*
  +----------------------------------------------------------------------+
  | (C) Copyright IBM Corporation 2005.                                  |
  +----------------------------------------------------------------------+
  |                                                                      |
  | Licensed under the Apache License, Version 2.0 (the "License"); you  |
  | may not use this file except in compliance with the License. You may |
  | obtain a copy of the License at                                      |
  | http://www.apache.org/licenses/LICENSE-2.0                           |
  |                                                                      |
  | Unless required by applicable law or agreed to in writing, software  |
  | distributed under the License is distributed on an "AS IS" BASIS,    |
  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or      |
  | implied. See the License for the specific language governing         |
  | permissions and limitations under the License.                       |
  +----------------------------------------------------------------------+
  | Authors: Sushant Koduru, Lynh Nguyen, Kanchana Padmanabhan,          |
  |          Dan Scott, Helmut Tessarek, Sam Ruby                        |
  +----------------------------------------------------------------------+

  $Id: ibm_db2.c,v 1.29 2005/11/25 18:04:00 dbs Exp $
*/

#define	TODO 0

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

#include <ruby.h>
#include "ruby_ibm_db2.h"
#include <ctype.h>

#define RUBY_ERROR(message) \
  rb_throw(message, Qnil)

/* True global resources - no need for thread safety here */
static VALUE le_conn_struct, le_stmt_struct, le_pconn_struct, le_row_struct;
static VALUE le_client_info, le_server_info;
static struct _ibm_db2_globals *ibm_db2_globals;

static void _ruby_db2_check_sql_errors( SQLHANDLE handle, SQLSMALLINT hType, int rc, int cpy_to_global, char* ret_str, int API, SQLSMALLINT recno );
static void _ruby_db2_assign_options( void* handle, int type, char* opt_key, long data );
static void _ruby_db2_clear_conn_err_cache();
static void _ruby_db2_clear_stmt_err_cache();
static char * _ruby_db2_instance_name;

/* Defines a linked list structure for caching param data */
typedef struct _param_cache_node {
	SQLSMALLINT	data_type;			/* Datatype */
	SQLUINTEGER	param_size;			/* param size */
	SQLSMALLINT nullable;			/* is Nullable */
	SQLSMALLINT	scale;				/* Decimal scale */
	SQLUINTEGER file_options;		/* File options if DB2_PARAM_FILE */
	SQLINTEGER	bind_indicator;		/* indicator variable for SQLBindParameter */
	int			param_num;			/* param number in stmt */
	int			param_type;			/* Type of param - INP/OUT/INP-OUT/FILE */
	char		*varname;			/* bound variable name */
	long		ivalue;				/* Temp storage value */
	double		fvalue;				/* Temp storage value */
	char		*svalue;			/* Temp storage value */
	struct _param_cache_node *next;	/* Pointer to next node */
} param_node;

typedef struct _conn_handle_struct {
	SQLHANDLE henv;
	SQLHANDLE hdbc;
	long auto_commit;
	long c_bin_mode;
	long c_case_mode;
	int handle_active;
	SQLSMALLINT error_recno_tracker;
	SQLSMALLINT errormsg_recno_tracker;
	int flag_pconnect; /* Indicates that this connection is persistent */
} conn_handle;

typedef union {
	int i_val;
	long l_val;
	double d_val;
	float f_val;
	short s_val;
	char *str_val;
} db2_row_data_type;

typedef struct {
	SQLINTEGER out_length;
	db2_row_data_type data;
} db2_row_type;

typedef struct _db2_result_set_info_struct {
	SQLCHAR		*name;
	SQLSMALLINT type;
	SQLUINTEGER size;
	SQLSMALLINT scale;
	SQLSMALLINT nullable;
} db2_result_set_info;

typedef struct _row_hash_struct {
	VALUE hash;
} row_hash_struct;

typedef struct _stmt_handle_struct {
	SQLHANDLE hdbc;
	SQLHANDLE hstmt;
	long s_bin_mode;
	long cursor_type;
	long s_case_mode;
	SQLSMALLINT error_recno_tracker;
	SQLSMALLINT errormsg_recno_tracker;

	/* Parameter Caching variables */
	param_node *head_cache_list;
	param_node *current_node;

	int num_params;				/* Number of Params */
	int file_param;				/* if option passed in is FILE_PARAM */
	int num_columns;
	db2_result_set_info *column_info;
	db2_row_type *row_data;
} stmt_handle;

void ruby_init_db2();

/* equivalent functions on different platforms */
#ifdef _WIN32
#define STRCASECMP stricmp
#else
#define STRCASECMP strcasecmp
#endif

static VALUE mDB2;
static VALUE id_new;
static VALUE id_keys;
static VALUE id_id2name;

#define RUBY_FE(function) \
	rb_define_singleton_method(mDB2, #function, db2_##function, -1);

#define RUBY_FALIAS(alias, function) \
	rb_define_singleton_method(mDB2, #alias, db2_##function, -1);

/*  Every user visible function must have an entry in Init_ibm_db2
*/
void Init_ibm_db2(void) {
	mDB2 = rb_define_module("DB2");

	RUBY_FE(connect)
	RUBY_FE(commit)
	RUBY_FE(pconnect)
	RUBY_FE(autocommit)
	RUBY_FE(bind_param)
	RUBY_FE(close)
#if TODO
	RUBY_FE(column_privileges)
	RUBY_FALIAS(columnprivileges, column_privileges)
#endif
	RUBY_FE(columns)
#if TODO
	RUBY_FE(foreign_keys)
	RUBY_FALIAS(foreignkeys, foreign_keys)
	RUBY_FE(primary_keys)
	RUBY_FALIAS(primarykeys, primary_keys)
	RUBY_FE(procedure_columns)
	RUBY_FALIAS(procedurecolumns, procedure_columns)
	RUBY_FE(procedures)
	RUBY_FE(special_columns)
	RUBY_FALIAS(specialcolumns, special_columns)
	RUBY_FE(statistics)
	RUBY_FE(table_privileges)
	RUBY_FALIAS(tableprivileges, table_privileges)
#endif
	RUBY_FE(tables)
	RUBY_FE(exec)
	RUBY_FE(prepare)
	RUBY_FE(execute)
	RUBY_FE(stmt_errormsg)
	RUBY_FE(conn_errormsg)
	RUBY_FE(conn_error)
	RUBY_FE(stmt_error)
	RUBY_FE(next_result)
	RUBY_FE(num_fields)
	RUBY_FE(num_rows)
	RUBY_FE(field_name)
	RUBY_FE(field_display_size)
	RUBY_FE(field_num)
#if TODO
	RUBY_FE(field_precision)
	RUBY_FE(field_scale)
#endif
	RUBY_FE(field_type)
	RUBY_FE(field_width)
#if TODO
	RUBY_FE(cursor_type)
#endif
	RUBY_FE(rollback)
#if TODO
	RUBY_FE(free_stmt)
#endif
	RUBY_FE(result)
	RUBY_FE(fetch_row)
	RUBY_FE(fetch_assoc)
	RUBY_FE(fetch_array)
	RUBY_FE(fetch_both)
	RUBY_FE(free_result)
#if TODO
	RUBY_FE(set_option)
	RUBY_FALIAS(setoption, set_option)
#endif
	RUBY_FE(fetch_object)
	RUBY_FE(server_info)
	RUBY_FE(client_info)

	ruby_init_db2();

	id_keys = rb_intern("keys");
	id_new = rb_intern("new");
	id_id2name = rb_intern("id2name");

};
/*  */

#if TODO
/* {{{ PHP_INI
*/
PHP_INI_BEGIN()
	STD_PHP_INI_ENTRY("ibm_db2.binmode", "1", PHP_INI_ALL, ONUPDATEFUNCTION,
		bin_mode, zend_ibm_db2_globals, ibm_db2_globals)
	PHP_INI_ENTRY("ibm_db2.instance_name", NULL, PHP_INI_SYSTEM, NULL)
PHP_INI_END()
/* }}} */
#else
#define INI_STR(name) NULL
#endif

static void ruby_ibm_db2_init_globals(struct _ibm_db2_globals *ibm_db2_globals)
{
	/* env handle */
	ibm_db2_globals->bin_mode = 1;

	memset(ibm_db2_globals->__ruby_conn_err_msg, 0, DB2_MAX_ERR_MSG_LEN);
	memset(ibm_db2_globals->__ruby_stmt_err_msg, 0, DB2_MAX_ERR_MSG_LEN);
	memset(ibm_db2_globals->__ruby_conn_err_state, 0, SQL_SQLSTATE_SIZE + 1);
	memset(ibm_db2_globals->__ruby_stmt_err_state, 0, SQL_SQLSTATE_SIZE + 1);
}
/*  */

/* TODO persistent storage */
#define pefree(data, persistent) free(data)
#define efree(data) free(data)
static VALUE persistent_list;

char *estrdup(char *data) {
	int len = strlen(data);
	char *dup = ALLOC_N(char, len+1);
	strcpy(dup, data);
	return dup;
}

char *estrndup(char *data, int max) {
	int len = strlen(data);
	char *dup;
	if (len > max) len = max;
    dup = ALLOC_N(char, len+1);
	strcpy(dup, data);
	return dup;
}

char *strtolower(char *data, int max) {
	while (max--) data[max] = tolower(data[max]);
	return data;
}

char *strtoupper(char *data, int max) {
	while (max--) data[max] = toupper(data[max]);
	return data;
}

/*  static void _ruby_db2_mark_conn_struct */
static void _ruby_db2_mark_conn_struct(conn_handle *handle)
{
}
/*  */

/*  static void _ruby_db2_free_conn_struct */
static void _ruby_db2_free_conn_struct(conn_handle *handle)
{
	int rc;

	/* Disconnect from DB. If stmt is allocated, it is freed automatically*/
	if ( handle->handle_active ) {
		rc = SQLDisconnect((SQLHDBC)handle->hdbc);
		rc = SQLFreeHandle(SQL_HANDLE_DBC, handle->hdbc);
		rc = SQLFreeHandle(SQL_HANDLE_ENV, handle->henv);
	}
	if ( handle != NULL ) {
		if ( handle->flag_pconnect ) {
			/* Important to use regular free, we don't want the handled collected by efree */
			pefree(handle, 1);
		} else {
			efree(handle);
		}
	}
}
/*  */

/*  static void _ruby_db2_mark_pconn_struct */
static void _ruby_db2_mark_pconn_struct(conn_handle *handle)
{
}
/*  */

/*  static void _ruby_db2_free_pconn_struct */
static void _ruby_db2_free_pconn_struct(conn_handle *handle)
{
	_ruby_db2_free_conn_struct(handle);
}
/*  */

/*  static void _ruby_db2_mark_row_struct */
static void _ruby_db2_mark_row_struct(row_hash_struct *handle)
{
	rb_gc_mark(handle->hash);
}
/*  */

/*  static void _ruby_db2_free_row_struct */
static void _ruby_db2_free_row_struct(row_hash_struct *handle)
{
	free(handle);
}
/*  */

/*  static void _ruby_db2_free_result_struct(stmt_handle* handle)
	*/
static void _ruby_db2_free_result_struct(stmt_handle* handle)
{
	int i;
	param_node *curr_ptr = NULL, *prev_ptr = NULL;

	if ( handle != NULL ) {
		/* Free param cache list */
		curr_ptr = handle->head_cache_list;
		prev_ptr = handle->head_cache_list;

		while (curr_ptr != NULL) {
			curr_ptr = curr_ptr->next;

			if (prev_ptr->varname) {
				efree(prev_ptr->varname);
			}

			if (prev_ptr->svalue) efree(prev_ptr->svalue);
			efree(prev_ptr);

			prev_ptr = curr_ptr;
		}
		/* free row data cache */
		if (handle->row_data) {
			for (i=0; i<handle->num_columns;i++) {
				switch (handle->column_info[i].type) {
				case SQL_CHAR:
				case SQL_VARCHAR:
				case SQL_LONGVARCHAR:
				case SQL_TYPE_DATE:
				case SQL_TYPE_TIME:
				case SQL_TYPE_TIMESTAMP:
				case SQL_BIGINT:
				case SQL_DECIMAL:
				case SQL_NUMERIC:
					if ( handle->row_data[i].data.str_val != NULL ) {
						efree(handle->row_data[i].data.str_val);
						handle->row_data[i].data.str_val = NULL;
					}

				}
			}
			efree(handle->row_data);
			handle->row_data = NULL;
		}

		/* free column info cache */
		if ( handle->column_info ) {
			for (i=0; i<handle->num_columns; i++) {
				efree(handle->column_info[i].name);
			}
			efree(handle->column_info);
			handle->column_info = NULL;
			handle->num_columns = 0;
		}
	}
}
/*  */

/*  static stmt_handle *_db2_new_stmt_struct(conn_handle* conn_res)
	*/
static stmt_handle *_db2_new_stmt_struct(conn_handle* conn_res)
{
	stmt_handle *stmt_res;

	stmt_res = ALLOC(stmt_handle);
	memset(stmt_res, 0, sizeof(stmt_handle));

	/* Initialize stmt resource so parsing assigns updated options if needed */
	stmt_res->hdbc = conn_res->hdbc;
	stmt_res->s_bin_mode = conn_res->c_bin_mode;
	stmt_res->cursor_type = DB2_FORWARD_ONLY;
	stmt_res->s_case_mode = conn_res->c_case_mode;

	stmt_res->head_cache_list = NULL;
	stmt_res->current_node = NULL;

	stmt_res->num_params = 0;
	stmt_res->file_param = 0;

	stmt_res->column_info = NULL;
	stmt_res->num_columns = 0;

	stmt_res->error_recno_tracker = 1;
	stmt_res->errormsg_recno_tracker = 1;

	stmt_res->row_data = NULL;

	return stmt_res;
}
/*  */

/*  static _ruby_db2_mark_stmt_struct */
static void _ruby_db2_mark_stmt_struct(stmt_handle *handle)
{
	// TODO: mark conn struct ?
}
/*  */

/*  static _ruby_db2_free_stmt_struct */
static void _ruby_db2_free_stmt_struct(stmt_handle *handle)
{
	int rc;

	rc = SQLFreeHandle( SQL_HANDLE_STMT, handle->hstmt);

	if ( handle ) {
		_ruby_db2_free_result_struct(handle);
		efree(handle);
	}
}
/*  */

/* allow direct access to hash object as object attributes */
RUBY_FUNCTION(db2_row_object)
{
	row_hash_struct *row_res;
	VALUE symbol;
	VALUE rest;
	VALUE index;

    Data_Get_Struct(self, row_hash_struct, row_res);

	rb_scan_args(argc, argv, "1*", &symbol, &rest);

	if (symbol == ID2SYM(id_keys)) {
	    return rb_funcall(row_res->hash, id_keys, 0);
	} else {
	    index = rb_funcall(symbol, id_id2name, 0);
	    return rb_hash_aref(row_res->hash, index);
	}
}

/*  Module initialiation
*/
void ruby_init_db2()
{
#ifndef _WIN32
	/* Declare variables for DB2 instance settings */
	char * tmp_name;
	char * instance_name;
#endif

	ibm_db2_globals = ALLOC(struct _ibm_db2_globals);
	memset(ibm_db2_globals, 0, sizeof(struct _ibm_db2_globals));
	ruby_ibm_db2_init_globals(ibm_db2_globals);

	rb_define_const(mDB2, "BINARY", INT2NUM(1));
	rb_define_const(mDB2, "CONVERT", INT2NUM(2));
	rb_define_const(mDB2, "PASSTHRU", INT2NUM(3));

	rb_define_const(mDB2, "SCROLLABLE", INT2NUM(SQL_CURSOR_KEYSET_DRIVEN));
	rb_define_const(mDB2, "FORWARD_ONLY", INT2NUM(SQL_SCROLL_FORWARD_ONLY));
	rb_define_const(mDB2, "PARAM_IN", INT2NUM(SQL_PARAM_INPUT));
	rb_define_const(mDB2, "PARAM_OUT", INT2NUM(SQL_PARAM_OUTPUT));
	rb_define_const(mDB2, "PARAM_INOUT", INT2NUM(SQL_PARAM_INPUT_OUTPUT));
	/* This number chosen is just a place holder to decide binding function to call */
	rb_define_const(mDB2, "PARAM_FILE", INT2NUM(11));

	rb_define_const(mDB2, "AUTOCOMMIT_ON", INT2NUM(SQL_AUTOCOMMIT_ON));
	rb_define_const(mDB2, "AUTOCOMMIT_OFF", INT2NUM(SQL_AUTOCOMMIT_OFF));

	rb_define_const(mDB2, "DOUBLE", INT2NUM(SQL_DOUBLE));
	/* This is how CLI defines SQL_C_LONG */
	rb_define_const(mDB2, "LONG", INT2NUM(SQL_INTEGER));
	rb_define_const(mDB2, "CHAR", INT2NUM(SQL_CHAR));

	rb_define_const(mDB2, "CASE_NATURAL", INT2NUM(0));
	rb_define_const(mDB2, "CASE_LOWER", INT2NUM(1));
	rb_define_const(mDB2, "CASE_UPPER", INT2NUM(2));

	rb_global_variable(&persistent_list);

	/* REGISTER_INI_ENTRIES(); */

#ifndef _WIN32
	/* Tell DB2 where to find its libraries */
	tmp_name = INI_STR("ibm_db2.instance_name");
	if (NULL != tmp_name) {
		instance_name = (char *)malloc(strlen(DB2_VAR_INSTANCE) + strlen(tmp_name) + 1);
		strcpy(instance_name, DB2_VAR_INSTANCE);
		strcat(instance_name, tmp_name);
		putenv(instance_name);
		_ruby_db2_instance_name = instance_name;
	}
#endif

#ifdef _AIX
	/* atexit() handler in the DB2/AIX library segfaults in PHP CLI */
	/* DB2NOEXITLIST env variable prevents DB2 from invoking atexit() */
	putenv("DB2NOEXITLIST=TRUE");
#endif

	persistent_list = rb_hash_new();

    le_conn_struct = rb_define_class_under(mDB2, "Connection", rb_cObject);
    le_pconn_struct = rb_define_class_under(mDB2, "PConnection", rb_cObject);
    le_stmt_struct = rb_define_class_under(mDB2, "Statement", rb_cObject);
    le_row_struct = rb_define_class_under(mDB2, "RowObject", rb_cObject);
    le_client_info = rb_define_class_under(mDB2, "ClientInfo", rb_cObject);
    le_server_info = rb_define_class_under(mDB2, "ServerInfo", rb_cObject);

	rb_define_method(le_row_struct, "method_missing", db2_row_object, -1);

    rb_attr(le_client_info, rb_intern("DRIVER_NAME"), 1, 0, 0);
    rb_attr(le_client_info, rb_intern("DRIVER_VER"), 1, 0, 0);
    rb_attr(le_client_info, rb_intern("DATA_SOURCE_NAME"), 1, 0, 0);
    rb_attr(le_client_info, rb_intern("DRIVER_ODBC_VER"), 1, 0, 0);
    rb_attr(le_client_info, rb_intern("ODBC_VER"), 1, 0, 0);
    rb_attr(le_client_info, rb_intern("ODBC_SQL_CONFORMANCE"), 1, 0, 0);
    rb_attr(le_client_info, rb_intern("APPL_CODEPAGE"), 1, 0, 0);
    rb_attr(le_client_info, rb_intern("CONN_CODEPAGE"), 1, 0, 0);

    rb_attr(le_server_info, rb_intern("DBMS_NAME"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("DBMS_VER"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("DB_CODEPAGE"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("DB_NAME"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("INST_NAME"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("SPECIAL_CHARS"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("KEYWORDS"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("DFT_ISOLATION"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("ISOLATION_OPTION"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("SQL_CONFORMANCE"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("PROCEDURES"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("IDENTIFIER_QUOTE_CHAR"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("LIKE_ESCAPE_CLAUSE"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("MAX_COL_NAME_LEN"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("MAX_ROW_SIZE"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("MAX_IDENTIFIER_LEN"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("MAX_INDEX_SIZE"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("MAX_PROC_NAME_LEN"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("MAX_SCHEMA_NAME_LEN"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("MAX_STATEMENT_LEN"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("MAX_TABLE_NAME_LEN"), 1, 0, 0);
    rb_attr(le_server_info, rb_intern("NON_NULLABLE_COLUMNS"), 1, 0, 0);
}
/*  */

/*  static void _ruby_db2_init_error_info(stmt_handle *stmt_res)
*/
static void _ruby_db2_init_error_info(stmt_handle *stmt_res)
{
	stmt_res->error_recno_tracker = 1;
	stmt_res->errormsg_recno_tracker = 1;
}
/*  */

/*  static void _ruby_db2_check_sql_errors( SQLHANDLE handle, SQLSMALLINT hType, int rc, int cpy_to_global, char* ret_str, int API SQLSMALLINT recno)
*/
static void _ruby_db2_check_sql_errors( SQLHANDLE handle, SQLSMALLINT hType, int rc, int cpy_to_global, char* ret_str, int API, SQLSMALLINT recno )
{
	SQLCHAR msg[SQL_MAX_MESSAGE_LENGTH + 1];
	SQLCHAR sqlstate[SQL_SQLSTATE_SIZE + 1];
	SQLCHAR errMsg[DB2_MAX_ERR_MSG_LEN];
	SQLINTEGER sqlcode;
	SQLSMALLINT length;
	char *p;

	if ( SQLGetDiagRec(hType, handle, recno, sqlstate, &sqlcode, msg,
			SQL_MAX_MESSAGE_LENGTH + 1, &length ) == SQL_SUCCESS) {

		p = strchr( (char*)msg, '\n' );
		if (p) *p = '\0';

		sprintf((char*)errMsg, "%s SQLCODE=%d", (char*)msg, (int)sqlcode);
		errMsg[DB2_MAX_ERR_MSG_LEN] = '\0';

		switch (rc) {
			case SQL_ERROR:
				/* Need to copy the error msg and sqlstate into the symbol Table to cache these results */
				if ( cpy_to_global ) {
					switch (hType) {
						case SQL_HANDLE_DBC:
							strncpy(IBM_DB2_G(__ruby_conn_err_state), (char*)sqlstate, SQL_SQLSTATE_SIZE+1);
							strncpy(IBM_DB2_G(__ruby_conn_err_msg), (char*)errMsg, DB2_MAX_ERR_MSG_LEN);
							break;

						case SQL_HANDLE_STMT:
							strncpy(IBM_DB2_G(__ruby_stmt_err_state), (char*)sqlstate, SQL_SQLSTATE_SIZE+1);
							strncpy(IBM_DB2_G(__ruby_stmt_err_msg), (char*)errMsg, DB2_MAX_ERR_MSG_LEN);
							break;
					}
				}

				/* This call was made from db2_errmsg or db2_error */
				/* Check for error and return */
				switch (API) {
					case DB2_ERR:
						if ( ret_str != NULL ) {
							strncpy(ret_str, (char*)sqlstate, SQL_SQLSTATE_SIZE+1);
						}
						return;
					case DB2_ERRMSG:
						if ( ret_str != NULL ) {
							strncpy(ret_str, (char*)msg, DB2_MAX_ERR_MSG_LEN);
						}
						return;
					default:
						break;
				}

				break;

			default:
				break;
		}
	}
}
/*  */

/*  static void _ruby_db2_assign_options( void *handle, int type, char* opt_key, long data )
	*/
static void _ruby_db2_assign_options( void *handle, int type, char *opt_key, long data )
{
	int rc = 0;
	SQLINTEGER autocommit;

	if ( !STRCASECMP(opt_key, "cursor")) {
		if ( type == SQL_HANDLE_STMT ) {
			if (((stmt_handle *)handle)->cursor_type != data ) {
				switch (data) {
					case DB2_SCROLLABLE:
						((stmt_handle *)handle)->cursor_type = DB2_SCROLLABLE;
						rc = SQLSetStmtAttr((SQLHSTMT)((stmt_handle *)handle)->hstmt,
							SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)SQL_CURSOR_KEYSET_DRIVEN,
							SQL_IS_INTEGER );
						break;

					case DB2_FORWARD_ONLY:
						rc = SQLSetStmtAttr((SQLHSTMT)((stmt_handle *)handle)->hstmt,
							SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)SQL_SCROLL_FORWARD_ONLY,
							SQL_IS_INTEGER );
						((stmt_handle *)handle)->cursor_type = DB2_FORWARD_ONLY;
						break;

					default:
						RUBY_ERROR("CURSOR statement attribute value must be one of DB2_SCROLLABLE or DB2_FORWARD_ONLY");
						break;
				}
			}
		} else {
			RUBY_ERROR("CURSOR statement attribute can only be set on statement resources");
		}
	} else if (!STRCASECMP(opt_key, "autocommit")) {
		if (type == SQL_HANDLE_DBC ) {
			if (((conn_handle *)handle)->auto_commit != data) {
				switch (data) {
					case DB2_AUTOCOMMIT_ON:
						/*	Setting AUTOCOMMIT again here. The user could modify
							this option, close the connection, and reopen it again
							with this option.
						*/
						((conn_handle*)handle)->auto_commit = 1;
						autocommit = SQL_AUTOCOMMIT_ON;
						rc = SQLSetConnectAttr((SQLHSTMT)((conn_handle*)handle)->hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)autocommit, SQL_NTS);
						break;

					case DB2_AUTOCOMMIT_OFF:
						((conn_handle*)handle)->auto_commit = 0;
						autocommit = SQL_AUTOCOMMIT_OFF;
						rc = SQLSetConnectAttr((SQLHSTMT)((conn_handle*)handle)->hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)autocommit, SQL_NTS);
						break;

					default:
						RUBY_ERROR("AUTOCOMMIT connection attribute value must be one of DB2_AUTOCOMMIT_ON or DB2_AUTOCOMMIT_OFF");
						break;
				}
			}
		} else {
			RUBY_ERROR("AUTOCOMMIT connection attribute can only be set on connection resources");
		}
	} else if (!STRCASECMP(opt_key, "binmode")) {
		switch (data) {
			case DB2_BINARY:
				switch (type) {
					case SQL_HANDLE_DBC:
						((conn_handle*)handle)->c_bin_mode = DB2_BINARY;
						break;

					case SQL_HANDLE_STMT:
						((stmt_handle *)handle)->s_bin_mode = DB2_BINARY;
						break;

					default:
						RUBY_ERROR("BINMODE attribute can only be set on connection or statement resources");
				}
				break;
			case DB2_PASSTHRU:
				switch (type) {
					case SQL_HANDLE_DBC:
						((conn_handle*)handle)->c_bin_mode = DB2_PASSTHRU;
						break;

					case SQL_HANDLE_STMT:
						((stmt_handle *)handle)->s_bin_mode = DB2_PASSTHRU;
						break;

					default:
						RUBY_ERROR("BINMODE attribute can only be set on connection or statement resources");
				}
				break;
			case DB2_CONVERT:
				switch (type) {
					case SQL_HANDLE_DBC:
						((conn_handle*)handle)->c_bin_mode = DB2_CONVERT;
						break;

					case SQL_HANDLE_STMT:
						((stmt_handle *)handle)->s_bin_mode = DB2_CONVERT;
						break;

					default:
						RUBY_ERROR("BINMODE attribute can only be set on connection or statement resources");
				}
				break;
		}
	} else if (!STRCASECMP(opt_key, "db2_attr_case")) {
		switch (type) {
			case SQL_HANDLE_DBC:
				switch (data) {
					case DB2_CASE_LOWER:
						((conn_handle*)handle)->c_case_mode = DB2_CASE_LOWER;
						break;
					case DB2_CASE_UPPER:
						((conn_handle*)handle)->c_case_mode = DB2_CASE_UPPER;
						break;
					case DB2_CASE_NATURAL:
						((conn_handle*)handle)->c_case_mode = DB2_CASE_NATURAL;
						break;
					default:
						RUBY_ERROR("DB2_ATTR_CASE attribute must be one of DB2_CASE_LOWER, DB2_CASE_UPPER, or DB2_CASE_NATURAL");
				}
				break;
			case SQL_HANDLE_STMT:
				switch (data) {
					case DB2_CASE_LOWER:
						((stmt_handle*)handle)->s_case_mode = DB2_CASE_LOWER;
						break;
					case DB2_CASE_UPPER:
						((stmt_handle*)handle)->s_case_mode = DB2_CASE_UPPER;
						break;
					case DB2_CASE_NATURAL:
						((stmt_handle*)handle)->s_case_mode = DB2_CASE_NATURAL;
						break;
					default:
						RUBY_ERROR("DB2_ATTR_CASE attribute must be one of DB2_CASE_LOWER, DB2_CASE_UPPER, or DB2_CASE_NATURAL");
				}
				break;
			default:
				RUBY_ERROR("DB2_ATTR_CASE attribute can only be set on connection or statement resources");
		}
	} else {
		RUBY_ERROR("Incorrect option setting passed in");
	}
}
/*  */

/*  static int _ruby_db2_parse_options( zval *options, int type, void *handle)
*/
static int _ruby_db2_parse_options ( VALUE options, int type, void *handle )
{
	int numOpts = 0, i = 0;
	VALUE keys;
	VALUE key; /* Holds the Option Index Key */
	VALUE data;

	if ( !NIL_P(options) ) {
	    keys = rb_funcall(options, id_keys, 0);
		numOpts = RARRAY(keys)->len;

		for ( i = 0; i < numOpts; i++) {
			key = rb_ary_entry(keys,i);
			data = rb_hash_aref(options,key);

			/* Assign options to handle. */
			/* Sets the options in the handle with CLI/ODBC calls */
			_ruby_db2_assign_options( handle, type, STR2CSTR(key), NUM2LONG(data) );
		}
	}

	return SQL_SUCCESS;
}
/*  */

/*  static int _ruby_db2_get_result_set_info(stmt_handle *stmt_res)
initialize the result set information of each column. This must be done once
*/
static int _ruby_db2_get_result_set_info(stmt_handle *stmt_res)
{
	int rc = -1, i;
	SQLSMALLINT nResultCols = 0, name_length;
	SQLCHAR tmp_name[BUFSIZ];
	rc = SQLNumResultCols((SQLHSTMT)stmt_res->hstmt, &nResultCols);
	if ( rc == SQL_ERROR || nResultCols == 0) {
		return -1;
	}
	stmt_res->num_columns = nResultCols;
	stmt_res->column_info = ALLOC_N(db2_result_set_info, nResultCols);
	memset(stmt_res->column_info, 0, sizeof(db2_result_set_info)*nResultCols);
	/* return a set of attributes for a column */
	for (i = 0 ; i < nResultCols; i++) {
		rc = SQLDescribeCol((SQLHSTMT)stmt_res->hstmt, (SQLSMALLINT)(i + 1 ),
			tmp_name, BUFSIZ, &name_length, &stmt_res->column_info[i].type,
			&stmt_res->column_info[i].size, &stmt_res->column_info[i].scale,
			&stmt_res->column_info[i].nullable);
		if ( rc == SQL_ERROR ) {
			return -1;
		}
		if ( name_length <= 0 ) {
			stmt_res->column_info[i].name = (SQLCHAR*)estrdup("");
		} else if (name_length >= BUFSIZ ) {
			/* column name is longer than BUFSIZ*/
			stmt_res->column_info[i].name = (SQLCHAR*)ALLOC_N(char, name_length+1);
			rc = SQLDescribeCol((SQLHSTMT)stmt_res->hstmt, (SQLSMALLINT)(i + 1 ),
				stmt_res->column_info[i].name, name_length, &name_length,
				&stmt_res->column_info[i].type, &stmt_res->column_info[i].size,
				&stmt_res->column_info[i].scale, &stmt_res->column_info[i].nullable);
			if ( rc == SQL_ERROR ) {
				return -1;
			}
		} else {
			stmt_res->column_info[i].name = (SQLCHAR*)estrdup((char*)tmp_name);
		}
	}
	return 0;
}
/*  */

/*  static int _ruby_db2_bind_column_helper(stmt_handle *stmt_res)
	bind columns to data, this must be done once
*/
static int _ruby_db2_bind_column_helper(stmt_handle *stmt_res)
{
	SQLINTEGER in_length;
	SQLSMALLINT column_type;
	db2_row_data_type *row_data;
	int i, rc = SQL_SUCCESS;

	stmt_res->row_data = ALLOC_N(db2_row_type, stmt_res->num_columns);
	memset(stmt_res->row_data,0,sizeof(db2_row_type)*stmt_res->num_columns);

	for (i=0; i<stmt_res->num_columns; i++) {
		column_type = stmt_res->column_info[i].type;
		row_data = &stmt_res->row_data[i].data;
		switch(column_type) {
			case SQL_CHAR:
			case SQL_VARCHAR:
			case SQL_LONGVARCHAR:
				in_length = stmt_res->column_info[i].size+1;
				row_data->str_val = ALLOC_N(char, in_length);

				rc = SQLBindCol((SQLHSTMT)stmt_res->hstmt, (SQLUSMALLINT)(i+1),
					SQL_C_DEFAULT, row_data->str_val, in_length,
					(SQLINTEGER *)(&stmt_res->row_data[i].out_length));
				break;

			case SQL_BINARY:
			case SQL_LONGVARBINARY:
			case SQL_VARBINARY:
				if ( stmt_res->s_bin_mode == DB2_CONVERT ) {
					in_length = 2*(stmt_res->column_info[i].size)+1;
					row_data->str_val = ALLOC_N(char, in_length);

					rc = SQLBindCol((SQLHSTMT)stmt_res->hstmt, (SQLUSMALLINT)(i+1),
						SQL_C_CHAR, row_data->str_val, in_length,
						(SQLINTEGER *)(&stmt_res->row_data[i].out_length));
				} else {
					in_length = stmt_res->column_info[i].size+1;
					row_data->str_val = (char*)ALLOC_N(char, in_length);

					rc = SQLBindCol((SQLHSTMT)stmt_res->hstmt, (SQLUSMALLINT)(i+1),
						SQL_C_DEFAULT, row_data->str_val, in_length,
						(SQLINTEGER *)(&stmt_res->row_data[i].out_length));
				}
				break;

			case SQL_TYPE_DATE:
			case SQL_TYPE_TIME:
			case SQL_TYPE_TIMESTAMP:
			case SQL_BIGINT:
				in_length = stmt_res->column_info[i].size+1;
				row_data->str_val = ALLOC_N(char, in_length);
				rc = SQLBindCol((SQLHSTMT)stmt_res->hstmt, (SQLUSMALLINT)(i+1),
					SQL_C_CHAR, row_data->str_val, in_length,
					(SQLINTEGER *)(&stmt_res->row_data[i].out_length));
				break;

			case SQL_SMALLINT:
				rc = SQLBindCol((SQLHSTMT)stmt_res->hstmt, (SQLUSMALLINT)(i+1),
					SQL_C_DEFAULT, &row_data->s_val, sizeof(row_data->s_val),
					(SQLINTEGER *)(&stmt_res->row_data[i].out_length));
				break;

			case SQL_INTEGER:
				rc = SQLBindCol((SQLHSTMT)stmt_res->hstmt, (SQLUSMALLINT)(i+1),
					SQL_C_DEFAULT, &row_data->i_val, sizeof(row_data->i_val),
					(SQLINTEGER *)(&stmt_res->row_data[i].out_length));
				break;

			case SQL_REAL:
			case SQL_FLOAT:
				rc = SQLBindCol((SQLHSTMT)stmt_res->hstmt, (SQLUSMALLINT)(i+1),
					SQL_C_DEFAULT, &row_data->f_val, sizeof(row_data->f_val),
					(SQLINTEGER *)(&stmt_res->row_data[i].out_length));
				break;

			case SQL_DOUBLE:
				rc = SQLBindCol((SQLHSTMT)stmt_res->hstmt, (SQLUSMALLINT)(i+1),
					SQL_C_DEFAULT, &row_data->d_val, sizeof(row_data->d_val),
					(SQLINTEGER *)(&stmt_res->row_data[i].out_length));
				break;

			case SQL_DECIMAL:
			case SQL_NUMERIC:
				in_length = stmt_res->column_info[i].size +
					stmt_res->column_info[i].scale + 2 + 1;
				row_data->str_val = ALLOC_N(char, in_length);
				rc = SQLBindCol((SQLHSTMT)stmt_res->hstmt, (SQLUSMALLINT)(i+1),
					SQL_C_CHAR, row_data->str_val, in_length,
					(SQLINTEGER *)(&stmt_res->row_data[i].out_length));
				break;

			case SQL_CLOB:
			case SQL_BLOB:
				stmt_res->row_data[i].out_length = 0;
				/* we do getdata call instead */
				break;

			default:
				break;
			}
		}
	return rc;
}
/*  */

/*  static void _ruby_db2_clear_stmt_err_cache ()
*/
static void _ruby_db2_clear_stmt_err_cache()
{
	memset(IBM_DB2_G(__ruby_stmt_err_msg), 0, DB2_MAX_ERR_MSG_LEN);
	memset(IBM_DB2_G(__ruby_stmt_err_state), 0, SQL_SQLSTATE_SIZE + 1);
}
/*  */

/*  static int _ruby_db2_connect_helper( argc, argv, isPersistent )
*/
static VALUE _ruby_db2_connect_helper( int argc, VALUE *argv, int isPersistent )
{
	char *database = NULL;
	char *uid = NULL;
	char *password = NULL;
	long database_len;
	long uid_len;
	long password_len;
	VALUE r_db, r_uid, r_passwd, options;
	int rc = 0;
	SQLINTEGER conn_alive;
	conn_handle *conn_res = NULL;
	int reused = 0;
	int hKeyLen = 0;
	char *hKey = NULL;
	VALUE entry = Qnil;

	SQLHANDLE pHenv = 0;
	conn_alive = 1;

	rb_scan_args(argc, argv, "31", &r_db, &r_uid, &r_passwd, &options);
	database = rb_str2cstr(r_db, &database_len);
	uid = rb_str2cstr(r_uid, &uid_len);
	password = rb_str2cstr(r_passwd, &password_len);

	do {
		/* Check if we already have a connection for this userID & database combination */
		if (isPersistent) {
			hKeyLen = strlen(database) + strlen(uid) + strlen(password) + 9;
			hKey = ALLOC_N(char, hKeyLen);

			sprintf(hKey, "__db2_%s.%s.%s", uid, database, password);

			if ( !NIL_P(entry=rb_hash_aref(persistent_list, rb_str_new2(hKey))) ) {
				Data_Get_Struct(entry, conn_handle, conn_res);

				/* Need to reinitialize connection? */
				rc = SQLGetConnectAttr(conn_res->hdbc, SQL_ATTR_PING_DB, (SQLPOINTER)&conn_alive, 0, NULL);
				if ( (rc == SQL_SUCCESS) && conn_alive ) {
					reused = 1;
				} /* else will re-connect since connection is dead */
			}
		} else {
			/* Need to check for max pconnections? */
		}
		if (conn_res == NULL) {
			conn_res = ALLOC(conn_handle);
		    memset(conn_res, 0, sizeof(conn_handle));
		}
		/* We need to set this early, in case we get an error below,
			so we know how to free the connection */
		conn_res->flag_pconnect = isPersistent;
		/* Allocate ENV handles if not present */
		if ( !conn_res->henv ) {
			rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &pHenv);
			if (rc != SQL_SUCCESS) {
				_ruby_db2_check_sql_errors( pHenv, SQL_HANDLE_ENV, rc, 1, NULL, -1, 1);
				break;
			}

			rc = SQLSetEnvAttr((SQLHENV)pHenv, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);

			conn_res->henv = pHenv;
		} else {
			pHenv = conn_res->henv;
		}

		if (! reused) {
			/* Alloc CONNECT Handle */
			rc = SQLAllocHandle( SQL_HANDLE_DBC, pHenv, &(conn_res->hdbc));
			if (rc != SQL_SUCCESS) {
				_ruby_db2_check_sql_errors(pHenv, SQL_HANDLE_ENV, rc, 1, NULL, -1, 1);
				break;
			}
		}

		/* Set this after the connection handle has been allocated to avoid
		unnecessary network flows. Initialize the structure to default values */
		conn_res->auto_commit = SQL_AUTOCOMMIT_ON;
		rc = SQLSetConnectAttr((SQLHDBC)conn_res->hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)(conn_res->auto_commit), SQL_NTS);

		conn_res->c_bin_mode = IBM_DB2_G(bin_mode);

		conn_res->error_recno_tracker = 1;
		conn_res->errormsg_recno_tracker = 1;

		/* handle not active as of yet */
		conn_res->handle_active = 0;

		/* Set Options */
		if ( !NIL_P(options) && !isPersistent ) {
			rc = _ruby_db2_parse_options( options, SQL_HANDLE_DBC, conn_res );
			if (rc != SQL_SUCCESS) {
				RUBY_ERROR("Options Array must have string indexes");
			}
		}

		if (! reused) {
			/* Connect */
			/* If the string contains a =, use SQLDriverConnect */
			if ( strstr(database, "=") != NULL ) {
				rc = SQLDriverConnect((SQLHDBC)conn_res->hdbc, (SQLHWND)NULL,
						(SQLCHAR*)database, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT );
			} else {
				rc = SQLConnect( (SQLHDBC)conn_res->hdbc, (SQLCHAR *)database,
						(SQLSMALLINT)database_len, (SQLCHAR *)uid, (SQLSMALLINT)uid_len,
						(SQLCHAR *)password, (SQLSMALLINT)password_len );
			}

			if ( rc != SQL_SUCCESS ) {
				_ruby_db2_check_sql_errors(conn_res->hdbc, SQL_HANDLE_DBC, rc, 1, NULL, -1, 1);
				break;
			}
		}
		conn_res->handle_active = 1;
	} while (0);

	if (hKey != NULL) {
		if (! reused && rc == SQL_SUCCESS) {
			/* If we created a new persistent connection, add it to the persistent_list */
            entry = Data_Wrap_Struct(le_pconn_struct,
                _ruby_db2_mark_pconn_struct, _ruby_db2_free_pconn_struct,
                conn_res);
            rb_hash_aset(persistent_list, rb_str_new2(hKey), entry);
		}
		efree(hKey);
	}

	if ( rc != SQL_SUCCESS ) {
		if (conn_res != NULL && conn_res->handle_active) {
			rc = SQLFreeHandle( SQL_HANDLE_DBC, conn_res->hdbc);
		}

		/* free memory */
		if (conn_res != NULL) {
			efree(conn_res);
		}

		return Qfalse;
	} else if (!NIL_P(entry)) {
		return entry;
	} else if (isPersistent) {
        return Data_Wrap_Struct(le_pconn_struct,
            _ruby_db2_mark_pconn_struct, _ruby_db2_free_pconn_struct,
            conn_res);
	} else {
        return Data_Wrap_Struct(le_conn_struct,
            _ruby_db2_mark_conn_struct, _ruby_db2_free_conn_struct,
            conn_res);
	}
}
/*  */

/*  static void _ruby_db2_clear_conn_err_cache ()
*/
static void _ruby_db2_clear_conn_err_cache()
{
	/* Clear out the cached conn messages */
	memset(IBM_DB2_G(__ruby_conn_err_msg), 0, DB2_MAX_ERR_MSG_LEN);
	memset(IBM_DB2_G(__ruby_conn_err_state), 0, SQL_SQLSTATE_SIZE + 1);
}
/*  */

/*  proto resource db2_connect(string database, string uid, string password [, array options])
Returns a connection to a database */
RUBY_FUNCTION(db2_connect)
{
	_ruby_db2_clear_conn_err_cache();

	return _ruby_db2_connect_helper( argc, argv, 0 );
}
/*  */

/*  proto resource db2_pconnect(string database_name, string username, string password [, array options])
Returns a persistent connection to a database */
RUBY_FUNCTION(db2_pconnect)
{
	_ruby_db2_clear_conn_err_cache();

	return _ruby_db2_connect_helper( argc, argv, 1);
}
/*  */

/*  proto mixed db2_autocommit(resource connection[, bool value])
Returns or sets the AUTOCOMMIT state for a database connection */
RUBY_FUNCTION(db2_autocommit)
{
	VALUE value;
	VALUE connection = Qnil;
	conn_handle *conn_res;
	int rc;
	SQLINTEGER autocommit;

	rb_scan_args(argc, argv, "11", &connection, &value);

	if (!NIL_P(connection)) {
		Data_Get_Struct(connection, conn_handle, conn_res);

		/* If value in handle is different from value passed in */
		if (argc == 2) {
			autocommit = FIX2INT(value);
			if(autocommit != (conn_res->auto_commit)) {
				rc = SQLSetConnectAttr((SQLHDBC)conn_res->hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)autocommit, SQL_IS_INTEGER);
				conn_res->auto_commit = autocommit;
			}
			return Qtrue;
		} else {
			return INT2NUM(conn_res->auto_commit);
		}
	}

	return Qnil;
}
/*  */

/*  static void _ruby_db2_add_param_cache( stmt_handle *stmt_res, int param_no, char *varname, int param_type, SQLSMALLINT data_type, SQLSMALLINT precision, SQLSMALLINT scale, SQLSMALLINT nullable )
*/
static void _ruby_db2_add_param_cache( stmt_handle *stmt_res, int param_no, char *varname, int varname_len, int param_type, SQLSMALLINT data_type, SQLUINTEGER precision, SQLSMALLINT scale, SQLSMALLINT nullable )
{
	param_node *tmp_curr = NULL, *prev = stmt_res->head_cache_list, *curr = stmt_res->head_cache_list;

	while ( (curr != NULL) && (curr->param_num < param_no) ) {
		prev = curr;
		curr = curr->next;
	}

	if ( curr == NULL || curr->param_num != param_no ) {
		/* Allocate memory and make new node to be added */
		tmp_curr = ALLOC(param_node);
		memset(tmp_curr, 0, sizeof(param_node));

		/* assign values */
		tmp_curr->data_type = data_type;
		tmp_curr->param_size = precision;
		tmp_curr->nullable = nullable;
		tmp_curr->scale = scale;
		tmp_curr->param_num = param_no;
		tmp_curr->file_options = SQL_FILE_READ;
		tmp_curr->param_type = param_type;

		/* Set this flag in stmt_res if a FILE INPUT is present */
		if ( param_type == DB2_PARAM_FILE) {
			stmt_res->file_param = 1;
		}

		if ( varname != NULL) {
			tmp_curr->varname = estrndup(varname, varname_len);
		}

		/* link pointers for the list */
		if ( prev == NULL ) {
			stmt_res->head_cache_list = tmp_curr;
		} else {
			prev->next = tmp_curr;
		}
		tmp_curr->next = curr;

		/* Increment num params added */
		stmt_res->num_params++;
	} else {
		/* Both the nodes are for the same param no */
		/* Replace Information */
		curr->data_type = data_type;
		curr->param_size = precision;
		curr->nullable = nullable;
		curr->scale = scale;
		curr->param_num = param_no;
		curr->file_options = SQL_FILE_READ;
		curr->param_type = param_type;

		/* Set this flag in stmt_res if a FILE INPUT is present */
		if ( param_type == DB2_PARAM_FILE) {
			stmt_res->file_param = 1;
		}

		/* Free and assign the variable name again */
		/* Var lengths can be variable and different in both cases. */
		/* This shouldn't happen often anyway */
		if ( varname != NULL) {
			efree(curr->varname);
			curr->varname = estrndup(varname, varname_len);
		}
	}
}
/*  */

/*  proto bool db2_bind_param(resource stmt, long param_no, string varname [, long param_type [, long data_type [, long precision [, long scale]]]])
Binds a PHP variable to an SQL statement parameter */
RUBY_FUNCTION(db2_bind_param)
{
	char *varname = NULL;
	long varname_len;
	long param_type = DB2_PARAM_IN;
	/* LONG types used for data being passed in */
	long param_no = 0;
	long data_type = 0;
	long precision = 0;
	long scale = 0;
	SQLSMALLINT sql_data_type = 0;
	SQLUINTEGER sql_precision = 0;
	SQLSMALLINT sql_scale = 0;
	SQLSMALLINT sql_nullable = SQL_NO_NULLS;

	VALUE stmt = Qnil;
	stmt_handle *stmt_res;
	int rc = 0;

	VALUE r_param_no, r_varname, r_param_type=Qnil;
	VALUE r_data_type=Qnil, r_precision=Qnil, r_scale=Qnil;
	rb_scan_args(argc, argv, "34", &stmt, &r_param_no, &r_varname, 
		&r_param_type, &r_data_type, &r_precision, &r_scale);
	param_no = NUM2INT(r_param_no);
	varname = rb_str2cstr(r_varname, &varname_len);
	if (!NIL_P(r_param_type)) param_type = NUM2LONG(r_param_type);
	if (!NIL_P(r_data_type)) data_type = NUM2LONG(r_data_type);
	if (!NIL_P(r_precision)) precision = NUM2LONG(r_precision);
	if (!NIL_P(r_scale)) scale = NUM2LONG(r_scale);

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);

		/* Check for Param options */
		switch (argc) {
			/* if argc == 3, then the default value for param_type will be used */
			case 3:
				param_type = DB2_PARAM_IN;
				/* Fall through */

			/* Otherwise, param_type will contain the value passed in */
			case 4:
			case 5:
			case 6:
				/* No param data specified */
				rc = SQLDescribeParam((SQLHSTMT)stmt_res->hstmt, param_no, &sql_data_type, &sql_precision, &sql_scale, &sql_nullable);
				if ( rc == SQL_ERROR ) {
					RUBY_ERROR("Describe Param Failed");
					return Qfalse;
				}
				/* Add to cache */
				_ruby_db2_add_param_cache( stmt_res, param_no, varname, varname_len, param_type, sql_data_type, sql_precision, sql_scale, sql_nullable );
				break;

			case 7:
				/* Cache param data passed */
				/* I am using a linked list of nodes here because I dont know before hand how many params are being passed in/bound. */
				/* To determine this, a call to SQLNumParams is necessary. This is take away any advantages an array would have over linked list access */
				/* Data is being copied over to the correct types for subsequent CLI call because this might cause problems on other platforms such as AIX */
				sql_data_type = (SQLSMALLINT)data_type;
				sql_precision = (SQLUINTEGER)precision;
				sql_scale = (SQLSMALLINT)scale;
				_ruby_db2_add_param_cache( stmt_res, param_no, varname, varname_len, param_type, sql_data_type, sql_precision, sql_scale, sql_nullable );
				break;

			default:
				/* WRONG_PARAM_COUNT; */
				return Qfalse;
		}
		/* end Switch */

		/* We bind data with DB2 CLI in db2_execute() */
		/* This will save network flow if we need to override params in it */

		return Qtrue;
	} else {
		return Qfalse;
	}
}
/*  */

/*  proto bool db2_close(resource connection)
Closes a database connection */
RUBY_FUNCTION(db2_close)
{
	VALUE connection = Qnil;
	conn_handle *conn_res;
	int rc;

	rb_scan_args(argc, argv, "1", &connection);

	if (!NIL_P(connection)) {
		/* Check to see if it's a persistent connection; if so, just return true */
		Data_Get_Struct(connection, conn_handle, conn_res);

		if ( conn_res->handle_active && !conn_res->flag_pconnect ) {
			/* Disconnect from DB. If stmt is allocated, it is freed automatically */
			if (conn_res->auto_commit == 0) {
				rc = SQLEndTran(SQL_HANDLE_DBC, (SQLHDBC)conn_res->hdbc, SQL_ROLLBACK);
				if ( rc == SQL_ERROR )
					return Qfalse;
			}
			rc = SQLDisconnect((SQLHDBC)conn_res->hdbc);
			if ( rc == SQL_ERROR ) {
				return Qfalse;
			}

			rc = SQLFreeHandle( SQL_HANDLE_DBC, conn_res->hdbc);
			if ( rc == SQL_ERROR ) {
				return Qfalse;
			}

			conn_res->handle_active = 0;

			return Qtrue;
		} else if ( conn_res->flag_pconnect ) {
			/* Do we need to call FreeStmt or something to close cursors? */
			return Qtrue;
		} else {
			return Qfalse;
		}
	} else {
		return Qfalse;
	}
}
/*  */

#if TODO
/*  proto resource db2_column_privileges(resource connection, string qualifier, string owner, string table_name, string column_name)
Returns a result set listing the columns and associated privileges for a table */
RUBY_FUNCTION(db2_column_privileges)
{
	char *qualifier = NULL;
	char *owner = NULL;
	char *table_name = NULL;
	char *column_name = NULL;
	int argc = ZEND_NUM_ARGS();
	int connection_id = -1;
	VALUE r_qualifier = Qnil;
	VALUE r_owner = Qnil;
	VALUE r_table_name = Qnil;
	VALUE r_column_name = Qnil;
	VALUE connection = Qnil;
	conn_handle *conn_res;
	stmt_handle *stmt_res;
	int rc;

	rb_scan_args(argc, argv, "14", &connection, 
		&r_qualifier, &r_owner, &r_table_name, &r_table_type);
	if (!NIL_P(r_qualifier)) qualifier=(SQLCHAR*)STR2CSTR(r_qualifier);
	if (!NIL_P(r_owner)) owner=(SQLCHAR*)STR2CSTR(r_owner);
	if (!NIL_P(r_table_name)) table_name=(SQLCHAR*)STR2CSTR(r_table_name);
	if (!NIL_P(r_column_name)) column_name=(SQLCHAR*)STR2CSTR(r_column_name);

	if (!NIL_P(connection)) {
		Data_Get_Struct(connection, conn_handle, conn_res);

		if (!conn_res) {
			RUBY_ERROR("Conn Resource cannot be found");
			return Qfalse;
		}

		stmt_res = _db2_new_stmt_struct(conn_res);

		rc = SQLAllocHandle(SQL_HANDLE_STMT, conn_res->hdbc, &(stmt_res->hstmt));
		if (rc == SQL_ERROR) {
			return Qfalse;
		}
		rc = SQLColumnPrivileges((SQLHSTMT)stmt_res->hstmt, qualifier, SQL_NTS,
						owner,SQL_NTS, table_name,SQL_NTS, column_name,SQL_NTS);
		if (rc == SQL_ERROR ) {
			return Qfalse;
		}
		ZEND_REGISTER_RESOURCE(return_value, stmt_res, le_stmt_struct);
	} else {
		return Qfalse;
	}
}
/*  */
#endif

/*  proto resource db2_columns(resource connection, string qualifier, string owner, string table_name, string column_name)
Returns a result set listing the columns and associated metadata for a table */
RUBY_FUNCTION(db2_columns)
{
	SQLCHAR *qualifier = NULL;
	SQLCHAR *owner = NULL;
	SQLCHAR *table_name = NULL;
	SQLCHAR *column_name = NULL;
	VALUE r_qualifier = Qnil;
	VALUE r_owner = Qnil;
	VALUE r_table_name = Qnil;
	VALUE r_column_name = Qnil;
	VALUE connection = Qnil;
	conn_handle *conn_res;
	stmt_handle *stmt_res;
	int rc;

	rb_scan_args(argc, argv, "14", &connection, 
		&r_qualifier, &r_owner, &r_table_name, &r_column_name);
	if (!NIL_P(r_qualifier)) qualifier=(SQLCHAR*)STR2CSTR(r_qualifier);
	if (!NIL_P(r_owner)) owner=(SQLCHAR*)STR2CSTR(r_owner);
	if (!NIL_P(r_table_name)) table_name=(SQLCHAR*)STR2CSTR(r_table_name);
	if (!NIL_P(r_column_name)) column_name=(SQLCHAR*)STR2CSTR(r_column_name);

	if (!NIL_P(connection)) {
		Data_Get_Struct(connection, conn_handle, conn_res);

		stmt_res = _db2_new_stmt_struct(conn_res);

		rc = SQLAllocHandle(SQL_HANDLE_STMT, conn_res->hdbc, &(stmt_res->hstmt));
		if (rc == SQL_ERROR) {
			return Qfalse;
		}
		rc = SQLColumns((SQLHSTMT)stmt_res->hstmt, qualifier, SQL_NTS,
						owner,SQL_NTS, table_name,SQL_NTS, column_name,SQL_NTS);
		if (rc == SQL_ERROR ) {
			return Qfalse;
		}
        return Data_Wrap_Struct(le_stmt_struct,
            _ruby_db2_mark_stmt_struct, _ruby_db2_free_stmt_struct,
            stmt_res);
	} else {
		return Qfalse;
	}
}
/*  */

#if TODO
/*  proto resource db2_foreign_keys(resource connection, string qualifier, string owner, string table_name, string column_name)
Returns a result set listing the foreign keys for a table */

RUBY_FUNCTION(db2_foreign_keys)
{
	char *qualifier = NULL;
	char *owner = NULL;
	char *table_name = NULL;
	int argc = ZEND_NUM_ARGS();
	int connection_id = -1;
	int qualifier_len;
	int owner_len;
	int table_name_len;
	zval *connection = NULL;
	conn_handle *conn_res;
	stmt_handle *stmt_res;
	int rc;


	if (zend_parse_parameters(argc TSRMLS_CC, "rsss", &connection, &qualifier,
		&qualifier_len, &owner, &owner_len, &table_name, &table_name_len) == FAILURE) {
		return;
	}

	if (connection) {
		ZEND_FETCH_RESOURCE2(conn_res, conn_handle*, &connection, connection_id,
			"Connection Resource", le_conn_struct, le_pconn_struct);

		stmt_res = _db2_new_stmt_struct(conn_res);

		rc = SQLAllocHandle(SQL_HANDLE_STMT, conn_res->hdbc, &(stmt_res->hstmt));
		if (rc == SQL_ERROR) {
			return Qfalse;
		}
		rc = SQLForeignKeys((SQLHSTMT)stmt_res->hstmt, qualifier, SQL_NTS,
						owner,SQL_NTS, table_name,SQL_NTS, "",SQL_NTS,
						"",SQL_NTS,"",SQL_NTS);
		if (rc == SQL_ERROR ) {
			return Qfalse;
		}
		ZEND_REGISTER_RESOURCE(return_value, stmt_res, le_stmt_struct);

	} else {
		return Qfalse;
	}
}
/*  */

/*  proto resource db2_primary_keys(resource connection, string qualifier, string owner, string table_name, string column_name)
Returns a result set listing primary keys for a table */

RUBY_FUNCTION(db2_primary_keys)
{
	char *qualifier = NULL;
	char *owner = NULL;
	char *table_name = NULL;
	int argc = ZEND_NUM_ARGS();
	int connection_id = -1;
	int qualifier_len;
	int owner_len;
	int table_name_len;
	zval *connection = NULL;
	conn_handle *conn_res;
	stmt_handle *stmt_res;
	int rc;

	if (zend_parse_parameters(argc TSRMLS_CC, "rsss", &connection, &qualifier,
		&qualifier_len, &owner, &owner_len, &table_name, &table_name_len) == FAILURE) {
		return;
	}

	if (connection) {
		ZEND_FETCH_RESOURCE2(conn_res, conn_handle*, &connection, connection_id,
			"Connection Resource", le_conn_struct, le_pconn_struct);

		stmt_res = _db2_new_stmt_struct(conn_res);

		rc = SQLAllocHandle(SQL_HANDLE_STMT, conn_res->hdbc, &(stmt_res->hstmt));
		if (rc == SQL_ERROR) {
			return Qfalse;
		}
		rc = SQLPrimaryKeys((SQLHSTMT)stmt_res->hstmt, qualifier, SQL_NTS,
						owner,SQL_NTS, table_name,SQL_NTS);
		if (rc == SQL_ERROR ) {
			return Qfalse;
		}
		ZEND_REGISTER_RESOURCE(return_value, stmt_res, le_stmt_struct);

	} else {
		return Qfalse;
	}
}
/*  */

/*  proto resource db2_procedure_columns(resource connection, string qualifier, string owner, string table_name, string column_name)
Returns a result set listing the input and output parameters for a stored procedure */
RUBY_FUNCTION(db2_procedure_columns)
{
	char *qualifier = NULL;
	char *owner = NULL;
	char *proc_name = NULL;
	char *column_name = NULL;
	int argc = ZEND_NUM_ARGS();
	int qualifier_len;
	int owner_len;
	int proc_name_len;
	int column_name_len;
	int connection_id = -1;
	int rc = 0;
	zval *connection = NULL;
	conn_handle *conn_res;
	stmt_handle *stmt_res;

	if (zend_parse_parameters(argc TSRMLS_CC, "rssss", &connection, &qualifier,
		&qualifier_len, &owner, &owner_len, &proc_name, &proc_name_len,
		&column_name, &column_name_len) == FAILURE) {
		return;
	}

	if (connection) {
		ZEND_FETCH_RESOURCE(conn_res, conn_handle*, &connection, connection_id, "Connection Resource", le_conn_struct);

		stmt_res = _db2_new_stmt_struct(conn_res);

		rc = SQLAllocHandle(SQL_HANDLE_STMT, conn_res->hdbc, &(stmt_res->hstmt));
		if (rc == SQL_ERROR) {
			return Qfalse;
		}
		rc = SQLProcedureColumns((SQLHSTMT)stmt_res->hstmt, qualifier, SQL_NTS, owner,
			SQL_NTS, proc_name, SQL_NTS, column_name, SQL_NTS);
		if (rc == SQL_ERROR ) {
			return Qfalse;
		}
		ZEND_REGISTER_RESOURCE(return_value, stmt_res, le_stmt_struct);
	} else {
		return Qfalse;
	}
}
/*  */

/*  proto resource db2_procedures(resource connection, string qualifier, string owner, string proc_name)
Returns a result set listing the stored procedures registered in a database */
RUBY_FUNCTION(db2_procedures)
{
	char *qualifier = NULL;
	char *owner = NULL;
	char *proc_name = NULL;
	int argc = ZEND_NUM_ARGS();
	int qualifier_len;
	int owner_len;
	int proc_name_len;
	zval *connection = NULL;
	int connection_id = -1;
	int rc = 0;
	conn_handle *conn_res;
	stmt_handle *stmt_res;

	if (zend_parse_parameters(argc TSRMLS_CC, "rsss", &connection, &qualifier,
		&qualifier_len, &owner, &owner_len, &proc_name, &proc_name_len) == FAILURE) {
		return;
	}

	if (connection) {
		ZEND_FETCH_RESOURCE(conn_res, conn_handle*, &connection, connection_id, "Connection Resource", le_conn_struct);

		stmt_res = _db2_new_stmt_struct(conn_res);

		rc = SQLAllocHandle(SQL_HANDLE_STMT, conn_res->hdbc, &(stmt_res->hstmt));
		if (rc == SQL_ERROR) {
			return Qfalse;
		}
		rc = SQLProcedures((SQLHSTMT)stmt_res->hstmt, qualifier, SQL_NTS, owner,
			SQL_NTS, proc_name, SQL_NTS);
		if (rc == SQL_ERROR ) {
			return Qfalse;
		}
		ZEND_REGISTER_RESOURCE(return_value, stmt_res, le_stmt_struct);
	} else {
		return Qfalse;
	}
}
/*  */

/*  proto resource db2_special_columns(resource connection, string qualifier, string owner, string table_name, string column_name)
Returns a result set listing the unique row identifier columns for a table */
RUBY_FUNCTION(db2_special_columns)
{
	char *qualifier = NULL;
	char *owner = NULL;
	char *table_name = NULL;
	int argc = ZEND_NUM_ARGS();
	int connection_id = -1;
	int qualifier_len;
	int owner_len;
	int table_name_len;
	int scope;
	zval *connection = NULL;
	conn_handle *conn_res;
	stmt_handle *stmt_res;
	int rc;

	if (zend_parse_parameters(argc TSRMLS_CC, "rsssl", &connection, &qualifier,
		&qualifier_len, &owner, &owner_len, &table_name, &table_name_len,&scope) == FAILURE) {
		return;
	}

	if (connection) {
		ZEND_FETCH_RESOURCE2(conn_res, conn_handle*, &connection, connection_id,
			"Connection Resource", le_conn_struct, le_pconn_struct);

		stmt_res = _db2_new_stmt_struct(conn_res);

		rc = SQLAllocHandle(SQL_HANDLE_STMT, conn_res->hdbc, &(stmt_res->hstmt));
		if (rc == SQL_ERROR) {
			return Qfalse;
		}
		rc = SQLSpecialColumns((SQLHSTMT)stmt_res->hstmt,SQL_BEST_ROWID, qualifier, SQL_NTS,
						owner,SQL_NTS, table_name,SQL_NTS,scope,SQL_NULLABLE);
		if (rc == SQL_ERROR ) {
			return Qfalse;
		}
		ZEND_REGISTER_RESOURCE(return_value, stmt_res, le_stmt_struct);

	} else {
		return Qfalse;
	}
}
/*  */

/*  proto resource db2_statistics(resource connection, string qualifier, string owner, string table_name, string column_name)
Returns a result set listing the index and statistics for a table */
RUBY_FUNCTION(db2_statistics)
{
char *qualifier = NULL;
	char *owner = NULL;
	char *table_name = NULL;
	int argc = ZEND_NUM_ARGS();
	int qualifier_len;
	int owner_len;
	int table_name_len;
	zend_bool unique;
	zval *connection = NULL;
	int connection_id = -1;
	int rc = 0;
	SQLUSMALLINT sql_unique;
	conn_handle *conn_res;
	stmt_handle *stmt_res;

	if (zend_parse_parameters(argc TSRMLS_CC, "rsssb", &connection, &qualifier,
		&qualifier_len, &owner, &owner_len, &table_name, &table_name_len,
		&unique) == FAILURE) {
		return;
	}

	if (connection) {
		ZEND_FETCH_RESOURCE(conn_res, conn_handle*, &connection, connection_id, "Connection Resource", le_conn_struct);

		stmt_res = _db2_new_stmt_struct(conn_res);
		sql_unique = unique;

		rc = SQLAllocHandle(SQL_HANDLE_STMT, conn_res->hdbc, &(stmt_res->hstmt));
		if (rc == SQL_ERROR) {
			return Qfalse;
		}
		rc = SQLStatistics((SQLHSTMT)stmt_res->hstmt, qualifier, SQL_NTS, owner,
			SQL_NTS, table_name, SQL_NTS, sql_unique, SQL_QUICK);
		if (rc == SQL_ERROR ) {
			return Qfalse;
		}
		ZEND_REGISTER_RESOURCE(return_value, stmt_res, le_stmt_struct);
	} else {
		return Qfalse;
	}
}
/*  */

/*  proto resource db2_table_privileges(resource connection, string qualifier, string owner, string table_name, string column_name)
Returns a result set listing the tables and associated privileges in a database */

RUBY_FUNCTION(db2_table_privileges)
{
	char *qualifier = NULL;
	char *owner = NULL;
	char *table_name = NULL;
	int argc = ZEND_NUM_ARGS();
	int connection_id = -1;
	int qualifier_len;
	int owner_len;
	int table_name_len;
	zval *connection = NULL;
	conn_handle *conn_res;
	stmt_handle *stmt_res;
	int rc;

	if (zend_parse_parameters(argc TSRMLS_CC, "r|sss", &connection, &qualifier,
		&qualifier_len, &owner, &owner_len, &table_name, &table_name_len) == FAILURE) {
		return;
	}

	if (connection) {
		ZEND_FETCH_RESOURCE2(conn_res, conn_handle*, &connection, connection_id,
			"Connection Resource", le_conn_struct, le_pconn_struct);
		if (!conn_res) {
			RUBY_ERROR("Conn Resource cannot be found");
			return Qfalse;
		}

		stmt_res = _db2_new_stmt_struct(conn_res);

		rc = SQLAllocHandle(SQL_HANDLE_STMT, conn_res->hdbc, &(stmt_res->hstmt));
		if (rc == SQL_ERROR) {
			return Qfalse;
		}
		rc = SQLTablePrivileges((SQLHSTMT)stmt_res->hstmt, qualifier, SQL_NTS, owner,
			SQL_NTS, table_name, SQL_NTS);
		if (rc == SQL_ERROR ) {
			return Qfalse;
		}
		ZEND_REGISTER_RESOURCE(return_value, stmt_res, le_stmt_struct);
	} else {
		return Qfalse;
	}
}
/*  */
#endif

/*  proto resource db2_tables(resource connection, string qualifier, string owner, string table_name, string table_type)
Returns a result set listing the tables and associated metadata in a database */
RUBY_FUNCTION(db2_tables)
{
	SQLCHAR *qualifier = NULL;
	SQLCHAR *owner = NULL;
	SQLCHAR *table_name = NULL;
	SQLCHAR *table_type = NULL;
	VALUE r_qualifier;
	VALUE r_owner;
	VALUE r_table_name;
	VALUE r_table_type;
	VALUE connection = Qnil;
	conn_handle *conn_res;
	stmt_handle *stmt_res;
	int rc;

	rb_scan_args(argc, argv, "14", &connection, 
		&r_qualifier, &r_owner, &r_table_name, &r_table_type);
	if (!NIL_P(r_qualifier)) qualifier=(SQLCHAR*)STR2CSTR(r_qualifier);
	if (!NIL_P(r_owner)) owner=(SQLCHAR*)STR2CSTR(r_owner);
	if (!NIL_P(r_table_name)) table_name=(SQLCHAR*)STR2CSTR(r_table_name);
	if (!NIL_P(r_table_type)) table_type=(SQLCHAR*)STR2CSTR(r_table_type);

	if (!NIL_P(connection)) {
		Data_Get_Struct(connection, conn_handle, conn_res);

		stmt_res = _db2_new_stmt_struct(conn_res);

		rc = SQLAllocHandle(SQL_HANDLE_STMT, conn_res->hdbc, &(stmt_res->hstmt));
		if (rc == SQL_ERROR) {
			return Qfalse;
		}
		rc = SQLTables((SQLHSTMT)stmt_res->hstmt, qualifier, SQL_NTS, owner,
			SQL_NTS, table_name, SQL_NTS, table_type, SQL_NTS);
		if (rc == SQL_ERROR ) {
			return Qfalse;
		}
        return Data_Wrap_Struct(le_stmt_struct,
            _ruby_db2_mark_stmt_struct, _ruby_db2_free_stmt_struct,
            stmt_res);
	} else {
		return Qfalse;
	}
}
/*  */

/*  proto bool db2_commit(resource connection)
Commits a transaction */
RUBY_FUNCTION(db2_commit)
{
	VALUE connection = Qnil;
	conn_handle *conn_res;
	int rc;

	rb_scan_args(argc, argv, "1", &connection);

	if (!NIL_P(connection)) {
		Data_Get_Struct(connection, conn_handle, conn_res);

		rc = SQLEndTran(SQL_HANDLE_DBC, conn_res->hdbc, SQL_COMMIT);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			return Qtrue;
		}
	}

	return Qfalse;
}
/*  */

/*  static int _ruby_db2_do_prepare(SQLHANDLE hdbc, string stmt_string, stmt_handle *stmt_res, zval *options)
*/
static int _ruby_db2_do_prepare(SQLHANDLE hdbc, VALUE stmt_string, stmt_handle *stmt_res, VALUE options)
{
	int rc;

	/* alloc handle and return only if it errors */
	rc = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &(stmt_res->hstmt));
	if ( rc < SQL_SUCCESS ) {
		_ruby_db2_check_sql_errors(hdbc, SQL_HANDLE_DBC, rc, 1, NULL, -1, 1);
		return rc;
	}

	if (!NIL_P(options)) {
		rc = _ruby_db2_parse_options( options, SQL_HANDLE_STMT, stmt_res );
		if ( rc == SQL_ERROR ) {
			RUBY_ERROR("Options Array must have string indexes");
		}
	}

	/* Prepare the stmt. The cursor type requested has already been set in _ruby_db2_assign_options */
	rc = SQLPrepare((SQLHSTMT)stmt_res->hstmt, (SQLCHAR*)STR2CSTR(stmt_string), SQL_NTS);
	if ( rc == SQL_ERROR ) {
		_ruby_db2_check_sql_errors(stmt_res->hstmt, SQL_HANDLE_STMT, rc, 1, NULL, -1, 1);
	}

	return rc;
}
/*  */

/*  static int _ruby_db2_execute_stmt(conn_handle *conn_res, stmt_handle *stmt_res)
*/
static int _ruby_db2_execute_stmt(stmt_handle *stmt_res)
{
	int rc;

	rc = SQLExecute((SQLHSTMT)stmt_res->hstmt);
	if ( rc == SQL_ERROR ) {
		_ruby_db2_check_sql_errors(stmt_res->hstmt, SQL_HANDLE_STMT, rc, 1, NULL, -1, 1);
	}

	return rc;
}
/*  */

/*  proto resource db2_exec(resource connection, string stmt_string [, array options])
Executes an SQL statement directly */
RUBY_FUNCTION(db2_exec)
{
	VALUE stmt = Qnil;
	VALUE connection = Qnil;
	VALUE options = Qnil;
	stmt_handle *stmt_res;
	conn_handle *conn_res;
	int rc;

	/* This function basically is a wrap of the _ruby_db2_do_prepare and _ruby_db2_execute_stmt */
	/* After completing statement execution, it returns the statement resource */
	rb_scan_args(argc, argv, "21", &connection, &stmt, &options);

	if (!NIL_P(connection)) {
		Data_Get_Struct(connection, conn_handle, conn_res);

		_ruby_db2_clear_stmt_err_cache();

		stmt_res = _db2_new_stmt_struct(conn_res);

		/* Allocates the stmt handle */
		/* Prepares the statement */
		/* returns the stat_handle back to the calling function */
		rc = _ruby_db2_do_prepare(conn_res->hdbc, stmt, stmt_res, options);
		if ( rc < SQL_SUCCESS ) {
			efree(stmt_res);
			RUBY_ERROR("Statement Prepare Failed");
			return Qfalse;
		}

		rc = _ruby_db2_execute_stmt(stmt_res);
		if ( rc < SQL_SUCCESS ) {
			efree(stmt_res);
			RUBY_ERROR("Statement Execute Failed");
			return Qfalse;
		}

        return Data_Wrap_Struct(le_stmt_struct,
            _ruby_db2_mark_stmt_struct, _ruby_db2_free_stmt_struct,
            stmt_res);
	}

	return Qnil;
}
/*  */

/*  proto bool db2_free_result(resource)
Frees resources associated with a result set */
RUBY_FUNCTION(db2_free_result)
{
	VALUE stmt = Qnil;
	stmt_handle *stmt_res;
	int rc = 0;

	rb_scan_args(argc, argv, "1", &stmt);

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);
		if ( stmt_res->hstmt ) {
			rc = SQLFreeHandle( SQL_HANDLE_STMT, stmt_res->hstmt);
			stmt_res->hstmt = 0;
		}
		_ruby_db2_free_result_struct(stmt_res);
	}
	return Qtrue;
}
/*  */

/*  proto resource db2_prepare(resource connection, string stmt_string [, array options])
Prepares an SQL statement */
RUBY_FUNCTION(db2_prepare)
{
	VALUE stmt = Qnil;
	VALUE connection = Qnil;
	VALUE options = Qnil;
	conn_handle *conn_res;
	stmt_handle *stmt_res;
	int rc;

	rb_scan_args(argc, argv, "21", &connection, &stmt, &options);

	if (!NIL_P(connection)) {
		Data_Get_Struct(connection, conn_handle, conn_res);

		_ruby_db2_clear_stmt_err_cache();

		/* Initialize stmt resource members with default values. */
		/* Parsing will update options if needed */

		stmt_res = _db2_new_stmt_struct(conn_res);

		/* Allocates the stmt handle */
		/* Prepares the statement */
		/* returns the stat_handle back to the calling function */
		rc = _ruby_db2_do_prepare(conn_res->hdbc, stmt, stmt_res, options);
		if ( rc < SQL_SUCCESS ) {
			RUBY_ERROR("Statement Prepare Failed");
			return Qfalse;
		}

        return Data_Wrap_Struct(le_stmt_struct,
            _ruby_db2_mark_stmt_struct, _ruby_db2_free_stmt_struct,
            stmt_res);
	}

	return Qnil;
}
/*  */

/*  static param_node* build_list( stmt_res, param_no, data_type, precision, scale, nullable )
*/
static param_node* build_list( stmt_handle *stmt_res, int param_no, SQLSMALLINT data_type, SQLUINTEGER precision, SQLSMALLINT scale, SQLSMALLINT nullable )
{
	param_node *tmp_curr = NULL, *curr = stmt_res->head_cache_list, *prev = NULL;

	/* Allocate memory and make new node to be added */
	tmp_curr = ALLOC(param_node);
	memset(tmp_curr,0,sizeof(param_node));
	/* assign values */
	tmp_curr->data_type = data_type;
	tmp_curr->param_size = precision;
	tmp_curr->nullable = nullable;
	tmp_curr->scale = scale;
	tmp_curr->param_num = param_no;
	tmp_curr->file_options = SQL_FILE_READ;
	tmp_curr->param_type = DB2_PARAM_IN;

	while ( curr != NULL ) {
		prev = curr;
		curr = curr->next;
	}

	if (stmt_res->head_cache_list == NULL) {
		stmt_res->head_cache_list = tmp_curr;
	} else {
		prev->next = tmp_curr;
	}

	tmp_curr->next = curr;

	return tmp_curr;
}
/*  */

/*  static int _ruby_db2_bind_data( stmt_handle *stmt_res, param_node *curr, zval **bind_data )
*/
static int _ruby_db2_bind_data( stmt_handle *stmt_res, param_node *curr, VALUE *bind_data)
{
	int rc;
	SQLSMALLINT valueType;
	SQLPOINTER	paramValuePtr;

	/* Have to use SQLBindFileToParam if PARAM is type DB2_PARAM_FILE */
	if ( curr->param_type == DB2_PARAM_FILE) {
		/* Only string types can be bound */
		if ( TYPE(*bind_data) != T_STRING) {
			return SQL_ERROR;
		}

		curr->bind_indicator = 0;
		curr->svalue = rb_str2cstr(*bind_data, &curr->ivalue);
		valueType = curr->ivalue;
		/* Bind file name string */
		rc = SQLBindFileToParam((SQLHSTMT)stmt_res->hstmt, curr->param_num,
			curr->data_type, (SQLCHAR*)curr->svalue,
			(SQLSMALLINT*)&(curr->ivalue), &(curr->file_options), 
			curr->ivalue, &(curr->bind_indicator));

		return rc;
	}

	switch(TYPE(*bind_data)) {
		case T_FIXNUM:
			curr->ivalue = FIX2INT(*bind_data);
			rc = SQLBindParameter(stmt_res->hstmt, curr->param_num,
				curr->param_type, SQL_C_LONG, curr->data_type,
				curr->param_size, curr->scale, &curr->ivalue, 0, NULL);
			curr->data_type = SQL_C_LONG;
			break;

		/* Convert BOOLEAN types to LONG for DB2 / Cloudscape */
		case T_FALSE:
			curr->ivalue = 0;
			rc = SQLBindParameter(stmt_res->hstmt, curr->param_num,
				curr->param_type, SQL_C_LONG, curr->data_type, curr->param_size,
				curr->scale, &curr->ivalue, 0, NULL);
			curr->data_type = SQL_C_LONG;
			break;

		case T_TRUE:
			curr->ivalue = 1;
			rc = SQLBindParameter(stmt_res->hstmt, curr->param_num,
				curr->param_type, SQL_C_LONG, curr->data_type, curr->param_size,
				curr->scale, &curr->ivalue, 0, NULL);
			curr->data_type = SQL_C_LONG;
			break;

		case T_FLOAT:
			curr->fvalue = NUM2DBL(*bind_data);
			rc = SQLBindParameter(stmt_res->hstmt, curr->param_num,
				curr->param_type, SQL_C_DOUBLE, curr->data_type, curr->param_size,
				curr->scale, &curr->fvalue, 0, NULL);
			curr->data_type = SQL_C_DOUBLE;
			break;

		case T_STRING:
			curr->svalue = rb_str2cstr(*bind_data, &curr->ivalue);
			curr->svalue = memcpy(ALLOC_N(char, curr->ivalue+1), curr->svalue, curr->ivalue);
			curr->svalue[curr->ivalue] = '\0';
			switch ( curr->data_type ) {
				case SQL_CLOB:
					curr->bind_indicator = SQL_DATA_AT_EXEC;
					valueType = SQL_C_CHAR;
					/* The correct dataPtr will be set during SQLPutData with the len from this struct */
					paramValuePtr = (SQLPOINTER)curr;
					break;

				case SQL_BLOB:
					curr->bind_indicator = SQL_DATA_AT_EXEC;
					valueType = SQL_C_BINARY;
					paramValuePtr = (SQLPOINTER)curr;
					break;

				case SQL_BINARY:
				case SQL_LONGVARBINARY:
				case SQL_VARBINARY:
					/* account for bin_mode settings as well */
					curr->bind_indicator = SQL_NTS;
					valueType = SQL_C_BINARY;
					paramValuePtr = (SQLPOINTER)(curr->svalue);
					break;

				/* This option should handle most other types such as DATE, VARCHAR etc */
				default:
					valueType = SQL_C_CHAR;
					curr->bind_indicator = SQL_NTS;
					paramValuePtr = (SQLPOINTER)(curr->svalue);
			}

			rc = SQLBindParameter(stmt_res->hstmt, curr->param_num,
				curr->param_type, valueType, curr->data_type, curr->param_size,
				curr->scale, paramValuePtr, curr->ivalue, &(curr->bind_indicator));
			curr->data_type = valueType;
			break;

		case T_NIL:
			curr->ivalue = SQL_NULL_DATA;
			rc = SQLBindParameter(stmt_res->hstmt, curr->param_num,
				curr->param_type, SQL_C_DEFAULT, curr->data_type, curr->param_size,
				curr->scale, &curr->ivalue, 0, &curr->ivalue);
			break;

		default:
			return SQL_ERROR;
	}
	return rc;
}
/*  */

/*  static int _ruby_db2_execute_helper(stmt_res, data, int bind_cmp_list)
	*/
static int _ruby_db2_execute_helper(stmt_handle *stmt_res, VALUE *data, int bind_cmp_list, int bind_params)
{
	int rc=SQL_SUCCESS;
	param_node *curr = NULL;	/* To traverse the list */
	VALUE bind_data;			/* Data value from symbol table */

	/* Used in call to SQLDescribeParam if needed */
	int param_no;
	SQLSMALLINT data_type;
	SQLUINTEGER precision;
	SQLSMALLINT scale;
	SQLSMALLINT nullable;

	/* This variable means that we bind the complete list of params cached */
	/* The values used are fetched from the active symbol table */
	/* TODO: Enhance this part to check for stmt_res->file_param */
	/* If this flag is set, then use SQLBindParam, else use SQLExtendedBind */
	if ( bind_cmp_list ) {
		/* Bind the complete list sequentially */
		/* Used when no parameters array is passed in */
		curr = stmt_res->head_cache_list;

		while (curr != NULL ) {
			/* Fetch data from symbol table */
			bind_data = rb_eval_string(curr->varname);
			rc = _ruby_db2_bind_data( stmt_res, curr, &bind_data);
			if ( rc == SQL_ERROR ) {
				RUBY_ERROR("Binding Error 1");
				return rc;
			}
			curr = curr->next;
		}

		return 0;
	} else {
		/* Bind only the data value passed in to the Current Node */
		if ( data != NULL ) {
			if ( bind_params ) {

				/*
					This condition applies if the parameter has not been
					bound using db2_bind_param. Need to describe the
					parameter and then bind it.
				*/

				param_no = ++stmt_res->num_params;

				rc = SQLDescribeParam((SQLHSTMT)stmt_res->hstmt, param_no,
					(SQLSMALLINT*)&data_type, &precision, (SQLSMALLINT*)&scale,
					(SQLSMALLINT*)&nullable);
				if ( rc == SQL_ERROR ) {
					RUBY_ERROR("Describe Param Failed");
					return rc;
				}

				curr = build_list( stmt_res, param_no, data_type, precision, scale, nullable );

				rc = _ruby_db2_bind_data( stmt_res, curr, data);
				if ( rc == SQL_ERROR ) {
					RUBY_ERROR("Binding Error 2");
					return rc;
				}
			} else {
				/*
					This is always at least the head_cache_node -- assigned in
					db2_execute(), if params have been bound.
				*/
				curr = stmt_res->current_node;

				if ( curr != NULL ) {
					rc = _ruby_db2_bind_data( stmt_res, curr, data);
					if ( rc == SQL_ERROR ) {
						RUBY_ERROR("Binding Error 2");
						return rc;
					}
					stmt_res->current_node = curr->next;
				}
			}
			return rc;
		}
	}
	return rc;
}
/*  */

void var_assign(char *name, VALUE value) {
#if TODO
	// this compiles and links, but fails to resolve at runtime
    rb_dvar_asgn(rb_intern(name_id), value);
#else
	// so we do it the hard way
    ID inspect;	
	char *expr, *statement;
	long expr_len;

    inspect = rb_intern("inspect");
	value = rb_funcall(value, inspect, 0);

	expr = rb_str2cstr(value, &expr_len);
	statement = ALLOC_N(char, strlen(name)+1+expr_len+1);
	strcpy(statement, name);
	strcat(statement, "=");
	strcat(statement, expr);
	rb_eval_string(statement);
	free(statement);
#endif
}

/*  proto bool db2_execute(resource stmt [, array parameters_array])
Executes a prepared SQL statement */
RUBY_FUNCTION(db2_execute)
{
	VALUE stmt = Qnil;
	VALUE parameters_array = Qnil;
	stmt_handle *stmt_res;
	int rc, numOpts, i, bind_params = 0;
	SQLSMALLINT num;
	SQLPOINTER valuePtr;

	/* This is used to loop over the param cache */
	param_node *tmp_curr, *prev_ptr, *curr_ptr;

	VALUE data;

	rb_scan_args(argc, argv, "11", &stmt, &parameters_array);

	/* Get values from symbol tables */
	/* Assign values into param nodes */
	/* Check types/conversions */
	/* Bind parameters */
	/* Execute */
	/* Return values back to symbol table for OUT params */

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);

		/* Free any cursors that might have been allocated in a previous call to SQLExecute */
		SQLFreeStmt((SQLHSTMT)stmt_res->hstmt, SQL_CLOSE);

		/* This ensures that each call to db2_execute start from scratch */
		stmt_res->current_node = stmt_res->head_cache_list;

		rc = SQLNumParams((SQLHSTMT)stmt_res->hstmt, (SQLSMALLINT*)&num);

		if ( num != 0 ) {
			/* Parameter Handling */
			if ( !NIL_P(parameters_array) ) {
				/* Make sure db2_bind_param has been called */
				/* If the param list is NULL -- ERROR */
				if ( stmt_res->head_cache_list == NULL ) {
					bind_params = 1;
				}

				if (TYPE(parameters_array) != T_ARRAY) {
					RUBY_ERROR("Param not an array");
					return Qfalse;
				}

				numOpts = RARRAY(parameters_array)->len;
				if (numOpts > num) {
					/* More are passed in -- Warning - Use the max number present */
					RUBY_ERROR("Param count incorrect");
					numOpts = stmt_res->num_params;
				} else if (numOpts < num) {
					/* If there are less params passed in, than are present -- Error */
					RUBY_ERROR("Param count incorrect");
					return Qfalse;
				}

				for ( i = 0; i < numOpts; i++) {
					/* Bind values from the parameters_array to params */
					data = rb_ary_entry(parameters_array,i);

					/*
						The 0 denotes that you work only with the current node.
						The 4th argument specifies whether the data passed in
						has been described. So we need to call SQLDescribeParam
						before binding depending on this.
					*/
					rc = _ruby_db2_execute_helper(stmt_res, &data, 0, bind_params);
					if ( rc == SQL_ERROR) {
						RUBY_ERROR("Binding Error");
						return Qfalse;
					}
				}
			} else {
				/* No additional params passed in. Use values already bound. */
				if ( num > stmt_res->num_params ) {
					/* More parameters than we expected */
					RUBY_ERROR("More parameters bound than present");
				} else if ( num < stmt_res->num_params ) {
					/* Fewer parameters than we expected */
					RUBY_ERROR("Less parameters bound than present");
					return Qfalse;
				}

				/* Param cache node list is empty -- No params bound */
				if ( stmt_res->head_cache_list == NULL ) {
					RUBY_ERROR("Parameters not bound");
					return Qfalse;
				} else {
					/* The 1 denotes that you work with the whole list */
					/* And bind sequentially */
					rc = _ruby_db2_execute_helper(stmt_res, NULL, 1, 0);
					if ( rc == SQL_ERROR ) {
						RUBY_ERROR("Binding Error 3");
						return Qfalse;
					}
				}
			}
		} else {
			/* No Parameters */
			/* We just execute the statement. No additional work needed. */
			rc = SQLExecute((SQLHSTMT)stmt_res->hstmt);
			if ( rc == SQL_ERROR ) {
				RUBY_ERROR("Statement Execute Failed");
				return Qfalse;
			}
			return Qtrue;
		}

		/* Execute Stmt -- All parameters bound */
		rc = SQLExecute((SQLHSTMT)stmt_res->hstmt);
		if ( rc == SQL_ERROR ) {
			RUBY_ERROR("Statement Execute Failed");
			return Qfalse;
		}

		if ( rc == SQL_NEED_DATA ) {
			while ( (SQLParamData((SQLHSTMT)stmt_res->hstmt, (SQLPOINTER *)&valuePtr)) == SQL_NEED_DATA ) {
				/* passing data value for a parameter */
				rc = SQLPutData((SQLHSTMT)stmt_res->hstmt, (SQLPOINTER)(((param_node*)valuePtr)->svalue), ((param_node*)valuePtr)->ivalue);
				if ( rc == SQL_ERROR ) {
					RUBY_ERROR("Sending data failed");
					return Qfalse;
				}
			}
		}

		/* cleanup dynamic bindings if present */
		if ( bind_params == 1 ) {
			/* Free param cache list */
			curr_ptr = stmt_res->head_cache_list;
			prev_ptr = stmt_res->head_cache_list;

			while (curr_ptr != NULL) {
				curr_ptr = curr_ptr->next;

				/* Free Values */
				if ( prev_ptr->svalue) {
					efree(prev_ptr->svalue);
				}

				efree(prev_ptr);

				prev_ptr = curr_ptr;
			}

			stmt_res->head_cache_list = NULL;
			stmt_res->num_params = 0;
		} else {
			/* Bind the IN/OUT Params back into the active symbol table */
			tmp_curr = stmt_res->head_cache_list;
			while (tmp_curr != NULL) {
				switch(tmp_curr->param_type) {
					case DB2_PARAM_OUT:
					case DB2_PARAM_INOUT:
						if( (tmp_curr->bind_indicator != SQL_NULL_DATA 
							 && tmp_curr->bind_indicator != SQL_NO_TOTAL )){
	                        switch (tmp_curr->data_type) {
		                        case SQL_SMALLINT:
		                        case SQL_INTEGER:
		                        case SQL_BIGINT:
								    var_assign(tmp_curr->varname, INT2NUM(tmp_curr->ivalue));
			                        break;
		                        case SQL_REAL:
		                        case SQL_FLOAT:
		                        case SQL_DOUBLE:
		                        case SQL_DECIMAL:
		                        case SQL_NUMERIC:
								    var_assign(tmp_curr->varname, rb_float_new(tmp_curr->fvalue));
			                        break;
		                        default:
								    var_assign(tmp_curr->varname, rb_str_new2(tmp_curr->svalue));
			                        break;
	                        }

				        }
					default:
					    break;
				}
				tmp_curr = tmp_curr->next;
			}
		}

		if ( rc != SQL_ERROR ) {
			return Qtrue;
		}
	}

	return Qnil;
}
/*  */

/*  proto string db2_conn_errormsg([resource connection])
Returns a string containing the last connection error message */
RUBY_FUNCTION(db2_conn_errormsg)
{
	VALUE connection = Qnil;
	conn_handle *conn_res;
	char* return_str = NULL;	/* This variable is used by _ruby_db2_check_sql_errors to return err strings */

	rb_scan_args(argc, argv, "01", &connection);

	if (!NIL_P(connection)) {
		Data_Get_Struct(connection, conn_handle, conn_res);
		return_str = ALLOC_N(char, SQL_SQLSTATE_SIZE + 1);

		_ruby_db2_check_sql_errors(conn_res->hdbc, SQL_HANDLE_DBC, -1, 0, return_str, DB2_ERRMSG, conn_res->errormsg_recno_tracker);
		if(conn_res->errormsg_recno_tracker - conn_res->error_recno_tracker >= 1)
			conn_res->error_recno_tracker = conn_res->errormsg_recno_tracker;
		conn_res->errormsg_recno_tracker++;

		return rb_str_new2(return_str);
	} else {
		return rb_str_new2(IBM_DB2_G(__ruby_conn_err_msg));
	}
}
/*  */

/*  proto string db2_stmt_errormsg([resource stmt])
Returns a string containing the last SQL statement error message */
RUBY_FUNCTION(db2_stmt_errormsg)
{
	VALUE stmt = Qnil;
	stmt_handle *stmt_res;
	char* return_str = NULL; /* This variable is used by _ruby_db2_check_sql_errors to return err strings */

	rb_scan_args(argc, argv, "01", &stmt);

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);

		return_str = ALLOC_N(char, DB2_MAX_ERR_MSG_LEN);
		memset(return_str, 0, DB2_MAX_ERR_MSG_LEN);

		_ruby_db2_check_sql_errors(stmt_res->hstmt, SQL_HANDLE_STMT, -1, 0, return_str, DB2_ERRMSG, stmt_res->errormsg_recno_tracker);
		if(stmt_res->errormsg_recno_tracker - stmt_res->error_recno_tracker >= 1)
			stmt_res->error_recno_tracker = stmt_res->errormsg_recno_tracker;
		stmt_res->errormsg_recno_tracker++;

		return rb_str_new2(return_str);
	} else {
		return rb_str_new2(IBM_DB2_G(__ruby_stmt_err_msg));
	}
}
/*  */

/*  proto string db2_conn_error([resource connection])
Returns a string containing the SQLSTATE returned by the last connection attempt */
RUBY_FUNCTION(db2_conn_error)
{
	VALUE connection = Qnil;
	conn_handle *conn_res;

	char *return_str = NULL; /* This variable is used by _ruby_db2_check_sql_errors to return err strings */
	rb_scan_args(argc, argv, "01", &connection);

	if (!NIL_P(connection)) {
		Data_Get_Struct(connection, conn_handle, conn_res);
		return_str = ALLOC_N(char, SQL_SQLSTATE_SIZE + 1);
		memset(return_str, 0, SQL_SQLSTATE_SIZE + 1);

		_ruby_db2_check_sql_errors(conn_res->hdbc, SQL_HANDLE_DBC, -1, 0, return_str, DB2_ERR, conn_res->error_recno_tracker);
		if (conn_res->error_recno_tracker - conn_res->errormsg_recno_tracker >= 1) {
			conn_res->errormsg_recno_tracker = conn_res->error_recno_tracker;
		}
		conn_res->error_recno_tracker++;

		return rb_str_new2(return_str);
	} else {
		return rb_str_new2(IBM_DB2_G(__ruby_conn_err_state));
	}
}
/*  */

/*  proto string db2_stmt_error([resource stmt])
Returns a string containing the SQLSTATE returned by an SQL statement */
RUBY_FUNCTION(db2_stmt_error)
{
	VALUE stmt = Qnil;
	stmt_handle *stmt_res;
	char* return_str = NULL; /* This variable is used by _ruby_db2_check_sql_errors to return err strings */

	rb_scan_args(argc, argv, "01", &stmt);

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);

		return_str = ALLOC_N(char, DB2_MAX_ERR_MSG_LEN);
		memset(return_str, 0, DB2_MAX_ERR_MSG_LEN);

		_ruby_db2_check_sql_errors(stmt_res->hstmt, SQL_HANDLE_STMT, -1, 0, return_str, DB2_ERR, stmt_res->error_recno_tracker);

		if (stmt_res->error_recno_tracker - stmt_res->errormsg_recno_tracker >= 1) {
			stmt_res->errormsg_recno_tracker = stmt_res->error_recno_tracker;
		}
		stmt_res->error_recno_tracker++;

		return rb_str_new2(return_str);
	} else {
		return rb_str_new2(IBM_DB2_G(__ruby_stmt_err_state));
	}
}
/*  */

/*  proto resource db2_next_result(resource stmt)
Requests the next result set from a stored procedure */
RUBY_FUNCTION(db2_next_result)
{
	VALUE stmt = Qnil;
	stmt_handle *stmt_res, *new_stmt_res=NULL;
	int rc = 0;
	SQLHANDLE new_hstmt;

	rb_scan_args(argc, argv, "01", &stmt);

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);

		_ruby_db2_clear_stmt_err_cache();

		/* alloc handle and return only if it errors */
		rc = SQLAllocHandle(SQL_HANDLE_STMT, stmt_res->hdbc, &new_hstmt);
		if ( rc < SQL_SUCCESS ) {
			_ruby_db2_check_sql_errors(stmt_res->hdbc, SQL_HANDLE_DBC, rc, 1, NULL, -1, 1);
			return Qfalse;
		}
		rc = SQLNextResult((SQLHSTMT)stmt_res->hstmt, (SQLHSTMT)new_hstmt);
		if( rc != SQL_SUCCESS ) {
			if(rc < SQL_SUCCESS) {
				_ruby_db2_check_sql_errors(stmt_res->hstmt, SQL_HANDLE_STMT, rc, 1, NULL, -1, 1);
			}
			SQLFreeHandle(SQL_HANDLE_STMT, new_hstmt);
			return Qfalse;
		}

		/* Initialize stmt resource members with default values. */
		/* Parsing will update options if needed */
		new_stmt_res = ALLOC(stmt_handle);
		memset(new_stmt_res, 0, sizeof(stmt_handle));
		new_stmt_res->s_bin_mode = stmt_res->s_bin_mode;
		new_stmt_res->cursor_type = stmt_res->cursor_type;
		new_stmt_res->s_case_mode = stmt_res->s_case_mode;
		new_stmt_res->head_cache_list = NULL;
		new_stmt_res->current_node = NULL;
		new_stmt_res->num_params = 0;
		new_stmt_res->file_param = 0;
		new_stmt_res->column_info = NULL;
		new_stmt_res->num_columns = 0;
		new_stmt_res->row_data = NULL;
		new_stmt_res->hstmt = new_hstmt;
		new_stmt_res->hdbc = stmt_res->hdbc;

        return Data_Wrap_Struct(le_stmt_struct,
            _ruby_db2_mark_stmt_struct, _ruby_db2_free_stmt_struct,
            new_stmt_res);
	} else {
		return Qfalse;
	}
}
/*  */

/*  proto int db2_num_fields(resource stmt)
Returns the number of fields contained in a result set */
RUBY_FUNCTION(db2_num_fields)
{
	VALUE stmt = Qnil;
	stmt_handle *stmt_res;
	int rc = 0;
	SQLSMALLINT indx = 0;

	rb_scan_args(argc, argv, "1", &stmt);

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);

		rc = SQLNumResultCols((SQLHSTMT)stmt_res->hstmt, &indx);
		if ( rc == SQL_ERROR ) {
			RUBY_ERROR("SQLNumResultCols failed");
			return Qfalse;
		}

		return INT2NUM(indx);
	}

	return Qnil;
}
/*  */

/*  proto int db2_num_rows(resource stmt)
Returns the number of rows affected by an SQL statement */
RUBY_FUNCTION(db2_num_rows)
{
	VALUE stmt = Qnil;
	stmt_handle *stmt_res;
	int rc = 0;
	SQLINTEGER count = 0;

	rb_scan_args(argc, argv, "1", &stmt);

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);

		rc = SQLRowCount((SQLHSTMT)stmt_res->hstmt, &count);
		if ( rc == SQL_ERROR ) {
			RUBY_ERROR("SQLRowCount failed");
			return Qfalse;
		}

		return INT2NUM(count);
	}

	return Qnil;
}
/*  */

/*  static int _ruby_db2_get_column_by_name(stmt_handle *stmt_res, char *col_name, int col)
	*/
static int _ruby_db2_get_column_by_name(stmt_handle *stmt_res, char *col_name, int col)
{
	int i;
	/* get column header info*/
	if ( stmt_res->column_info == NULL ) {
		if (_ruby_db2_get_result_set_info(stmt_res)<0) {
			return -1;
		}
	}
	if ( col_name == NULL ) {
		if ( col >= 0 && col < stmt_res->num_columns) {
			return col;
		} else {
			return -1;
		}
	}
	/* should start from 0 */
	i=0;
	while (i < stmt_res->num_columns) {
		if (strcmp((char*)stmt_res->column_info[i].name,col_name) == 0) {
			return i;
		}
		i++;
	}
	return -1;
}
/*  */

/*  proto string db2_field_name(resource stmt, mixed column)
Returns the name of the column in the result set */
RUBY_FUNCTION(db2_field_name)
{
	VALUE stmt = Qnil;
	VALUE column = Qnil;
	stmt_handle* stmt_res = NULL;
	char *col_name = NULL;
	int col = -1;

	rb_scan_args(argc, argv, "2", &stmt, &column);

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);
	}
	if ( TYPE(column)==T_FIXNUM ) {
		col = FIX2LONG(column);
	} else if (RTEST(column)) {
		col_name = STR2CSTR(column);
	}
	col = _ruby_db2_get_column_by_name(stmt_res,col_name, col);
	if ( col < 0 ) {
		return Qfalse;
	}
	return rb_str_new2((char*)stmt_res->column_info[col].name);
}
/*  */

/*  proto long db2_field_display_size(resource stmt, mixed column)
Returns the maximum number of bytes required to display a column */
RUBY_FUNCTION(db2_field_display_size)
{
	VALUE stmt = Qnil;
	VALUE column = Qnil;
	int col =- 1;
	char *col_name = NULL;
	stmt_handle *stmt_res = NULL;
	int rc;
	SQLINTEGER colDataDisplaySize;

	rb_scan_args(argc, argv, "2", &stmt, &column);

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);
	}
	if ( TYPE(column)==T_FIXNUM ) {
		col = FIX2LONG(column);
	} else if (RTEST(column)) {
		col_name = STR2CSTR(column);
	}
	col = _ruby_db2_get_column_by_name(stmt_res,col_name, col);
	if ( col < 0 ) {
		return Qfalse;
	}
	rc = SQLColAttribute((SQLHSTMT)stmt_res->hstmt,(SQLSMALLINT)col+1,
			SQL_DESC_DISPLAY_SIZE,NULL,0, NULL,&colDataDisplaySize);
	if ( rc < SQL_SUCCESS ) {
		return Qfalse;
	}
	return INT2NUM(colDataDisplaySize);
}
/*  */

/*  proto long db2_field_num(resource stmt, mixed column)
Returns the position of the named column in a result set */
RUBY_FUNCTION(db2_field_num)
{
	VALUE stmt = Qnil;
	VALUE column = Qnil;
	stmt_handle* stmt_res = NULL;
	char *col_name = NULL;
	int col = -1;

	rb_scan_args(argc, argv, "2", &stmt, &column);

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);
	}
	if ( TYPE(column)==T_FIXNUM ) {
		col = FIX2LONG(column);
	} else if (RTEST(column)) {
		col_name = STR2CSTR(column);
	}
	col = _ruby_db2_get_column_by_name(stmt_res,col_name, col);
	if ( col < 0 ) {
		return Qfalse;
	}
	return INT2NUM(col);
}
/*  */

#if TODO
/*  proto long db2_field_precision(resource stmt, mixed column)
Returns the precision for the indicated column in a result set */
RUBY_FUNCTION(db2_field_precision)
{
	int argc = ZEND_NUM_ARGS();
	int stmt_id = -1;
	zval *stmt = NULL;
	zval *column = NULL;
	stmt_handle* stmt_res = NULL;
	char *col_name = NULL;
	int col = -1;

	if (zend_parse_parameters(argc TSRMLS_CC, "rz", &stmt, &column) == FAILURE) {
		return;
	}

	if ( stmt ) {
		ZEND_FETCH_RESOURCE(stmt_res, stmt_handle*, &stmt, stmt_id, "Statement Resource", le_stmt_struct);
	}
	if ( Z_TYPE_P(column)==IS_LONG ) {
		col = Z_LVAL_P(column);
	} else {
		col_name = Z_STRVAL_P(column);
	}
	col = _ruby_db2_get_column_by_name(stmt_res,col_name, col);
	if ( col < 0 ) {
		return Qfalse;
	}
	return INT2NUM(stmt_res->column_info[col].size);

}
/*  */

/*  proto long db2_field_scale(resource stmt, mixed column)
Returns the scale of the indicated column in a result set */
RUBY_FUNCTION(db2_field_scale)
{
	int argc = ZEND_NUM_ARGS();
	int stmt_id = -1;
	zval *stmt = NULL;
	zval *column = NULL;
	stmt_handle* stmt_res = NULL;
	char *col_name = NULL;
	int col = -1;

	if (zend_parse_parameters(argc TSRMLS_CC, "rz", &stmt, &column) == FAILURE) {
		return;
	}

	if ( stmt ) {
		ZEND_FETCH_RESOURCE(stmt_res, stmt_handle*, &stmt, stmt_id, "Statement Resource", le_stmt_struct);
	}
	if ( Z_TYPE_P(column)==IS_LONG ) {
		col = Z_LVAL_P(column);
	} else {
		col_name = Z_STRVAL_P(column);
	}
	col = _ruby_db2_get_column_by_name(stmt_res,col_name, col);
	if ( col < 0 ) {
		return Qfalse;
	}
	return INT2NUM(stmt_res->column_info[col].scale);
}
/*  */
#endif

/*  proto string db2_field_type(resource stmt, mixed column)
Returns the data type of the indicated column in a result set */
RUBY_FUNCTION(db2_field_type)
{
	VALUE stmt = Qnil;
	VALUE column = Qnil;
	stmt_handle* stmt_res = NULL;
	char *col_name = NULL;
	char *str_val = "";
	int col = -1;

	rb_scan_args(argc, argv, "2", &stmt, &column);

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);
	}
	if ( TYPE(column)==T_FIXNUM ) {
		col = FIX2LONG(column);
	} else if (RTEST(column)) {
		col_name = STR2CSTR(column);
	}
	col = _ruby_db2_get_column_by_name(stmt_res,col_name, col);
	if ( col < 0 ) {
		return Qfalse;
	}
	col = _ruby_db2_get_column_by_name(stmt_res,col_name, col);
	if ( col < 0 ) {
		return Qfalse;
	}
	switch (stmt_res->column_info[col].type) {
		case SQL_SMALLINT:
		case SQL_INTEGER:
		case SQL_BIGINT:
			str_val = "int";
			break;
		case SQL_REAL:
		case SQL_FLOAT:
		case SQL_DOUBLE:
		case SQL_DECIMAL:
		case SQL_NUMERIC:
			str_val = "real";
			break;
		case SQL_CLOB:
		case SQL_BLOB:
			str_val = "blob";
			break;
		default:
			str_val = "string";
			break;
	}
	return rb_str_new2(str_val);
}
/*  */

/*  proto long db2_field_width(resource stmt, mixed column)
Returns the width of the current value of the indicated column in a result set */
RUBY_FUNCTION(db2_field_width)
{
	VALUE stmt = Qnil;
	VALUE column = Qnil;
	int col=-1;
	char *col_name = NULL;
	stmt_handle *stmt_res = NULL;
	int rc;
	SQLINTEGER colDataSize;

	rb_scan_args(argc, argv, "2", &stmt, &column);

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);
	}
	if ( TYPE(column)==T_FIXNUM ) {
		col = FIX2LONG(column);
	} else if (RTEST(column)) {
		col_name = STR2CSTR(column);
	}
	col = _ruby_db2_get_column_by_name(stmt_res,col_name, col);
	if ( col < 0 ) {
		return Qfalse;
	}
	rc = SQLColAttribute((SQLHSTMT)stmt_res->hstmt,(SQLSMALLINT)col+1,
			SQL_DESC_LENGTH,NULL,0, NULL,&colDataSize);
	if ( rc != SQL_SUCCESS ) {
		return Qfalse;
	}
	return INT2NUM(colDataSize);
}
/*  */

#if TODO
/*  proto long db2_cursor_type(resource stmt)
Returns the cursor type used by the indicated statement resource */
RUBY_FUNCTION(db2_cursor_type)
{
	int argc = ZEND_NUM_ARGS();
	zval *stmt = NULL;
	stmt_handle *stmt_res = NULL;
	int stmt_id = -1;

	if (zend_parse_parameters(argc TSRMLS_CC, "r", &stmt) == FAILURE) {
		return;
	}

	if (stmt) {
		ZEND_FETCH_RESOURCE(stmt_res, stmt_handle*, &stmt, stmt_id, "Statement Resource", le_stmt_struct);
	}

	return INT2NUM(stmt_res->cursor_type != DB2_FORWARD_ONLY);
}
/*  */
#endif

/*  proto bool db2_rollback(resource connection)
Rolls back a transaction */
RUBY_FUNCTION(db2_rollback)
{
	VALUE connection = Qnil;
	conn_handle *conn_res;
	int rc;

	rb_scan_args(argc, argv, "1", &connection);

	if (!NIL_P(connection)) {
		Data_Get_Struct(connection, conn_handle, conn_res);

		rc = SQLEndTran(SQL_HANDLE_DBC, conn_res->hdbc, SQL_ROLLBACK);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			return Qtrue;
		}
	}

	return Qfalse;
}
/*  */

#if TODO
/*  proto bool db2_free_stmt(resource stmt)
Frees resources associated with the indicated statement resource */
RUBY_FUNCTION(db2_free_stmt)
{
	int argc = ZEND_NUM_ARGS();
	zval *stmt = NULL;
	int stmt_id = -1, rc = 0;
	stmt_handle *stmt_res;

	if (zend_parse_parameters(argc TSRMLS_CC, "r", &stmt) == FAILURE) {
		return Qfalse;
	}

	if (stmt) {
		ZEND_FETCH_RESOURCE(stmt_res, stmt_handle*, &stmt, stmt_id, "Statement Resource", le_stmt_struct);

		rc = SQLFreeStmt((SQLHSTMT)stmt_res->hstmt, SQL_CLOSE);
		rc = SQLFreeHandle( SQL_HANDLE_STMT, stmt_res->hstmt);

		return Qtrue;
	} else {
		return Qfalse;
	}
}
/*  */
#endif

/*  static RETCODE _ruby_db2_get_data(stmt_handle *stmt_res, int col_num, short ctype, void *buff, int in_length, SQLINTEGER *out_length) */
static RETCODE _ruby_db2_get_data(stmt_handle *stmt_res, int col_num, short ctype, void *buff, int in_length, SQLINTEGER *out_length)
{
	RETCODE rc=SQL_SUCCESS;

	rc = SQLGetData((SQLHSTMT)stmt_res->hstmt, col_num, ctype, buff, in_length, out_length);
	return rc;
}
/*  */

/*  proto mixed db2_result(resource stmt, mixed column)
Returns a single column from a row in the result set */
RUBY_FUNCTION(db2_result)
{
	VALUE stmt = Qnil;
	VALUE column = Qnil;
	stmt_handle *stmt_res;
	long col_num;
	RETCODE rc;
	void	*out_ptr;
	char	*out_char_ptr;
	SQLINTEGER in_length, out_length=-10; /*Initialize out_length to some meaningless value*/
	SQLSMALLINT column_type, lob_bind_type= SQL_C_BINARY;
	double double_val;
	long long_val;
	VALUE return_value = Qnil;

	rb_scan_args(argc, argv, "2", &stmt, &column);

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);

		if(TYPE(column) == T_STRING) {
			col_num = _ruby_db2_get_column_by_name(stmt_res, STR2CSTR(column), -1);
		} else {
			col_num = NUM2INT(column);
		}

		/* get column header info*/
		if ( stmt_res->column_info == NULL ) {
			if (_ruby_db2_get_result_set_info(stmt_res)<0) {
				RUBY_ERROR("Column information cannot be retrieved");
				return Qfalse;
			}
		}

		if(col_num < 0 || col_num >= stmt_res->num_columns) {
			RUBY_ERROR("Column ordinal out of range");
		}

		/* get the data */
		column_type = stmt_res->column_info[col_num].type;
		switch(column_type) {
			case SQL_CHAR:
			case SQL_VARCHAR:
			case SQL_LONGVARCHAR:
			case SQL_TYPE_DATE:
			case SQL_TYPE_TIME:
			case SQL_TYPE_TIMESTAMP:
			case SQL_BIGINT:
			case SQL_DECIMAL:
			case SQL_NUMERIC:
				in_length = stmt_res->column_info[col_num].size+1;
				out_ptr = (SQLPOINTER)ALLOC_N(char,in_length);
				if ( out_ptr == NULL ) {
					RUBY_ERROR("Cannot Allocate Memory");
					return Qfalse;
				}
				rc = _ruby_db2_get_data(stmt_res, col_num+1, SQL_C_CHAR, out_ptr, in_length, &out_length);
				if ( rc == SQL_ERROR ) {
					return Qfalse;
				}
				if (out_length == SQL_NULL_DATA) {
					return Qnil;
				} else {
					return_value = rb_str_new2((char*)out_ptr);
					efree(out_ptr);
					return return_value;
				}
				break;

			case SQL_SMALLINT:
			case SQL_INTEGER:
				rc = _ruby_db2_get_data(stmt_res, col_num+1, SQL_C_SLONG, &long_val, sizeof(long_val), &out_length);
				if ( rc == SQL_ERROR ) {
					return Qfalse;
				}
				if (out_length == SQL_NULL_DATA) {
					return Qnil;
				} else {
					return INT2NUM(long_val);
				}
				break;

			case SQL_REAL:
			case SQL_FLOAT:
			case SQL_DOUBLE:
				rc = _ruby_db2_get_data(stmt_res, col_num+1, SQL_C_DOUBLE, &double_val, sizeof(double_val), &out_length);
				if ( rc == SQL_ERROR ) {
					return Qfalse;
				}
				if (out_length == SQL_NULL_DATA) {
					return Qnil;
				} else {
					return rb_float_new(double_val);
				}
				break;

			case SQL_CLOB:
				rc = _ruby_db2_get_data(stmt_res, col_num+1, SQL_C_CHAR, NULL, 0, (SQLINTEGER *)&in_length);
				if ( rc == SQL_ERROR ) {
					return Qfalse;
				}
				if (out_length == SQL_NULL_DATA) {
					return Qnil;
				}
				out_char_ptr = (char*)ALLOC_N(char,in_length+1);
				if ( out_char_ptr == NULL ) {
					RUBY_ERROR("Cannot Allocate Memory for LOB Data");
					return Qfalse;
				}
				rc = _ruby_db2_get_data(stmt_res, col_num+1, SQL_C_CHAR, (void*)out_char_ptr, in_length+1, &out_length);
				if (rc == SQL_ERROR) {
					return Qfalse;
				}

				out_char_ptr[in_length] = '\0';
				return rb_str_new2(out_char_ptr);
				break;

			case SQL_BLOB:
			case SQL_BINARY:
			case SQL_LONGVARBINARY:
			case SQL_VARBINARY:
				rc = _ruby_db2_get_data(stmt_res, col_num+1, SQL_C_BINARY, NULL, 0, (SQLINTEGER *)&in_length);
				if ( rc == SQL_ERROR ) {
					return Qfalse;
				}
				if (in_length == SQL_NULL_DATA) {
					return Qnil;
				}

				switch (stmt_res->s_bin_mode) {
					case DB2_PASSTHRU:
						return rb_str_new("",0);
						break;
						/* returns here */
					case DB2_CONVERT:
						in_length *= 2;
						lob_bind_type = SQL_C_CHAR;
						/* fall-through */

					case DB2_BINARY:

						out_ptr = (SQLPOINTER)ALLOC_N(char,in_length);
						if ( out_ptr == NULL ) {
							RUBY_ERROR("Cannot Allocate Memory for LOB Data");
							return Qfalse;
						}
						rc = _ruby_db2_get_data(stmt_res, col_num+1, lob_bind_type, out_ptr, in_length, &out_length);
						if (rc == SQL_ERROR) {
							return Qfalse;
						}
						return rb_str_new((char*)out_ptr,out_length);
					default:
						break;
				}
				break;
			default:
				break;
		}
	} else {
		/* throw error? */
		/* do the same in all APIs*/
	}

	return Qfalse;
}
/*  */

/*  static void _ruby_db2_bind_fetch_helper(INTERNAL_FUNCTION_PARAMETERS, int op)
*/
static VALUE _ruby_db2_bind_fetch_helper(int argc, VALUE *argv, int op)
{
	int rc = -1, i;
	long row_number=-1;
	VALUE stmt = Qnil;
	stmt_handle *stmt_res = NULL;
	SQLSMALLINT column_type, lob_bind_type = SQL_C_BINARY;
	db2_row_data_type *row_data;
	SQLINTEGER out_length, tmp_length;
	unsigned char *out_ptr;
	VALUE return_value = Qnil;

	VALUE r_row_number = Qnil;
	rb_scan_args(argc, argv, "11", &stmt, &r_row_number);
	if (!NIL_P(r_row_number)) row_number = NUM2LONG(r_row_number);

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);
    }

	_ruby_db2_init_error_info(stmt_res);

	/* get column header info*/
	if ( stmt_res->column_info == NULL ) {
		if (_ruby_db2_get_result_set_info(stmt_res)<0) {
			RUBY_ERROR("Column information cannot be retrieved");
			return Qfalse;
		}
	}
	/* bind the data */
	if ( stmt_res->row_data == NULL ) {
		rc = _ruby_db2_bind_column_helper(stmt_res);
		if ( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO ) {
			RUBY_ERROR("Column binding cannot be done");
			return Qfalse;
		}
	}
	/* check if row_number is present */
	if (argc == 2 && row_number > 0) {
		rc = SQLFetchScroll((SQLHSTMT)stmt_res->hstmt, SQL_FETCH_ABSOLUTE, row_number);
	} else if (argc == 2 && row_number < 0) {
		RUBY_ERROR("Requested row number must be a positive value");
		return Qfalse;
	} else {
		/*row_number is NULL or 0; just fetch next row*/
		rc = SQLFetch((SQLHSTMT)stmt_res->hstmt);
	}

	if (rc == SQL_NO_DATA_FOUND) {
		return Qfalse;
	} else if ( rc != SQL_SUCCESS ) {
		RUBY_ERROR("Fetch Failure");
		return Qfalse;
	}
	/* copy the data over return_value */
	if ( op & DB2_FETCH_ASSOC ) {
	    return_value = rb_hash_new();
	} else if ( op == DB2_FETCH_INDEX ) {
	    return_value = rb_ary_new();
	}

	for (i=0; i<stmt_res->num_columns; i++) {
		column_type = stmt_res->column_info[i].type;
		row_data = &stmt_res->row_data[i].data;
		out_length = stmt_res->row_data[i].out_length;

		switch(stmt_res->s_case_mode) {
			case DB2_CASE_LOWER:
				stmt_res->column_info[i].name = (SQLCHAR*)strtolower((char*)stmt_res->column_info[i].name, strlen((char*)stmt_res->column_info[i].name));
				break;
			case DB2_CASE_UPPER:
				stmt_res->column_info[i].name = (SQLCHAR*)strtoupper((char*)stmt_res->column_info[i].name, strlen((char*)stmt_res->column_info[i].name));
				break;
			case DB2_CASE_NATURAL:
			default:
				break;
		}

		if (out_length == SQL_NULL_DATA) {
			if ( op & DB2_FETCH_ASSOC ) {
				rb_hash_aset(return_value, rb_str_new2((char*)stmt_res->column_info[i].name), Qnil);
			}
			if ( op == DB2_FETCH_INDEX ) {
				rb_ary_store(return_value, i, Qnil);
			} else if ( op == DB2_FETCH_BOTH ) {
				rb_hash_aset(return_value, INT2NUM(i), Qnil);
			}
		} else {
			switch(column_type) {
				case SQL_CHAR:
				case SQL_VARCHAR:
				case SQL_LONGVARCHAR:
				case SQL_TYPE_DATE:
				case SQL_TYPE_TIME:
				case SQL_TYPE_TIMESTAMP:
				case SQL_BIGINT:
				case SQL_DECIMAL:
				case SQL_NUMERIC:

					if ( op & DB2_FETCH_ASSOC ) {
						rb_hash_aset(return_value, rb_str_new2((char*)stmt_res->column_info[i].name), rb_str_new2(row_data->str_val));
					}
					if ( op == DB2_FETCH_INDEX ) {
						rb_ary_store(return_value, i, rb_str_new2(row_data->str_val));
					} else if ( op == DB2_FETCH_BOTH ) {
						rb_hash_aset(return_value, INT2NUM(i), rb_str_new2(row_data->str_val));
					}
					break;
				case SQL_SMALLINT:
					if ( op & DB2_FETCH_ASSOC ) {
						rb_hash_aset(return_value, rb_str_new2((char*)stmt_res->column_info[i].name), INT2NUM(row_data->s_val));
					}
					if ( op == DB2_FETCH_INDEX ) {
						rb_ary_store(return_value, i, INT2NUM(row_data->s_val));
					} else if ( op == DB2_FETCH_BOTH ) {
						rb_hash_aset(return_value, INT2NUM(i), INT2NUM(row_data->s_val));
					}
					break;
				case SQL_INTEGER:
					if ( op & DB2_FETCH_ASSOC ) {
						rb_hash_aset(return_value, rb_str_new2((char*)stmt_res->column_info[i].name), INT2NUM(row_data->i_val));
					}
					if ( op == DB2_FETCH_INDEX ) {
						rb_ary_store(return_value, i, INT2NUM(row_data->i_val));
					} else if ( op == DB2_FETCH_BOTH ) {
						rb_hash_aset(return_value, INT2NUM(i), INT2NUM(row_data->i_val));
					}
					break;

				case SQL_REAL:
				case SQL_FLOAT:
					if ( op & DB2_FETCH_ASSOC ) {
						rb_hash_aset(return_value, rb_str_new2((char*)stmt_res->column_info[i].name), rb_float_new(row_data->f_val));
					}
					if ( op == DB2_FETCH_INDEX ) {
						rb_ary_store(return_value, i, rb_float_new(row_data->f_val));
					} else if ( op == DB2_FETCH_BOTH ) {
						rb_hash_aset(return_value, INT2NUM(i), rb_float_new(row_data->f_val));
					}
					break;

				case SQL_DOUBLE:
					if ( op & DB2_FETCH_ASSOC ) {
						rb_hash_aset(return_value, rb_str_new2((char*)stmt_res->column_info[i].name), rb_float_new(row_data->d_val));
					}
					if ( op == DB2_FETCH_INDEX ) {
						rb_ary_store(return_value, i, rb_float_new(row_data->d_val));
					} else if ( op == DB2_FETCH_BOTH ) {
						rb_hash_aset(return_value, INT2NUM(i), rb_float_new(row_data->d_val));
					}
					break;

				case SQL_BINARY:
				case SQL_LONGVARBINARY:
				case SQL_VARBINARY:
					if ( stmt_res->s_bin_mode == DB2_PASSTHRU ) {
						if ( op & DB2_FETCH_ASSOC ) {
							rb_hash_aset(return_value, rb_str_new2((char*)stmt_res->column_info[i].name), rb_str_new("",0));
						}
						if ( op == DB2_FETCH_INDEX ) {
							rb_ary_store(return_value, i, rb_str_new("",0));
						} else if ( op == DB2_FETCH_BOTH ) {
							rb_hash_aset(return_value, INT2NUM(i), rb_str_new("",0));
						}
					} else {
						if ( op & DB2_FETCH_ASSOC ) {
							rb_hash_aset(return_value, rb_str_new2((char*)stmt_res->column_info[i].name), rb_str_new2(row_data->str_val));
						}
						if ( op == DB2_FETCH_INDEX ) {
							rb_ary_store(return_value, i, rb_str_new2(row_data->str_val));
						} else if ( op == DB2_FETCH_BOTH ) {
							rb_hash_aset(return_value, INT2NUM(i), rb_str_new2(row_data->str_val));
						}
					}
					break;

				case SQL_BLOB:
					out_ptr = NULL;
					rc = _ruby_db2_get_data(stmt_res, i+1, SQL_C_BINARY, NULL, 0, (SQLINTEGER *)&tmp_length);
					if ( rc == SQL_ERROR ) {
							RUBY_ERROR("Cannot Determine LOB Size");
							return Qfalse;
					}

					if (tmp_length == SQL_NULL_DATA) {
						if ( op & DB2_FETCH_ASSOC ) {
							rb_hash_aset(return_value, rb_str_new2((char*)stmt_res->column_info[i].name), Qnil);
						}
						if ( op == DB2_FETCH_INDEX ) {
							rb_ary_store(return_value, i, Qnil);
						} else if ( op == DB2_FETCH_BOTH ) {
							rb_hash_aset(return_value, INT2NUM(i), Qnil);
						}
					} else {
						switch (stmt_res->s_bin_mode) {
							case DB2_PASSTHRU:
								if ( op & DB2_FETCH_ASSOC ) {
										rb_hash_aset(return_value, rb_str_new2((char*)stmt_res->column_info[i].name), Qnil);
								}
								if ( op == DB2_FETCH_INDEX ) {
										rb_ary_store(return_value, i, Qnil);
								} else if ( op == DB2_FETCH_BOTH ) {
										rb_hash_aset(return_value, INT2NUM(i), Qnil);
								}
								break;

							case DB2_CONVERT:
								tmp_length = 2*tmp_length + 1;
								lob_bind_type = SQL_C_CHAR;
								/* fall-through */

							case DB2_BINARY:
								out_ptr = (SQLPOINTER)ALLOC_N(char, tmp_length);

								if ( out_ptr == NULL ) {
									RUBY_ERROR("Cannot Allocate Memory for LOB Data");
									return Qfalse;
								}
								rc = _ruby_db2_get_data(stmt_res, i+1, lob_bind_type, out_ptr, tmp_length, &out_length);
								if (rc == SQL_ERROR) {
									return Qfalse;
								}

								if ( op & DB2_FETCH_ASSOC ) {
									rb_hash_aset(return_value, rb_str_new2((char*)stmt_res->column_info[i].name), rb_str_new((char*)out_ptr, out_length));
								}
								if ( op == DB2_FETCH_INDEX ) {
									rb_ary_store(return_value, i, rb_str_new((char*)out_ptr, out_length));
								} else if ( op == DB2_FETCH_BOTH ) {
									rb_hash_aset(return_value, INT2NUM(i), rb_str_new((char*)out_ptr, out_length));
								}
								break;
							default:
								break;
						}
					}
					break;

				case SQL_CLOB:
					out_ptr = NULL;
					rc = _ruby_db2_get_data(stmt_res, i+1, SQL_C_CHAR, NULL, 0, (SQLINTEGER *)&tmp_length);
					if ( rc == SQL_ERROR ) {
							RUBY_ERROR("Cannot Determine LOB Size");
							return Qfalse;
					}

					if (tmp_length == SQL_NULL_DATA) {
						if ( op & DB2_FETCH_ASSOC ) {
							rb_hash_aset(return_value, rb_str_new2((char*)stmt_res->column_info[i].name), Qnil);
						}
						if ( op == DB2_FETCH_INDEX ) {
							rb_ary_store(return_value, i, Qnil);
						} else if ( op == DB2_FETCH_BOTH ) {
							rb_hash_aset(return_value, INT2NUM(i), Qnil);
						}
					} else {
						out_ptr = (SQLPOINTER)ALLOC_N(char, tmp_length+1);

						if ( out_ptr == NULL ) {
							RUBY_ERROR("Cannot Allocate Memory for LOB Data");
							return Qfalse;
						}

						rc = _ruby_db2_get_data(stmt_res, i+1, SQL_C_CHAR, out_ptr, tmp_length+1, &out_length);
						if (rc == SQL_ERROR) {
							return Qfalse;
						}
						out_ptr[tmp_length] = '\0';

						if ( op & DB2_FETCH_ASSOC ) {
							rb_hash_aset(return_value, rb_str_new2((char*)stmt_res->column_info[i].name), rb_str_new((char*)out_ptr, tmp_length+1));
						}
						if ( op == DB2_FETCH_INDEX ) {
							rb_ary_store(return_value, i, rb_str_new((char*)out_ptr, tmp_length+1));
						} else if ( op == DB2_FETCH_BOTH ) {
							rb_hash_aset(return_value, INT2NUM(i), rb_str_new((char*)out_ptr, tmp_length+1));
						}
					}
					break;

				default:
					break;
			}
		}
	}

	return return_value;
}
/*  */

/*  proto bool db2_fetch_row(resource stmt [, int row_number])
Sets the fetch pointer to the next or requested row in a result set */
RUBY_FUNCTION(db2_fetch_row)
{
	VALUE row_number;
	VALUE stmt = Qnil;
	stmt_handle* stmt_res = NULL;
	int rc;

	rb_scan_args(argc, argv, "11", &stmt, &row_number);

	if (!NIL_P(stmt)) {
		Data_Get_Struct(stmt, stmt_handle, stmt_res);
	}

	/*check if row_number is present*/
	if (argc == 2 && NUM2LONG(row_number) > 0) {
		rc = SQLFetchScroll((SQLHSTMT)stmt_res->hstmt, SQL_FETCH_ABSOLUTE, NUM2LONG(row_number));
	} else if (argc == 2 && NUM2LONG(row_number) < 0) {
		RUBY_ERROR("Requested row number must be a positive value");
		return Qfalse;
	} else {
		/*row_number is NULL or 0; just fetch next row*/
		rc = SQLFetch((SQLHSTMT)stmt_res->hstmt);
	}

	if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) {
		return Qtrue;
	} else {
		return Qfalse;
	}
}
/*  */

/*  proto array db2_fetch_assoc(resource stmt [, int row_number])
Returns an array, indexed by column name, representing a row in a result set */
RUBY_FUNCTION(db2_fetch_assoc)
{
	return _ruby_db2_bind_fetch_helper(argc, argv, DB2_FETCH_ASSOC);
}
/*  */

/*  proto object db2_fetch_object(resource stmt [, int row_number])
Returns an object with properties that correspond to the fetched row */
RUBY_FUNCTION(db2_fetch_object)
{
	row_hash_struct *row_res;

	row_res = ALLOC(row_hash_struct);
	row_res->hash = _ruby_db2_bind_fetch_helper(argc, argv, DB2_FETCH_ASSOC);

	if (RTEST(row_res->hash)) {
        return Data_Wrap_Struct(le_row_struct,
            _ruby_db2_mark_row_struct, _ruby_db2_free_row_struct,
            row_res);
	} else {
		free(row_res);
		return Qfalse;
	}
}
/*  */

/*  proto array db2_fetch_array(resource stmt [, int row_number])
Returns an array, indexed by column position, representing a row in a result set */
RUBY_FUNCTION(db2_fetch_array)
{
	return _ruby_db2_bind_fetch_helper(argc, argv, DB2_FETCH_INDEX);
}
/*  */

/*  proto array db2_fetch_both(resource stmt [, int row_number])
Returns an array, indexed by both column name and position, representing a row in a result set */
RUBY_FUNCTION(db2_fetch_both)
{
	return _ruby_db2_bind_fetch_helper(argc, argv, DB2_FETCH_BOTH);
}
/*  */

#if TODO
/*  proto bool db2_set_option(resource stmt, array options, int type)
Sets the specified option in the resource. TYPE field specifies the resource type (1 = Connection) */
RUBY_FUNCTION(db2_set_option)
{
	int argc = ZEND_NUM_ARGS();
	int id = -1;
	zval *resc = NULL;
	zval *options;
	stmt_handle *stmt_res;
	conn_handle *conn_res;
	int rc = 0;
	long type;

	if (zend_parse_parameters(argc TSRMLS_CC, "ral", &resc, &options, &type) == FAILURE) {
		return;
	}

	if (resc) {
		if ( type == 1 ) {
			ZEND_FETCH_RESOURCE2(conn_res, conn_handle*, &resc, id, "Connection Resource", le_conn_struct, le_pconn_struct);

			rc = _ruby_db2_parse_options( options, SQL_HANDLE_DBC, conn_res TSRMLS_CC );
			if (rc == SQL_ERROR) {
				RUBY_ERROR("Options Array must have string indexes");
				return Qfalse;
			}
		} else {
			ZEND_FETCH_RESOURCE(stmt_res, stmt_handle*, &resc, id, "Statement Resource", le_stmt_struct);

			rc = _ruby_db2_parse_options( options, SQL_HANDLE_STMT, stmt_res TSRMLS_CC );
			if (rc == SQL_ERROR) {
				RUBY_ERROR("Options Array must have string indexes");
				return Qfalse;
			}
		}

		return Qtrue;
	} else {
		return Qfalse;
	}
}
/*  */
#endif

/*  proto object db2_server_info(resource connection)
Returns an object with properties that describe the DB2 database server */
RUBY_FUNCTION(db2_server_info)
{
	VALUE connection = Qnil;
	conn_handle *conn_res;
	int rc = 0;
	char buffer11[11];
	char buffer255[255];
	char buffer2k[2048];
	SQLSMALLINT bufferint16;
	SQLUINTEGER bufferint32;
	SQLINTEGER bitmask;

	VALUE return_value = rb_funcall(le_server_info, id_new, 0);

	rb_scan_args(argc, argv, "1", &connection);

	if (!NIL_P(connection)) {
		Data_Get_Struct(connection, conn_handle, conn_res);

		/* DBMS_NAME */
		memset(buffer255, 0, sizeof(buffer255));
		rc = SQLGetInfo(conn_res->hdbc, SQL_DBMS_NAME, (SQLPOINTER)buffer255, sizeof(buffer255), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@DBMS_NAME", rb_str_new2(buffer255));
		}

		/* DBMS_VER */
		memset(buffer11, 0, sizeof(buffer11));
		rc = SQLGetInfo(conn_res->hdbc, SQL_DBMS_VER, (SQLPOINTER)buffer11, sizeof(buffer11), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@DBMS_VER", rb_str_new2(buffer11));
		}

		/* DB_CODEPAGE */
		bufferint32 = 0;
		rc = SQLGetInfo(conn_res->hdbc, SQL_DATABASE_CODEPAGE, &bufferint32, sizeof(bufferint32), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@DB_CODEPAGE", INT2NUM(bufferint32));
		}

		/* DB_NAME */
		memset(buffer255, 0, sizeof(buffer255));
		rc = SQLGetInfo(conn_res->hdbc, SQL_DATABASE_NAME, (SQLPOINTER)buffer255, sizeof(buffer255), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@DB_NAME", rb_str_new2(buffer255));
		}

		/* INST_NAME */
		memset(buffer255, 0, sizeof(buffer255));
		rc = SQLGetInfo(conn_res->hdbc, SQL_SERVER_NAME, (SQLPOINTER)buffer255, sizeof(buffer255), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@INST_NAME", rb_str_new2(buffer255));
		}

		/* SPECIAL_CHARS */
		memset(buffer255, 0, sizeof(buffer255));
		rc = SQLGetInfo(conn_res->hdbc, SQL_SPECIAL_CHARACTERS, (SQLPOINTER)buffer255, sizeof(buffer255), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@SPECIAL_CHARS", rb_str_new2(buffer255));
		}

		/* KEYWORDS */
		memset(buffer2k, 0, sizeof(buffer2k));
		rc = SQLGetInfo(conn_res->hdbc, SQL_KEYWORDS, (SQLPOINTER)buffer2k, sizeof(buffer2k), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			char *keyword, *last;
			VALUE karray;

	        karray = rb_ary_new();

			for (keyword = last = buffer2k; *last; last++) {
				if (*last == ',') {
					*last = '\0';
					rb_ary_push(karray, rb_str_new2(keyword));
					keyword = last+1;
				}
			}
			if (*keyword) rb_ary_push(karray, rb_str_new2(keyword));

			rb_iv_set(return_value, "@KEYWORDS", karray);
		}

		/* DFT_ISOLATION */
		bitmask = 0; memset(buffer11, 0, sizeof(buffer11));
		rc = SQLGetInfo(conn_res->hdbc, SQL_DEFAULT_TXN_ISOLATION, &bitmask, sizeof(bitmask), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			if( bitmask & SQL_TXN_READ_UNCOMMITTED ) {
				strcpy(buffer11, "UR");
			}
			if( bitmask & SQL_TXN_READ_COMMITTED ) {
				strcpy(buffer11, "CS");
			}
			if( bitmask & SQL_TXN_REPEATABLE_READ ) {
				strcpy(buffer11, "RS");
			}
			if( bitmask & SQL_TXN_SERIALIZABLE ) {
				strcpy(buffer11, "RR");
			}
			if( bitmask & SQL_TXN_NOCOMMIT ) {
				strcpy(buffer11, "NC");
			}

			rb_iv_set(return_value, "@DFT_ISOLATION", rb_str_new2(buffer11));
		}

		/* ISOLATION_OPTION */
		bitmask = 0; memset(buffer11, 0, sizeof(buffer11));
		rc = SQLGetInfo(conn_res->hdbc, SQL_TXN_ISOLATION_OPTION, &bitmask, sizeof(bitmask), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			VALUE array;

	        array = rb_ary_new();

			if( bitmask & SQL_TXN_READ_UNCOMMITTED ) {
				rb_ary_push(array, rb_str_new2("UR"));
			}
			if( bitmask & SQL_TXN_READ_COMMITTED ) {
				rb_ary_push(array, rb_str_new2("CS"));
			}
			if( bitmask & SQL_TXN_REPEATABLE_READ ) {
				rb_ary_push(array, rb_str_new2("RS"));
			}
			if( bitmask & SQL_TXN_SERIALIZABLE ) {
				rb_ary_push(array, rb_str_new2("RR"));
			}
			if( bitmask & SQL_TXN_NOCOMMIT ) {
				rb_ary_push(array, rb_str_new2("NC"));
			}

			rb_iv_set(return_value, "@ISOLATION_OPTION", array);
		}

		/* SQL_CONFORMANCE */
		bufferint32 = 0;
		memset(buffer255, 0, sizeof(buffer255));
		rc = SQLGetInfo(conn_res->hdbc, SQL_ODBC_SQL_CONFORMANCE, &bufferint32, sizeof(bufferint32), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			switch (bufferint32) {
				case SQL_SC_SQL92_ENTRY:
					strcpy(buffer255, "ENTRY");
					break;
				case SQL_SC_FIPS127_2_TRANSITIONAL:
					strcpy(buffer255, "FIPS127");
					break;
				case SQL_SC_SQL92_FULL:
					strcpy(buffer255, "FULL");
					break;
				case SQL_SC_SQL92_INTERMEDIATE:
					strcpy(buffer255, "INTERMEDIATE");
					break;
				default:
					break;
			}
			rb_iv_set(return_value, "@SQL_CONFORMANCE", rb_str_new2(buffer255));
		}

		/* PROCEDURES */
		memset(buffer11, 0, sizeof(buffer11));
		rc = SQLGetInfo(conn_res->hdbc, SQL_PROCEDURES, (SQLPOINTER)buffer11, sizeof(buffer11), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			if( strcmp(buffer11, "Y") == 0 ) {
				rb_iv_set(return_value, "@PROCEDURES", Qtrue);
			} else {
				rb_iv_set(return_value, "@PROCEDURES", Qfalse);
			}
		}

		/* IDENTIFIER_QUOTE_CHAR */
		memset(buffer11, 0, sizeof(buffer11));
		rc = SQLGetInfo(conn_res->hdbc, SQL_IDENTIFIER_QUOTE_CHAR, (SQLPOINTER)buffer11, sizeof(buffer11), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@IDENTIFIER_QUOTE_CHAR", rb_str_new2(buffer11));
		}

		/* LIKE_ESCAPE_CLAUSE */
		memset(buffer11, 0, sizeof(buffer11));
		rc = SQLGetInfo(conn_res->hdbc, SQL_LIKE_ESCAPE_CLAUSE, (SQLPOINTER)buffer11, sizeof(buffer11), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			if( strcmp(buffer11, "Y") == 0 ) {
				rb_iv_set(return_value, "@LIKE_ESCAPE_CLAUSE", Qtrue);
			} else {
				rb_iv_set(return_value, "@LIKE_ESCAPE_CLAUSE", Qfalse);
			}
		}

		/* MAX_COL_NAME_LEN */
		bufferint16 = 0;
		rc = SQLGetInfo(conn_res->hdbc, SQL_MAX_COLUMN_NAME_LEN, &bufferint16, sizeof(bufferint16), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@MAX_COL_NAME_LEN", INT2NUM(bufferint16));
		}

		/* MAX_ROW_SIZE */
		bufferint32 = 0;
		rc = SQLGetInfo(conn_res->hdbc, SQL_MAX_ROW_SIZE, &bufferint32, sizeof(bufferint32), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@MAX_ROW_SIZE", INT2NUM(bufferint32));
		}

		/* MAX_IDENTIFIER_LEN */
		bufferint16 = 0;
		rc = SQLGetInfo(conn_res->hdbc, SQL_MAX_IDENTIFIER_LEN, &bufferint16, sizeof(bufferint16), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@MAX_IDENTIFIER_LEN", INT2NUM(bufferint16));
		}

		/* MAX_INDEX_SIZE */
		bufferint32 = 0;
		rc = SQLGetInfo(conn_res->hdbc, SQL_MAX_INDEX_SIZE, &bufferint32, sizeof(bufferint32), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@MAX_INDEX_SIZE", INT2NUM(bufferint32));
		}

		/* MAX_PROC_NAME_LEN */
		bufferint16 = 0;
		rc = SQLGetInfo(conn_res->hdbc, SQL_MAX_PROCEDURE_NAME_LEN, &bufferint16, sizeof(bufferint16), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@MAX_PROC_NAME_LEN", INT2NUM(bufferint16));
		}

		/* MAX_SCHEMA_NAME_LEN */
		bufferint16 = 0;
		rc = SQLGetInfo(conn_res->hdbc, SQL_MAX_SCHEMA_NAME_LEN, &bufferint16, sizeof(bufferint16), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@MAX_SCHEMA_NAME_LEN", INT2NUM(bufferint16));
		}

		/* MAX_STATEMENT_LEN */
		bufferint32 = 0;
		rc = SQLGetInfo(conn_res->hdbc, SQL_MAX_STATEMENT_LEN, &bufferint32, sizeof(bufferint32), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@MAX_STATEMENT_LEN", INT2NUM(bufferint32));
		}

		/* MAX_TABLE_NAME_LEN */
		bufferint16 = 0;
		rc = SQLGetInfo(conn_res->hdbc, SQL_MAX_TABLE_NAME_LEN, &bufferint16, sizeof(bufferint16), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@MAX_TABLE_NAME_LEN", INT2NUM(bufferint16));
		}

		/* NON_NULLABLE_COLUMNS */
		bufferint16 = 0;
		rc = SQLGetInfo(conn_res->hdbc, SQL_NON_NULLABLE_COLUMNS, &bufferint16, sizeof(bufferint16), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			VALUE rv = Qnil;
			switch (bufferint16) {
				case SQL_NNC_NON_NULL:
					rv = Qtrue;
					break;
				case SQL_NNC_NULL:
					rv = Qfalse;
					break;
				default:
					break;
			}
			rb_iv_set(return_value, "@NON_NULLABLE_COLUMNS", rv);
		}

	    return return_value;
	}

	return Qnil;
}
/*  */

/*  proto object db2_client_info(resource connection)
Returns an object with properties that describe the DB2 database client */
RUBY_FUNCTION(db2_client_info)
{
	VALUE connection = Qnil;
	conn_handle *conn_res;
	int rc = 0;
	char buffer255[255];
	SQLSMALLINT bufferint16;
	SQLUINTEGER bufferint32;

	VALUE return_value = rb_funcall(le_client_info, id_new, 0);

	rb_scan_args(argc, argv, "1", &connection);

	if (!NIL_P(connection)) {
		Data_Get_Struct(connection, conn_handle, conn_res);

		/* DRIVER_NAME */
		memset(buffer255, 0, sizeof(buffer255));
		rc = SQLGetInfo(conn_res->hdbc, SQL_DRIVER_NAME, (SQLPOINTER)buffer255, sizeof(buffer255), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@DRIVER_NAME", rb_str_new2(buffer255));
		}

		/* DRIVER_VER */
		memset(buffer255, 0, sizeof(buffer255));
		rc = SQLGetInfo(conn_res->hdbc, SQL_DRIVER_VER, (SQLPOINTER)buffer255, sizeof(buffer255), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@DRIVER_VER", rb_str_new2(buffer255));
		}

		/* DATA_SOURCE_NAME */
		memset(buffer255, 0, sizeof(buffer255));
		rc = SQLGetInfo(conn_res->hdbc, SQL_DATA_SOURCE_NAME, (SQLPOINTER)buffer255, sizeof(buffer255), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@DATA_SOURCE_NAME", rb_str_new2(buffer255));
		}

		/* DRIVER_ODBC_VER */
		memset(buffer255, 0, sizeof(buffer255));
		rc = SQLGetInfo(conn_res->hdbc, SQL_DRIVER_ODBC_VER, (SQLPOINTER)buffer255, sizeof(buffer255), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@DRIVER_ODBC_VER", rb_str_new2(buffer255));
		}

		/* ODBC_VER */
		memset(buffer255, 0, sizeof(buffer255));
		rc = SQLGetInfo(conn_res->hdbc, SQL_ODBC_VER, (SQLPOINTER)buffer255, sizeof(buffer255), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@ODBC_VER", rb_str_new2(buffer255));
		}

		/* ODBC_SQL_CONFORMANCE */
		bufferint16 = 0; memset(buffer255, 0, sizeof(buffer255));
		rc = SQLGetInfo(conn_res->hdbc, SQL_ODBC_SQL_CONFORMANCE, &bufferint16, sizeof(bufferint16), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			switch (bufferint16) {
				case SQL_OSC_MINIMUM:
					strcpy(buffer255, "MINIMUM");
					break;
				case SQL_OSC_CORE:
					strcpy(buffer255, "CORE");
					break;
				case SQL_OSC_EXTENDED:
					strcpy(buffer255, "EXTENDED");
					break;
				default:
					break;
			}
			rb_iv_set(return_value, "@ODBC_SQL_CONFORMANCE", rb_str_new2(buffer255));
		}

		/* APPL_CODEPAGE */
		bufferint32 = 0;
		rc = SQLGetInfo(conn_res->hdbc, SQL_APPLICATION_CODEPAGE, &bufferint32, sizeof(bufferint32), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@APPL_CODEPAGE", INT2NUM(bufferint32));
		}

		/* CONN_CODEPAGE */
		bufferint32 = 0;
		rc = SQLGetInfo(conn_res->hdbc, SQL_CONNECT_CODEPAGE, &bufferint32, sizeof(bufferint32), NULL);

		if ( rc == SQL_ERROR ) {
			return Qfalse;
		} else {
			rb_iv_set(return_value, "@CONN_CODEPAGE", INT2NUM(bufferint32));
		}

	    return return_value;
	}

	return Qnil;
}
/*  */

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */
