/* This file is part of the Q programming system.

   The Q programming system is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   The Q programming system is distributed in the hope that it will be
   useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */


#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#ifdef _WIN32
#define STDC_HEADERS 1
#define HAVE_STRDUP 1
#define HAVE_MEMCPY 1
#define HAVE_LIMITS_H 1
#include <windows.h>
#endif

/* system headers */

#include <stdio.h>
#include <ctype.h>

/* check for standard C headers */
#if STDC_HEADERS
# include <stdlib.h>
# include <string.h>
#else
# ifndef HAVE_STRCHR
#  define strchr index
#  define strrchr rindex
# endif
char *strchr (), *strrchr ();
#endif

#ifdef HAVE_MALLOC_H
#include <stdlib.h>
#endif

#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <sql.h>
#include <sqlext.h>

#if (ODBCVER < 0x0300)
#error "Sorry, this module requires ODBC 3.0 or later!"
#endif

#include <gmp.h>
#include <libq.h>

#ifdef DMALLOC
#include <dmalloc.h>
#endif

MODULE(odbc)

#ifndef HAVE_STRDUP

static char *strdup(char *s)
{
  char *t;
  return ((t=malloc(strlen(s)+1))?strcpy(t, s):NULL);
}

#endif

#ifndef HAVE_MEMCPY

#if __GNUC__ > 1
#define memcpy(TO,FROM,COUNT)	__builtin_memcpy(TO,FROM,COUNT)
#else
static void *memcpy (to, from, count)
     char *to;
     char *from;
     int count;
{
  register char *f = from;
  register char *t = to;
  register int i = count;

  while (i-- > 0)
    *t++ = *f++;
  return (void*)to;
}
#endif

#endif

/* ByteStr data structure, see clib.c */

typedef struct bstr {
  long size;
  unsigned char *v;
} bstr_t;

/* Query parameter structure */

typedef struct {
  short type; /* SQL parameter type */
  short ctype; /* C parameter type */
  long len; /* length or indicator */
  long buflen; /* real buffer length */
  long prec; /* precision */
  void *ptr; /* buffer pointer */
  union {
    long iv; /* integer parameter */
    double fv; /* floating point parameter */
    char *buf; /* string or byte string parameter */
  } data;
} ODBCParam;

/* ODBC handle structure */

typedef struct {
  SQLHENV henv; /* environment handle */
  SQLHDBC hdbc; /* connection handle */
  SQLHSTMT hstmt; /* statement handle */
  unsigned char exec; /* set while statement is being executed */
  short *coltype; /* column types in current result set */
  short cols; /* number of columns */
  ODBCParam *argv; /* marked parameters */
  int argc; /* number of marked parameters */
} ODBCHandle;

static int init_args(ODBCHandle *db, int argc)
{
  int i;
  if (!(db->argv = malloc(argc*sizeof(ODBCParam))))
    return 0;
  db->argc = argc;
  for (i = 0; i < argc; i++) {
    db->argv[i].type = SQL_UNKNOWN_TYPE;
    db->argv[i].len = SQL_NULL_DATA;
  }
  return 1;
}

static void free_args(ODBCHandle *db)
{
  if (db->argv) {
    int i;
    SQLFreeStmt(db->hstmt, SQL_RESET_PARAMS);
    for (i = 0; i < db->argc; i++)
      if ((db->argv[i].type == SQL_BIGINT || db->argv[i].type == SQL_CHAR ||
	   db->argv[i].type == SQL_BINARY) &&
	  db->argv[i].data.buf)
	free(db->argv[i].data.buf);
    free(db->argv);
    db->argv = NULL;
    db->argc = 0;
  }
}

