/* File:      odbc_xsb.c
** Author(s): Lily Dong, David S. Warren
** Contact:   xsb-contact@cs.sunysb.edu
** 
** Copyright (C) The Research Foundation of SUNY, 1986, 1993-1998
** 
** XSB is free software; you can redistribute it and/or modify it under the
** terms of the GNU Library General Public License as published by the Free
** Software Foundation; either version 2 of the License, or (at your option)
** any later version.
** 
** XSB 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 Library General Public License for
** more details.
** 
** You should have received a copy of the GNU Library General Public License
** along with XSB; if not, write to the Free Software Foundation,
** Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
** $Id: odbc_xsb.c,v 1.29 2003/04/16 14:11:31 lfcastro Exp $
** 
*/

#include "xsb_config.h"
#include "cell_xsb.h"

#ifdef CYGWIN
#define FAR
#include "sql.h"
#include "sqlext.h"
#include "odbc_string.h"
#else
#ifdef WIN_NT
#include <windows.h>
#endif
#include <sql.h>
#include <sqlext.h>
#include <string.h>
#endif

#include <stdio.h>

#include <stdlib.h>
#include <assert.h>

#include "cinterf.h"
#include "deref.h"

#include "error_xsb.h"
#include "export.h"
#include "register.h"
#include "ptoc_tag_xsb_i.h"
#include "io_builtins_xsb.h"

#define MAXCURSORNUM                    25
#define MAXVARSTRLEN                    2000
#define MAXI(a,b)                       ((a)>(b)?(a):(b))

static Cell     nullStrAtom;
static int      serverConnected = 0;
/* static int      numberOfCursors = 0; */
static long      SQL_NTSval = SQL_NTS;

static HENV henv = NULL;
/*HDBC hdbc;*/
UCHAR uid[128];

struct Cursor {
  struct Cursor *NCursor; /* Next Cursor in cursor chain*/
  struct Cursor *PCursor; /* Prev Cursor in cursor chain*/
  HDBC hdbc;            /* connection handle for this cursor*/
  int Status;           /* status of the cursor*/
  UCHAR *Sql;           /* pointer to the sql statement*/
  HSTMT hstmt;          /* the statement handle*/
  int NumBindVars;      /* number of bind values*/
  UCHAR **BindList;     /* pointer to array of pointers to the bind values*/
  int *BindTypes;       /* types of the bind values*/
  SWORD NumCols;        /* number of columns selected*/
  SWORD *ColTypes;      /* pointer to array of column types*/
  UDWORD *ColLen;       /* pointer to array of max column lengths*/
  UDWORD *OutLen;       /* pointer to array of actual column lengths*/
  UCHAR **Data;         /* pointer to array of pointers to data*/
};

/* global cursor table*/
struct Cursor *FCursor;  /* root of curser chain*/
struct Cursor *LCursor;  /* tail of curser chain*/

/* Number of Cursors per Connection */
struct NumberofCursors{
  HDBC hdbc;
  int CursorCount;
  struct NumberofCursors *NCurNum;
};

struct NumberofCursors *FCurNum; /* First in the list of Number of Cursors */

