#include "odbc.h"

/*
 *
 * PART 1
 *
 * Connecting to a data source
 *
 */

/* Call SQLAllocHandle and get an environment handle. After that
 * call odbc_set_environment to set the ODBC version */
s48_value odbc_alloc_environment_handle() 
{
  SQLHENV henv;
  SQLRETURN retval;

#if (ODBCVER >= 0x300)
  retval = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
  ODBC_DEBUG_PRINTF_2("SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, ...): %x\n", henv);
#else
  retval = SQLAllocEnv(&henv);
  ODBC_DEBUG_PRINTF_2("SQLAllocEnv(...): %x\n", henv);
#endif /* (ODBCVER >= 0x300) */

  return s48_list_2(s48_enter_integer(retval), 
		    SQL_SUCCEEDED(retval) ? s48_enter_integer((long)henv) :
		    S48_UNSPECIFIC);
}

/* given a valid environment handle (type SQLHENV) this function
 * sets the environment attributes. This needs to be done before
 * allocating a connection handle */
s48_value odbc_sql_set_env_attr(SQLHENV env_handle) 
{
  SQLRETURN retval;

  retval = SQLSetEnvAttr(env_handle, SQL_ATTR_ODBC_VERSION, 
			 (void *)SQL_OV_ODBC3, 0);
  ODBC_DEBUG_PRINTF_2("odbc_set_environment() %x\n", env_handle);  

  return s48_enter_integer(retval);
}

/* Given a valid environment handle get a connection handle */
s48_value odbc_alloc_connection_handle(s48_value env_handle) 
{
  SQLHDBC hdbc;
  SQLRETURN retval;
  SQLHENV envh;

  envh = (SQLHENV) s48_extract_integer(env_handle);

#if (ODBCVER >= 0x300)
  retval = SQLAllocHandle(SQL_HANDLE_DBC, envh, &hdbc);
  ODBC_DEBUG_PRINTF_3("SQLAllocHandle(SQL_HANDLE_DBC, %x, ...): %x\n", envh, hdbc);
#else
  retval = SQLAllocConnect(envh, &hdbc);
  ODBC_DEBUG_PRINTF_3("SQLAllocConnect(%x, ...): %x\n", envh, hdbc);
#endif /* ODBCVER >= 0x300 */

  return s48_list_2(s48_enter_integer(retval),
		    SQL_SUCCEEDED(retval) ? s48_enter_integer((long)hdbc) : 
		    S48_UNSPECIFIC);
}

/* Given a valid connection handle get a statement handle */
s48_value odbc_alloc_statement_handle(s48_value conn_handle) 
{
  SQLHSTMT hstmt;
  SQLRETURN retval;
  SQLHANDLE ch;

  ch = (SQLHANDLE) s48_extract_integer(conn_handle);

#if (ODBCVER >= 0x300)
  retval = SQLAllocHandle(SQL_HANDLE_STMT, ch, &hstmt);
  ODBC_DEBUG_PRINTF_3("SQLAllocHandle() %x %x\n", ch, hstmt);
#else
  retval = SQLAllocStmt(ch, &hstmt);
  ODBC_DEBUG_PRINTF_3("SQLAllocStmt() %x %x\n", ch, hstmt);
#endif /* ODBCVER >= 0x300 */

  return s48_list_2(s48_enter_integer(retval),
		    SQL_SUCCEEDED(retval) ? s48_enter_integer((long)hstmt) : 
		    S48_UNSPECIFIC);
}

/* Connect to a server */
s48_value odbc_sql_connect(s48_value connection_handle,
			   s48_value ds_name,
			   s48_value user_name,
			   s48_value authentication) 
{
  SQLHDBC ch;
  SQLCHAR *dsn, *user, *auth;
  SQLRETURN retval;
  
  dsn = (SQLCHAR *) s48_extract_string(ds_name);
  user = (SQLCHAR *) s48_extract_string(user_name);
  auth = (SQLCHAR *) s48_extract_string(authentication);
  ch = (SQLHDBC) s48_extract_integer(connection_handle);

  ODBC_DEBUG_PRINTF_5("odbc_sql_connect() %x '%s' '%s' '%s'\n",
		      ch, dsn, user,auth);
  retval = SQLConnect(ch,
		      dsn, S48_STRING_LENGTH(ds_name),
		      user, S48_STRING_LENGTH(user_name),
		      auth, S48_STRING_LENGTH(authentication));

  return s48_enter_integer(retval);
}

s48_value odbc_sql_browse_connect(s48_value conn_handle, s48_value conn_string)
{
  SQLHDBC ch;
  SQLCHAR *buffer = NULL;
  SQLRETURN retval;
  SQLSMALLINT buffer_needed, buffer_len;
  s48_value res = S48_UNSPECIFIC;

  S48_DECLARE_GC_PROTECT(1);
  S48_GC_PROTECT_1(res);
  ch = (SQLHDBC) s48_extract_integer(conn_handle);
  ODBC_DEBUG_PRINTF_3("odbc_sql_browse_connect() %x '%s'\n", 
		      ch, s48_extract_string(conn_string));
  buffer_len = odbc_initial_retval_buffer_size;

  for (;;)
    {
      odbc_sql_alloc((void **) &buffer, buffer_len, sizeof(SQLCHAR));

      retval = SQLBrowseConnect(ch, (SQLCHAR *) s48_extract_string(conn_string),
				(SQLSMALLINT) S48_STRING_LENGTH(conn_string),
				buffer, buffer_len, &buffer_needed);

      if (SQL_SUCCEEDED(retval) && (buffer_needed > buffer_len))
	buffer_len = buffer_needed+1;
      else
	break;
    }

  res = s48_list_2(s48_enter_integer(retval),
		   SQL_SUCCEEDED(retval) ? s48_enter_string(buffer) : 
		   S48_UNSPECIFIC);
  free(buffer);
  S48_GC_UNPROTECT();
  return res;
}

/*
 *
 * PART 2
 *
 * Obtaining information about a driver and data source
 *
 */

/* Returns a list of available data sources. */
s48_value odbc_sql_data_sources(s48_value env_handle)
{
  SQLHENV eh;
  SQLUSMALLINT dir;
  SQLRETURN retval;
  int first;
  SQLSMALLINT server_name_len, driver_descr_len;
  SQLSMALLINT server_name_needed, driver_descr_needed;
  SQLCHAR *server_name = NULL;
  SQLCHAR *driver_descr = NULL;
  s48_value res_list = S48_NULL;
  s48_value res = S48_UNSPECIFIC;

  S48_DECLARE_GC_PROTECT(2);
  S48_GC_PROTECT_2(res_list, res);
  eh = (SQLHENV) s48_extract_integer(env_handle);
  ODBC_DEBUG_PRINTF_1("odbc_sql_data_sources\n");
  server_name_len = driver_descr_len = odbc_initial_retval_buffer_size;

  first = 1; 
  for (;;) 
    {
      for (;;)
	{
	  odbc_sql_alloc((void **) &server_name, server_name_len, sizeof(SQLCHAR));
	  odbc_sql_alloc((void **) &driver_descr, driver_descr_len, sizeof(SQLCHAR));
	  
	  retval = SQLDataSources(eh, 
				  (first ? SQL_FETCH_FIRST : SQL_FETCH_NEXT),
				  server_name, sizeof(SQLCHAR)*server_name_len, 
				  &server_name_needed,
				  driver_descr, sizeof(SQLCHAR)*driver_descr_len, 
				  &driver_descr_needed);
	  
	  if (SQL_SUCCEEDED(retval) && ((server_name_needed > server_name_len) || 
					(driver_descr_needed > driver_descr_len)))
	    {
	      if (server_name_needed > server_name_len)
		server_name_len = server_name_needed + 1;
	      if (driver_descr_needed > driver_descr_len)
		driver_descr_len = driver_descr_needed + 1;
	    }
	  else
	    break;
	}

      if (SQL_SUCCEEDED(retval))
	res_list = s48_cons(s48_cons(s48_enter_string(server_name), 
				     s48_enter_string(driver_descr)),
			    res_list);
      else
	res_list = S48_UNSPECIFIC;
      
      if (retval == SQL_NO_DATA)
	{
	  res = s48_list_2(s48_enter_integer(retval), res_list);
	  free(server_name);
	  free(driver_descr);
	  S48_GC_UNPROTECT();
	  return res;
	}
      first = 0;
    }
  
  /* never reached */
  S48_GC_UNPROTECT();
  return s48_list_2(s48_enter_integer(retval), S48_UNSPECIFIC);
}

/* Returns the list of installed drivers and their attributes. */
s48_value odbc_sql_drivers(s48_value env_handle) 
{
#if (ODBCVER >= 0x0200)
  SQLHENV eh;
  SQLRETURN retval;
  int first;
  SQLSMALLINT driver_descr_len, driver_attr_len;
  SQLSMALLINT driver_descr_needed, driver_attr_needed;
  SQLCHAR *driver_attr = NULL;
  SQLCHAR *driver_descr = NULL;
  s48_value res_list = S48_NULL;
  s48_value res = S48_UNSPECIFIC;

  S48_DECLARE_GC_PROTECT(2);
  S48_GC_PROTECT_2(res, res_list);
  eh = (SQLHENV) s48_extract_integer(env_handle);
  ODBC_DEBUG_PRINTF_1("odbc_sql_drivers\n");
  driver_descr_len = driver_attr_len = odbc_initial_retval_buffer_size;
  first = 0;
 
  for (;;)
    {
      for (;;)
	{
	  odbc_sql_alloc((void **) &driver_descr, driver_descr_len, sizeof(SQLCHAR));
	  odbc_sql_alloc((void **) &driver_attr, driver_attr_len, sizeof(SQLCHAR));

	  retval = SQLDrivers(eh,
			      (SQLUSMALLINT) (first ? SQL_FETCH_FIRST : SQL_FETCH_NEXT),
			      driver_descr, driver_descr_len, &driver_descr_needed,
			      driver_attr, driver_attr_len, &driver_attr_needed);

	  if (SQL_SUCCEEDED(retval) && ((driver_descr_needed > driver_descr_len) ||
					(driver_attr_needed > driver_attr_len)))
	    {
	      if (driver_descr_needed > driver_descr_len)
		driver_descr_len = driver_descr_needed + 1;
	      if (driver_attr_needed > driver_attr_len)
		driver_attr_len = driver_attr_needed + 1;
	    }
	  else
	    break;
	}

      if (SQL_SUCCEEDED(retval))
	res_list = s48_cons(s48_cons(s48_enter_string(driver_descr),
				     s48_enter_string(driver_attr)),
			    res_list);
	  
      if (retval == SQL_NO_DATA)
	{
	  res = s48_list_2(s48_enter_integer(retval), res_list);
	  free(driver_descr);
	  free(driver_attr);
	  S48_GC_UNPROTECT();
	  return res;
	}

      first = 1;
    }

  /* not reached */
  S48_GC_UNPROTECT();
  return res;
#else
  RAISE_API_VERSION_MISMATCH("SQLDrivers", ODBCVER, 0x200);
#endif /* ODBCVER >= 0x200 */
}