static int set_arg(ODBCHandle *db, int i, expr x)
{
  long iv;
  double fv;
  char *s;
  bstr_t *m;
  mpz_t z;
  if (isint(x, &iv)) {
    db->argv[i].type = SQL_INTEGER;
    db->argv[i].ctype = SQL_C_SLONG;
    db->argv[i].len = sizeof(long);
    db->argv[i].buflen = sizeof(long);
    db->argv[i].prec = 10;
    db->argv[i].data.iv = iv;
    db->argv[i].ptr = &db->argv[i].data.iv;
    return 1;
  } else if (ismpz(x, z)) {
    /* convert big integer values to BIGINTs via a string representation,
       so we don't have to fiddle with long long's here */
    db->argv[i].type = SQL_BIGINT;
    db->argv[i].ctype = SQL_C_CHAR;
    db->argv[i].len = SQL_NTS;
    db->argv[i].data.buf = mpz_get_str(NULL, 10, z);
    if (!db->argv[i].data.buf) return 0;
    db->argv[i].buflen = strlen(db->argv[i].data.buf)+1;
    db->argv[i].prec = db->argv[i].buflen-1;
    db->argv[i].ptr = db->argv[i].data.buf;
    return 1;
  } else if (isfloat(x, &fv)) {
    db->argv[i].type = SQL_DOUBLE;
    db->argv[i].ctype = SQL_C_DOUBLE;
    db->argv[i].len = sizeof(double);
    db->argv[i].buflen = sizeof(double);
    db->argv[i].prec = 15;
    db->argv[i].data.fv = fv;
    db->argv[i].ptr = &db->argv[i].data.fv;
    return 1;
  } else if (isstr(x, &s)) {
    db->argv[i].type = SQL_CHAR;
    db->argv[i].ctype = SQL_C_CHAR;
    db->argv[i].len = SQL_NTS;
    db->argv[i].buflen = strlen(s)+1;
    db->argv[i].prec = db->argv[i].buflen-1;
    db->argv[i].data.buf = strdup(s);
    db->argv[i].ptr = db->argv[i].data.buf;
    return db->argv[i].data.buf != NULL;
  } else if (isobj(x, type(ByteStr), (void**)&m)) {
    db->argv[i].type = SQL_BINARY;
    db->argv[i].ctype = SQL_C_BINARY;
    db->argv[i].len = m->size;
    db->argv[i].buflen = m->size;
    db->argv[i].prec = m->size;
    if (m->size > 0) {
      if (!(db->argv[i].data.buf = malloc(m->size)))
	return 0;
      memcpy(db->argv[i].data.buf, m->v, m->size);
    } else
      db->argv[i].data.buf = NULL;
    db->argv[i].ptr = db->argv[i].data.buf;
    return 1;
  } else if (isvoid(x)) {
    db->argv[i].type = SQL_CHAR;
    db->argv[i].ctype = SQL_C_DEFAULT;
    db->argv[i].len = SQL_NULL_DATA;
    db->argv[i].buflen = 0;
    db->argv[i].prec = 0;
    db->argv[i].data.buf = NULL;
    db->argv[i].ptr = NULL;
    return 1;
  } else
    return 0;
}

static void sql_close(ODBCHandle *db)
{
  if (db->exec) {
    if (db->coltype) free(db->coltype);
    free_args(db);
    SQLFreeStmt(db->hstmt, SQL_CLOSE);
    db->coltype = NULL;
    db->cols = 0;
    db->exec = 0;
  }
}

DESTRUCTOR(odbc,ODBCHandle,ptr)
{
  ODBCHandle *db = (ODBCHandle*)ptr;
  if (db->henv) {
    sql_close(db);
    SQLCloseCursor(db->hstmt);
    SQLFreeHandle(SQL_HANDLE_STMT, db->hstmt);
    SQLDisconnect(db->hdbc);
    SQLFreeHandle(SQL_HANDLE_DBC, db->hdbc);
    SQLFreeHandle(SQL_HANDLE_ENV, db->henv);
  }
  free(db);
}

static expr mkerr(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt)
{
  char stat[10], msg[300];
  SQLINTEGER err;
  short len;
  /* check for SQL statement errors */
  if (hstmt && SQLGetDiagRec(SQL_HANDLE_STMT, hstmt, 1, stat, &err,
			     msg, sizeof(msg), &len) == SQL_SUCCESS)
    goto exit;
  /* check for connection errors */
  if (hdbc && SQLGetDiagRec(SQL_HANDLE_DBC, hdbc, 1, stat, &err,
			    msg, sizeof(msg), &len) == SQL_SUCCESS)
    goto exit;
  /* check for environment errors */
  if (henv && SQLGetDiagRec(SQL_HANDLE_ENV, henv, 1, stat, &err,
			    msg, sizeof(msg), &len) == SQL_SUCCESS)
    goto exit;
  return __FAIL;
 exit:
  return mkapp(mkapp(mksym(sym(odbc_error)), mkstr(strdup(msg))),
	       mkstr(strdup(stat)));
}