SWORD ODBCToXSBType(SWORD odbcType)
{
  switch (odbcType) {
  case SQL_TINYINT:
  case SQL_SMALLINT:
  case SQL_INTEGER:
    return SQL_C_SLONG;
  case SQL_DECIMAL:
  case SQL_NUMERIC:
  case SQL_REAL:
  case SQL_FLOAT:
  case SQL_DOUBLE:
    return SQL_C_FLOAT;
  case SQL_BINARY:
  case SQL_VARBINARY:
  case SQL_LONGVARBINARY:
    return SQL_BINARY;
  case SQL_DATE:
  case SQL_TIME:
  case SQL_TIMESTAMP: 
  case SQL_CHAR:
  case SQL_VARCHAR:
  default:
    return SQL_C_CHAR;
  }
}

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     PrintErrorMsg()*/
/*  PARAMETERS:*/
/*     struct Cursor *c - pointer to a cursor, or NULL*/
/*  NOTES:*/
/*     PrintErrorMsg() prints out the error message that associates*/
/*     with the statement handler of cursor i.*/
/*-----------------------------------------------------------------------------*/
int PrintErrorMsg(struct Cursor *cur)
{
  UCHAR FAR *szsqlstate;
  SDWORD FAR *pfnativeerror;
  UCHAR FAR *szerrormsg;
  SWORD cberrormsgmax;
  SWORD FAR *pcberrormsg;
  RETCODE rc;

  szsqlstate=(UCHAR FAR *)malloc(sizeof(UCHAR FAR)*10);
  pfnativeerror=(SDWORD FAR *)malloc(sizeof(SDWORD FAR));
  szerrormsg=(UCHAR FAR *)malloc(sizeof(UCHAR FAR)*SQL_MAX_MESSAGE_LENGTH);
  pcberrormsg=(SWORD FAR *)malloc(sizeof(SWORD FAR));
  cberrormsgmax=SQL_MAX_MESSAGE_LENGTH-1;
  if (cur != NULL)
    rc = SQLError(SQL_NULL_HENV, cur->hdbc, cur->hstmt, szsqlstate,
		  pfnativeerror, szerrormsg,cberrormsgmax,pcberrormsg);
  else
    rc = SQLError(SQL_NULL_HENV, NULL, SQL_NULL_HSTMT, szsqlstate,
		  pfnativeerror, szerrormsg,cberrormsgmax,pcberrormsg);
  if ((rc == SQL_SUCCESS) || (rc == SQL_SUCCESS_WITH_INFO)) { 
    printf("ODBC SYSCALL ERROR (CODE %s): %s\n", szsqlstate, szerrormsg);
  }
  free(szsqlstate);
  free(pfnativeerror);
  free(szerrormsg);
  free(pcberrormsg);
  return 1;
}

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     SetCursorClose()*/
/*  PARAMETER:*/
/*     struct Cursor *cur - pointer to current cursor*/
/*  NOTES:*/
/*     free all the memory resource allocated for cursor cur*/
/*-----------------------------------------------------------------------------*/
void SetCursorClose(struct Cursor *cur)
{
  int j;

  SQLFreeStmt(cur->hstmt, SQL_CLOSE);    /* free statement handler*/

  if (cur->NumBindVars) {                 /* free bind variable list*/
    for (j = 0; j < cur->NumBindVars; j++) 
      if (cur->BindTypes[j] != 2) free((void *)cur->BindList[j]);
    free(cur->BindList);
    free(cur->BindTypes);
  }

  if (cur->NumCols) {                  /* free the resulting row set*/
    for (j = 0; j < cur->NumCols; j++)
      free(cur->Data[j]);
    free(cur->ColTypes);
    free(cur->ColLen);
    free(cur->OutLen);                
    free(cur->Data);
  }

  /* free memory for the sql statement associated w/ this cursor*/
  if (cur->Sql) free(cur->Sql);  
  /* initialize the variables.  set them to the right value*/
  cur->Sql = 0;
  cur->NumCols =
    cur->Status =
    cur->NumBindVars = 0;
}

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     ODBCConnect()*/
/*  PARAMETERS:*/
/*     R1: 1*/
/*     R2: 0 -> call connect with server/user/pw */
/*         1 -> call driver_connect with full connection string */
/*     R3: Server or connection_string*/
/*     R4: User name or don't care*/
/*     R5: Password or don't care*/
/*     R6: var, for returned Connection ID.*/
/*  NOTES:*/
/*     This function is called when a user wants to start a db session,*/
/*     assuming that she doesn't have one open.  It initializes system*/
/*     resources for the new session, including allocations of various things:*/
/*     environment handler, connection handler, statement handlers and then*/
/*     tries to connect to the database, either by server,userid,pw if R2=0,*/
/*     or thrugh a driver by using r3 as the connections tring, if R2=1. */ 
/*     If any of these allocations or connection fails, function returns a*/
/*     failure code 1.  Otherwise 0. */
/*-----------------------------------------------------------------------------*/
void ODBCConnect()
{
  UCHAR *server;
  UCHAR *pwd;
  UCHAR *connectIn;
  HDBC hdbc = NULL;
  RETCODE rc;

  /* if we don't yet have an environment, allocate one.*/
  if (!henv) {
    /* allocate environment handler*/
    rc = SQLAllocEnv(&henv);
    if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
      xsb_error("Environment allocation failed");   
      ctop_int(6, 0);
      return;
    }
    /*    SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC2, 
		SQL_IS_UINTEGER);
    */

    LCursor = FCursor = NULL;
    FCurNum = NULL;
    nullStrAtom = makestring(string_find("NULL",1));
  }

  /* allocate connection handler*/
  rc = SQLAllocConnect(henv, &hdbc);
  if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
    xsb_error("Connection Resources Allocation Failed");
    ctop_int(6, 0);
    return;
  }

  if (!ptoc_int(2)) {
    /* get server name, user id and password*/
    server = (UCHAR *)ptoc_string(3);
    strcpy(uid, (UCHAR *)ptoc_string(4));
    pwd = (UCHAR *)ptoc_string(5);

    /* connect to database*/
    rc = SQLConnect(hdbc, server, SQL_NTS, uid, SQL_NTS, pwd, SQL_NTS);
    if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
      SQLFreeConnect(hdbc);
      xsb_error("Connection to server %s failed", server);   
      ctop_int(6, 0);
      return;
    }
  } else {
    /* connecting through driver using a connection string */
    connectIn = (UCHAR *)ptoc_longstring(3);
    rc = SQLDriverConnect(hdbc, NULL, connectIn, SQL_NTS, NULL, 0, NULL,SQL_DRIVER_NOPROMPT);
    if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
      SQLFreeConnect(hdbc);
      xsb_error("Connection to driver failed: %s", connectIn);   
      ctop_int(6, 0);
      return;
    }
  }

  serverConnected = 1;
  ctop_int(6, (long)hdbc);
  return;
}

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     ODBCDisconnect()    */
/*  PARAMETERS:*/
/*     R1: 2*/
/*     R2: hdbc if closing a particular connection*/
/*         0 if closing entire ODBC session.*/
/*  NOTES:*/
/*     Disconnect us from the server and free all the system resources we*/
/*     allocated for the session - statement handlers, connection handler,*/
/*     environment handler and memory space. */
/*-----------------------------------------------------------------------------*/
void ODBCDisconnect()
{
  struct Cursor *cur = FCursor;
  struct Cursor *tcur;
  struct NumberofCursors *numi = FCurNum, *numj = FCurNum;
  HDBC hdbc = (HDBC)ptoc_int(2);

  if (!serverConnected) return;

  if (hdbc == NULL) {  /* close entire connection*/
    if (FCursor != NULL) 
      xsb_abort("Must close all connections before shutting down");
    SQLFreeEnv(henv);
    serverConnected = 0;
    return;
  }

  /* only free cursors associated with this connection (hdbc)*/
  while((numj != NULL) && (numj->hdbc != hdbc)){
    if(numj != FCurNum) numi=numi->NCurNum;
    numj=numj->NCurNum;
  }
  
  if(numj != NULL){
    if(numj == FCurNum) FCurNum=numj->NCurNum;
    else numi->NCurNum=numj->NCurNum;
    free(numj);
  }

  while (cur != NULL) {
    if (cur->hdbc == hdbc) {
      tcur = cur->NCursor;
      if (cur->Status != 0) SetCursorClose(cur);
      SQLFreeStmt(cur->hstmt,SQL_DROP);
      if (cur->PCursor) (cur->PCursor)->NCursor = cur->NCursor;
      else FCursor = cur->NCursor;
      if (cur->NCursor) (cur->NCursor)->PCursor = cur->PCursor;
      else LCursor = cur->PCursor;
      free(cur);
      /*      (num->CursorCount)-- */
       cur = tcur;
    }
    else cur = cur->NCursor;
  }

  SQLDisconnect(hdbc);
  SQLFreeConnect(hdbc);
  /*  SQLFreeEnv(henv);*/
  serverConnected = 0;
}

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     FindFreeCursor()*/
/*  PARAMETERS:*/
/*     R1: 7*/
/*     R2: Connection Handle*/
/*     R3: SQL Statement*/
/*     R4: var, in which Cursor addr is returned*/
/*  NOTES:*/
/*     Find a free statement handler and return its index number into the*/
/*     global cursor table.  It gives priority to a closed cursor with same*/
/*     stantement number over ordinary closed cursors.  If there is no handler*/
/*     left,  function returns NULL. possible cursor status values are*/
/*     0 - never been used - no resource associated w/ the cursor */
/*     1 - used before but having been closed-the cursor has all the resource*/
/*     2 - reusing a used cursor w/ the same statement number, no resource*/
/*         needs to be allocated*/
/*     3 - using a cursor that has no resource - it needs to be allocated*/
/*-----------------------------------------------------------------------------*/
void FindFreeCursor()
{ 
  struct Cursor *curi = FCursor, *curj = NULL, *curk = NULL;
  struct NumberofCursors *num = FCurNum;
  HDBC hdbc = (HDBC)ptoc_int(2);
  char *Sql_stmt = ptoc_longstring(3);
  RETCODE rc;

  /* search */
  while (curi != NULL) {
    if (curi->hdbc == hdbc) { /* only look at stmt handles for this connection */
      if (curi->Status == 0) curj = curi; /* cursor never been used*/
      else {
	if (curi->Status == 1) {    /* a closed cursor*/
	  /* same statement as this one, so grab and return it*/
	  if (!strcmp(curi->Sql,Sql_stmt)) {
	    if (curi != FCursor) {
	      (curi->PCursor)->NCursor = curi->NCursor;
	      if (curi == LCursor) LCursor = curi->PCursor;
	      else (curi->NCursor)->PCursor = curi->PCursor;
	      FCursor->PCursor = curi;
	      curi->PCursor = NULL;
	      curi->NCursor = FCursor;
	      FCursor = curi;
	    }
	    curi->Status = 2;
	    ctop_int(4, (long)curi);
	    /*printf("reuse cursor: %p\n",curi);*/
	    return;
	  } else {
	    curk = curi;                      /* otherwise just record it*/
	  }
	}
      }
    }
    curi = curi->NCursor;
  }

  /* done w/ the search; see what was found*/
  if (curj != NULL) {   /* give priority to an unused cursor*/
    curi = curj;
    /*printf("take unused cursor: %p\n",curi);*/
  }
  else {
    while((num != NULL) && (num->hdbc != hdbc)){
      num=num->NCurNum;
    }
    if(num == NULL){
      num = (struct NumberofCursors *)malloc(sizeof(struct NumberofCursors));
      num->hdbc = hdbc;
      num->NCurNum=FCurNum;
      FCurNum=num;
      num->CursorCount=0;
    }

    if (num->CursorCount < MAXCURSORNUM) { /* allocate a new cursor if allowed*/
    /* problem here: should have numberOfCursors for each connection */
    curi = (struct Cursor *)calloc(sizeof(struct Cursor),1);
    curi->PCursor = NULL;
    curi->NCursor = FCursor;
    if (FCursor == NULL) LCursor = curi;
    else FCursor->PCursor = curi;
    FCursor = curi;

    rc = SQLAllocStmt(hdbc,&(curi->hstmt));
    if (!((rc==SQL_SUCCESS) ||
	  (rc==SQL_SUCCESS_WITH_INFO))) {
      free(curi);
      /*      numberOfCursors--; */
      xsb_abort("while trying to allocate ODBC statement\n");
    }

    num->CursorCount++;

    /*printf("allocate a new cursor: %p\n",curi);*/
    }
    else if (curk == NULL) {  /* no cursor left*/
      ctop_int(4, 0);
      return;
    }
    else {                    /* steal a cursor*/
      curi = curk;
      SetCursorClose(curi);
      /*printf("steal a cursor: %p\n",curi);*/
    } 
  }

  /* move to front of list.*/
  if (curi != FCursor) {
    (curi->PCursor)->NCursor = curi->NCursor;
    if (curi == LCursor) LCursor = curi->PCursor;
    else (curi->NCursor)->PCursor = curi->PCursor;
    FCursor->PCursor = curi;
    curi->PCursor = NULL;
    curi->NCursor = FCursor;
    FCursor = curi;
  }

  curi->hdbc = hdbc;
  curi->Sql = (UCHAR *)strdup(Sql_stmt);
  if (!curi->Sql)
    xsb_exit("Not enough memory for SQL stmt in FindFreeCursor!");
  curi->Status = 3;
  ctop_int(4, (long)curi);
  return;    
}

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     SetBindVarNum() */
/*  PARAMETERS:*/
/*     R1: 3*/
/*     R2: Cursor Address*/
/*     R3: Number of bind values*/
/*  NOTES:*/
/*     set the number of bind variables.  Note that the memory to*/
/*     store their values is not allocated here since we don't know*/
/*     their type: no information on how much memory is needed.  If*/
/*     we're reusing an old statement handler we don't have to worry*/
/*     about these things.  All we need to do is to make sure that*/
/*     the statement is indeed the same statement w/ the same bind*/
/*     variable number.*/
/*-----------------------------------------------------------------------------*/
void SetBindVarNum()
{
  struct Cursor *cur = (struct Cursor *)ptoc_int(2);
  int NumBindVars = ptoc_int(3);

  if (cur->Status == 2) {
    if (cur->NumBindVars != NumBindVars)
      xsb_exit("Number of Bind values provided does not agree with query\n");
    return;
  }
    
  cur->NumBindVars = NumBindVars;
  cur->BindList = malloc(sizeof(UCHAR *) * NumBindVars);
  if (!cur->BindList)
    xsb_exit("Not enough memory for cur->BindList!");
  cur->BindTypes = malloc(sizeof(int) * NumBindVars);
  if (!cur->BindTypes)
    xsb_exit("Not enough memory for cur->BindTypes!");
}

