/*
** Module:  informixmodule.c
**
** Description:  Informix library module for Python.
**
** Programmer:  George Cutrell, george.cutrell@mci.com
**
** Date:  07/29/96
**
** Compile Instructions:
**  o Must have Informix CLI installed.
**  o Add the following line to ../Modules/Setup:
**      informix informixmodule.c -I$(INFORMIXDIR)/odbc/include -L$(INFORMIXDIR)/lib -lodbc -R$(INFORMIXDIR)/lib
**  o Then, from the Modules subdirectory, perform the following:
**      make -f Makefile.pre Makefile
**      make clean
**      make
**
** Notes:  This implementation was modeled after the Sybase implementation except
**         that results returned from a query are returned as a list of dictionary
**         objects instead of a list of tuples.
*/


#include <stdio.h>
#include <sql.h>
#include <sqlext.h>
#include "allobjects.h"
#include "modsupport.h"


static object *InformixError;	/* exception informix.error */


typedef struct {
	OB_HEAD
	HENV	henv;		/* Handle - Environment		*/
	HDBC	hdbc;		/* Handle - Database connect	*/
	HSTMT	hstmt;	/* Handle - SQL statement	*/
} infdbobject;

extern typeobject InfDbtype; /* Forward */

#define MSG_LNG    256            /* Maximum message length          */
int print_err(henv, hdbc, hstmt)
HENV	henv;
HDBC	hdbc;
HSTMT	hstmt;
{
	RETCODE	rc;			/* general return code for API		*/
	UCHAR	szSqlState[MSG_LNG];	/* SQL state string			*/
	SDWORD	pfNativeError;		/* Native error code			*/
	UCHAR	szErrorMsg[MSG_LNG];	/* Error msg text buffer pointer	*/
	SWORD	pcbErrorMsg;		/* Error msg text Available bytes	*/
	char	msgtext[MSG_LNG];	/* message text work area		*/
	
	rc = SQLError(henv, hdbc, hstmt, szSqlState, &pfNativeError, szErrorMsg,
			MSG_LNG, &pcbErrorMsg);
	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO){
		switch (rc){
		case SQL_NO_DATA_FOUND:
			printf("SQLERROR() couldn't find text, RC=%d\n", rc);
			break;
		case SQL_ERROR:
			printf("SQLERROR() couldn't access text, RC=%d\n", rc);
			break;
		case SQL_INVALID_HANDLE:
			printf("SQLERROR() had invalid handle, RC=%d\n", rc);
			break;
		default:
			printf("SQLERROR() unknown return code, RC=%d\n", rc);
			break;
		}
		printf( msgtext );	/* display error message for user  */
		}
	else
		printf("{error} STATE=%s, CODE=%ld, MSG=%s\n", szSqlState,
			pfNativeError, szErrorMsg);
	
	return (1);                    /* TRUE is secondary return code   */
}

static infdbobject *newinfdbobject( user, passwd, server )
char *user;
char *passwd;
char *server;
{
	infdbobject *s = NULL;
	RETCODE	rc;

	s = NEWOBJ(infdbobject, &InfDbtype);
	if( s != NULL ){
		rc = SQLAllocEnv(&s->henv);
		if (rc != SQL_SUCCESS & rc != SQL_SUCCESS_WITH_INFO){
			print_err(SQL_NULL_HENV, SQL_NULL_HDBC, SQL_NULL_HSTMT);
			return NULL;
		}
		rc = SQLAllocConnect(s->henv, &s->hdbc);
		if (rc != SQL_SUCCESS & rc != SQL_SUCCESS_WITH_INFO){
			print_err(s->henv, SQL_NULL_HDBC, SQL_NULL_HSTMT);
			return NULL;
		}
		if( user && passwd && server ){
			rc = SQLConnect(s->hdbc, server, SQL_NTS, user, SQL_NTS, passwd, SQL_NTS);
			if (rc != SQL_SUCCESS & rc != SQL_SUCCESS_WITH_INFO){
				print_err(s->henv, s->hdbc, SQL_NULL_HSTMT);
				return NULL;
			}
		}
		else {
			DEL(s);
			return( NULL );
		}
	}
	return s;
}

/* OBJECT FUNCTIONS: sybdb */

/* 	Function to return results in a dictionary instead of a list.  The key
	will be the column name and value will be as you guessed a the column
	value
*/
typedef struct _colinfo{
	UCHAR   name[50];
	SWORD   len;
	SWORD   type;
	UDWORD  datalen;
	SWORD   scale;
	SWORD   nullable;
} COLINFO;