FUNCTION(odbc,odbc_sources,argc,argv)
{
  if (argc == 0) {
    SQLHENV henv;
    long ret;
    expr *xv;
    int n;
    char l_dsn[100],l_desc[100];
    short l_len1, l_len2, l_next;
    /* create an environment handle */
    if ((ret = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv)) != SQL_SUCCESS &&
	ret != SQL_SUCCESS_WITH_INFO)
      return __FAIL;
    if ((ret = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
			     (SQLPOINTER) SQL_OV_ODBC3,
			     SQL_IS_UINTEGER)) != SQL_SUCCESS &&
	ret != SQL_SUCCESS_WITH_INFO) {
      expr msg = mkerr(henv, 0, 0);
      SQLFreeHandle(SQL_HANDLE_ENV, henv);
      return msg;
    }
    /* count the number of data sources */
    for (n = 0, l_next = SQL_FETCH_FIRST;
	 SQLDataSources(henv, l_next, l_dsn, sizeof(l_dsn), &l_len1,
			l_desc, sizeof(l_desc), &l_len2) == SQL_SUCCESS;
	 l_next = SQL_FETCH_NEXT)
      n++;
    if (!(xv = malloc(n*sizeof(expr)))) {
      SQLFreeHandle(SQL_HANDLE_ENV, henv);
      return __ERROR;
    }
    /* retrieve the data source names and descriptions */
    for (n = 0, l_next = SQL_FETCH_FIRST;
	 SQLDataSources(henv, l_next, l_dsn, sizeof(l_dsn), &l_len1,
			l_desc, sizeof(l_desc), &l_len2) == SQL_SUCCESS;
	 l_next = SQL_FETCH_NEXT)
      xv[n++] = mktuplel(2, mkstr(strdup(l_dsn)), mkstr(strdup(l_desc)));
    /* free the environment handle */
    SQLFreeHandle(SQL_HANDLE_ENV, henv);
    return mklistv(n, xv);
  } else
    return __FAIL;
}

FUNCTION(odbc,odbc_drivers,argc,argv)
{
  if (argc == 0) {
    SQLHENV henv;
    long ret;
    expr *xv;
    int n;
    char l_drv[100],l_attr[10000];
    short l_len1, l_len2, l_next;
    /* create an environment handle */
    if ((ret = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv)) != SQL_SUCCESS &&
	ret != SQL_SUCCESS_WITH_INFO)
      return __FAIL;
    if ((ret = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
			     (SQLPOINTER) SQL_OV_ODBC3,
			     SQL_IS_UINTEGER)) != SQL_SUCCESS &&
	ret != SQL_SUCCESS_WITH_INFO) {
      expr msg = mkerr(henv, 0, 0);
      SQLFreeHandle(SQL_HANDLE_ENV, henv);
      return msg;
    }
    /* count the number of driver descriptions */
    for (n = 0, l_next = SQL_FETCH_FIRST;
	 SQLDrivers(henv, l_next, l_drv, sizeof(l_drv), &l_len1,
		    l_attr, sizeof(l_attr), &l_len2) == SQL_SUCCESS;
	 l_next = SQL_FETCH_NEXT)
      n++;
    if (!(xv = malloc(n*sizeof(expr)))) {
      SQLFreeHandle(SQL_HANDLE_ENV, henv);
      return __ERROR;
    }
    /* retrieve the driver and descriptions */
    for (n = 0, l_next = SQL_FETCH_FIRST;
	 SQLDrivers(henv, l_next, l_drv, sizeof(l_drv), &l_len1,
		    l_attr, sizeof(l_attr), &l_len2) == SQL_SUCCESS;
	 l_next = SQL_FETCH_NEXT) {
      int k;
      char *l_attrp;
      expr *yv;
      /* count the number of attributes */
      for (k = 0, l_attrp = l_attr; *l_attrp;
	   l_attrp = l_attrp+strlen(l_attrp)+1)
	k++;
      if (!(yv = malloc(k*sizeof(expr)))) {
	int i;
	for (i = 0; i < n; i++)
	  dispose(xv[i]);
	free(xv);
	SQLFreeHandle(SQL_HANDLE_ENV, henv);
	return __ERROR;
      }
      /* get the attribute strings */
      for (k = 0, l_attrp = l_attr; *l_attrp;
	   l_attrp = l_attrp+strlen(l_attrp)+1)
	yv[k++] = mkstr(strdup(l_attrp));
      xv[n++] = mktuplel(2, mkstr(strdup(l_drv)), mklistv(k, yv));
    }
    /* free the environment handle */
    SQLFreeHandle(SQL_HANDLE_ENV, henv);
    return mklistv(n, xv);
  } else
    return __FAIL;
}