void write_canonical_term(Cell prologterm);
extern char *wcan_string;
extern int wcan_disp;
extern int letter_flag;

#define MAX_BIND_VALS 30
char *term_string[MAX_BIND_VALS] = {0};

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     SetBindVal()   */
/*  PARAMETERS:*/
/*     R1: 6*/
/*     R2: Cursor Addr*/
/*     R3: Bind variable index (starting from 0)*/
/*     R4: Value to give the bind variable*/
/*     R5: var, for returned status*/
/*  NOTES:*/
/*     set the bind variables' values. */
/*     allocate memory if it is needed(status == 3)*/
/*-----------------------------------------------------------------------------*/
void SetBindVal()
{
  RETCODE rc;
  struct Cursor *cur = (struct Cursor *)ptoc_int(2);
  int j = ptoc_int(3);
  Cell BindVal = ptoc_tag(4);

  if (!((j >= 0) && (j < cur->NumBindVars)))
    xsb_exit("Abnormal argument in SetBindVal!");
    
  /* if we're reusing an opened cursor w/ the statement number*/
  /* reallocate BindVar if type has changed (May not be such a good idea?)*/
  if (cur->Status == 2) {
    if (isinteger(BindVal)) {
      if (cur->BindTypes[j] != 0) {
	if (cur->BindTypes[j] != 2) free((void *)cur->BindList[j]);
	cur->BindList[j] = (UCHAR *)malloc(sizeof(int));
	cur->BindTypes[j] = 0;
	rc = SQLBindParameter(cur->hstmt, (short)(j+1), SQL_PARAM_INPUT, 
			      SQL_C_SLONG, SQL_INTEGER,
			      0, 0, (int *)(cur->BindList[j]), 0, NULL);
	if (rc != SQL_SUCCESS) {
	  ctop_int(5,PrintErrorMsg(cur));
	  SetCursorClose(cur);
	  return;
	}
      }
      *((int *)cur->BindList[j]) = oint_val(BindVal);
    } else if (isfloat(BindVal)) {
      if (cur->BindTypes[j] != 1) { 
	/*printf("ODBC: Changing Type: flt to %d\n",cur->BindTypes[j]);*/
	if (cur->BindTypes[j] != 2) free((void *)cur->BindList[j]);
	cur->BindList[j] = (UCHAR *)malloc(sizeof(float));
	cur->BindTypes[j] = 1;
	rc = SQLBindParameter(cur->hstmt, (short)(j+1), SQL_PARAM_INPUT, 
			      SQL_C_FLOAT, SQL_FLOAT,
			      0, 0, (float *)(cur->BindList[j]), 0, NULL);
	if (rc != SQL_SUCCESS) {
	  ctop_int(5,PrintErrorMsg(cur));
	  SetCursorClose(cur);
	  return;
	}
      }
      *((float *)cur->BindList[j]) = (float)float_val(BindVal);
    } else if (isstring(BindVal)) {
      if (cur->BindTypes[j] != 2) { 
	/*printf("ODBC: Changing Type: str to %d\n",cur->BindTypes[j]);*/
	free((void *)cur->BindList[j]);
	cur->BindTypes[j] = 2;
	/* SQLBindParameter will be done anyway*/
      }
      cur->BindList[j] = string_val(BindVal);
    } else if (isconstr(BindVal) && get_arity(get_str_psc(BindVal))==1) {
      letter_flag = 1;
      wcan_disp = 0;
      write_canonical_term(p2p_arg(BindVal,1));
      if (term_string[j]) free(term_string[j]);
      term_string[j] = malloc(wcan_disp+1);
      strncpy(term_string[j],wcan_string,wcan_disp);
      term_string[j][wcan_disp] = '\0';
      cur->BindTypes[j] = 2;
      cur->BindList[j] = term_string[j];
    } else {
      xsb_exit("Unknown bind variable type, %d", cur->BindTypes[j]);
    }
    ctop_int(5,0);
    return;
  }
    
  /* otherwise, memory needs to be allocated in this case*/
  if (isinteger(BindVal)) {
    cur->BindTypes[j] = 0;
    cur->BindList[j] = (UCHAR *)malloc(sizeof(int));
    if (!cur->BindList[j])
      xsb_exit("Not enough memory for an int in SetBindVal!");
    *((int *)cur->BindList[j]) = oint_val(BindVal);
  } else if (isfloat(BindVal)) {
    cur->BindTypes[j] = 1;
    cur->BindList[j] = (UCHAR *)malloc(sizeof(float));
    if (!cur->BindList[j])
      xsb_exit("Not enough memory for a float in SetBindVal!");
    *((float *)cur->BindList[j]) = (float)float_val(BindVal);
  } else if (isstring(BindVal)) {
    cur->BindTypes[j] = 2;
    cur->BindList[j] = string_val(BindVal);
  } else if (isconstr(BindVal) && get_arity(get_str_psc(BindVal))==1) {
      letter_flag = 1;
      wcan_disp = 0;
      write_canonical_term(p2p_arg(BindVal,1));
      if (term_string[j]) free(term_string[j]);
      term_string[j] = malloc(wcan_disp+1);
      strncpy(term_string[j],wcan_string,wcan_disp);
      term_string[j][wcan_disp] = '\0';
      cur->BindTypes[j] = 2;
      cur->BindList[j] = term_string[j];
  } else {
    xsb_exit("Unknown bind variable type, %d", cur->BindTypes[j]);
  }
  ctop_int(5,0);
  return;
}