/* Returns information about a specific driver and data source. 
 * (use if the information is an integer) */
s48_value odbc_sql_get_info_int(s48_value conn_handle, s48_value info_key)
{
  SQLHDBC ch;
  SQLUSMALLINT ik;
  SQLRETURN retval;
  SQLUINTEGER info;
  SQLSMALLINT buffer_size;

  ODBC_DEBUG_PRINTF_1("odbc_sql_get_info_int\n");
  ch = (SQLHDBC) s48_extract_integer(conn_handle);
  ik = (SQLUSMALLINT) s48_extract_integer(info_key);
  retval = SQLGetInfo(ch, ik, &info, sizeof(SQLUINTEGER), &buffer_size);

  return s48_list_2(s48_enter_integer(retval),
		    SQL_SUCCEEDED(retval) ? s48_enter_integer(info) : 
		    S48_UNSPECIFIC);
}

/* Returns information about a specific driver and data source. 
 * (use if the information is a string) */
s48_value odbc_sql_get_info_string(s48_value conn_handle, s48_value info_key)
{
  SQLHDBC ch;
  SQLUSMALLINT ik;
  SQLRETURN retval;
  SQLCHAR *buffer = NULL;
  SQLSMALLINT buffer_needed, buffer_len;
  s48_value res = S48_UNSPECIFIC;

  S48_DECLARE_GC_PROTECT(1);
  S48_GC_PROTECT_1(res);
  ODBC_DEBUG_PRINTF_1("odbc_sql_get_info_string\n");
  ch = (SQLHDBC) s48_extract_integer(conn_handle);
  ik = (SQLUSMALLINT) s48_extract_integer(info_key);
  buffer_len = odbc_initial_retval_buffer_size;

  for (;;)
    {
      odbc_sql_alloc((void **) &buffer, buffer_len, sizeof(SQLCHAR));
      retval = SQLGetInfo(ch, ik, buffer, buffer_len, &buffer_needed);
      
      if (SQL_SUCCEEDED(retval) && (buffer_needed > buffer_len))
	buffer_len = buffer_needed+1;
      else 
	break;
    }

  res = s48_list_2(s48_enter_integer(retval),
		   SQL_SUCCEEDED(retval) ? s48_enter_string(buffer) : 
		   S48_UNSPECIFIC);
  free(buffer);
  S48_GC_UNPROTECT();
  return res;
}

/* Returns supported driver functions. (for a multiple functions) */
s48_value odbc_sql_get_func_exists(s48_value conn_handle, s48_value fun_id)
{
  SQLHDBC ch;
  SQLUSMALLINT fi, supported[SQL_API_ODBC3_ALL_FUNCTIONS_SIZE];
  SQLRETURN retval;
  int i;
  
  ODBC_DEBUG_PRINTF_1("odbc_sql_func_exists\n");
  ch = (SQLHDBC) s48_extract_integer(conn_handle);
  fi = (SQLUSMALLINT) s48_extract_integer(fun_id);
  retval = SQLGetFunctions(ch, fi, supported);
 
  if (SQL_SUCCEEDED(retval))
    return 
      s48_list_2(s48_enter_integer(retval),
		 SQL_FUNC_EXISTS(supported, fi) == SQL_TRUE ? S48_TRUE : 
		 S48_FALSE);
  else
    return s48_list_2(s48_enter_integer(retval), S48_UNSPECIFIC);
}

/* Returns information about supported data types. */
s48_value odbc_sql_get_type_info(s48_value stmt_handle, s48_value data_type)
{
  SQLHSTMT sh;
  SQLSMALLINT dt;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_get_type_info\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  dt = (SQLSMALLINT) s48_extract_integer(data_type);
  retval = SQLGetTypeInfo(sh, dt);

  return s48_enter_integer(retval);
}

/*
 *
 * PART 3
 *
 * Setting and retrieving driver attributes
 *
 */