FUNCTION(odbc,odbc_connect,argc,argv)
{
  char *conn;
  if (argc == 1 && isstr(argv[0], &conn)) {
    ODBCHandle *db = (ODBCHandle*)malloc(sizeof(ODBCHandle));
    long ret;
    short buflen;
    char buf[1024];
    if (!db) return __ERROR;
    /* create the environment handle */
    if ((ret = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &db->henv)) !=
	SQL_SUCCESS &&
	ret != SQL_SUCCESS_WITH_INFO)
      return __FAIL;
    if ((ret = SQLSetEnvAttr(db->henv, SQL_ATTR_ODBC_VERSION,
			     (SQLPOINTER) SQL_OV_ODBC3,
			     SQL_IS_UINTEGER)) != SQL_SUCCESS &&
	ret != SQL_SUCCESS_WITH_INFO) {
      expr msg = mkerr(db->henv, 0, 0);
      SQLFreeHandle(SQL_HANDLE_ENV, db->henv);
      return msg;
    }
    /* create the connection handle */
    if ((ret = SQLAllocHandle(SQL_HANDLE_DBC, db->henv, &db->hdbc)) !=
	SQL_SUCCESS &&
	ret != SQL_SUCCESS_WITH_INFO) {
      expr msg = mkerr(db->henv, 0, 0);
      SQLFreeHandle(SQL_HANDLE_ENV, db->henv);
      return msg;
    }
    /* connect */
    if ((ret = SQLDriverConnect(db->hdbc, 0, conn, SQL_NTS,
				buf, sizeof(buf), &buflen,
				SQL_DRIVER_NOPROMPT)) != SQL_SUCCESS &&
	ret != SQL_SUCCESS_WITH_INFO) {
      expr msg = mkerr(db->henv, db->hdbc, 0);
      SQLFreeHandle(SQL_HANDLE_DBC, db->hdbc);
      SQLFreeHandle(SQL_HANDLE_ENV, db->henv);
      return msg;
    }
    /* create the statement handle */
    if ((ret = SQLAllocHandle(SQL_HANDLE_STMT, db->hdbc, &db->hstmt)) !=
	SQL_SUCCESS &&
	ret != SQL_SUCCESS_WITH_INFO) {
      expr msg = mkerr(db->henv, db->hdbc, 0);
      SQLDisconnect(db->hdbc);
      SQLFreeHandle(SQL_HANDLE_DBC, db->hdbc);
      SQLFreeHandle(SQL_HANDLE_ENV, db->henv);
      return msg;
    }
    /* initialize statement properties */
    db->argv = NULL;
    db->argc = 0;
    db->coltype = NULL;
    db->cols = 0;
    db->exec = 0;
    /* return the result */
    return mkobj(type(ODBCHandle), db);
  } else
    return __FAIL;
}

FUNCTION(odbc,odbc_disconnect,argc,argv)
{
  ODBCHandle *db;
  if (argc == 1 && isobj(argv[0], type(ODBCHandle), (void**)&db) &&
      db->henv) {
    sql_close(db);
    SQLCloseCursor(db->hstmt);
    SQLFreeHandle(SQL_HANDLE_STMT, db->hstmt);
    db->hstmt = 0;
    SQLDisconnect(db->hdbc);
    SQLFreeHandle(SQL_HANDLE_DBC, db->hdbc);
    db->hdbc = 0;
    SQLFreeHandle(SQL_HANDLE_ENV, db->henv);
    db->henv = 0;
    return mkvoid;
  } else
    return __FAIL;
}