static object
  *getDictionaryResults (henvp, hdbcp, hstmtp)
HENV *henvp;
HDBC *hdbcp;
HSTMT *hstmtp;
{
	object *results;
	object *dictionary;
	object *row;
	object *o;
	int i,j;
	SWORD cols;
	RETCODE	rc;
	COLINFO *pColInfo = NULL;
	SDWORD len;


	/*
	** Determine number of columns in result set
	*/
	results = newlistobject(0);
	rc = SQLNumResultCols(hstmtp, &cols);
	if (rc != SQL_SUCCESS & rc != SQL_SUCCESS_WITH_INFO){
		print_err(henvp, hdbcp, hstmtp);
		return NULL;
	}
	if( cols == 0 )
		return( results );

	/*
	** Get column description information
	*/
	pColInfo = (COLINFO *)malloc(sizeof(COLINFO)*cols);
	for(i=0; i<cols; i++){
		memset(pColInfo[i].name, '\0', sizeof(pColInfo[i].name));
      rc = SQLDescribeCol(hstmtp, i+1,
				pColInfo[i].name,
				sizeof(pColInfo[i].name),
				&pColInfo[i].len,
				&pColInfo[i].type,
				&pColInfo[i].datalen,
				&pColInfo[i].scale,
				&pColInfo[i].nullable);
		if (rc != SQL_SUCCESS & rc != SQL_SUCCESS_WITH_INFO){
			print_err(henvp, hdbcp, hstmtp);
			return NULL;
		}

		/*
		** For stored procedures that don't alias the return values,
		**  we'll force them to be unique.
		*/
		if( !strcmp(pColInfo[i].name, "(expression)") )
			sprintf(pColInfo[i].name, "Column %d", i);
	}

	/*
	** Loop thru results
	*/
	j=0;
	while( 1 ){
		rc = SQLFetch(hstmtp);
		if( (rc == SQL_SUCCESS) || (rc == SQL_SUCCESS_WITH_INFO) ){
			j++;
			row = newdictobject();
			for(i=0; i<cols; i++){
				switch(pColInfo[i].type){
					case SQL_CHAR:
					case SQL_VARCHAR:
					case SQL_LONGVARCHAR:
					case SQL_BINARY:
					case SQL_VARBINARY:
					case SQL_LONGVARBINARY:
					case SQL_DATE:
					case SQL_TIME:
					case SQL_TIMESTAMP:{
						char *p = (char *)malloc(sizeof(char)*pColInfo[i].datalen);
						memset(p, '\0', pColInfo[i].datalen);
						SQLGetData(hstmtp, i+1, SQL_C_CHAR, p, pColInfo[i].datalen, &len);
						o = newsizedstringobject((char *)p, pColInfo[i].datalen);
						dictinsert( row, pColInfo[i].name, o );
						free(p);
						break;
					}
					case SQL_BIT:
					case SQL_TINYINT:
					case SQL_SMALLINT:
					case SQL_INTEGER:{
						long l;
						SQLGetData(hstmtp, i+1, SQL_C_LONG, &l, sizeof(long), &len);
						o = newintobject(l);
						dictinsert( row, pColInfo[i].name, o );
						break;
					}
					case SQL_NUMERIC:
					case SQL_BIGINT:
					case SQL_DECIMAL:
					case SQL_REAL:
					case SQL_FLOAT:
					case SQL_DOUBLE:{
						float f;
						SQLGetData(hstmtp, i+1, SQL_C_FLOAT, &f, sizeof(float), &len);
						o = newfloatobject(f);
						dictinsert( row, pColInfo[i].name, o );
						break;
					}
				}
			}
			addlistitem(results,row);
		} else if( rc == SQL_NO_DATA_FOUND )
			break;		/* No more result rows */
		else {
			print_err(henvp, hdbcp, hstmtp);		/* Some sort of error */
			free(pColInfo);
			return NULL;
		}
	}

	free(pColInfo);

	return (results);
}

static object
  *sybdb_sql (self, args)
object *self;
object *args;
{
	char *sql;
	object *results;
	HENV *henvp;
	HDBC *hdbcp;
	HSTMT *hstmtp;
	RETCODE rc;

	henvp = ((infdbobject *)self)->henv;
	hdbcp = ((infdbobject *)self)->hdbc;
	hstmtp = ((infdbobject *)self)->hstmt;