s48_value odbc_sql_set_connect_attr_int(s48_value conn_handle,
					s48_value attribute,
					s48_value value)
{
#if (ODBCVER >= 0x300)
  SQLHDBC ch;
  SQLINTEGER attr;
  SQLUINTEGER val;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_set_connect_attr_int\n");
  ch = (SQLHDBC) s48_extract_integer(conn_handle);
  attr = (SQLINTEGER) s48_extract_integer(attribute);
  val = (SQLUINTEGER) s48_extract_integer(value);
  retval = SQLSetConnectAttr(ch, attr, &val, 0);

  return s48_enter_integer(retval);
#else
  RAISE_API_VERSION_MISMATCH("SQLSetConnectAttr", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

s48_value odbc_sql_set_connect_attr_string(s48_value conn_handle,
					   s48_value attribute,
					   s48_value value)
{
#if (ODBCVER >= 0x300)
  SQLHDBC ch;
  SQLINTEGER attr;
  SQLCHAR *val;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_set_connect_attr_string\n");
  ch = (SQLHDBC) s48_extract_integer(conn_handle);
  attr = (SQLINTEGER) s48_extract_integer(attribute);
  val = (SQLCHAR *) s48_extract_string(value);
  retval = SQLSetConnectAttr(ch, attr, val, S48_STRING_LENGTH(value));

  return s48_enter_integer(retval);
#else
  RAISE_API_VERSION_MISMATCH("SQLSetConnectAttr", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

s48_value odbc_sql_get_connect_attr_string(s48_value conn_handle, 
					   s48_value attribute)
{
#if (ODBCVER >= 0x300)
  SQLHDBC ch;
  SQLINTEGER attr;
  SQLCHAR *buffer = NULL;
  SQLINTEGER buffer_needed, buffer_len;
  SQLRETURN retval;
  s48_value res = S48_UNSPECIFIC;

  S48_DECLARE_GC_PROTECT(1);
  S48_GC_PROTECT_1(res);
  ODBC_DEBUG_PRINTF_1("odbc_sql_get_connect_attr_string\n");
  buffer_len = odbc_initial_retval_buffer_size;

  for (;;) {
    odbc_sql_alloc((void **) &buffer, buffer_len, sizeof(SQLCHAR));
    retval = SQLGetConnectAttr(ch, attr, buffer, buffer_len, &buffer_needed);

    if (SQL_SUCCEEDED(retval) && (buffer_needed > buffer_len))
      buffer_needed = buffer_len;
    else
      break;
  }
  
  res = s48_list_2(s48_enter_integer(retval),
		   SQL_SUCCEEDED(retval) ? s48_enter_string(buffer) : 
		   S48_UNSPECIFIC);
  free(buffer);
  S48_GC_UNPROTECT();
  return res;
#else
  RAISE_API_VERSION_MISMATCH("SQLGetConnectAttr", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

s48_value odbc_sql_get_connect_attr_int(s48_value conn_handle,
					s48_value attribute) 
{
#if (ODBCVER >= 0x300)  
  SQLHDBC ch;
  SQLINTEGER attr;
  SQLUINTEGER buffer;
  SQLINTEGER buffer_size;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_get_connect_attr_int\n");
  retval = SQLGetConnectAttr(ch, attr,
			     &buffer, sizeof(SQLUINTEGER), &buffer_size);

  return s48_list_2(s48_enter_integer(retval),
		    SQL_SUCCEEDED(retval) ? s48_enter_integer(buffer) :
		    S48_UNSPECIFIC);
#else
  RAISE_API_VERSION_MISMATCH("SQLGetConnectAttr", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

s48_value odbc_sql_set_env_attr_int(s48_value env_handle,
				    s48_value attribute,
				    s48_value value)
{
#if (ODBCVER >= 0x300)
  SQLHENV eh;
  SQLINTEGER attr;
  SQLUINTEGER val;
  SQLRETURN retval;

  eh = (SQLHENV) s48_extract_integer(env_handle);
  attr = (SQLINTEGER) s48_extract_integer(attribute);
  val = s48_extract_integer(value);
  ODBC_DEBUG_PRINTF_4("odbc_sql_set_env_attr_int eh %x attr %d value %d\n", eh, attr, val);
  retval = SQLSetEnvAttr(eh, attr, (SQLPOINTER) &val, sizeof(SQLUINTEGER));
  
  return s48_enter_integer(retval);
#else
  RAISE_API_VERSION_MISMATCH("SQLSetConnectAttr", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

s48_value odbc_sql_get_env_attr_int(s48_value env_handle,
				    s48_value attribute,
				    s48_value value) 
{
#if (ODBCVER >= 0x300)
  SQLHENV eh;
  SQLINTEGER attr;
  SQLUINTEGER val, str_len;
  SQLRETURN retval;

  eh = (SQLHENV) s48_extract_integer(env_handle);
  attr = (SQLINTEGER) s48_extract_integer(attribute);
  val = (SQLUINTEGER) s48_extract_integer(value);
  ODBC_DEBUG_PRINTF_4("odbc_sql_get_env_attr_int eh %x attr %d value %d\n", eh, attr, val);
  retval = SQLGetEnvAttr(eh, attr, &val, sizeof(SQLUINTEGER), &str_len);

  return s48_list_2(s48_enter_integer(retval),
		    SQL_SUCCEEDED(retval) ? s48_enter_integer(val) : 
		    S48_UNSPECIFIC);
#else
  RAISE_API_VERSION_MISMATCH("SQLGetEnvAttr", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

/* Sets a statement attribute */
s48_value odbc_sql_set_stmt_attr_int(s48_value stmt_handle,
				     s48_value attribute,
				     s48_value value)
{
#if (ODBCVER >= 0x300)
  SQLHSTMT sh;
  SQLINTEGER attr;
  SQLUINTEGER val;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_get_stmt_attr_int\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  attr = (SQLINTEGER) s48_extract_integer(attribute);
  val = (SQLUINTEGER) s48_extract_integer(value);
  retval = SQLSetStmtAttr(sh, attr, &val, 0);

  return s48_enter_integer(retval);
#else
  RAISE_API_VERSION_MISMATCH("SQLSetStmtAttr", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

s48_value odbc_sql_set_stmt_attr_string(s48_value stmt_handle,
					s48_value attribute,
					s48_value value)
{
#if (ODBCVER >= 0x300)
  SQLHSTMT sh;
  SQLINTEGER attr;
  SQLCHAR *val;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_set_stmt_attr_string\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  attr = (SQLINTEGER) s48_extract_integer(attribute);
  val = (SQLCHAR *) s48_extract_string(value);
  retval = SQLSetStmtAttr(sh, attr, val, S48_STRING_LENGTH(value));

  return s48_enter_integer(retval);
#else
  RAISE_API_VERSION_MISMATCH("SQLSetStmtAttr", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

s48_value odbc_sql_get_stmt_attr_int(s48_value stmt_handle, s48_value attribute)
{
#if (ODBCVER >= 0x300)
  SQLHSTMT sh;
  SQLINTEGER attr, val, buf_size;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_get_stmt_attr_int\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  attr = (SQLINTEGER) s48_extract_integer(attribute);
  retval = SQLGetStmtAttr(sh, attr, &val, sizeof(SQLINTEGER), &buf_size);

  return s48_list_2(s48_enter_integer(retval),
		    SQL_SUCCEEDED(retval) ? s48_enter_integer(val) : 
		    S48_UNSPECIFIC);
#else
  RAISE_API_VERSION_MISMATCH("SQLGetStmtAttr", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

s48_value odbc_sql_get_stmt_attr_string(s48_value stmt_handle,
					s48_value attribute) 
{
#if (ODBCVER >= 0x300)
  SQLHSTMT sh;
  SQLINTEGER attr, buffer_len, buffer_needed;
  SQLCHAR *buffer = NULL;
  SQLRETURN retval;
  s48_value res = S48_UNSPECIFIC;

  S48_DECLARE_GC_PROTECT(1);
  S48_GC_PROTECT_1(res);
  ODBC_DEBUG_PRINTF_1("odbc_sql_get_stmt_attr_string\n");
  buffer_len = odbc_initial_retval_buffer_size;

  for (;;)
    {
      odbc_sql_alloc((void **) &buffer, buffer_len, sizeof(SQLCHAR));
      retval = SQLGetStmtAttr(sh, attr, &buffer, buffer_len, &buffer_needed);
      
      if (SQL_SUCCEEDED(retval) && (buffer_needed > buffer_len))
	buffer_len = buffer_needed+1;
      else 
	break;
    }

  res = s48_list_2(s48_enter_integer(retval),
		   SQL_SUCCEEDED(retval) ? s48_enter_string(buffer) : 
		   S48_UNSPECIFIC);
  free(buffer);
  S48_GC_UNPROTECT();
  return res;
#else
  RAISE_API_VERSION_MISMATCH("SQLGetStmtAttr", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}
  
/*
 *
 * PART 4
 *
 * Setting and retrieving descriptor fields
 *
 */

/* Returns the value of a single descriptor field (for integers) */
s48_value odbc_sql_get_desc_field_int(s48_value desc_handle, s48_value rec_number,
				      s48_value field_id)
{
#if (ODBCVER >= 0x300)
  SQLHDESC dh;
  SQLSMALLINT rn, fi;
  SQLINTEGER value, buffer_len;
  SQLRETURN retval;
  s48_value res = S48_UNSPECIFIC;
  s48_value scheme_int = S48_UNSPECIFIC;

  S48_DECLARE_GC_PROTECT(2);
  S48_GC_PROTECT_2(res, scheme_int);
  ODBC_DEBUG_PRINTF_1("odbc_sql_get_desc_field_int\n");  
  dh = (SQLHDESC) s48_extract_integer(desc_handle);
  rn = (SQLSMALLINT) s48_extract_integer(rec_number);
  fi = (SQLSMALLINT) s48_extract_integer(field_id);

  retval = SQLGetDescField(dh, rn, fi, (SQLPOINTER) &value, 
			   sizeof(SQLINTEGER), &buffer_len);

  if (SQL_SUCCEEDED(retval))
    switch (buffer_len)
      {
      case SQL_IS_INTEGER:
	scheme_int = s48_enter_integer((SQLINTEGER) value);
	break;
      case SQL_IS_UINTEGER:
	scheme_int = s48_enter_integer((SQLUINTEGER) value);
	break;
      case SQL_IS_SMALLINT:
	  scheme_int = s48_enter_integer((SQLSMALLINT) value);
	  break;
      case SQL_IS_USMALLINT:
	scheme_int = s48_enter_integer((SQLUSMALLINT) value);
	break;
      default:
	RAISE_UNKNOWN_INTEGER_TYPE_ERROR("SQLGetDescField", buffer_len);
      }

  res = s48_list_2(s48_enter_integer(retval), scheme_int);
  S48_GC_UNPROTECT();
  return res;
#else
  RAISE_API_VERSION_MISMATCH("SQLGetDescField", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

/* Returns the value of a single descriptor field (for strings/binary data) */
s48_value odbc_sql_get_desc_field_string(s48_value desc_handle, s48_value rec_number,
					 s48_value field_id)
{
#if (ODBCVER >= 0x300)
  SQLHDESC dh;
  SQLSMALLINT rn, fi;
  SQLCHAR *buffer = NULL;
  SQLINTEGER buffer_len, buffer_needed;
  SQLRETURN retval;
  s48_value res = S48_UNSPECIFIC;
  s48_value scheme_str = S48_UNSPECIFIC;

  S48_DECLARE_GC_PROTECT(2);
  S48_GC_PROTECT_2(res, scheme_str);
  ODBC_DEBUG_PRINTF_1("odbc_sql_get_desc_field_string\n");
  dh = (SQLHDESC) s48_extract_integer(desc_handle);
  rn = (SQLSMALLINT) s48_extract_integer(rec_number);
  fi = (SQLSMALLINT) s48_extract_integer(field_id);

  buffer_len = odbc_initial_retval_buffer_size;
  for (;;)
    {
      odbc_sql_alloc((void **) &buffer, buffer_len, sizeof(SQLCHAR));
      retval = SQLGetDescField(dh, rn, fi, (SQLPOINTER) buffer,
			       buffer_len, &buffer_needed);

      if (SQL_SUCCEEDED(retval) && (buffer_needed > buffer_len))
	buffer_len = buffer_needed+1;
      else
	break;
    }

  if (SQL_SUCCEEDED(retval))
    scheme_str = s48_enter_string(buffer);
  
  free(buffer);
  res = s48_list_2(s48_enter_integer(retval), scheme_str);
  S48_GC_UNPROTECT();
  return res;
#else
  RAISE_API_VERSION_MISMATCH("SQLGetDescField", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

/*
 *
 * PART 5
 *
 * Preparing SQL requests
 *
 */

/* Prepare a SQL statement for execution */
s48_value odbc_sql_prepare(s48_value stmt_handle, s48_value stmt_txt)
{
  SQLHSTMT sh;
  SQLCHAR *query;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_prepare\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  query = (SQLCHAR *) s48_extract_string(stmt_txt);
  retval = SQLPrepare(sh, query, S48_STRING_LENGTH(stmt_txt));

  return s48_enter_integer(retval);
}

/* FIXME: implement SQLBindParameter */

s48_value odbc_sql_get_cursor_name(s48_value stmt_handle)
{
  SQLHSTMT sh;
  SQLRETURN retval;
  SQLCHAR *buffer = NULL;
  SQLSMALLINT buffer_len, buffer_needed;
  s48_value res = S48_UNSPECIFIC;
  s48_value scheme_str = S48_UNSPECIFIC;

  S48_DECLARE_GC_PROTECT(2);
  S48_GC_PROTECT_2(res, scheme_str);
  ODBC_DEBUG_PRINTF_1("odbc_sql_get_cursor_name\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  buffer_len = odbc_initial_retval_buffer_size;
  for (;;)
    {
      odbc_sql_alloc((void **) &buffer, buffer_len, sizeof(SQLCHAR));
      retval = SQLGetCursorName(sh, buffer, buffer_len, &buffer_needed);
      
      if (SQL_SUCCEEDED(retval) && (buffer_needed > buffer_len))
	buffer_len = buffer_needed+1;
      else
	break;
    }

  if (SQL_SUCCEEDED(retval))
    scheme_str = s48_enter_string(buffer);

  free(buffer);
  res = s48_list_2(s48_enter_integer(retval), scheme_str);
  S48_GC_UNPROTECT();
  return res;
}

s48_value odbc_sql_set_cursor_name(s48_value stmt_handle, s48_value cursorname)
{
  SQLHSTMT sh;
  SQLCHAR *cn;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_set_cursor_name\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  cn = (SQLCHAR *) s48_extract_string(cursorname);
  retval = SQLSetCursorName(sh, cn, S48_STRING_LENGTH(cursorname));

  return s48_enter_integer(retval);
}

/*
 *
 * PART 6
 *
 * Submitting requests
 *
 */

s48_value odbc_sql_execute(s48_value stmt_handle)
{
  SQLHSTMT sh;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_execute\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  retval = SQLExecute(sh);

  return s48_enter_integer(retval);
}
  
s48_value odbc_sql_execute_direct(s48_value stmt_handle,
				  s48_value stmt)
{
  SQLHSTMT sh;
  SQLCHAR  *query;
  SQLRETURN retval;
  
  ODBC_DEBUG_PRINTF_1("odbc_sql_execute_direct\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  query = (SQLCHAR *) s48_extract_string(stmt);
  retval = SQLExecDirect(sh, query, S48_STRING_LENGTH(stmt));

  return s48_enter_integer(retval);
}

/* Returns the text of an SQL statement as translated by the driver. */
s48_value odbc_sql_native_sql(s48_value conn_handle, s48_value stmt_txt)
{
  SQLHDBC ch;
  SQLCHAR *stmt_in, *stmt_out = NULL;
  SQLINTEGER buffer_len, buffer_needed;
  SQLRETURN retval;
  s48_value res = S48_UNSPECIFIC;
  s48_value scheme_str = S48_UNSPECIFIC;

  S48_DECLARE_GC_PROTECT(2);
  S48_GC_PROTECT_2(res, scheme_str);
  ch = (SQLHDBC) s48_extract_integer(conn_handle);
  stmt_in = (SQLCHAR *) s48_extract_string(stmt_txt);
  ODBC_DEBUG_PRINTF_1("odbc_sql_native_sql\n");
  
  buffer_len = odbc_initial_retval_buffer_size;
  for (;;) 
    {
      odbc_sql_alloc((void **) &stmt_out, buffer_len, sizeof(SQLCHAR));
      retval = SQLNativeSql(ch, stmt_in, S48_STRING_LENGTH(stmt_txt),
			    stmt_out, buffer_len, &buffer_needed);

      if (SQL_SUCCEEDED(retval) && buffer_needed > buffer_len)
	buffer_len = buffer_needed+1;
      else
	break;
    }

  if (SQL_SUCCEEDED(retval))
    scheme_str = s48_enter_string(stmt_out);
  
  free(stmt_out);
  res = s48_list_2(s48_enter_integer(retval), scheme_str);
  S48_GC_UNPROTECT();
  return res;
}

/* Returns the description for a specific parameter in a statement */
s48_value odbc_sql_describe_param(s48_value stmt_handle, s48_value parameter_no)
{
  SQLHSTMT sh;
  SQLUSMALLINT pn;
  SQLUINTEGER ps;
  SQLSMALLINT dt, dd, n;
  SQLRETURN retval;
  s48_value res = S48_UNSPECIFIC;
  s48_value scheme_rec = S48_UNSPECIFIC;

  S48_DECLARE_GC_PROTECT(2);
  S48_GC_PROTECT_2(res, scheme_rec);
  ODBC_DEBUG_PRINTF_1("odbc_sql_describe_param\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  pn = (SQLUSMALLINT) s48_extract_integer(parameter_no);
  retval = SQLDescribeParam(sh, pn, &dt, &ps, &dd, &n);

  if (SQL_SUCCEEDED(retval))
    {
      scheme_rec = s48_make_record(odbc_parameter_record_type);
      S48_RECORD_SET(scheme_rec, SR_ODBC_PARAMETER_TYPE, s48_enter_integer(dt));
      S48_RECORD_SET(scheme_rec, SR_ODBC_PARAMETER_SIZE, s48_enter_integer(ps));
      S48_RECORD_SET(scheme_rec, SR_ODBC_PARAMETER_DIGITS, s48_enter_integer(dd));
      S48_RECORD_SET(scheme_rec, SR_ODBC_PARAMETER_NULLABLE, s48_enter_integer(n));
    }

  res = s48_list_2(s48_enter_integer(retval), scheme_rec);
  S48_GC_UNPROTECT();
  return res;
}

/* Returns the number of parameters in a statement */
s48_value odbc_sql_num_params(s48_value stmt_handle)
{
  SQLHSTMT sh;
  SQLSMALLINT params;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_num_params\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  retval = SQLNumParams(sh, &params);

  return s48_list_2(s48_enter_integer(retval),
		    SQL_SUCCEEDED(retval) ? s48_enter_integer(params) :
		    S48_UNSPECIFIC);
}

/*
 *
 * PART 7
 *
 * Retrieving results and information about results
 *
 */

s48_value odbc_sql_row_count(s48_value stmt_handle)
{
  SQLHSTMT sh;
  SQLRETURN retval;
  SQLINTEGER rowcount;

  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  ODBC_DEBUG_PRINTF_1("odbc_sql_row_count\n");
  retval = SQLRowCount(sh, &rowcount);

  return s48_list_2(s48_enter_integer(retval),
		    SQL_SUCCEEDED(retval) ? s48_enter_integer(rowcount) :
		    S48_UNSPECIFIC);
}
  
s48_value odbc_sql_get_data(s48_value stmt_handle, s48_value column_number, 
			    s48_value target_type)
{
  SQLHSTMT sh;
  SQLUSMALLINT cn;
  SQLSMALLINT tt, buffer_len;
  SQLINTEGER buffer_needed;
  SQLRETURN retval;
  void *buffer = NULL;
  s48_value result = S48_UNSPECIFIC;

  S48_DECLARE_GC_PROTECT(1);
  S48_GC_PROTECT_1(result);
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  cn = (SQLUSMALLINT) s48_extract_integer(column_number);
  tt = (SQLSMALLINT) s48_extract_integer(target_type);
  ODBC_DEBUG_PRINTF_4("odbc_sql_get_data() sh:%x cn:%d tt:%d\n", sh, cn, tt);

  buffer_len = odbc_initial_retval_buffer_size;
  for (;;)
    {
      odbc_sql_alloc((void **) &buffer, buffer_len, sizeof_sql_c_type_identifier(tt));
      retval = SQLGetData(sh, cn, tt, buffer, buffer_len, &buffer_needed);

      if (SQL_SUCCEEDED(retval) && (buffer_needed > buffer_len))
	buffer_len = buffer_needed+1;
      else
	break;
    }

  if (SQL_SUCCEEDED(retval))
    {
      result = s48_list_2(s48_enter_integer(retval), buffer_to_s48_value(buffer, tt));
      free(buffer);
    }
  else
    result = s48_list_2(s48_enter_integer(retval), S48_UNSPECIFIC);

  S48_GC_UNPROTECT();
  return result;
}

/* Positions a cursor within a fetched block of data and allows an application
   to refresh data in the rowset or to update or delete data in the result
   set */
s48_value odbc_sql_set_pos(s48_value stmt_handle, s48_value row_number,
			   s48_value operation, s48_value lock_type)
{
  SQLHSTMT sh;
  SQLUSMALLINT rn, op, lt;
  SQLRETURN retval;

  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  rn = (SQLUSMALLINT) s48_extract_integer(row_number);
  op = (SQLUSMALLINT) s48_extract_integer(operation);
  lt = (SQLUSMALLINT) s48_extract_integer(lock_type);
  ODBC_DEBUG_PRINTF_5("odbc_sql_set_pos() sh:%x rn:%d op:%d lt:%d\n", sh, rn, op, lt);
  retval = SQLSetPos(sh, rn, op, lt);

  return s48_enter_integer(retval);
}

/* Performs bulk insertions and bulk bookmark operations, including
   update, delete, and fetch by bookmark. */
s48_value odbc_sql_bulk_operations(s48_value stmt_handle, s48_value operation)
{
#if (ODBCVER >= 0x300)
  SQLHSTMT sh;
  SQLUSMALLINT op;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_bulk_operations\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  op = (SQLUSMALLINT) s48_extract_integer(operation);
  retval = SQLBulkOperations(sh, op);
  
  return s48_enter_integer(retval);
#else
  RAISE_API_VERSION_MISMATCH("SQLBulkOperations", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

/* Determines whether there are more result sets available and, if so,
   initializes processing for the next result set */
s48_value odbc_sql_more_results(s48_value stmt_handle)
{
  SQLHSTMT sh;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_more_results\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  retval = SQLMoreResults(sh);

  return s48_enter_integer(retval);
}

s48_value odbc_sql_fetch(s48_value stmt_handle)
{
  SQLHSTMT sh;
  SQLRETURN retval;
  
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  ODBC_DEBUG_PRINTF_2("odbc_sql_fetch() %x\n", sh);
  retval = SQLFetch(sh);

  return s48_enter_integer(retval);
}

s48_value odbc_sql_num_result_cols(s48_value stmt_handle)
{
  SQLHSTMT sh;
  SQLSMALLINT numcols;
  SQLRETURN retval;

  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  ODBC_DEBUG_PRINTF_1("odbc_sql_num_result_cols\n");
  retval = SQLNumResultCols(sh, &numcols);

  return s48_list_2(s48_enter_integer(retval),
		    SQL_SUCCEEDED(retval) ? s48_enter_integer(numcols) :
		    S48_UNSPECIFIC);
}

s48_value odbc_sql_describe_col(s48_value stmt_handle, s48_value column_number)
{
  SQLHSTMT sh;
  SQLSMALLINT cn;
  SQLCHAR *buffer = NULL;
  SQLSMALLINT buffer_len, buffer_needed, data_type, digits, nullable;
  SQLUINTEGER col_size;
  SQLRETURN retval;
  s48_value res = S48_UNSPECIFIC;
  s48_value col_rec = S48_UNSPECIFIC;

  S48_DECLARE_GC_PROTECT(2);
  S48_GC_PROTECT_2(res, col_rec);
  ODBC_DEBUG_PRINTF_1("odbc_sql_describe_col\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  cn = (SQLSMALLINT) s48_extract_integer(column_number);

  buffer_len = odbc_initial_retval_buffer_size;
  for (;;)
    {
      odbc_sql_alloc((void **) &buffer, buffer_len, sizeof(SQLCHAR));
      retval = SQLDescribeCol(sh, cn,
			      buffer, buffer_len, &buffer_needed,
			      &data_type, &col_size, &digits,
			      &nullable);
      
      if (SQL_SUCCEEDED(retval) && (buffer_needed > buffer_len))
	buffer_len = buffer_needed+1;
      else
	break;
    }

  if (SQL_SUCCEEDED(retval))
    {
      col_rec = s48_make_record(odbc_column_record_type);
      S48_RECORD_SET(col_rec, SR_ODBC_COLUMN_NAME, 
		     s48_enter_string(buffer));
      S48_RECORD_SET(col_rec, SR_ODBC_COLUMN_TYPE,
		     s48_enter_integer(data_type));
      S48_RECORD_SET(col_rec, SR_ODBC_COLUMN_SIZE,
		     s48_enter_integer(col_size));
      S48_RECORD_SET(col_rec, SR_ODBC_COLUMN_DIGITS,
		     s48_enter_integer(digits));
      S48_RECORD_SET(col_rec, SR_ODBC_COLUMN_NULLABLE,
		     s48_enter_integer(nullable));
    }

  res = s48_list_2(s48_enter_integer(retval), col_rec);
  free(buffer);
  S48_GC_UNPROTECT();
  return res;
}

/* Describes attributes of a column in the result set */
s48_value odbc_sql_col_attribute(s48_value stmt_handle, s48_value column_number,
				 s48_value field_id)
{
#if (ODBCVER >= 0x300)
  SQLHSTMT sh;
  SQLUSMALLINT cn, fi;
  SQLCHAR *buffer = NULL;
  SQLSMALLINT buffer_len, buffer_needed;
  SQLINTEGER intbuffer;
  SQLRETURN retval;
  s48_value res_string = S48_UNSPECIFIC;
  s48_value res_pair = S48_UNSPECIFIC;
  s48_value res = S48_UNSPECIFIC;

  S48_DECLARE_GC_PROTECT(3);
  S48_GC_PROTECT_3(res, res_pair, res_string);
  ODBC_DEBUG_PRINTF_1("odbc_sql_col_attribute\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  cn = (SQLUSMALLINT) s48_extract_integer(column_number);
  fi = (SQLUSMALLINT) s48_extract_integer(field_id);

  buffer_len = odbc_initial_retval_buffer_size;
  for (;;)
    {
      odbc_sql_alloc((void **) &buffer, buffer_len, sizeof(SQLCHAR));
      
      retval = SQLColAttribute(sh, cn, fi, 
			       buffer, buffer_len, &buffer_needed,
			       &intbuffer);

      if (SQL_SUCCEEDED(retval) && (buffer_needed > buffer_len))
	buffer_len = buffer_needed+1;
      else
	break;
    }

  if (SQL_SUCCEEDED(retval))
    {
      res_string = s48_enter_string(buffer);
      res_pair = s48_cons(res_string, s48_enter_integer(intbuffer));
    }

  free(buffer);
  res = s48_list_2(s48_enter_integer(retval), res_pair);
  S48_GC_UNPROTECT();
  return res;
#else
  RAISE_API_VERSION_MISMATCH("SQLColAttribute", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

ColumnRecPtr bindcol_lookup_binding(SQLHSTMT stmt_handle, SQLUSMALLINT column_no)
{
  StmtRecPtr stmt;
  ColumnRecPtr col;

  ODBC_DEBUG_PRINTF_3("bindcol_lookup_binding() %x %d\n", stmt_handle, column_no);
  stmt = global_bindcol_list;

  while (stmt != NULL)
    {
      if (stmt->stmt_handle == stmt_handle)
	{
	  col = stmt->col_recs;
	  while ((col != NULL) && (col->col_no != column_no))
	    col = col->next;
	  return col;
	}
      col = col->next;
    }
  return NULL;
}

s48_value bindcol_lookup_binding_scheme(s48_value stmt_handle, s48_value column_no)
{
  SQLHSTMT sh;
  SQLUSMALLINT cn;
  ColumnRecPtr col;
  
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  cn = (SQLUSMALLINT) s48_extract_integer(column_no);

  ODBC_DEBUG_PRINTF_3("bindcol_lookup_binding_scheme() sh:%x cn:%d\n", sh, cn);
  col = bindcol_lookup_binding(sh, cn);

  if (col == NULL)
    RAISE_ODBC_BINDCOL_UNBOUND_COLUMN_ERROR((long)sh, cn)
  else
    {
      ODBC_DEBUG_PRINTF_2("bindcol_lookup_binding_scheme(): col_buf:%x\n", col->col_buffer);
      return buffer_to_s48_value(col->col_buffer, col->target_type);
    }
}

/* Semantics differs from original SQLBindCol(), due to the need of
   bookkeeping a target buffer list in C. odbc_sql_bindcol() handles
   column binding and rebinding, but not unbinding. To unbind a column
   use bindcol_unbind_column() or bindcol_finalize_bindcols(). */
s48_value odbc_sql_bindcol(s48_value stmt_handle, s48_value column_no,
			   s48_value target_type, s48_value buffer_len)
{
  SQLHSTMT sh;
  SQLUSMALLINT cn;
  SQLSMALLINT tt;
  SQLLEN bl;
  SQLRETURN retval;
  ColumnRecPtr col = NULL;

  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  cn = (SQLUSMALLINT) s48_extract_integer(column_no);
  tt = (SQLSMALLINT) s48_extract_integer(target_type);
  bl = (SQLLEN) s48_extract_integer(buffer_len);

  ODBC_DEBUG_PRINTF_5("odbc_sql_bindcol() %x %d %d %d\n", sh, cn, tt, bl);
  
  /* try to look up ColumRec */
  col = bindcol_lookup_binding(sh, cn);
  
  if (col == NULL)
    {
      /* There is no binding for this stmt-handle/column-no yet */
      ODBC_DEBUG_PRINTF_1("odbc_sql_bindcol(): new binding\n");
      col = (ColumnRec *) malloc(sizeof(ColumnRec));
      if (col == NULL)
	RAISE_ODBC_BUFFER_ALLOC_ERROR(sizeof(ColumnRec));

      /* initialize it */
      col->col_no = cn;
      col->target_type = tt;
      col->buffer_len = bl;
      col->buffer_needed = 0;
      col->next = NULL;
      
      col->col_buffer = (void *) malloc(sizeof_sql_c_type_identifier(tt)*bl);
      ODBC_DEBUG_PRINTF_3("odbc_sql_bindcol() malloc %x %d\n",
			  col->col_buffer, sizeof_sql_c_type_identifier(tt)*bl);
      if (col->col_buffer == NULL)
	RAISE_ODBC_BUFFER_ALLOC_ERROR(sizeof_sql_c_type_identifier(tt)*bl);

      /* store col in global_bindcol_list */
      bindcol_bind_column(sh, cn, col);
    }
  else 
    {
      /* user wishes to rebind column for whatever reason */
      ODBC_DEBUG_PRINTF_1("odbc_sql_bindcol(): rebinding existing binding\n");
  
      if (tt != col->target_type)
	RAISE_ODBC_BINDCOL_REBINDING_ERROR("While rebinding buffers: old/new target type do not match")
      
      /* free the old buffer, allocate a new one */
      free(col->col_buffer);
      col->col_buffer = (void *) malloc(sizeof_sql_c_type_identifier(tt)*bl);
      col->buffer_len = sizeof_sql_c_type_identifier(tt)*bl;
      ODBC_DEBUG_PRINTF_3("odbc_sql_bindcol() reallocate %x %d\n", col->col_buffer,
			  sizeof_sql_c_type_identifier(tt)*bl);
      if (col->col_buffer == NULL)
	RAISE_ODBC_BUFFER_ALLOC_ERROR(sizeof_sql_c_type_identifier(tt)*bl);
    }

  /* at this point ColumRecPtr col has been created or updated, call SQLBindCol() */
  retval = SQLBindCol(sh, cn, tt, col->col_buffer, col->buffer_len, &col->buffer_needed);

  return s48_enter_integer(retval);
}

void bindcol_bind_column(SQLHSTMT stmt_handle, SQLUSMALLINT column_no, ColumnRecPtr new_col)
{
  StmtRecPtr stmt, prev_stmt, new_stmt;
  ColumnRecPtr col, prev_col;

  ODBC_DEBUG_PRINTF_4("bindcol_bind_column() %x %d %x\n", stmt_handle, column_no, new_col);
  prev_stmt = stmt = global_bindcol_list;  
  while (stmt != NULL)
    {
      if (stmt->stmt_handle == stmt_handle)
	{
	  prev_col = col = stmt->col_recs;
	  while ((col != NULL) && (col->col_no != column_no))
	    {
	      prev_col = col;
	      col = col->next;
	    }
	  if (col == NULL)
	    {
	      prev_col->next = new_col;
	      return;
	    }
	}
      prev_stmt = stmt;
      stmt = stmt->next;
    }
  if (stmt == NULL)
    {
      ODBC_DEBUG_PRINTF_1("bindcol_bind_column() global_bindcol_list is empty\n");
      new_stmt = (StmtRecPtr) malloc(sizeof(StmtRec));
      if (new_stmt == NULL)
	RAISE_ODBC_BUFFER_ALLOC_ERROR(sizeof(StmtRec));
      new_stmt->next = NULL;
      new_stmt->stmt_handle = stmt_handle;
      new_stmt->col_recs = new_col;
      if (global_bindcol_list == NULL)
	global_bindcol_list = new_stmt;
      else
	prev_stmt->next = new_stmt;
    }
}

void bindcol_unbind_colum(SQLHSTMT stmt_handle, SQLUSMALLINT column_no)
{
  StmtRecPtr stmt, prev_stmt;
  ColumnRecPtr col, prev_col;

  ODBC_DEBUG_PRINTF_3("bindcol_unbind_colum() %x %d\n", stmt_handle, column_no);

  prev_stmt = stmt = global_bindcol_list;
  while (stmt != NULL)
    {
      if (stmt->stmt_handle == stmt_handle)
	{
	  prev_col = col = stmt->col_recs;
	  while ((col != NULL) && (col->col_no != column_no))
	    {
	      prev_col = col;
	      col = col->next;
	    }
	  if (col == NULL)
	    RAISE_ODBC_BINDCOL_UNBOUND_COLUMN_ERROR((long)stmt_handle, column_no)
	  ODBC_DEBUG_PRINTF_2("bindcol_unbind_colum() free %x\n", col->col_buffer);
	  free(col->col_buffer);
	  prev_col->next = col->next;
	  free(col);
	  if (stmt->col_recs == NULL)
	    {
	      prev_stmt->next = stmt->next;
	      free(stmt);
	    }
	  return;
	}
      prev_stmt = stmt;
      stmt = stmt->next;
    }
  RAISE_ODBC_BINDCOL_UNBOUND_COLUMN_ERROR((long)stmt_handle, column_no)
}

s48_value bindcol_finalize_bindcols(s48_value stmt_handle)
{
  SQLHSTMT sh;
  StmtRecPtr stmt, prev_stmt;
  ColumnRecPtr col, prev_col, first_col;
  SQLRETURN retval;

  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  ODBC_DEBUG_PRINTF_2("bindcol_finalize_bindcols() %x\n", sh);

  prev_stmt = stmt = global_bindcol_list;
  while (stmt != NULL)
    {
      if (stmt->stmt_handle == sh)
	{
	  /* a rather dumb approach... */
	  first_col = stmt->col_recs;
	  while (first_col != NULL)
	    {
	      col = first_col;
	      while (col != NULL)
		{
		  if (col->next == NULL)
		    {
		      /* this bindcol_rec is the last in chain */
		      SQLLEN dummy;

 		      retval = SQLBindCol(sh, col->col_no, col->target_type, 
					  NULL, 0, &dummy);
		      free(col->col_buffer);
		      free(col);
		      prev_col->next = NULL;
		    }
		  prev_col = col;
		  col = col->next;
		}
	    }
	}
      stmt = stmt->next;
    }
  return S48_UNSPECIFIC;
}

/*
 *
 * PART 8
 *
 * Obtaining information about the data source's
 * system tables (catalog functxbions)
 *
 */

/* Returns a list of columns and associated privileges for one or more tables */
s48_value odbc_sql_column_privileges(s48_value stmt_handle, s48_value catalog_name,
				s48_value schema_name, s48_value table_name,
				s48_value column_name)
{
  SQLHSTMT sh;
  SQLCHAR *catalog, *schema, *table, *column;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_column_privileges\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  catalog = (SQLCHAR *) s48_extract_string(catalog_name);
  schema = (SQLCHAR *) s48_extract_string(schema_name);
  table = (SQLCHAR *) s48_extract_string(table_name);
  column = (SQLCHAR *) s48_extract_string(column_name);
  retval = SQLColumnPrivileges(sh, 
			       catalog, S48_STRING_LENGTH(catalog_name),
			       schema, S48_STRING_LENGTH(schema_name),
			       table, S48_STRING_LENGTH(table_name),
			       column, S48_STRING_LENGTH(column_name));

  return s48_enter_integer(retval);
}

/* Returns the list of column names in a specified table */
s48_value odbc_sql_columns(s48_value stmt_handle, s48_value catalog_name,
			   s48_value schema_name, s48_value table_name,
			   s48_value column_name)
{
  SQLHSTMT sh;
  SQLCHAR *catalog, *schema, *table, *column;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_columns\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  catalog = (SQLCHAR *) s48_extract_string(catalog_name);
  schema = (SQLCHAR *) s48_extract_string(schema_name);
  table = (SQLCHAR *) s48_extract_string(table_name);
  column = (SQLCHAR *) s48_extract_string(column_name);
  retval = SQLColumns(sh, 
		      catalog, S48_STRING_LENGTH(catalog_name),
		      schema, S48_STRING_LENGTH(schema_name),
		      table, S48_STRING_LENGTH(table_name),
		      column, S48_STRING_LENGTH(column_name));

  return s48_enter_integer(retval);
}

/* Returns a list of columns names that make up foreign keys, 
   if the exist for a specified table */
s48_value odbc_sql_foreign_keys(s48_value stmt_handle, s48_value pk_catalog_name,
				s48_value pk_schema_name, s48_value pk_table_name,
				s48_value fk_catalog_name, s48_value fk_schema_name,
				s48_value fk_table_name)
{
  SQLHSTMT sh;
  SQLCHAR *pk_catalog, *pk_schema, *pk_table;
  SQLCHAR *fk_catalog, *fk_schema, *fk_table;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_foreign_keys\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  pk_catalog = (SQLCHAR *) s48_extract_string(pk_catalog_name);
  pk_schema = (SQLCHAR *) s48_extract_string(pk_schema_name);
  pk_table = (SQLCHAR *) s48_extract_string(pk_table_name);
  fk_catalog = (SQLCHAR *) s48_extract_string(fk_catalog_name);
  fk_schema = (SQLCHAR *) s48_extract_string(fk_schema_name);
  fk_table = (SQLCHAR *) s48_extract_string(fk_table_name);
  retval = SQLForeignKeys(sh,
			  pk_catalog, S48_STRING_LENGTH(pk_catalog_name),
			  pk_schema, S48_STRING_LENGTH(pk_schema_name),
			  pk_table, S48_STRING_LENGTH(pk_table_name),
			  fk_catalog, S48_STRING_LENGTH(fk_catalog_name),
			  fk_schema, S48_STRING_LENGTH(fk_schema_name),
			  fk_table, S48_STRING_LENGTH(fk_table_name));

  return s48_enter_integer(retval);
}

/* Returns the list of column names that make up the primary key for a table */
s48_value odbc_sql_primary_keys(s48_value stmt_handle, s48_value catalog_name,
				s48_value schema_name, s48_value table_name)
{
  SQLHSTMT sh;
  SQLCHAR *catalog, *schema, *table;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_primary_keys\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  catalog = (SQLCHAR *) s48_extract_string(catalog_name);
  schema = (SQLCHAR *) s48_extract_string(schema_name);
  table = (SQLCHAR *) s48_extract_string(table_name);
  retval = SQLPrimaryKeys(sh, 
			  catalog, S48_STRING_LENGTH(catalog_name),
			  schema, S48_STRING_LENGTH(schema_name),
			  table, S48_STRING_LENGTH(table_name));
  
  return s48_enter_integer(retval);
}

/* Returns the list of input and output parameters, as well as the columns
   that make up the result set for the specified procedures */
s48_value odbc_sql_procedure_columns(s48_value stmt_handle, s48_value catalog_name,
				     s48_value schema_name, s48_value proc_name,
				     s48_value column_name)
{
  SQLHSTMT sh;
  SQLCHAR *catalog, *schema, *proc, *column;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_procedure_columns\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  catalog = (SQLCHAR *) s48_extract_string(catalog_name);
  schema = (SQLCHAR *) s48_extract_string(schema_name);
  proc = (SQLCHAR *) s48_extract_string(proc_name);
  column = (SQLCHAR *) s48_extract_string(column_name);
  retval = SQLProcedureColumns(sh,
			       catalog, S48_STRING_LENGTH(catalog_name),
			       schema, S48_STRING_LENGTH(schema_name),
			       proc, S48_STRING_LENGTH(proc_name),
			       column, S48_STRING_LENGTH(column_name));
  
  return s48_enter_integer(retval);
}

/* Returns the list of procedure names stored in a specific data source. */
s48_value odbc_sql_procedures(s48_value stmt_handle, s48_value catalog_name,
			      s48_value schema_name, s48_value proc_name)
{
  SQLHSTMT sh;
  SQLCHAR *catalog, *schema, *proc;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_procedures\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  catalog = (SQLCHAR *) s48_extract_string(catalog_name);
  schema = (SQLCHAR *) s48_extract_string(schema_name);
  proc = (SQLCHAR *) s48_extract_string(proc_name);
  retval = SQLProcedures(sh,
			 catalog, S48_STRING_LENGTH(catalog_name),
			 schema, S48_STRING_LENGTH(schema_name),
			 proc, S48_STRING_LENGTH(proc_name));

  return s48_enter_integer(retval);
}

/* Returns information about the optimal set of columns that uniquely identifies
   a row in a specified table, or the columns that are automatically updated
   when any value in the row is updated by a transaction */
s48_value odbc_sql_special_columns(s48_value stmt_handle, s48_value identifier_type,
				   s48_value catalog_name, s48_value schema_name,
				   s48_value table_name, s48_value scope,
				   s48_value nullable)
{
  SQLHSTMT sh;
  SQLCHAR *catalog, *schema, *table;
  SQLSMALLINT s, n, it;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_special_columns\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  it = (SQLSMALLINT) s48_extract_integer(identifier_type);
  catalog = (SQLCHAR *) s48_extract_string(catalog_name);
  schema = (SQLCHAR *) s48_extract_string(schema_name);
  table = (SQLCHAR *) s48_extract_string(table_name);
  s = (SQLSMALLINT) s48_extract_integer(scope);
  n = (SQLSMALLINT) s48_extract_integer(nullable);
  retval = SQLSpecialColumns(sh, it,
			     catalog, S48_STRING_LENGTH(catalog_name),
			     schema, S48_STRING_LENGTH(schema_name),
			     table, S48_STRING_LENGTH(table_name),
			     s, n);
  
  return s48_enter_integer(retval);
}

/* Returns statistics about a single table and the list of indexes associated 
   with the table */
s48_value odbc_sql_statistics(s48_value stmt_handle, s48_value catalog_name,
			      s48_value schema_name, s48_value table_name,
			      s48_value unique, s48_value reserved)
{
  SQLHSTMT sh;
  SQLCHAR *catalog, *schema, *table;
  SQLSMALLINT u, r;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_statistics\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  catalog = (SQLCHAR *) s48_extract_string(catalog_name);
  schema = (SQLCHAR *) s48_extract_string(schema_name);
  table = (SQLCHAR *) s48_extract_string(table_name);
  u = (SQLSMALLINT) s48_extract_integer(unique);
  r = (SQLSMALLINT) s48_extract_integer(reserved);
  retval = SQLStatistics(sh,
			 catalog, S48_STRING_LENGTH(catalog_name),
			 schema, S48_STRING_LENGTH(schema_name),
			 table, S48_STRING_LENGTH(table_name),
			 u, r);
  
  return s48_enter_integer(retval);
}

/* Returns a list of tables and the privileges associated with each table */
s48_value odbc_sql_table_privileges(s48_value stmt_handle, s48_value catalog_name,
				    s48_value schema_name, s48_value table_name)
{
  SQLHSTMT sh;
  SQLCHAR *catalog, *schema, *table;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_table_privileges\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  catalog = (SQLCHAR *) s48_extract_string(catalog_name);
  schema = (SQLCHAR *) s48_extract_string(schema_name);
  table = (SQLCHAR *) s48_extract_string(table_name);
  retval = SQLTablePrivileges(sh,
			      catalog, S48_STRING_LENGTH(catalog_name),
			      schema, S48_STRING_LENGTH(schema_name),
			      table, S48_STRING_LENGTH(table_name));

  return s48_enter_integer(retval);
}

/* Returns the list of table names stored in a specific data source */
s48_value odbc_sql_tables(s48_value stmt_handle, s48_value catalog_name,
			  s48_value schema_name, s48_value table_name,
			  s48_value table_type)
{
  SQLHSTMT sh;
  SQLCHAR *catalog, *schema, *tablen, *tablet;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_tables\n");
  retval = SQLTables(sh,
		     catalog, S48_STRING_LENGTH(catalog_name),
		     schema, S48_STRING_LENGTH(schema_name),
		     tablen, S48_STRING_LENGTH(table_name),
		     tablet, S48_STRING_LENGTH(table_type));

  return s48_enter_integer(retval);
}

/*
 *
 * PART 9
 *
 * Terminating a statement
 *
 */

/* Ends statement processing, discards pending resilt, and, 
 * optionally, frees all resources associated with the
 * statement handle */ 
s48_value odbc_sql_free_statement(s48_value stmt_handle, s48_value option)
{
  SQLHSTMT sh;
  SQLUSMALLINT opt;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_free_statement\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  opt = (SQLUSMALLINT) s48_extract_integer(option);
  retval = SQLFreeStmt(sh, opt);

  return s48_enter_integer(retval);
}

/* Closes a cursor that has been opened on a statement handle */
s48_value odbc_sql_close_cursor(s48_value stmt_handle)
{
#if (ODBCVER >= 0x300)
  SQLHSTMT sh;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_close_cursor\n");
  sh = (SQLHSTMT) s48_extract_integer(stmt_handle);
  retval = SQLCloseCursor(sh);

  return s48_enter_integer(retval);
#else
  RAISE_API_VERSION_MISMATCH("SQLCloseCursor", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

/* Cancels an SQL statement */
s48_value odbc_sql_cancel(s48_value stmt_handle)
{
  SQLHSTMT sh;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_cancel\n");
  retval = SQLCancel(sh);
  
  return s48_enter_integer(retval);
}

/* Commits or rolls back a transaction */
s48_value odbc_sql_endtran(s48_value handle_type, s48_value handle,
			   s48_value completion_type)
{
#if (ODBCVER >= 0x300)
  SQLSMALLINT ht, ct;
  SQLHANDLE h;
  SQLRETURN retval;

  ODBC_DEBUG_PRINTF_1("odbc_sql_endtran\n");
  ht = (SQLSMALLINT) s48_extract_integer(handle_type);
  h = (SQLHANDLE) s48_extract_integer(handle);
  ct = (SQLSMALLINT) s48_extract_integer(completion_type);

  retval = SQLEndTran(ht, h, ct);
  return s48_enter_integer(retval);
#else
  RAISE_API_VERSION_MISMATCH("SQLEndTran", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

/*
 *
 * PART 10
 *
 * Terminating a connection
 *
 */

/* Closes the connection */
s48_value odbc_sql_disconnect(s48_value conn_handle)
{
  SQLHDBC ch;
  SQLRETURN retval;

  ch = (SQLHDBC) s48_extract_integer(conn_handle);
  ODBC_DEBUG_PRINTF_2("odbc_sql_disconnect() %x\n", ch);
  retval = SQLDisconnect(ch);

  return s48_enter_integer(retval);
}

/* Free a handle */
s48_value odbc_sql_free_handle(s48_value handle_type, s48_value handle) 
{
#if (ODBCVER >= 0x300)
  SQLSMALLINT ht;
  SQLHANDLE h;
  SQLRETURN retval;

  ht = (SQLSMALLINT) s48_extract_integer(handle_type);
  h = (SQLHANDLE) s48_extract_integer(handle);
  ODBC_DEBUG_PRINTF_3("odbc_sql_free_handle() %x %d\n", h, ht);
  retval = SQLFreeHandle(ht, h);
  
  return s48_enter_integer(retval);
#else
  RAISE_API_VERSION_MISMATCH("SQLFreeHandle", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}

/*
 *
 * PART 11
 *
 * misc. functions
 *
 */

s48_value odbc_sql_get_diag_recs(s48_value handle_type, s48_value handle)
{
#if (ODBCVER >= 0x300)
  SQLSMALLINT ht;
  SQLHANDLE h;
  SQLCHAR sql_state[6];
  SQLINTEGER native_error;
  SQLCHAR *buffer = NULL;
  SQLSMALLINT i, more_recs, buffer_len, buffer_needed;
  SQLRETURN retval;
  s48_value res = S48_UNSPECIFIC;
  s48_value rec_list = S48_NULL;
  s48_value diag_rec = S48_UNSPECIFIC;

  S48_DECLARE_GC_PROTECT(3);
  S48_GC_PROTECT_3(res, rec_list, diag_rec);
  ht = (SQLSMALLINT) s48_extract_integer(handle_type);
  h = (SQLHANDLE) s48_extract_integer(handle);
  ODBC_DEBUG_PRINTF_4("SQLGetDiagRec(%d, %x, %i)\n", ht, h, i);

  i = more_recs = 1;
  buffer_len = odbc_initial_retval_buffer_size;
  while (more_recs) {

    diag_rec = s48_make_record(odbc_diag_record_type);

    for (;;)
      {
	odbc_sql_alloc((void **) &buffer, buffer_len, sizeof(SQLCHAR));
	retval = SQLGetDiagRec(ht, h, i, 
			       sql_state, &native_error,
			       buffer, buffer_len, &buffer_needed);

	if (SQL_SUCCEEDED(retval) && (buffer_needed > buffer_len))
	  {
	    ODBC_DEBUG_PRINTF_3("buffer_needed %d buffer_len %d\n", buffer_needed, buffer_len);
	    buffer_len = buffer_needed+1;
	  }
	else
	  break;
      }

    switch (retval)
      {
      case SQL_SUCCESS: case SQL_SUCCESS_WITH_INFO:
	{
	  S48_RECORD_SET(diag_rec, SR_ODBC_DIAG_SQL_STATE, s48_enter_string(sql_state));
	  S48_RECORD_SET(diag_rec, SR_ODBC_DIAG_NATIVE_ERROR, s48_enter_integer(native_error));
	  S48_RECORD_SET(diag_rec, SR_ODBC_DIAG_MESSAGE, s48_enter_string(buffer));
	  rec_list = s48_cons(diag_rec, rec_list);
	  free(buffer);
	  buffer = NULL;
	  break;
	}
      case SQL_NO_DATA:
	{ 
	  more_recs = 0;
	  break;
	}
      default:
	{
	  free(buffer);
	  res = s48_list_2(s48_enter_integer(retval), S48_UNSPECIFIC);
	  S48_GC_UNPROTECT();
	  return res;
	}
      }
    i++;
  }
  
  res = s48_list_2(s48_enter_integer(retval), rec_list);
  free(buffer);
  S48_GC_UNPROTECT(); 
  return res;
#else
  RAISE_API_VERSION_MISMATCH("SQLGetDiagRec", ODBCVER, 0x300);
#endif /* ODBCVER >= 0x300 */
}    
    
#if ODBC_DEBUG_MSGS
/* print detailed debug information */

void odbc_debug_msgs(SQLSMALLINT handle_type, SQLHANDLE handle) 
{

  SQLCHAR sql_state[5];
  SQLINTEGER native_error;
  SQLCHAR message[ERROR_MSG_BUFFER_LEN];
  SQLSMALLINT i, message_len;
  SQLRETURN retval;

  printf("fetching diag_rec from odbc driver.\n");
  i = 1;
  while (1) {

    retval = SQLGetDiagRec(handle_type, handle,
			   i, sql_state,
			   &native_error, 
			   message, ERROR_MSG_BUFFER_LEN-1, 
			   &message_len);
	 
    if (retval == SQL_NO_DATA)
      break;
    
    if (retval == SQL_INVALID_HANDLE) {
      printf("ODBC: Could not get debug information: invalid handle provided\n");
      break;
    }
    
    if (retval == SQL_ERROR) {
      printf("ODBC: SQLGetDiagRec returned SQL_ERROR\n");
      break;
    }
    
    if (retval == SQL_SUCCESS_WITH_INFO)
      printf("ODBC warning: error message buffer too small.\n");
    
    if (retval == SQL_SUCCESS) {
      printf("\nODBC status record %d:\n", i);
      printf("SQL state: %s\n", (char *)sql_state);
      /* TODO: 
       * Need to find out how to printf the
       * native_error here 
       */
      printf("error msg: %s\n", message);
    }
    
    i++;
  }
}
#endif
 
/* convert Scheme sql-date record to SQL_DATE_STRUCT */
void sql_date_record_to_struct(s48_value sql_date, SQL_DATE_STRUCT *ds)
{  
  ds->year = (SQLSMALLINT) 
    s48_extract_integer(S48_RECORD_REF(sql_date, SR_SQL_DATE_YEAR));

  ds->month = (SQLUSMALLINT)
    s48_extract_integer(S48_RECORD_REF(sql_date, SR_SQL_DATE_MONTH));
  
  ds->day = (SQLUSMALLINT)
    s48_extract_integer(S48_RECORD_REF(sql_date, SR_SQL_DATE_DAY));
}

/* convert SQL_DATE_STRUCT to Scheme sql-date record */
s48_value struct_to_sql_date_record(SQL_DATE_STRUCT *ds)
{
  s48_value sql_date;

  S48_DECLARE_GC_PROTECT(1);
  S48_GC_PROTECT_1(sql_date);

  sql_date = s48_make_record(sql_date_record_type);

  S48_RECORD_SET(sql_date, SR_SQL_DATE_YEAR,
		 s48_enter_integer(ds->year));
  S48_RECORD_SET(sql_date, SR_SQL_DATE_MONTH,
		 s48_enter_integer(ds->month));
  S48_RECORD_SET(sql_date, SR_SQL_DATE_DAY,
		 s48_enter_integer(ds->day));

  S48_GC_UNPROTECT();
  return sql_date;
}
  
/* convert Scheme sql-time record to SQL_TIME_STRUCT */
void sql_time_record_to_struct(s48_value sql_time, SQL_TIME_STRUCT *ts)
{
  ts->hour = (SQLUSMALLINT)
    s48_extract_integer(S48_RECORD_REF(sql_time, SR_SQL_TIME_HOUR));
  
  ts->minute = (SQLUSMALLINT)
    s48_extract_integer(S48_RECORD_REF(sql_time, SR_SQL_TIME_MINUTE));
  
  ts->second = (SQLUSMALLINT)
    s48_extract_integer(S48_RECORD_REF(sql_time, SR_SQL_TIME_SECOND));
}

/* convert SQL_TIME_STRUCT to Scheme sql-time record */
s48_value struct_to_sql_time_record(SQL_TIME_STRUCT *ts)
{
  s48_value sql_time;

  S48_DECLARE_GC_PROTECT(1);
  S48_GC_PROTECT_1(sql_time);

  sql_time = s48_make_record(sql_time_record_type);
  
  S48_RECORD_SET(sql_time, SR_SQL_TIME_HOUR,
		 s48_extract_integer(ts->hour));
  
  S48_RECORD_SET(sql_time, SR_SQL_TIME_MINUTE,
		 s48_extract_integer(ts->minute));
  
  S48_RECORD_SET(sql_time, SR_SQL_TIME_SECOND,
		 s48_extract_integer(ts->second));

  S48_GC_UNPROTECT();
  return sql_time;
}

/* convert Scheme sql-timestamp record to SQL_TIMESTAMP_STRUCT */ 
void sql_timestamp_record_to_struct(s48_value sql_timestamp, 
				    SQL_TIMESTAMP_STRUCT *ts)
{
  ts->year = (SQLSMALLINT)
    s48_extract_integer(S48_RECORD_REF(sql_timestamp, SR_SQL_TIMESTAMP_YEAR));
  
  ts->month = (SQLUSMALLINT)
    s48_extract_integer(S48_RECORD_REF(sql_timestamp, SR_SQL_TIMESTAMP_MONTH));
  
  ts->day = (SQLUSMALLINT)
    s48_extract_integer(S48_RECORD_REF(sql_timestamp, SR_SQL_TIMESTAMP_DAY));
  
  ts->hour = (SQLUSMALLINT)
    s48_extract_integer(S48_RECORD_REF(sql_timestamp, SR_SQL_TIMESTAMP_HOUR));
  
  ts->minute = (SQLUSMALLINT)
    s48_extract_integer(S48_RECORD_REF(sql_timestamp, SR_SQL_TIMESTAMP_MINUTE));

  ts->second = (SQLUSMALLINT)
    s48_extract_integer(S48_RECORD_REF(sql_timestamp, SR_SQL_TIMESTAMP_SECOND));

  ts->fraction = (SQLUINTEGER)
    s48_extract_integer(S48_RECORD_REF(sql_timestamp, SR_SQL_TIMESTAMP_FRACTION));
}

/* convert SQL_TIMESTAMP_STRUCT to Scheme sql-timestamp record */
s48_value struct_to_sql_timestamp_record(SQL_TIMESTAMP_STRUCT *ts)
{
  s48_value sql_timestamp;

  S48_DECLARE_GC_PROTECT(1);
  S48_GC_PROTECT_1(sql_timestamp);

  sql_timestamp = s48_make_record(sql_timestamp_record_type);

  S48_RECORD_SET(sql_timestamp, SR_SQL_TIMESTAMP_YEAR,
		 s48_extract_integer(ts->year));
  
  S48_RECORD_SET(sql_timestamp, SR_SQL_TIMESTAMP_MONTH,
		 s48_extract_integer(ts->month));
  
  S48_RECORD_SET(sql_timestamp, SR_SQL_TIMESTAMP_DAY,
		 s48_extract_integer(ts->day));

  S48_RECORD_SET(sql_timestamp, SR_SQL_TIMESTAMP_HOUR,
		 s48_extract_integer(ts->hour));
  
  S48_RECORD_SET(sql_timestamp, SR_SQL_TIMESTAMP_MINUTE,
		 s48_extract_integer(ts->minute));

  S48_RECORD_SET(sql_timestamp, SR_SQL_TIMESTAMP_SECOND,
		 s48_extract_integer(ts->second));

  S48_RECORD_SET(sql_timestamp, SR_SQL_TIMESTAMP_FRACTION,
		 s48_extract_integer(ts->fraction));

  S48_GC_UNPROTECT();
  return sql_timestamp;
}

/* gets a C type identifier and returns the size of this type,
   according to Appendix D of ODBC Programmer's reference */
size_t sizeof_sql_c_type_identifier(SQLSMALLINT ctypeid) 
{ 
  switch (ctypeid) 
    { 
    case SQL_C_CHAR: return sizeof(SQLCHAR);
    case SQL_C_SSHORT: case SQL_C_SHORT: return sizeof(SQLSMALLINT);
    case SQL_C_USHORT: return sizeof(SQLUSMALLINT);
    case SQL_C_SLONG: case SQL_C_LONG: return sizeof(SQLINTEGER);
    case SQL_C_ULONG: return sizeof(SQLUINTEGER);
    case SQL_C_FLOAT: return sizeof(SQLREAL);
    case SQL_C_DOUBLE: return sizeof(SQLDOUBLE);
    case SQL_C_BIT: return sizeof(SQLCHAR);
    case SQL_C_STINYINT: return sizeof(SQLSCHAR);
    case SQL_C_UTINYINT: return sizeof(SQLCHAR);
/*     case SQL_SBIGINT: return sizeof(SQLBIGINT); */
/*     case SQL_UBIGINT: return sizeof(SQLUBIGINT); */
    case SQL_C_BINARY: return sizeof(SQLCHAR);
      /* I don't know what to do with these types... */
/*     case SQL_C_BOOKMARK: return sizeof(BOOKMARK); */
/*     case SQL_C_VARBOOKMARK: return sizeof(SQLCHAR); */
    case SQL_C_TYPE_DATE: return sizeof(SQL_DATE_STRUCT);
    case SQL_C_TYPE_TIME: return sizeof(SQL_TIME_STRUCT);
    case SQL_C_TYPE_TIMESTAMP: return sizeof(SQL_TIMESTAMP_STRUCT);
    case SQL_C_NUMERIC: return sizeof(SQL_NUMERIC_STRUCT);
      /*     case SQL_C_GUID: return sizeof(SQLGUID); */
    default: 
      RAISE_ODBC_UNKNOWN_C_TYPE_IDENTIFIER_ERROR(0, ctypeid);
      break;
    }
}

	
void odbc_sql_alloc(void **buffer, size_t buffer_len, size_t type_len)
{
  ODBC_DEBUG_PRINTF_4("odbc_sql_alloc(): bf:%x bl:%d tl: %d\n", buffer, buffer_len, type_len);
  if (*buffer == NULL)
    {
      ODBC_DEBUG_PRINTF_3("calloc bl:%d tl:%d\n", buffer_len+1, type_len);
      *buffer = (void *) calloc(buffer_len+1, type_len);
    }
  else
    {
      ODBC_DEBUG_PRINTF_2("realloc bl:%d\n", buffer_len*type_len);
      *buffer = (void *) realloc(*buffer, (buffer_len+1)*type_len);
    }

  if (*buffer == NULL)
    RAISE_ODBC_BUFFER_ALLOC_ERROR((buffer_len+1)*type_len);
}

s48_value buffer_to_s48_value(void *buffer, SQLSMALLINT ctypeid)
{
  ODBC_DEBUG_PRINTF_3("buffer_to_s48_value(): %x %d\n", buffer, ctypeid);
  switch (ctypeid)
    {
    case SQL_C_CHAR:
      return s48_enter_string((SQLCHAR *) buffer);
    case SQL_C_SSHORT: case SQL_C_SHORT:
      return s48_enter_integer(*((SQLSMALLINT*) buffer));
    case SQL_C_USHORT:
      return s48_enter_integer(*((SQLUSMALLINT*) buffer));
    case SQL_C_SLONG: case SQL_C_LONG:
      return s48_enter_integer(*((SQLINTEGER*) buffer));
    case SQL_C_ULONG:
      return s48_enter_integer(*((SQLUINTEGER*) buffer));
      /* case SQL_C_FLOAT: */
      /* case SQL_C_DOUBLE: */
      /* case SQL_C_BIT: */
    case SQL_C_STINYINT:
      return s48_enter_integer(*((SQLSCHAR *) buffer));
    case SQL_C_UTINYINT:
      return s48_enter_integer(*((SQLCHAR *) buffer));
    case SQL_C_BINARY:
      return s48_enter_string((SQLCHAR *) buffer);
    case SQL_C_TYPE_DATE: 
      return struct_to_sql_date_record((SQL_DATE_STRUCT *) buffer);
    case SQL_C_TYPE_TIME:
      return struct_to_sql_time_record((SQL_TIME_STRUCT *) buffer);
    case SQL_C_TYPE_TIMESTAMP:
      return struct_to_sql_timestamp_record((SQL_TIMESTAMP_STRUCT *) buffer);
      /*    case SQL_C_NUMERIC: */
    default:
      RAISE_ODBC_UNKNOWN_C_TYPE_IDENTIFIER_ERROR((long)buffer, ctypeid);
      break;
    }
}

/* set initial return value buffer size */
s48_value odbc_set_initial_retval_buffer_size(s48_value nobytes)
{
  odbc_initial_retval_buffer_size = (SQLUSMALLINT) s48_extract_integer(nobytes);
}

/* get initial return value buffer size */
s48_value odbc_get_intial_retval_buffer_size()
{
  return s48_enter_integer((int) odbc_initial_retval_buffer_size);
}

void s48_init_odbc(void)
{
  /* bindings for record types */
  S48_GC_PROTECT_GLOBAL(odbc_diag_record_type);
  odbc_diag_record_type = s48_get_imported_binding("odbc-diag");

  S48_GC_PROTECT_GLOBAL(odbc_column_record_type);
  odbc_column_record_type = s48_get_imported_binding("odbc-column");

  S48_GC_PROTECT_GLOBAL(odbc_parameter_record_type);
  odbc_column_record_type = s48_get_imported_binding("odbc-parameter");

  S48_GC_PROTECT_GLOBAL(bindcol_buffer_record_type);
  bindcol_buffer_record_type = s48_get_imported_binding("bindcol-buffer");

  /* import conditions */
  S48_GC_PROTECT_GLOBAL(raise_odbc_api_version_mismatch_error);
  raise_odbc_api_version_mismatch_error 
    = s48_get_imported_binding("raise-odbc-api-version-mismatch-error");
  S48_GC_PROTECT_GLOBAL(raise_odbc_unknown_integer_type_error);
  raise_odbc_unknown_integer_type_error 
    = s48_get_imported_binding("raise-odbc-unknown-integer-type-error");
  S48_GC_PROTECT_GLOBAL(raise_odbc_buffer_alloc_error);
  raise_odbc_buffer_alloc_error
    = s48_get_imported_binding("raise-odbc-buffer-alloc-error");
  S48_GC_PROTECT_GLOBAL(raise_odbc_unknown_c_type_identifier_error);
  raise_odbc_unknown_c_type_identifier_error
    = s48_get_imported_binding("raise-odbc-unknown-c-type-identifier-error");
  S48_GC_PROTECT_GLOBAL(raise_odbc_bindcol_unbound_column_error);
  raise_odbc_bindcol_unbound_column_error
    = s48_get_imported_binding("raise-odbc-bindcol-unbound-column-error");
  S48_GC_PROTECT_GLOBAL(raise_odbc_bindcol_rebinding_error);
  raise_odbc_bindcol_rebinding_error
    = s48_get_imported_binding("raise-odbc-bindcol-rebinding-error");

  /* init global variables */
  global_bindcol_list = NULL;

  /* functions for SQLBindCol() */
  S48_EXPORT_FUNCTION(bindcol_lookup_binding_scheme);
  S48_EXPORT_FUNCTION(odbc_sql_bindcol);

  /* PART 1 */
  S48_EXPORT_FUNCTION(odbc_alloc_environment_handle);
  S48_EXPORT_FUNCTION(odbc_alloc_connection_handle);
  S48_EXPORT_FUNCTION(odbc_alloc_statement_handle);
    
  S48_EXPORT_FUNCTION(odbc_sql_connect);
  S48_EXPORT_FUNCTION(odbc_sql_browse_connect);

  /* PART 2 */
  S48_EXPORT_FUNCTION(odbc_sql_data_sources);
  S48_EXPORT_FUNCTION(odbc_sql_drivers);
  S48_EXPORT_FUNCTION(odbc_sql_get_info_int); 
  S48_EXPORT_FUNCTION(odbc_sql_get_info_string);
  S48_EXPORT_FUNCTION(odbc_sql_get_func_exists); 
  S48_EXPORT_FUNCTION(odbc_sql_get_type_info);

  /* PART 3 */
   S48_EXPORT_FUNCTION(odbc_sql_set_connect_attr_int);
   S48_EXPORT_FUNCTION(odbc_sql_set_connect_attr_string);
   S48_EXPORT_FUNCTION(odbc_sql_get_connect_attr_string);
   S48_EXPORT_FUNCTION(odbc_sql_get_connect_attr_int);
   S48_EXPORT_FUNCTION(odbc_sql_set_env_attr_int);
   S48_EXPORT_FUNCTION(odbc_sql_get_env_attr_int);
   S48_EXPORT_FUNCTION(odbc_sql_set_stmt_attr_int);
   S48_EXPORT_FUNCTION(odbc_sql_set_stmt_attr_string);
   S48_EXPORT_FUNCTION(odbc_sql_get_stmt_attr_int); 
   S48_EXPORT_FUNCTION(odbc_sql_get_stmt_attr_string);
  
   /* PART 4 */
   S48_EXPORT_FUNCTION(odbc_sql_get_desc_field_int);
   S48_EXPORT_FUNCTION(odbc_sql_get_desc_field_string);

   /* PART 5 */
   S48_EXPORT_FUNCTION(odbc_sql_prepare);
   S48_EXPORT_FUNCTION(odbc_sql_get_cursor_name);
   S48_EXPORT_FUNCTION(odbc_sql_set_cursor_name);

  /* PART 6 */
  S48_EXPORT_FUNCTION(odbc_sql_execute);
  S48_EXPORT_FUNCTION(odbc_sql_execute_direct);
  S48_EXPORT_FUNCTION(odbc_sql_native_sql);
  S48_EXPORT_FUNCTION(odbc_sql_describe_param);
  S48_EXPORT_FUNCTION(odbc_sql_num_params);

  /* PART 7 */
  S48_EXPORT_FUNCTION(odbc_sql_row_count);
  S48_EXPORT_FUNCTION(odbc_sql_get_data);
  S48_EXPORT_FUNCTION(odbc_sql_set_pos);
  S48_EXPORT_FUNCTION(odbc_sql_bulk_operations);
  S48_EXPORT_FUNCTION(odbc_sql_more_results);
  S48_EXPORT_FUNCTION(odbc_sql_fetch);
  S48_EXPORT_FUNCTION(odbc_sql_num_result_cols);
  S48_EXPORT_FUNCTION(odbc_sql_describe_col);
  S48_EXPORT_FUNCTION(odbc_sql_col_attribute);

  /* PART 8 */
  S48_EXPORT_FUNCTION(odbc_sql_column_privileges);
  S48_EXPORT_FUNCTION(odbc_sql_columns);
  S48_EXPORT_FUNCTION(odbc_sql_foreign_keys);
  S48_EXPORT_FUNCTION(odbc_sql_primary_keys);
  S48_EXPORT_FUNCTION(odbc_sql_procedure_columns);
  S48_EXPORT_FUNCTION(odbc_sql_procedures);
  S48_EXPORT_FUNCTION(odbc_sql_special_columns);
  S48_EXPORT_FUNCTION(odbc_sql_statistics);
  S48_EXPORT_FUNCTION(odbc_sql_table_privileges);
  S48_EXPORT_FUNCTION(odbc_sql_tables);

  /* PART 9 */
  S48_EXPORT_FUNCTION(odbc_sql_free_statement);
  S48_EXPORT_FUNCTION(odbc_sql_close_cursor);
  S48_EXPORT_FUNCTION(odbc_sql_cancel);
  S48_EXPORT_FUNCTION(odbc_sql_endtran);

  /* PART 10 */
  S48_EXPORT_FUNCTION(odbc_sql_disconnect);
  S48_EXPORT_FUNCTION(odbc_sql_free_handle);

  /* PART 11 */
  S48_EXPORT_FUNCTION(odbc_sql_get_diag_recs);

  /* misc functions */
  S48_EXPORT_FUNCTION(bindcol_finalize_bindcols);
  S48_EXPORT_FUNCTION(odbc_set_initial_retval_buffer_size);
  S48_EXPORT_FUNCTION(odbc_get_intial_retval_buffer_size);
  
}