FUNCTION(odbc,odbc_info,argc,argv)
{
  ODBCHandle *db;
  if (argc == 1 && isobj(argv[0], type(ODBCHandle), (void**)&db) &&
      db->henv) {
    long ret;
    int n = 0;
    expr *xv = malloc(8*sizeof(expr));
    char info[1024];
    short len;
    if (!xv) return __ERROR;
    if ((ret  = SQLGetInfo(db->hdbc, SQL_DATA_SOURCE_NAME,
			   info, sizeof(info), &len)) == SQL_SUCCESS ||
	ret == SQL_SUCCESS_WITH_INFO)
      xv[n++] = mkstr(strdup(info));
    else
      xv[n++] = mkstr(strdup(""));
    if ((ret  = SQLGetInfo(db->hdbc, SQL_DATABASE_NAME,
			   info, sizeof(info), &len)) == SQL_SUCCESS ||
	ret == SQL_SUCCESS_WITH_INFO)
      xv[n++] = mkstr(strdup(info));
    else
      xv[n++] = mkstr(strdup(""));
    if ((ret  = SQLGetInfo(db->hdbc, SQL_DBMS_NAME,
			   info, sizeof(info), &len)) == SQL_SUCCESS ||
	ret == SQL_SUCCESS_WITH_INFO)
      xv[n++] = mkstr(strdup(info));
    else
      xv[n++] = mkstr(strdup(""));
    if ((ret  = SQLGetInfo(db->hdbc, SQL_DBMS_VER,
			   info, sizeof(info), &len)) == SQL_SUCCESS ||
	ret == SQL_SUCCESS_WITH_INFO)
      xv[n++] = mkstr(strdup(info));
    else
      xv[n++] = mkstr(strdup(""));
    if ((ret  = SQLGetInfo(db->hdbc, SQL_DRIVER_NAME,
			   info, sizeof(info), &len)) == SQL_SUCCESS ||
	ret == SQL_SUCCESS_WITH_INFO)
      xv[n++] = mkstr(strdup(info));
    else
      xv[n++] = mkstr(strdup(""));
    if ((ret  = SQLGetInfo(db->hdbc, SQL_DRIVER_VER,
			   info, sizeof(info), &len)) == SQL_SUCCESS ||
	ret == SQL_SUCCESS_WITH_INFO)
      xv[n++] = mkstr(strdup(info));
    else
      xv[n++] = mkstr(strdup(""));
    if ((ret  = SQLGetInfo(db->hdbc, SQL_DRIVER_ODBC_VER,
			   info, sizeof(info), &len)) == SQL_SUCCESS ||
	ret == SQL_SUCCESS_WITH_INFO)
      xv[n++] = mkstr(strdup(info));
    else
      xv[n++] = mkstr(strdup(""));
    if ((ret  = SQLGetInfo(db->hdbc, SQL_ODBC_VER,
			   info, sizeof(info), &len)) == SQL_SUCCESS ||
	ret == SQL_SUCCESS_WITH_INFO)
      xv[n++] = mkstr(strdup(info));
    else
      xv[n++] = mkstr(strdup(""));
    return mktuplev(n, xv);
  } else
    return __FAIL;
}

#define BUFSZ 65536
#define BUFSZ2 5000