/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     Parse()*/
/*  PARAMETERS:*/
/*     R1: 2*/
/*     R2: the SQL statement for the cursor*/
/*     R3: var, returned cursor address*/
/*  NOTES:*/
/*     parse the sql statement and submit it to DBMS to execute.  if all these*/
/*     succeed, then prepare for resulting row fetching.  this includes*/
/*     determination of column number in the resulting rowset and the length*/
/*     of each column and memory allocation which is used to store each row.*/
/*-----------------------------------------------------------------------------*/
void Parse()
{
  int j;
  struct Cursor *cur = (struct Cursor *)ptoc_int(2);
  RETCODE rc;

  if (cur->Status == 2) { /* reusing opened cursor*/
    rc = SQLFreeStmt(cur->hstmt,SQL_CLOSE);
    if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO)) {
      ctop_int(3, PrintErrorMsg(cur));
      SetCursorClose(cur);
      return;
    }
    /* reset just char select vars, since they store addr of chars*/
    for (j = 0; j < cur->NumBindVars; j++) {
      if (cur->BindTypes[j] == 2)
	rc = SQLBindParameter(cur->hstmt, (short)(j+1), SQL_PARAM_INPUT, SQL_C_CHAR, 
			      SQL_CHAR, 0, 0,(char *) cur->BindList[j], 0, &SQL_NTSval);
    }
  } else {
    if (SQLPrepare(cur->hstmt, cur->Sql, SQL_NTS) != SQL_SUCCESS) {
      ctop_int(3,PrintErrorMsg(cur));
      SetCursorClose(cur);
      return;
    }
    
    /* set the bind variables*/
    for (j = 0; j < cur->NumBindVars; j++) {
      if (cur->BindTypes[j] == 2)
	/* we're sloppy here.  it's ok for us to use the default values*/
	rc = SQLBindParameter(cur->hstmt, (short)(j+1), SQL_PARAM_INPUT, SQL_C_CHAR, 
			      SQL_CHAR, 0, 0,(char *)cur->BindList[j], 0, &SQL_NTSval);
      else if (cur->BindTypes[j] == 1) {
	rc = SQLBindParameter(cur->hstmt, (short)(j+1), SQL_PARAM_INPUT, SQL_C_FLOAT, SQL_FLOAT,
			 0, 0, (float *)cur->BindList[j], 0, NULL);
      } else
	rc = SQLBindParameter(cur->hstmt, (short)(j+1), SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER,
			 0, 0, (int *)(cur->BindList[j]), 0, NULL);
      if (rc != SQL_SUCCESS) {
	ctop_int(3,PrintErrorMsg(cur));
	SetCursorClose(cur);
	return;
      }
    }
  }
  /* submit it for execution*/
  if (SQLExecute(cur->hstmt) != SQL_SUCCESS) {
    ctop_int(3,PrintErrorMsg(cur));
    SetCursorClose(cur);
    return;
  }
  ctop_int(3,0);
  return;
}

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     ODBCCommit()*/
/*  PARAMETERS:*/
/*     R1: 10*/
/*     R2: Connection Handle*/
/*     R3: var, returned status, 0 if successful*/
/*-----------------------------------------------------------------------------*/
void ODBCCommit()
{
  struct Cursor *cur = FCursor;
  HDBC hdbc = (HDBC)ptoc_int(2);
  RETCODE rc;

  if (((rc=SQLTransact(henv,hdbc,SQL_COMMIT)) == SQL_SUCCESS) ||
      (rc == SQL_SUCCESS_WITH_INFO)) { 
    /* close only those with right hdbc....*/
    while (cur != NULL) {
      if (cur->hdbc == hdbc && cur->Status != 0) SetCursorClose(cur);
      cur = cur->NCursor;
    }
    ctop_int(3,0);
  } else
    ctop_int(3,PrintErrorMsg(NULL));
  return;
}

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     ODBCRollback()*/
/*  PARAMETERS:*/
/*     R1: 11*/
/*     R2: Connection Handle*/
/*     R3: var, for returned status: 0 if successful*/
/*-----------------------------------------------------------------------------*/
void ODBCRollback()
{
  struct Cursor *cur = FCursor;
  HDBC hdbc = (HDBC)ptoc_int(2);
  RETCODE rc;

  if (((rc=SQLTransact(henv,hdbc,SQL_ROLLBACK)) == SQL_SUCCESS) ||
      (rc == SQL_SUCCESS_WITH_INFO)) {
    /* only close those with right hdbc*/
    while (cur != NULL) {
      if (cur->hdbc == hdbc && cur->Status != 0) SetCursorClose(cur);
      cur = cur->NCursor;
    }
    ctop_int(3,0);
  } else
    ctop_int(3, PrintErrorMsg(NULL));
  return;
}

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     ODBCColumns()*/
/*  PARAMETERS:*/
/*     R1: 12*/
/*     R2: Cursor Index*/
/*     R3: Table name*/
/*     R4: var for returned status: 0 if successful*/
/*-----------------------------------------------------------------------------*/
void ODBCColumns()
{
  struct Cursor *cur = (struct Cursor *)ptoc_int(2);
  char tmpstr[255];
  char *str1, *str2, *str3;
  RETCODE rc;

  strcpy(tmpstr,ptoc_string(3));
  str1 = strtok(tmpstr,".");
  str2 = str3 = NULL;
  if (str1) str2 = strtok(NULL,".");
  if (str2) str3 = strtok(NULL,".");
  if (!str3 && !str2) {str3 = str1; str1 = NULL;}
  else if (!str3) {str3 = str2; str2 = NULL;}
  /*  printf("str1 %s, str2 %s, str3 %s\n",str1,str2,str3);*/
  if (((rc=SQLColumns(cur->hstmt,
		      str1, SQL_NTS,
		      str2, SQL_NTS,
		      str3, SQL_NTS,
		      NULL,0)) == SQL_SUCCESS) ||
      (rc == SQL_SUCCESS_WITH_INFO)) {
    ctop_int(4,0);
  } else {
    ctop_int(4,PrintErrorMsg(cur));
    SetCursorClose(cur);
  }
  return; 
} 

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     ODBCTables()*/
/*  PARAMETERS:*/
/*     R1: 13*/
/*     R2: Cursor*/
/*     R3: var, returned status: 0 if successful*/
/*-----------------------------------------------------------------------------*/
void ODBCTables()
{
  struct Cursor *cur = (struct Cursor *)ptoc_int(2);
  RETCODE rc;
  
  if (cur->Status == 2) { /* reusing opened cursor*/
    rc = SQLFreeStmt(cur->hstmt,SQL_CLOSE);
    if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO)) {
      ctop_int(3, PrintErrorMsg(cur));
      SetCursorClose(cur);
      return;
    }
  }
  
  if (((rc=SQLTables(cur->hstmt,
		     NULL, 0,
		     NULL, 0,
		     NULL, 0,
		     NULL, 0)) == SQL_SUCCESS) ||
      (rc == SQL_SUCCESS_WITH_INFO)) {
    ctop_int(3,0);
  } else {
    ctop_int(3,PrintErrorMsg(cur));
    SetCursorClose(cur);
  }
  return; 
}

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     ODBCUserTables()*/
/*  PARAMETERS:*/
/*     R1: 14*/
/*     R2: Cursor*/
/*     R3: var, for returned status: 0 if successful*/
/*-----------------------------------------------------------------------------*/
void ODBCUserTables()
{
  struct Cursor *cur = (struct Cursor *)ptoc_int(2);
  UWORD TablePrivilegeExists;
  RETCODE rc;

  /* since some ODBC drivers don't implement the function SQLTablePrivileges*/
  /* we check it first*/
  SQLGetFunctions(cur->hdbc,SQL_API_SQLTABLEPRIVILEGES,&TablePrivilegeExists);
  if (!TablePrivilegeExists) {
    printf("Privilege concept does not exist in this DVMS: you probably can access any of the existing tables\n");
    ctop_int(3, 2);
    return;
  }
  if (((rc=SQLTablePrivileges(cur->hstmt,
			      NULL, 0,
			      NULL, 0,
			      NULL, 0)) == SQL_SUCCESS) ||
      (rc == SQL_SUCCESS_WITH_INFO)) 
    ctop_int(3,0);
  else {
    ctop_int(3,PrintErrorMsg(cur));
    SetCursorClose(cur);
  }
  return; 
}

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     DisplayColSize()*/
/*  PARAMETERS:*/
/*     SWORD coltype - column type which is a single word.*/
/*     UDWORD collen - column length which is returned by SQLDescribeCol*/
/*     UCHAR *colname - pointer to column name string*/
/*  RETURN VALUE:*/
/*     column length - the size of memory that is needed to store the column*/
/*     value for supported column types*/
/*     0             - otherwise*/
/*-----------------------------------------------------------------------------*/
UDWORD DisplayColSize(SWORD coltype, UDWORD collen, UCHAR *colname)
{
  switch (ODBCToXSBType(coltype)) {
  case SQL_C_SLONG: 
    return sizeof(long *);
  case SQL_C_CHAR: {
    UDWORD tmp = MAXI(collen+1, strlen((char *) colname));
    if (tmp < MAXVARSTRLEN) return tmp;
    else return MAXVARSTRLEN;
  }
  case SQL_C_BINARY: {
    return MAXVARSTRLEN;
  }
  case SQL_C_FLOAT:
    return sizeof(float *);
  default: 
    printf("Illegal ODBC Type: %d\n",coltype);
    return 0;
  }
}
extern xsbBool unify(Cell, Cell);

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     ODBCDataSources()*/
/*  PARAMETERS:*/
/*     R1: 1 - first call; 2 - subsequent calls */
/*     R2: var, returns DSN */
/*     R3: var, returns DS description */
/*     R4: var, returns status */
/*  NOTES:*/
/*-----------------------------------------------------------------------------*/
void ODBCDataSources()
{
  static SQLCHAR DSN[SQL_MAX_DSN_LENGTH+1];
  static SQLCHAR Description[SQL_MAX_DSN_LENGTH+1];
  RETCODE rc;
  int seq;
  SWORD dsn_size, descr_size;
  Cell op2 = ptoc_tag(3);
  Cell op3 = ptoc_tag(4);

  if (!henv) {
    /* allocate environment handler*/
    rc = SQLAllocEnv(&henv);
    if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
      xsb_error("Environment allocation failed");   
      ctop_int(5,1);
      return;
    }
    LCursor = FCursor = NULL;
    FCurNum = NULL;
    nullStrAtom = makestring(string_find("NULL",1));
  }

  seq = ptoc_int(2);
  
  if (seq == 1) {
    rc = SQLDataSources(henv,SQL_FETCH_FIRST,DSN,
			SQL_MAX_DSN_LENGTH,&dsn_size,
			Description,SQL_MAX_DSN_LENGTH,
			&descr_size);
    if (rc == SQL_NO_DATA_FOUND) {
      ctop_int(5,2);
      return;
    }
    if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
      xsb_error("Environment allocation failed");   
      ctop_int(5,1);
      return;
    }
  } else {
    rc = SQLDataSources(henv,SQL_FETCH_NEXT,DSN,
			SQL_MAX_DSN_LENGTH,&dsn_size,
			Description,SQL_MAX_DSN_LENGTH,
			&descr_size);
    if (rc == SQL_NO_DATA_FOUND) {
      ctop_int(5,2);
      return;
    }
    if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
      xsb_error("Environment allocation failed");   
      ctop_int(5,1);
      return;
    }
  }
  XSB_Deref(op2);
  if (isref(op2))
    unify(op2, makestring(string_find(DSN,1)));
  else {
    xsb_error("[ODBCDataSources] Param 2 should be a free variable.");
    ctop_int(5,1);
    return;
  }
  XSB_Deref(op3);
  if (isref(op3))
    unify(op3, makestring(string_find(Description,1)));
  else {
    xsb_error("[ODBCDataSources] Param 3 should be a free variable.");
    ctop_int(5,1);
    return;
  }
  ctop_int(5,0);
  return;
}

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     DescribeSelect()*/
/*  PARAMETERS:*/
/*     R1: 15*/
/*     R2: Ptr to cursor record*/
/*     R3: var, for returned status:*/
/*         0 - the result row has at least one column and*/
/*         1 - something goes wrong, we can't retrieve column information, */
/*             memory allocation fails (if this happens program stops).*/
/*         2 - no column is affected*/
/*  NOTES:*/
/*     memory is also allocated for future data storage*/
/*-----------------------------------------------------------------------------*/
void ODBCDescribeSelect()
{
  int j;
  UCHAR colname[50];
  SWORD colnamelen;
  SWORD scale;
  SWORD nullable;
  UDWORD collen;
  struct Cursor *cur = (struct Cursor *)ptoc_int(2);

  cur->NumCols = 0;
  SQLNumResultCols(cur->hstmt, (SQLSMALLINT*)&(cur->NumCols));
  if (!(cur->NumCols)) {
    /* no columns are affected, set cursor status to unused */
    cur->Status = 1; 
    ctop_int(3,2);
    return;
  }
  /* if we aren't reusing a closed statement handle, we need to get*/
  /* resulting rowset info and allocate memory for it*/
  if (cur->Status != 2) {
    cur->ColTypes =
      (SWORD *)malloc(sizeof(SWORD) * cur->NumCols);
    if (!cur->ColTypes)
      xsb_exit("Not enough memory for ColTypes!");
    
    cur->Data =
      (UCHAR **)malloc(sizeof(char *) * cur->NumCols);
    if (!cur->Data)
      xsb_exit("Not enough memory for Data!");

    cur->OutLen =
      (UDWORD *)malloc(sizeof(UDWORD) * cur->NumCols);
    if (!cur->OutLen)
      xsb_exit("Not enough memory for OutLen!");

    cur->ColLen =
      (UDWORD *)malloc(sizeof(UDWORD) * cur->NumCols);
    if (!cur->ColLen)
      xsb_exit("Not enough memory for ColLen!");
    
    for (j = 0; j < cur->NumCols; j++) {
      SQLDescribeCol(cur->hstmt, (short)(j+1), (UCHAR FAR*)colname,
		     sizeof(colname), &colnamelen,
		     &(cur->ColTypes[j]),
		     &collen, &scale, &nullable);
      /* SQLServer returns this wierd type for a system table, treat it as varchar?*/
      if (cur->ColTypes[j] == -9) cur->ColTypes[j] = SQL_VARCHAR;
      colnamelen = (colnamelen > 49) ? 49 : colnamelen; 
      colname[colnamelen] = '\0';
      if (!(cur->ColLen[j] =
	    DisplayColSize(cur->ColTypes[j],collen,colname))) {
	/* let SetCursorClose function correctly free all the memory allocated*/
	/* for Data storage: cur->Data[j]'s*/
	cur->NumCols = j; /* set so close frees memory allocated thus far*/
	SetCursorClose(cur);
	/*	return(1);*/
	ctop_int(3,1);
	return;
      }
      cur->Data[j] =
	(UCHAR *) malloc(((unsigned) cur->ColLen[j]+1)*sizeof(UCHAR));
      if (!cur->Data[j])
	xsb_exit("Not enough memory for Data[j]!");
    }
  }
  /* bind them*/
  for (j = 0; j < cur->NumCols; j++) {
    SQLBindCol(cur->hstmt, (short)(j+1), 
	       ODBCToXSBType(cur->ColTypes[j]), cur->Data[j],
	       cur->ColLen[j], (SDWORD FAR *)(&(cur->OutLen[j])));
  }
  /*  return 0;*/
  ctop_int(3,0);
  return;
}

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     FetchNextRow()*/
/*  PARAMETERS:*/
/*     R1: 4*/
/*     R2: Cursor Address*/
/*     R3: var, for returned status*/
/*         0 => successful, a row is read and available*/
/*         1 => successful, no row avaliable*/
/*         2 => unsuccessful, an error occurred in fetching.*/
/*  NOTES:*/
/*     fetch next row of result rowset.*/
/*-----------------------------------------------------------------------------*/
void FetchNextRow()
{
  struct Cursor *cur = (struct Cursor *)ptoc_int(2);
  RETCODE rc;

  if (!serverConnected || cur->Status == 0) {
    ctop_int(3,2);
    return;
  }

  rc = SQLFetch(cur->hstmt);

  if ((rc == SQL_SUCCESS) || (rc == SQL_SUCCESS_WITH_INFO)) 
    ctop_int(3,0);
  else if (rc == SQL_NO_DATA_FOUND){
    cur->Status = 1; /* done w/fetching. set cursor status to unused */
    ctop_int(3,1);
  }
  else {
    SetCursorClose(cur);         /* error occured in fetching*/
    ctop_int(3,2);
  }
  return;
} 