	rc = SQLAllocStmt(hdbcp, (HSTMT *)&hstmtp);
	if (rc != SQL_SUCCESS & rc != SQL_SUCCESS_WITH_INFO){
		print_err(henvp, hdbcp, SQL_NULL_HSTMT);
		return NULL;
	}
	err_clear ();
	if (!getargs (args, "s", &sql)) {
		return NULL;
	}

	rc = SQLCancel(hstmtp);
	if (rc != SQL_SUCCESS & rc != SQL_SUCCESS_WITH_INFO){
		print_err(henvp, hdbcp, SQL_NULL_HSTMT);
		return NULL;
	}
	rc = SQLExecDirect(hstmtp, sql, SQL_NTS);
	if (rc != SQL_SUCCESS & rc != SQL_SUCCESS_WITH_INFO){
		print_err(henvp, hdbcp, hstmtp);
		return NULL;
	}

	results = getDictionaryResults(henvp, hdbcp, hstmtp);

	rc = SQLTransact(henvp, hdbcp, SQL_COMMIT);
	if (rc != SQL_SUCCESS ){
		print_err(henvp, hdbcp, hstmtp);
		return NULL;
	}
	rc = SQLFreeStmt(hstmtp, SQL_CLOSE);
	if (rc != SQL_SUCCESS ){
		print_err(henvp, hdbcp, hstmtp);
		return NULL;
	}
	rc = SQLFreeStmt(hstmtp, SQL_DROP);
	if (rc != SQL_SUCCESS ){
		print_err(henvp, hdbcp, hstmtp);
		return NULL;
	}

	return( results );
}

static struct methodlist infdb_methods[] = {
	{"sql",        sybdb_sql},
  {NULL,         NULL}		/* sentinel */
};


static void
infdb_dealloc(s)
     infdbobject *s;
{
	RETCODE rc;

	rc = SQLDisconnect(s->hdbc);      /* Disconnect from the data source */
	if (rc != SQL_SUCCESS ){
		print_err(s->henv, s->hdbc, SQL_NULL_HSTMT);
		return;
	}
	rc = SQLFreeConnect(s->hdbc);     /* Free the connection handle      */
	if (rc != SQL_SUCCESS ){
		print_err(s->henv, s->hdbc, SQL_NULL_HSTMT);
		return;
	}
	rc = SQLFreeEnv(s->henv);         /* Free the environment handle     */
	if (rc != SQL_SUCCESS ){
		print_err(s->henv, SQL_NULL_HDBC, SQL_NULL_HSTMT);
		return;
	}

	DEL(s);
}

static object *
infdb_getattr(s, name)
     infdbobject *s;
     char *name;
{
  return findmethod(infdb_methods, (object *) s, name);
}

typeobject InfDbtype = {
        OB_HEAD_INIT(&Typetype)
        0,
        "infdb",
        sizeof(infdbobject),
        0,
        infdb_dealloc,		/*tp_dealloc*/
        0,			/*tp_print*/
        infdb_getattr,		/*tp_getattr*/
        0,			/*tp_setattr*/
        0,			/*tp_compare*/
        0,			/*tp_repr*/
        0,			/*tp_as_number*/
        0,			/*tp_as_sequence*/
        0,			/*tp_as_mapping*/
};



/* MODULE FUNCTIONS: informix */

static object
  *informix_new (self, args)
object *self;		/* Not used */
object *args;
{
  char *user, *passwd, *server;
  object *db;

  err_clear ();
  if (!getargs (args, "(zzz)", &user, &passwd, &server)) {
    return NULL;
  }
  db = (object *) newinfdbobject(user, passwd, server);
  if (!db) {
    /* XXX Should be setting some errstr stuff here based on informix errors */
    err_setstr (InformixError, "Could not open connection to server.");
    return NULL;
  }
  return db;
}


/* List of module functions */
static struct methodlist informix_methods[]=
{
  {"new", informix_new},
  {NULL, NULL}			/* sentinel */
};


/* Module initialisation */
void initinformix ()
{
  object *m, *d;

  /* Create the module and add the functions */
  m = initmodule ("informix", informix_methods);
  /* Add some symbolic constants to the module */
  d = getmoduledict (m);
  InformixError = newstringobject ("informix.error");
  if (InformixError == NULL || dictinsert (d, "error", InformixError) != 0) {
    fatal ("can't define informix.error");
  }
  /* Check for errors */
  if (err_occurred ()){
    fatal ("can't initialize module informix");
  }
}