FUNCTION(odbc,sql_exec,argc,argv)
{
  ODBCHandle *db;
  char *query;
  if (argc == 3 && isobj(argv[0], type(ODBCHandle), (void**)&db) &&
      db->henv && isstr(argv[1], &query)) {
    long ret;
    expr res, *xs, *xv;
    int n;
    short i, cols, *coltype = NULL;
    char buf[BUFSZ2];
    /* finalize previous query */
    sql_close(db);
    /* prepare statement */
    if ((ret = SQLPrepare(db->hstmt, query, SQL_NTS)) != SQL_SUCCESS &&
	ret != SQL_SUCCESS_WITH_INFO)
      return mkerr(db->henv, db->hdbc, db->hstmt);
    /* bind parameters */
    if (isvoid(argv[2]))
      ;
    else if (istuple(argv[2], &n, &xv)) {
      if (!init_args(db, n))
	goto fatal;
      for (i = 0; i < n; i++)
	if (!set_arg(db, i, xv[i])) {
	  int alloc_error =
	    (db->argv[i].type == SQL_BIGINT ||
	     db->argv[i].type == SQL_CHAR ||
	     db->argv[i].type == SQL_BINARY) &&
	    !db->argv[i].data.buf;
	  free_args(db);
	  if (alloc_error)
	    goto fatal;
	  else
	    goto fail;
	}
    } else {
      if (!init_args(db, 1))
	goto fatal;
      if (!set_arg(db, 0, argv[2])) {
	int alloc_error =
	  (db->argv[0].type == SQL_BIGINT ||
	   db->argv[0].type == SQL_CHAR ||
	   db->argv[0].type == SQL_BINARY) &&
	  !db->argv[0].data.buf;
	free_args(db);
	if (alloc_error)
	  goto fatal;
	else
	  goto fail;
      }
    }
    for (i = 0; i < db->argc; i++)
      if ((ret = SQLBindParameter(db->hstmt, i+1, SQL_PARAM_INPUT,
				  db->argv[i].ctype,
				  db->argv[i].type,
				  db->argv[i].prec, 0,
				  db->argv[i].ptr,
				  db->argv[i].buflen,
				  &db->argv[i].len)) != SQL_SUCCESS &&
	  ret != SQL_SUCCESS_WITH_INFO)
	goto err;
    /* execute statement */
    if ((ret = SQLExecute(db->hstmt)) != SQL_SUCCESS &&
	ret != SQL_SUCCESS_WITH_INFO)
      return mkerr(db->henv, db->hdbc, db->hstmt);
    /* determine the number of columns */
    if ((ret = SQLNumResultCols(db->hstmt, &cols)) != SQL_SUCCESS &&
	ret != SQL_SUCCESS_WITH_INFO)
      goto err;
    if (cols == 0) {
      SQLINTEGER rows;
      if ((ret = SQLRowCount(db->hstmt, &rows)) == SQL_SUCCESS ||
	  ret == SQL_SUCCESS_WITH_INFO)
	res = mkint((long)rows);
      else
	res = mkint(0);
      db->exec = 1;
      goto exit;
    }
    /* get the column names and types */
    if (!(coltype = malloc(cols*sizeof(short))))
      goto fatal;
    if (!(xs = malloc(cols*sizeof(expr))))
      goto fatal;
    for (i = 0; i < cols; i++) {
      buf[0] = 0;
      if ((ret = SQLDescribeCol(db->hstmt, i+1, buf, sizeof(buf), NULL,
				&coltype[i], NULL, NULL, NULL))
	  != SQL_SUCCESS &&
	  ret != SQL_SUCCESS_WITH_INFO) {
	int j;
	for (j = 0; j < i; j++) dispose(xs[j]);
	free(xs);
	goto err;
      }
      xs[i] = mkstr(strdup(buf));
    }
    res = mktuplev(cols, xs);
    if (res) {
      db->coltype = coltype;
      db->cols = cols;
      coltype = NULL;
      db->exec = 1;
    } else {
      free_args(db);
      SQLFreeStmt(db->hstmt, SQL_CLOSE);
    }
    goto exit;
  fail:
    free_args(db);
    SQLFreeStmt(db->hstmt, SQL_CLOSE);
    res = __FAIL;
    goto exit;
  err:
    free_args(db);
    SQLFreeStmt(db->hstmt, SQL_CLOSE);
    res = mkerr(db->henv, db->hdbc, db->hstmt);
    goto exit;
  fatal:
    free_args(db);
    SQLFreeStmt(db->hstmt, SQL_CLOSE);
    res = __ERROR;
  exit:
    if (coltype) free(coltype);
    return res;
  } else
    return __FAIL;
}