/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     ODBCConnectOption() */
/*  PARAMETERS:*/
/*     R1: 16*/
/*     R2: Connection Handle*/
/*     R3: 0 -> get; 1 -> set*/
/*     R4: Option indicator (int)*/
/*     R5: if R2=0 -> variable that attr val is returned in*/
/*         if R2=1 -> value used to set attr*/
/*     R6: Return Code*/
/*  NOTES:*/
/*     either gets value of a connection option, or sets it.*/
/*-----------------------------------------------------------------------------*/
void ODBCConnectOption()
{
  HDBC hdbc = (HDBC)ptoc_int(2);
  int set = ptoc_int(3);
  long value = 0;
  RETCODE rc;

  if (set) {
    rc = SQLSetConnectOption(hdbc,(UWORD)ptoc_int(4),(UDWORD)ptoc_int(5));
  } else {
    rc = SQLGetConnectOption(hdbc,(UWORD)ptoc_int(4),(PTR)&value);
    ctop_int(5,value);
  }
  if ((rc == SQL_SUCCESS) || (rc == SQL_SUCCESS_WITH_INFO)) 
    ctop_int(6,0);
  else ctop_int(6,PrintErrorMsg(NULL));
}


/*-----------------------------------------------------------------------------*/
/*  FUNCTION NAME:*/
/*     GetColumn() */
/*  PARAMETERS:*/
/*     R1: 5*/
/*     R2: Cursor*/
/*     R3: Column Index*/
/*     R4: Column Value returned*/
/*     R5: Return Code*/
/*  NOTES:*/
/*     get the indicated column.*/
/*-----------------------------------------------------------------------------*/
int GetColumn()
{
  struct Cursor *cur = (struct Cursor *)ptoc_int(2);
  int ColCurNum = ptoc_int(3);
  Cell op1;
  Cell op = ptoc_tag(4);
  UDWORD len;

  if (ColCurNum < 0 || ColCurNum >= cur->NumCols) {
    /* no more columns in the result row*/
    ctop_int(5,1);   
    return TRUE;
  }

  ctop_int(5,0);

  /* get the data*/
  if (cur->OutLen[ColCurNum] == SQL_NULL_DATA) {
    /* column value is NULL*/
    return unify(op,nullStrAtom);
  }

  /* convert the string to either integer, float or string*/
  /* according to the column type and pass it back to XSB*/
  switch (ODBCToXSBType(cur->ColTypes[ColCurNum])) {
  case SQL_C_CHAR:
    /* convert the column string to a C string */
    len = ((cur->ColLen[ColCurNum] < cur->OutLen[ColCurNum])?
	   cur->ColLen[ColCurNum]:cur->OutLen[ColCurNum]);
    *(cur->Data[ColCurNum]+len) = '\0';

    /* compare strings here, so don't intern strings unnecessarily*/
    XSB_Deref(op);
    if (isref(op)) 
      return unify(op, makestring(string_find(cur->Data[ColCurNum],1))); 
    if (isconstr(op) && get_arity(get_str_psc(op)) == 1) {
      STRFILE strfile;
      
      op1 = cell(clref_val(op)+1);
      XSB_Deref(op1);
      
      strfile.strcnt = strlen(cur->Data[ColCurNum]);
      strfile.strptr = strfile.strbase = cur->Data[ColCurNum];
      read_canonical_term(NULL,&strfile,op1); /* terminating '.'? */
      return TRUE;
    }
    if (!isstring(op)) return FALSE;
    if (strcmp(string_val(op),cur->Data[ColCurNum])) return FALSE;
    return TRUE;
  case SQL_C_BINARY:
    /* convert the column string to a C string */
    len = ((cur->ColLen[ColCurNum] < cur->OutLen[ColCurNum])?
	   cur->ColLen[ColCurNum]:cur->OutLen[ColCurNum]);
    *(cur->Data[ColCurNum]+len) = '\0';

    /* compare strings here, so don't intern strings unnecessarily*/
    XSB_Deref(op);
    if (isref(op)) 
      return unify(op, makestring(string_find(cur->Data[ColCurNum],1))); 
    if (isconstr(op) && get_arity(get_str_psc(op)) == 1) {
      STRFILE strfile;
      
      op1 = cell(clref_val(op)+1);
      XSB_Deref(op1);
      
      strfile.strcnt = strlen(cur->Data[ColCurNum]);
      strfile.strptr = strfile.strbase = cur->Data[ColCurNum];
      read_canonical_term(NULL,&strfile,op1); /* terminating '.'? */
      return TRUE;
    }
    if (!isstring(op)) return FALSE;
    if (strcmp(string_val(op),cur->Data[ColCurNum])) return FALSE;
    return TRUE;
  case SQL_C_SLONG:
    return unify(op,makeint(*(long *)(cur->Data[ColCurNum])));
  case SQL_C_FLOAT:
    return unify(op,makefloat(*(float *)(cur->Data[ColCurNum])));
  }

  return FALSE;
}