FUNCTION(odbc,sql_fetch,argc,argv)
{
  ODBCHandle *db;
  if (argc == 1 && isobj(argv[0], type(ODBCHandle), (void**)&db) &&
      db->henv && db->coltype) {
    long ret;
    expr res, *xs;
    short i, j, cols = db->cols, *coltype = db->coltype;
    long iv, sz = BUFSZ;
    double fv;
    char *buf = malloc(sz);
    SDWORD len;
    if (!buf) goto fatal;
    /* fetch the next record */
    if ((ret = SQLFetch(db->hstmt)) == SQL_NO_DATA_FOUND) {
      res = __FAIL;
      goto exit;
    } else if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO)
      goto err;
    if (!(xs = malloc(cols*sizeof(expr))))
      goto fatal;
    /* get the columns */
    for (i = 0; i < cols; i++) {
      switch (coltype[i]) {
      case SQL_BIT:
      case SQL_TINYINT:
      case SQL_SMALLINT:
      case SQL_INTEGER:
	if ((ret = SQLGetData(db->hstmt, i+1, SQL_INTEGER, &iv,
			      sizeof(iv), &len) != SQL_SUCCESS) &&
	    ret != SQL_SUCCESS_WITH_INFO)
	  goto err2;
	if (len == SQL_NULL_DATA)
	  xs[i] = mkvoid;
	else
	  xs[i] = mkint(iv);
	break;
      case SQL_BIGINT:
	/* hack to get bigint values converted to mpz_t, without having to
	   fiddle around with long long values
	   XXXFIXME: we should really avoid the string conversion here */
	if ((ret = SQLGetData(db->hstmt, i+1, SQL_CHAR, buf,
			      sz, &len) != SQL_SUCCESS) &&
	    ret != SQL_SUCCESS_WITH_INFO)
	  goto err2;
	if (len == SQL_NULL_DATA)
	  xs[i] = mkvoid;
	else {
	  mpz_t z;
	  mpz_init(z);
	  mpz_set_str(z, buf, 0);
	  xs[i] = mkmpz(z);
	}
	break;
      case SQL_DOUBLE:
      case SQL_DECIMAL:
      case SQL_NUMERIC:
      case SQL_FLOAT:
      case SQL_REAL:
	if ((ret = SQLGetData(db->hstmt, i+1, SQL_DOUBLE, &fv,
			      sizeof(fv), &len) != SQL_SUCCESS) &&
	    ret != SQL_SUCCESS_WITH_INFO)
	  goto err2;
	if (len == SQL_NULL_DATA)
	  xs[i] = mkvoid;
	else
	  xs[i] = mkfloat(fv);
	break;
      case SQL_BINARY:
      case SQL_VARBINARY:
      case SQL_LONGVARBINARY: {
	char *bufp = buf;
	long total = 0, actsz = sz;
	*buf = 0;
	while (1) {
	  if ((ret = SQLGetData(db->hstmt, i+1, SQL_BINARY, bufp,
				actsz, &len)) == SQL_SUCCESS ||
	      ret == SQL_NO_DATA) {
	    if (len == SQL_NULL_DATA)
	      break;
	    if (INT_MAX - len <= total)
	      goto fatal2;
	    else
	      total += len;
	    break;
	  } else if (ret == SQL_SUCCESS_WITH_INFO) {
	    /* we probably need to make room for additional data */
	    char *buf1;
	    if (len == SQL_NULL_DATA)
	      break;
	    if (INT_MAX - BUFSZ <= total)
	      goto fatal2;
	    else
	      total += actsz;
	    if (!(buf1 = realloc(buf, sz+BUFSZ)))
	      goto fatal2;
	    buf = buf1;
	    bufp = buf+total;
	    sz += BUFSZ;
	    actsz = BUFSZ;
	  } else {
	    /* some other error, bail out */
	    goto err2;
	  }
	}
	if (len == SQL_NULL_DATA)
	  xs[i] = mkvoid;
	else if (total == 0) {
	  bstr_t *m;
	  if (!(m = malloc(sizeof(bstr_t))))
	    goto fatal2;
	  m->size = 0;
	  m->v = NULL;
	  xs[i] = mkobj(type(ByteStr), m);
	} else {
	  char *buf1 = realloc(buf, total);
	  bstr_t *m;
	  if (buf1) buf = buf1;
	  if (!(m = malloc(sizeof(bstr_t))))
	    goto fatal2;
	  m->size = total;
	  m->v = buf;
	  xs[i] = mkobj(type(ByteStr), m);
	  /* make a new buffer */
	  if (!(buf = malloc(BUFSZ)))
	    goto fatal2;
	  else
	    sz = BUFSZ;
	}
	break;
      }
      default: {
	char *bufp = buf;
	long total = 0, actsz = sz;
	*buf = 0;
	while (1) {
	  if ((ret = SQLGetData(db->hstmt, i+1, SQL_CHAR, bufp,
				actsz, &len)) == SQL_SUCCESS ||
	      ret == SQL_NO_DATA) {
	    if (len == SQL_NULL_DATA)
	      break;
	    if (INT_MAX - len <= total)
	      goto fatal2;
	    else
	      total += len;
	    break;
	  } else if (ret == SQL_SUCCESS_WITH_INFO) {
	    /* we probably need to make room for additional data */
	    char *buf1;
	    if (len == SQL_NULL_DATA)
	      break;
	    if (INT_MAX - BUFSZ <= total)
	      goto fatal2;
	    else
	      total += actsz-1;
	    if (!(buf1 = realloc(buf, sz+BUFSZ)))
	      goto fatal2;
	    buf = buf1;
	    bufp = buf+total;
	    sz += BUFSZ;
	    actsz = BUFSZ+1;
	  } else {
	    /* some other error, bail out */
	    goto err2;
	  }
	}
	if (len == SQL_NULL_DATA)
	  xs[i] = mkvoid;
	else if (sz == BUFSZ)
	  xs[i] = mkstr(strdup(buf));
	else {
	  /* avoid copying the (potentially large) buffer, instead make a new
	     one */
	  char *buf1 = realloc(buf, total);
	  if (buf1) buf = buf1;
	  xs[i] = mkstr(buf);
	  if (!(buf = malloc(BUFSZ)))
	    goto fatal2;
	  else
	    sz = BUFSZ;
	}
      }
      }
    }
    res = mktuplev(cols, xs);
    goto exit;
  err2:
    for (j = 0; j < i; j++) dispose(xs[j]);
    free(xs);
  err:
    res = mkerr(db->henv, db->hdbc, db->hstmt);
    goto exit;
  fatal2:
    for (j = 0; j < i; j++) dispose(xs[j]);
    free(xs);
  fatal:
    res = __ERROR;
  exit:
    if (buf) free(buf);
    return res;
  } else
    return __FAIL;
}

FUNCTION(odbc,sql_more,argc,argv)
{
  ODBCHandle *db;
  if (argc == 1 && isobj(argv[0], type(ODBCHandle), (void**)&db) &&
      db->henv && db->exec) {
    long ret;
    expr res, *xs;
    short i, cols, *coltype = NULL;
    char buf[BUFSZ2];
    SDWORD len;
    /* get the next result set */
    if ((ret = SQLMoreResults(db->hstmt)) == SQL_NO_DATA_FOUND) {
      res = __FAIL;
      goto exit;
    } else if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO)
      goto err;
    /* determine the number of columns */
    if ((ret = SQLNumResultCols(db->hstmt, &cols)) != SQL_SUCCESS &&
	ret != SQL_SUCCESS_WITH_INFO)
      goto err;
    if (cols == 0) {
      SQLINTEGER rows;
      if ((ret = SQLRowCount(db->hstmt, &rows)) == SQL_SUCCESS ||
	  ret == SQL_SUCCESS_WITH_INFO)
	res = mkint((long)rows);
      else
	res = mkint(0);
      if (db->coltype) free(db->coltype);
      db->coltype = NULL;
      db->cols = 0;
      goto exit;
    }
    /* get the column names and types */
    if (!(coltype = malloc(cols*sizeof(short))))
      goto fatal;
    if (!(xs = malloc(cols*sizeof(expr))))
      goto fatal;
    for (i = 0; i < cols; i++) {
      buf[0] = 0;
      if ((ret = SQLDescribeCol(db->hstmt, i+1, buf, sizeof(buf), NULL,
				&coltype[i], NULL, NULL, NULL))
	  != SQL_SUCCESS &&
	  ret != SQL_SUCCESS_WITH_INFO) {
	int j;
	for (j = 0; j < i; j++) dispose(xs[j]);
	free(xs);
	goto err;
      }
      xs[i] = mkstr(strdup(buf));
    }
    res = mktuplev(cols, xs);
    if (res) {
      free(db->coltype);
      if (db->coltype) db->coltype = coltype;
      db->cols = cols;
      coltype = NULL;
    }
    goto exit;
  err:
    res = mkerr(db->henv, db->hdbc, db->hstmt);
    goto exit;
  fatal:
    res = __ERROR;
  exit:
    if (coltype) free(coltype);
    return res;
  } else
    return __FAIL;
}

FUNCTION(odbc,sql_close,argc,argv)
{
  ODBCHandle *db;
  if (argc == 1 && isobj(argv[0], type(ODBCHandle), (void**)&db) &&
      db->henv && db->exec) {
    sql_close(db);
    return mkvoid;
  } else
    return __FAIL;
}


syntax highlighted by Code2HTML, v. 0.9.1