/*
        +==========================================================+
        |                                                          |
        |              ODBC extension for Win32 Perl               |
        |              -----------------------------               |
        |                                                          |
        |            by Dave Roth <rothd@roth.net>                 |
        |                                                          |
        |                  version v970208                         |
        |                                                          |
        | Copyright (c) 1996-1997 Dave Roth. All rights reserved.  |
        |   This program is free software; you can redistribute    |
        | it and/or modify it under the same terms as Perl itself. |
        |                                                          |
        +==========================================================+


          based on original code by Dan DeMaggio (dmag@umich.edu)

   Use under GNU General Public License or Larry Wall's "Artistic License"
*/

#define	__WIN32_ODBC__

#define WIN32_LEAN_AND_MEAN
#include <stdlib.h>
#include <math.h>	// VC-5.0 brainmelt
#include <windows.h>
#include <stdio.h>
	
	//	ODBC Stuff
#include <SQL.H>
#include <SQLEXT.H>
#include <ODBCINST.H>

	//	Win32 Perl Stuff
#if defined(__cplusplus)
extern "C" {
#endif
#include "EXTERN.h"
#include "perl.h"
#include "XSub.h"
#include "patchlevel.h"
#if (PATCHLEVEL < 5) && !defined(PL_sv_undef)
#define PL_sv_undef sv_undef
#endif

#if defined(__cplusplus)
}	
#endif
	//	Win32::ODBC Stuff					  	
#include "ODBCBuild.h"
#include "CResults.hpp"	
#include "CMom.hpp"
#include "odbc.h"
#include "constant.h"

#undef	__WIN32_ODBC__

extern	CMOM *cMom;

/*----------------------- P E R L   F U N C T I O N S -------------------*/


XS(XS_WIN32__ODBC_Constant)
{
	dXSARGS;
	if (items < 1)
	{
		croak("Usage: Win32::ODBC::Constant(name)\n");
    }
	{
	        STRLEN n_a;
		char* name = (char*)SvPV(ST(0),n_a);
		ST(0) = sv_newmortal();
		sv_setiv(ST(0), constant(name));
	}
	XSRETURN(1);
}


/*----------------------- M I S C   F U N C T I O N S -------------------*/
 	/*
 		Allocate memory for a new connection and set up the structure
	*/

ODBC_TYPE *NewODBC(){
	ODBC_TYPE *h = 0;
	int	iResult = 0;
	CMom	*cmDaughter;
	int	iTemp;


#ifdef _Debug
	if (ghDebug){
		DebugConnection("Creating a new ODBC object.", 0);
	}
#endif
	if (!(cmDaughter = (CMom *) ::cMom->operator[](GetCurrentThreadId()))){
		cmDaughter = new CMom(GetCurrentThreadId());
	}
	if (cmDaughter){ 

#ifdef _Debug
		DEBUG_DUMP("NewODBC: Entering Critical Section gCS")
#endif
		EnterCriticalSection(&gCS);
		if (! ghEnv){
			SQLAllocEnv(&ghEnv);
		}
		LeaveCriticalSection(&gCS);

#ifdef _Debug
		DEBUG_DUMP("NewODBC: Left Critical Section gCS    ")
#endif

		if (ghEnv){							   
			if (h= new ODBC_TYPE){
				if (h->Error = new ODBC_ERROR){
					if (cmDaughter->Add((void *)h)){
						iResult = 1;						
						h->szDSN = 0;
						h->szCommand = 0;
						CleanODBC(h);
							//	Add these values AFTER cleaning the ODBC Object
						h->conn = cmDaughter->operator[]((void *)h);
#ifdef _Debug
						DebugConnection("...created ODBC object.", h);
#endif
						h->henv = ghEnv;
							 
					}
				}
				if (!iResult){
					FreeODBC(h);
					delete h;
					h = 0;
				}
			}
		}
	}
#ifdef _Debug
	else{
		DebugConnection("...failed with new ODBC object: Could not find or create daughter.", h);
	}
if (!h){
	DebugConnection("...creation of ODBC object failed.", h);
}
#endif
	return h;
}

	/*
		Clean up an ODBC structure.
	*/

ODBC_TYPE *CleanODBC(ODBC_TYPE *h){
	h->hstmt	= SQL_NULL_HSTMT;
	h->henv		= SQL_NULL_HENV;
	h->hdbc		= 0;
	h->Results	= 0;
	h->iMaxBufSize	= DEFAULT_DATA_BUF_SIZE;
	h->numcols		= 0;

	h->uStmtCloseType = DEFAULT_STMT_CLOSE_TYPE;
	h->iDebug = 0;
	h->dNumOfRows = 0;
	strcpy(h->szUserDSN, "");
	if (h->szDSN){
		delete [] h->szDSN;
		h->szDSN = 0;
	}
	if (h->szCommand){
		delete [] h->szCommand;
		h->szCommand = 0;
	}
	CleanError(h->Error);
	return h;
}

 	/*
		Deallocates memory used for an ODBC structure.
		If the master list (ODBCLIST) is passed to this it will do nothing unless
		there are no other ODBC structures remaining.
	*/

int FreeODBC(ODBC_TYPE *h){
	int	iResult = 1;

#ifdef _Debug
	if (ghDebug){
		DebugConnection("FreeODBC has been called.\n", h);
	}
#endif
		//	There is a call in this function to itself if we delete the
		//	last connection. BECAUASE OF THIS do NOT put a critical section
		//	in this function lest we reach a deadlock condition.
	if (h->Results){
		delete h->Results;
		h->Results = 0;
	}
	if (h->hstmt){
			//	If ghDLL is NULL (process has already detached) then
			//	forget this (Win32 will purge the memory anyway).
		if (ghDLL) SQLFreeStmt(h->hstmt, SQL_DROP);
		h->hstmt = SQL_NULL_HSTMT;
	}
	if (h->hdbc){
		h->hdbc->iCount--;
		if (! h->hdbc->iCount){
					//	Disconnect ONLY if you have no parents
					//	If ghDLL is NULL (process has already detached) then
					//	forget this (Win32 will purge the memory anyway).
			if (ghDLL){
				if (h->hdbc->iConnected){
					SQLDisconnect(h->hdbc->hdbc);
					h->hdbc->hdbc = SQL_NULL_HDBC;
					h->hdbc->iConnected = 0;
				}
				SQLFreeConnect(h->hdbc->hdbc);
			}
			delete h->hdbc;
		}
		h->hdbc = 0;
	}
	if (h->Error){
		delete h->Error;
		h->Error = 0;
	}
	h->henv = SQL_NULL_HENV;

	
	if (h->iDebug){

#ifdef _Debug
		DebugConnection("Setting Debug Mode off for connection\n\t\t(due to object destuction).", h);
#endif

		RemoveDebug(h);
	}

#ifdef _Debug
	if (ghDebug){
		char szBuff[30];
		sprintf(szBuff, "FreeODBC was %s.\n", (iResult)? "successful":"unsuccessful");
		DebugConnection(szBuff, h);
	}
#endif

	if (iResult){
		CleanODBC(h);
	}
	return iResult;
}

void AddDebug(ODBC_TYPE *h){
	if (! h->iDebug){
		if (h->conn){
				//	Increase the debug flag ONLY if
			giDebug++;
		}else{
			giDebugGlobal = 1;
		}
		if (! ghDebug){
		#if _DEBUG
			AllocConsole();
   			SetConsoleTitle("DEBUG: ODBC.PLL");
			ghDebug = GetStdHandle(STD_ERROR_HANDLE);
		#endif _DEBUG
		}
		h->iDebug = 1;
	}
}

void RemoveDebug(ODBC_TYPE *h){
	if(h->iDebug){
		if (h->conn){
			giDebug--;
			if (giDebug < 1){
				giDebug = 0;
			}
		}else{
//			giDebugGlobal = 0;
		}
		if (!(giDebug + giDebugGlobal)){
		#ifdef _Debug
			DebugConnection("Closing Debug Output File.", h);
		#endif
			DEBUG_DUMP("RemoveDebug: Entering Critical Section gDCS")
			EnterCriticalSection(&gDCS);
			ghDebug = 0;
		#if _DEBUG
			FreeConsole();
		#endif
			CloseHandle(ghFile);
			ghFile = 0;	
			if(gszFile){
				delete [] gszFile;
				gszFile = 0;
			}
			LeaveCriticalSection(&gDCS);
			DEBUG_DUMP("RemoveDebug: Left Critical Section gDCS    ")
		}
		h->iDebug = 0;
	}
}

	/*
		Reset (clean) an ODBC structures error state information
	*/
void CleanError(ODBC_ERROR *h){
	if (h){
		strcpy(h->szError, "");
		strcpy((char *)h->szSqlState, "");
		strcpy(h->szFunction, "");
		strcpy(h->szFunctionLevel, "");
		h->ErrNum = 0;
		h->EOR = 0;
	}
	return;
}

	/*
		Reset an ODBC Stmt. (alloc memory if needed, free it up if needed)
	*/	  

RETCODE ResetStmt(ODBC_TYPE *h){
	RETCODE	iReturnCode = SQL_SUCCESS;

	if (h->uStmtCloseType != SQL_DONT_CLOSE){
	  			//	If the SQLFreeStmt() failed, should we reallocate the stmt? 
				//	For now let's just return the error code and skip the reallocation.
		if (h->hstmt != SQL_NULL_HSTMT){
			iReturnCode = SQLFreeStmt(h->hstmt, h->uStmtCloseType);
			if (iReturnCode == SQL_SUCCESS){
					//	We will need to realloc the hstmt ONLY if we DROPPED it!
				if(h->uStmtCloseType == SQL_DROP){
					h->hstmt = SQL_NULL_HSTMT;
				}
			}else{
				_NT_ODBC_Error(h, "ResetStmt", "1");
			}
		}
		if (h->hstmt == SQL_NULL_HSTMT){
			if ((iReturnCode = SQLAllocStmt(h->hdbc->hdbc, &h->hstmt)) != SQL_SUCCESS){
				_NT_ODBC_Error(h, "ResetStmt", "2");
			}
		}
	}	
	return iReturnCode;
}
	
	/*
		Process an ODBC structures error state from the ODBC API.
	  	This is called when an error is encountered via the ODBC API.
	*/

void _NT_ODBC_Error(ODBC_TYPE * h, char *szFunction, char *szFunctionLevel){
	SDWORD cbErrorMsg;

	if(!h){
		h = ODBCLIST;
	}
	strcpy((char *)h->Error->szSqlState, "");
	strcpy(h->Error->szError, "");
	SQLError(h->henv, h->hdbc->hdbc, h->hstmt, (UCHAR *)h->Error->szSqlState, (long *)&(h->Error->ErrNum), (unsigned char *)h->Error->szError, ODBC_BUFF_SIZE, (short *)&cbErrorMsg);

		//	Next couple of lines should be NOT needed. If there is no error, then
		//	we should not have come here in the first place. If, however, there
		//	is state information that may be relevant (SQL_SUCCESS_WITH_INFO)
	if (!h->Error->ErrNum){
		h->Error->ErrNum = 911;
	}
	strcpy(h->Error->szFunction, szFunction);
	strcpy(h->Error->szFunctionLevel, szFunctionLevel);

#ifdef _Debug
	if (giDebug){
		DebugDumpError(h);
	}
#endif

}

	/*
		Process an ODBC structures error state from Win32::ODBC.
		This is called when an error is encountered NOT from the ODBC API
		but from this body of code.
	*/

ODBC_TYPE *ODBCError(char *szString, ODBC_TYPE *h, char *szFunction, char * szFunctionLevel){
	if (!h){
		h = ODBCLIST;
	}
	if (h){
		h->Error->ErrNum = 911;
		strcpy(h->Error->szError, szString);
		strcpy(h->Error->szFunction, szFunction);
		strcpy(h->Error->szFunctionLevel, szFunctionLevel);
	}

#ifdef _Debug
	if (giDebug){
		DebugDumpError(h);
	}
#endif
	return h;
}

#ifdef _DEBUG
	void DebugDumpError(ODBC_TYPE *h){
		char *szBuff;
		int	iLength;

		if (h && h->iDebug && ghDebug){
			iLength = ((h->Error->szError)? strlen(h->Error->szError):0) + ((h->Error->szSqlState)? strlen((const char *)h->Error->szSqlState):0)
				  + ((h->Error->szFunction)? strlen(h->Error->szFunction):0) + ((h->Error->szFunctionLevel)? strlen(h->Error->szFunctionLevel):0)
				  + 100;
			if(szBuff = new char [iLength]){
				sprintf(szBuff, "ODBCError from connection %i:\n\tErrno: %i\n\tError:\"%s\"\n\tSQLState: %s\n\tFunction: %s\n\tLevel: %s\n",
							 h->conn, h->Error->ErrNum, h->Error->szError, h->Error->szSqlState, h->Error->szFunction, h->Error->szFunctionLevel);
				DebugConnection(szBuff, h);
			}
			delete [] szBuff;
		}
	}

	void DebugConnection(char *szString, ODBC_TYPE *h){
		DWORD	dCount;
		CMom	*cDaughter = 0;
		char	*szBuff;
		int		iLength;
	
		if (ghDebug){
			if (::cMom){
				cDaughter = (CMom *) ::cMom->operator[](GetCurrentThreadId());
			}
			iLength = 100;
			if (szBuff = new char [iLength]){
				sprintf(szBuff, "Thread %05i, Total Threads %03i, Connection %03i, Thread Connections %03i:\n\t", GetCurrentThreadId(), giThread, (h)? h->conn:-1, (cDaughter)? cDaughter->Total():0);

		DEBUG_DUMP("DebugConnection: Entering Critical Section gDCS");

				EnterCriticalSection(&gDCS);
				DebugPrint(szBuff);
				DebugPrint(szString);
				DebugPrint("\n\n");
				LeaveCriticalSection(&gDCS);

		DEBUG_DUMP("DebugConnection: Left Critical Section gDCS    ");

				delete [] szBuff;
			}
		}
		return;
	}

	void DebugDump(char *szString){
		if (giDebug){

		DEBUG_DUMP("DebugDump: Entering Critical Section gDCS");

			EnterCriticalSection(&gDCS);
			DebugPrint(szString);
			LeaveCriticalSection(&gDCS);

		DEBUG_DUMP("DebugDump: Left Critical Section gDCS    ");

		}
		return;
	}			

	void DebugPrint(char *szString){
		DWORD	dCount;
		if (ghDebug){
			WriteConsole(ghDebug, szString, strlen(szString), &dCount, 0);
		}
		if (!ghFile && gszFile){
			ghFile = CreateFile(gszFile, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS,  0, 0);
			DebugPrint("\n\t*******\n\tDumping debug text to: \"");
			DebugPrint(gszFile);
			DebugPrint("\"\n\t*******\n");
		}
		if(ghFile){
			WriteFile(ghFile, szString, strlen(szString), &dCount, 0);
			FlushFileBuffers(ghFile);
		}
		return;
	}
#endif	
	/*
		Map a column name (field name) to a column number that exists in a
		resulting dataset.
	*/

int	ColNameToNum(ODBC_TYPE *h, char *szName){
	int	iResult = 0;
	int	x;
	char	szBuff[ODBC_BUFF_SIZE];
	DWORD	dBuffLen = 0;

	for(x=1; x<=h->numcols; x++){
		SQLColAttributes(h->hstmt, x, SQL_COLUMN_NAME, szBuff, ODBC_BUFF_SIZE, (short *)&dBuffLen, NULL);
		if(!stricmp(szName, szBuff)){
			iResult = x;
			break;
		}
	}
	return iResult;
}


	/*
		Check if the specified connection is valid and return the pointer
		to the ODBC structure.
		Return a pointer to the master list (ODBCLIST) and genereate an error if
		there is no valid connection.
	*/
ODBC_TYPE * _NT_ODBC_Verify(int iODBC){
	CMom	*cmDaughter = 0;
	ODBC_TYPE *h = 0;
	
	if(::cMom){
//		EnterCriticalSection(&gCS);
		if (cmDaughter = (CMom *) ::cMom->operator[](GetCurrentThreadId())){
			if (!(h = (ODBC_TYPE *) cmDaughter->operator[]((DWORD)iODBC))){
				h = (ODBC_TYPE *) cmDaughter->operator[]((DWORD)0);
			}
		}
//		LeaveCriticalSection(&gCS);
	}
	return (h);
}

	/*
		Delete an ODBC structure. This will call other routines to release
		allocated memory and reset ODBC state information.
	*/
int DeleteConn(int iODBC){
	ODBC_TYPE	*h = 0;
	int	iResult = 0;

	if(::cMom){
		CMom	*cmDaughter = 0;
		if (cmDaughter = (CMom *) ::cMom->operator[](GetCurrentThreadId())){
			if (h = (ODBC_TYPE *) cmDaughter->operator[]((DWORD)iODBC)){
				if (FreeODBC(h)){
					cmDaughter->Remove((DWORD)iODBC);
					delete h;
					iResult++;
				}
			}
		}
	}
	return iResult;			
}




	/*
		Map an Stmt close type string to the actual Stmt close type value.
	*/

char *MapCloseType(UWORD uCloseType){
	char *szType;

	switch(uCloseType){
		case SQL_DONT_CLOSE:
			szType = "SQL_DONT_CLOSE";
			break;

		case SQL_DROP:
			szType = "SQL_DROP";
			break;

		case SQL_CLOSE:
			szType = "SQL_CLOSE";
			break;

		case SQL_UNBIND:
			szType = "SQL_UNBIND";
			break;

		case SQL_RESET_PARAMS:
			szType = "SQL_RESET_PARAMS";

		default:
			szType = 0;
	}
	return szType;
}



/*------------------- P E R L   O D B C   F U N C T I O N S ---------------*/

		/*
			ODBC_Connect
			Connects to and ODBC Data Source Name (DSN).
		*/
XS(XS_WIN32__ODBC_Connect) // ODBC_Connect(Connection string: input) returns connection #
{
	dXSARGS;

	char      szDSN[DSN_LENGTH]; // string to hold datasource name
	ODBC_TYPE * h;
	int         con_num; //connection #
	char		*szIn = 0;
	int			iTemp = 0;

	RETCODE retcode;           // Misc ODBC sh!t
	UCHAR   buff[ODBC_BUFF_SIZE];
	SDWORD  bufflenout;
	int     lenn = 0;
	STRLEN  n_a;

	if(items < 1 || !(items & 1)){
			//	We need at least 1 (DSN) entry. If more then we need them in pairs of
			//	two, hence (items & 1) make sure we have an odd number of entries...
			//	(dsn) + (ConnetOption, Value) [ + (ConnectOption, Value)] ...
		CROAK("usage: ($Connection, $Err, $ErrText) = ODBC_Connect($DSN [, $ConnectOption , $Value] ...)\n");
	}
	szIn = SvPV(ST(0), n_a); 

	if(strcspn(szIn, "[]{}(),;?*=!@") < strlen(szIn)){
		strncpy(szDSN, szIn, DSN_LENGTH - 1);
		szDSN[DSN_LENGTH - 1] = '\0';
	}else{
			//	Let's assume that the DSN will not exceed DSN_LENGTH
		strcpy(szDSN, "DSN=");	
		strcat(szDSN, szIn); // Data Source string
		strcat(szDSN, ";");
	}

	PUSHMARK(sp);

 		 	//	Allocate new ODBC connection
	if (!(h = NewODBC())){
		h = ODBCError("Could not allocate memory of an ODBC connection\n", (ODBC_TYPE*)0, "ODBC_Connect", "1");
	}else{
		strcpy(h->szUserDSN, szIn);
		if (h->hdbc = new ODBC_HDBC){
			h->hdbc->hdbc = SQL_NULL_HDBC;
			h->hdbc->iConnected = 0;
			h->hdbc->iCount = 1;	//	Set the count to include this particular instance
		}else{
			_NT_ODBC_Error(h, "ODBC_Connect could not allocate memory for an HDBC connection", "1a");
			DeleteConn(h->conn);
		}
	
	 	if (!h->Error->ErrNum){
			retcode = SQLAllocConnect(h->henv, &h->hdbc->hdbc);
			if (retcode != SQL_SUCCESS)
			{
				_NT_ODBC_Error((ODBC_TYPE*)0, "ODBC_Connect", "2");
				DeleteConn(h->conn);
			}
		}
		if (!h->Error->ErrNum){
				//	If any pre-connect SQLConnectOptions are specified, do them now...
			if (items > 1){
				int	iTemp = items -1;
				UWORD   uType;
    			UDWORD  udValue;
				char	szError[100];

				while (iTemp > 1){
					uType= SvIV(ST(iTemp - 1));
    				if (SvIOKp(ST(iTemp)) || SvNOKp(ST(iTemp))){
    					udValue = SvIV(ST(iTemp));
					}else{
						udValue = (UDWORD) SvPV(ST(iTemp), n_a);
					}
			        retcode = SQLSetConnectOption(h->hdbc->hdbc, uType, udValue);
			        if (retcode != SQL_SUCCESS){
						sprintf(szError, "2a: Connect Item number %i", (iTemp/2));
			 			_NT_ODBC_Error(h, "ODBC_Connect", szError);
						break;
			        }
					iTemp -= 2;
				}
			}
		}
		if (!h->Error->ErrNum){
			retcode = SQLDriverConnect(h->hdbc->hdbc, (HWND) NULL, (unsigned char *)szDSN, strlen(szDSN), buff, ODBC_BUFF_SIZE, (short *)&bufflenout, SQL_DRIVER_NOPROMPT);
			if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO){
				_NT_ODBC_Error(h, "ODBC_Connect", "4");
				strcpy(ODBCLIST->Error->szError, h->Error->szError);
				strcpy((char *)ODBCLIST->Error->szSqlState, (char *)h->Error->szSqlState);
				strcpy(ODBCLIST->Error->szFunction, h->Error->szFunction);
				strcpy(ODBCLIST->Error->szFunctionLevel, h->Error->szFunctionLevel);
					//	If connection fails it does not genereate an error num. Hmmm...
				if(! h->Error->ErrNum){
					h->Error->ErrNum = 911;
				}
				ODBCLIST->Error->ErrNum = h->Error->ErrNum;
				DeleteConn(h->conn);
				h = ODBCLIST;
			}else{
				h->hdbc->iConnected = 1;
				if (h->szDSN = new char [strlen((const char *)buff) + 1]){
					strcpy(h->szDSN, (char *) buff);
				}

				if (retcode == SQL_SUCCESS_WITH_INFO){
					_NT_ODBC_Error(h, "ODBC_Connect", "5");
					h->Error->ErrNum = 0;
				}
			}
		}
		if (!h->Error->ErrNum){
			retcode = ResetStmt(h);
			if (retcode != SQL_SUCCESS){
				DeleteConn(h->conn);
			}
		}
	}
	if (!h->Error->ErrNum){ // everything is happy
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		XPUSHs(sv_2mortal(newSVnv((double)h->conn)));
			//	Report the szError ONLY because it may contain state info.
		XPUSHs(sv_2mortal(newSVpv(h->Error->szError, strlen(h->Error->szError))));
	}else{
			//	Report the error
		ReturnError(h);
	}
	PUTBACK;
} 


XS(XS_WIN32__ODBC_Execute) // ODBC_Execute($connection, $sql_text) returns (0,@fieldnames) or (err, errtext)
{
	dXSARGS;
	ODBC_TYPE * h;
	int        con_num; // Connection #
	RETCODE retcode;          //ODBC gunk
	UCHAR  buff2[ODBC_BUFF_SIZE];
	SDWORD bufflenout;
	int lenn;
	UWORD  x;
	char * szSQL; 
	int len;
	STRLEN n_a;

	if(items < 2){
		CROAK("usage: ($err,@fields) = ODBC_Execute($connection, $sql_text)\nprint \"Oops: $field[0]\" if ($err);\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
	
	PUSHMARK(sp);

	if (h != ODBCLIST){
		szSQL = SvPV(ST(1), n_a);            // get SQL string
			//	Do we really want to resetsmtm now? 
		if (ResetStmt(h) == SQL_SUCCESS){
			if (h->szCommand){
				delete [] h->szCommand;
				h->szCommand = 0;
			}
			if (h->szCommand = new char [strlen(szSQL) + 1]){
				strcpy(h->szCommand, szSQL);
			} 
		}
	
	
		if (!h->Error->ErrNum){
			retcode = SQLExecDirect(h->hstmt, (unsigned char*)szSQL, strlen(szSQL));
			if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO){
				_NT_ODBC_Error(h, "ODBC_Execute", "1");
			}else{
				if(h->Results){
					delete h->Results;
				}
				h->Results = new CResults(h);
			}
		}
	}
	if (!h->Error->ErrNum){ // everything is happy
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		retcode = SQLNumResultCols(h->hstmt, (short *)&h->numcols);
		for(x=1; x<=h->numcols; x++){
			SQLColAttributes(h->hstmt, x, SQL_COLUMN_NAME, buff2, ODBC_BUFF_SIZE, (short *)&bufflenout, NULL);
			XPUSHs(sv_2mortal(newSVpv((char *)buff2, strlen((const char*)buff2))));
		}
	}else{										
			//	Report the error
		ReturnError(h);
	}
	PUTBACK;
}

	/*
		ODBC_Fetch
		Fetch a row from the current dataset.
	*/
XS(XS_WIN32__ODBC_Fetch) // ODBC_Fetch($connection) returns (0,@dataelements) or ($err,$errtext)
{
	dXSARGS;
	ODBC_TYPE * h;
	int        con_num;

	RETCODE retcode;         // yet more ODBC garbage
	UWORD	uType = SQL_FETCH_NEXT;
	SDWORD	sdRow = 1;
	DWORD	dRowSetSize = 1;

	UWORD	*rgfRowStatus = 0;
	UDWORD	udCRow = 0;
	int		iTemp;	

	if(items < 1 || items > 3){
		CROAK("usage: ($err,@col) = ODBC_Fetch($connection [, $Row [, $FetchType]])\n0die \"Oops: $col[0]\" if ($err);\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
		//	NOTICE: We do not perform a CleanODBC(h) here because a dirty
		//	h will tell us if there is an error such as EOR (end of records).
 	if (items > 1){
		sdRow = SvIV(ST(1));
//		uType = SQL_FETCH_RELATIVE;
	}
	if (items > 2){
		uType = SvIV(ST(2));
	}
					 
	PUSHMARK(sp);

	if (h != ODBCLIST){
		SQLGetStmtOption(h->hstmt, SQL_ROWSET_SIZE, &dRowSetSize);
//	SQLSetStmtOption(h->hstmt, SQL_ROWSET_SIZE, 1);
		if (!(rgfRowStatus = new UWORD [dRowSetSize])){
			ODBCError("Can not allocate memory for row status results", h, "ODBC_Fetch", "1b");
		}
		if (h->Results){
				//	Clear out the old results
			h->Results->Clean();
		}
		retcode = SQLExtendedFetch(h->hstmt, uType, sdRow, &udCRow, rgfRowStatus);
//	SQLSetStmtOption(h->hstmt, SQL_ROWSET_SIZE, dRowSetSize);

		if((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)){
			_NT_ODBC_Error(h, "ODBC_Fetch", "2");
		}
		if (retcode == SQL_NO_DATA_FOUND){
			ODBCError("No data records remain.", h, "ODBC_Fetch", "3");
			h->Error->EOR = 1;
		}
		if(retcode == SQL_SUCCESS_WITH_INFO){
			_NT_ODBC_Error(h, "ODBC_Fetch", "4");			
		}
	}
	if (! h->Error->ErrNum){
			 // everything is happy
		XPUSHs(sv_2mortal(newSVnv((double)0)));	
		if(items > 1){
			for(iTemp = 0; iTemp < udCRow; iTemp++){
				XPUSHs(sv_2mortal(newSVnv((double)rgfRowStatus[iTemp])));	
			}
		}else{
			XPUSHs(sv_2mortal(newSVnv((double)1)));	
		}
	}else{
			//	Report the error
		ReturnError(h);
	}

	if (rgfRowStatus){
		delete [] rgfRowStatus;
	}
	PUTBACK;
}

 
XS(XS_WIN32__ODBC_GetError)
{
	dXSARGS;
	ODBC_TYPE *h;

	h = _NT_ODBC_Verify(SvIV(ST(0)));

	PUSHMARK(sp);
	XPUSHs(sv_2mortal(newSVnv((double)h->Error->ErrNum)));
	XPUSHs(sv_2mortal(newSVpv(h->Error->szError, strlen(h->Error->szError))));
	XPUSHs(sv_2mortal(newSVpv((char *)h->Error->szSqlState, strlen(( const char *) h->Error->szSqlState))));
	if (h->iDebug){
		XPUSHs(sv_2mortal(newSVpv((char *)h->Error->szFunction, strlen(( const char *) h->Error->szFunction))));
		XPUSHs(sv_2mortal(newSVpv((char *)h->Error->szFunctionLevel, strlen(( const char *) h->Error->szFunctionLevel))));
	}
		
	PUTBACK;
}
		  

XS(XS_WIN32__ODBC_Disconnect) // usage: ODBC_Disconnect($conn) or ODBC_Disconnect() for all
{
	dXSARGS;
	ODBC_TYPE *h;
	int		iResult = 0;
	int		conn;
	
	if(items < 1){
		conn = 0;
	}else{
		conn = SvIV(ST(0));
	}
	PUSHMARK(sp);
	if (!(iResult = DeleteConn(conn))){
		h = ODBCError("No such connection", (ODBC_TYPE*) 0, "ODBC_Disconnect", "1");
	
	#ifdef _Debug
		if (h){
			if(h->iDebug){
				char szBuff[40];
				sprintf(szBuff, "ODBC_Disconnect: No such connection %i\n", conn);
				DebugConnection(szBuff, h);
			}
		}
	#endif

	}
	if (iResult){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		XPUSHs(sv_2mortal(newSVnv((double)iResult)));
	}else{
				//	Report the error
		ReturnError(h);
	}
		PUTBACK;
}

XS(XS_WIN32__ODBC_TableList) // ODBC_TableList($connection) returns (0,@TableNames) or ($err,$errtext)
{
	dXSARGS;
	RETCODE retcode;

	if(items != 5){
		CROAK("usage: ($Err,@ColumnNames) = ODBC_TableList($connection, $Qualifier, $Owner, $TableName, $TableType)\n");
	}

	retcode = TableColList(0);
}

XS(XS_WIN32__ODBC_ColumnList)
{
	dXSARGS;	
	RETCODE retcode;

	if(items != 5){
		CROAK("usage: ($Err,@ColumnNames) = ODBC_ColumnList($connection, $Qualifier, $Owner, $TableName, $ColumnName)\n");
	}

	retcode = TableColList(1);
}

RETCODE TableColList(int iType){
	dXSARGS;
	ODBC_TYPE * h;
	int        con_num, iTemp;
	UCHAR  buff2[ODBC_BUFF_SIZE];
	SDWORD bufflenout;
	UWORD  x;
	STRLEN n_a;

	RETCODE retcode;
	UCHAR	*szQualifier, *szOwner, *szName, *szType;
	SWORD	sQLen, sOLen, sNLen, sTLen = 0;
	UWORD	uTemp = 0;

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
	
	if (szQualifier = (unsigned char *) SvPV(ST(1), n_a)){
		if (!(sQLen = strlen((char *)szQualifier))){
			szQualifier = 0;
		}
	}

	if (szOwner = (unsigned char *) SvPV(ST(2), n_a)){
		if (!(sOLen = strlen((char *)szOwner))){
			szOwner = 0;
		}
	}

	if (szName = (unsigned char *) SvPV(ST(3), n_a)){
		if (!(sNLen = strlen((char *)szName))){
			szName = 0;
		}
	}

	if (szType = (unsigned char *) SvPV(ST(4), n_a)){
		if (!(sTLen = strlen((char *)szType))){
			szType = 0;
		}
	}
	
	PUSHMARK(sp);

	if (h != ODBCLIST){
		CleanError(h->Error);
		retcode = ResetStmt(h);
		if (h->szCommand){
			delete [] h->szCommand;
			h->szCommand = 0;
		}
				/*	The funny odd thing about this next line is that when we allocate
					memory of h->szCommand it will be based on a sprintf(). The format
					string for sprintf() will have references to szQualifier, szOwner, etc.
					IF any of those strings are 0 then the %s's in the format string may be
					replaced with "(null)" or some other indicator of a null pointer. Therefore
					in the event of szQualifier (or whatever) == 0 we will provide an arbitrary
					number of bytes for allocation (like 10).
				*/
							
		if (h->szCommand = new char [strlen((const char *) TABLE_COMMAND_STRING)
							+ 12	//	for the name of the function
							+ (szQualifier ? strlen((const char *)szQualifier):10)
							+ (szOwner ? strlen((const char *)szOwner):10)
							+ (szName ? strlen((const char *)szName):10)
							+ (szType ? strlen((const char *)szType):10) + 1]){
			sprintf(h->szCommand, TABLE_COMMAND_STRING, (iType)? "SQLColumns":"SQLTables", szQualifier, szOwner, szName, szType);
		}
		
		if(iType){
			retcode = SQLColumns(h->hstmt, szQualifier, sQLen, szOwner, sOLen, szName, sNLen, szType, sTLen);
		}else{
			retcode = SQLTables(h->hstmt, szQualifier, sQLen, szOwner, sOLen, szName, sNLen, szType, sTLen);
		}
		if(retcode != SQL_SUCCESS){
			_NT_ODBC_Error(h, "ODBC_TableColList", "1");
		}
	}
	if (!h->Error->ErrNum){
		if (h->Results){
			delete h->Results;
		}
		h->Results = new CResults (h);
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		retcode = SQLNumResultCols(h->hstmt, (short *)&h->numcols);
		for(x=1; x<=h->numcols; x++){
			*buff2 = '\0';
			SQLColAttributes(h->hstmt, x, SQL_COLUMN_NAME, buff2, ODBC_BUFF_SIZE, (short *)&bufflenout, NULL);
		   		/*	
					We need to convert the columns to uppercase since different
					ODBC drivers impliment this differently (Access uses upperc, MS SQL server uses lowerc)
					We should probably figure out a more reasonable solution.
				*/
			for(iTemp = strlen((char *)buff2) - 1; iTemp >= 0; iTemp--){
				buff2[iTemp] = (char) toupper(buff2[iTemp]);
			}
			XPUSHs(sv_2mortal(newSVpv((char *)buff2, strlen((const char*)buff2))));
		}
	}else{
		//	Report the error
		ReturnError(h);
	}
	PUTBACK;
	return retcode;
}

/*
	This next chunk of code (XS_WIN32__ODBC_MoreResults) was 
	graciously donated by Brian Dunfordshore <Brian_Dunfordshore@bridge.com>.
	Thanks Brian!
	96.07.10
*/
XS(XS_WIN32__ODBC_MoreResults) 	// usage: ODBC_MoreResults($conn)
{
	dXSARGS;
 	RETCODE retcode;         // yet more ODBC garbage
 	ODBC_TYPE *h;
 
 	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
 	PUSHMARK(sp);
 	if (h != ODBCLIST){
 		retcode = SQLMoreResults(h->hstmt);
 		if((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)){
 			_NT_ODBC_Error(h, "ODBC_MoreResults", "1");
 		}
 		if (retcode == SQL_NO_DATA_FOUND){
 			h->Error->EOR = 1;
 			strcpy(h->Error->szError, "No data records remain.");
 		}
 	}
 	if (!h->Error->ErrNum){		 // everything is happy
 		XPUSHs(sv_2mortal(newSVnv((double)0)));
 		XPUSHs(sv_2mortal(newSVnv((double)1)));	  //	Return a TRUE
 	}else{
			//	Report the error
		ReturnError(h);
 	}
	PUTBACK;
}

XS(XS_WIN32__ODBC_MaxBufSize) 
{
	dXSARGS;
	ODBC_TYPE * h;
	long	iSize;		

	if(items <1 || items > 2){
		CROAK("usage: ($Err, $Size) = ODBC_MaxBufSize($Connection [, $NewSize])\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
	PUSHMARK(sp);

	if (h != ODBCLIST){
		if (items > 1){
			iSize = SvIV(ST(1));
			iSize = ((iSize <= 0)? 0:iSize);
			iSize = ((iSize >= MAX_DATA_BUF_SIZE)? MAX_DATA_BUF_SIZE:iSize);
			h->iMaxBufSize = iSize;
		}
	
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		XPUSHs(sv_2mortal(newSVnv((double)h->iMaxBufSize)));
	}else{										
			//	Report the error
		ReturnError(h);
	}
	PUTBACK;

}

XS(XS_WIN32__ODBC_GetConnections) 
{
	dXSARGS;
	int	iTemp;
	ODBC_TYPE	*h;
	CMom	*cmDaughter = (CMom *) ::cMom->operator[](GetCurrentThreadId());

	if(items){
		CROAK("usage: (@ConnectionList) = ODBC_GetConnections()\n");
	}
	PUSHMARK(sp);

	for (iTemp = cmDaughter->TotalElements(); iTemp; iTemp--){
		h = (ODBC_TYPE *) cmDaughter->operator[]((DWORD)iTemp - 1);
		if (h){
			XPUSHs(sv_2mortal(newSVnv((double)h->conn)));
		}
	}
	PUTBACK;
}

XS(XS_WIN32__ODBC_GetDSN) 
{
	dXSARGS;
	ODBC_TYPE *h;
	char	*szDSN = 0;
	char	*szTemp = 0;
	DWORD	dSize = 1024;
	char	*szKeys= 0;
	char    *szValues = 0;
	char	*szPointer = 0;
	STRLEN  n_a;

	if(items > 2 || items < 1){
		CROAK("usage: ($Err, $DSN) = ODBC_GetDSN($Connection [, $DSN])\n");
	}
	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);

	if (items > 1){
		szDSN = SvPV(ST(1), n_a);
		if (! strlen(szDSN)){
			szDSN = 0;
		}
	}
	if (! szDSN){
		szDSN = h->szDSN;
		while(szDSN){
			if (szDSN[0] == ';'){
				szDSN++;
			}
			if (! strnicmp("DSN=", szDSN, 4)){
				szDSN += 4 * sizeof(char);
				break;
			}
			szDSN = strchr(szDSN, ';');
		}
	}

	PUSHMARK(sp);

	szTemp = szDSN;
	if (! (szDSN = new char [strlen(szTemp) + 1])){
		szDSN = 0;
		ODBCError("Could not allocate memory for DSN comparison", h, "ODBC_GetDSN", "1");
	}else{

		strcpy(szDSN, szTemp);
		szTemp = strchr(szDSN, ';');
		if (szTemp){
			*szTemp = '\0';
		}
		if (szKeys = new char [dSize + 1]){	
			SQLGetPrivateProfileString(	szDSN, 0, "", szKeys, dSize, "ODBC.INI");
			if (strcmp(szKeys, "")){
				szPointer = szKeys;
				if (szValues = new char [dSize + 1]){	
					XPUSHs(sv_2mortal(newSVnv((double)0)));
					while(*szPointer){
						SQLGetPrivateProfileString(	szDSN, szPointer, "", szValues, dSize, "ODBC.INI");
						XPUSHs(sv_2mortal(newSVpv(szPointer, strlen(szPointer))));
						XPUSHs(sv_2mortal(newSVpv(szValues, strlen(szValues))));
						szPointer += strlen(szPointer) + 1;
					}
					if(szValues) delete [] szValues;
				}else{
					ODBCError("Could not allocate enough memory", h, "ODBC_GetDSN", "2");
				}
			}else{
				ODBCError("Not a valid DSN", h, "ODBC_GetDSN", "3");
			}
			if(szKeys) delete [] szKeys;
		}else{
			ODBCError("Could not allocate enough memory", h, "ODBC_GetDSN", "4");
		}
	}

	if (h->Error->ErrNum){
			//	Report the error
		ReturnError(h);
	}
	if (szDSN){
		delete [] szDSN;
	}
	
	PUTBACK;
}

XS(XS_WIN32__ODBC_DataSources) 
{
	dXSARGS;
	ODBC_TYPE *h;
	UCHAR	szDSN[SQL_MAX_DSN_LENGTH + 1];
	SWORD	pcbDSN;
	UCHAR	szDesc[DS_DESCRIPTION_LENGTH];
	SWORD	pcbDesc;	
	char	*szRequestedDSN = 0;
	RETCODE	retcode;
	STRLEN  n_a;

	if(items > 1){
		CROAK("usage: ($Err, $DSN) = ODBC_DataSources([$DSN])\n");
	}
	
	h = ODBCLIST;

	if (items){
		szRequestedDSN = SvPV(ST(0), n_a);
		if (!strlen(szRequestedDSN)){
			szRequestedDSN = 0;
		}
	}

	PUSHMARK(sp);

	*szDSN = *szDesc = '\0';
	retcode= SQLDataSources(h->henv, SQL_FETCH_FIRST, szDSN, SQL_MAX_DSN_LENGTH + 1, &pcbDSN, szDesc, DS_DESCRIPTION_LENGTH, &pcbDesc);
	if(retcode == SQL_SUCCESS){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		
		while (retcode == SQL_SUCCESS){
			if ((szRequestedDSN && (!stricmp((const char *)szDSN, szRequestedDSN))) || !szRequestedDSN){
				XPUSHs(sv_2mortal(newSVpv((char *)szDSN, strlen((char*)szDSN))));
				XPUSHs(sv_2mortal(newSVpv((char *)szDesc, strlen((char*)szDesc))));
			}
			*szDSN = *szDesc = '\0';
			retcode= SQLDataSources(h->henv, SQL_FETCH_NEXT, szDSN, SQL_MAX_DSN_LENGTH + 1, &pcbDSN, szDesc, DS_DESCRIPTION_LENGTH, &pcbDesc);
		}

	}else{
		h = ODBCError("No such ODBC connection.", (ODBC_TYPE*) 0, "ODBC_DataSources", "1");
			//	Report the error
		ReturnError(h);
	}
	PUTBACK;
}

XS(XS_WIN32__ODBC_Drivers) 
{
	dXSARGS;
	ODBC_TYPE *h;
	UCHAR	szAttr[DS_DESCRIPTION_LENGTH + 1];
	SWORD	cbAttr;
	UCHAR	szDesc[DS_DESCRIPTION_LENGTH];
	SWORD	cbDesc;	
	char	*szTemp;
	RETCODE	retcode;

	if(items > 0){
		CROAK("usage: ($Err, $DSN) = ODBC_Drivers()\n");
	}
	
	h = ODBCLIST;
	PUSHMARK(sp);
	
	*szDesc = *szAttr = '\0';
	retcode = SQLDrivers(h->henv, SQL_FETCH_FIRST, szDesc, DS_DESCRIPTION_LENGTH, &cbDesc, szAttr, DS_DESCRIPTION_LENGTH, &cbAttr);
	if(retcode == SQL_SUCCESS){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		
		while (retcode == SQL_SUCCESS){
			szTemp = (char *) szAttr;
			while(szTemp[0] != '\0'){
				szTemp = strchr(szTemp, '\0');
				*szTemp++ = ';';
			}
			XPUSHs(sv_2mortal(newSVpv((char *)szDesc, strlen((char*)szDesc))));
			XPUSHs(sv_2mortal(newSVpv((char *)szAttr, strlen((char*)szAttr))));
			*szDesc = *szAttr = '\0';
			retcode = SQLDrivers(h->henv, SQL_FETCH_NEXT, szDesc, DS_DESCRIPTION_LENGTH, &cbDesc, szAttr, DS_DESCRIPTION_LENGTH, &cbAttr);
		}
	}else{
		h = ODBCError("No such ODBC connection.", (ODBC_TYPE*) 0, "ODBC_Drivers", "1");
			//	Report the error
		ReturnError(h);
	}
	PUTBACK;
}

XS(XS_WIN32__ODBC_RowCount) 
{
	dXSARGS;
	ODBC_TYPE *h;
	SDWORD	sdRows = 0L;	
	RETCODE	retcode;

	if(items != 1){
		CROAK("usage: ($Err, $NumOfRows) = ODBC_RowCount($Connection)\n");
	}
	
	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
	PUSHMARK(sp);
	
	retcode = SQLRowCount(h->hstmt, &sdRows);
	if(retcode == SQL_SUCCESS){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		XPUSHs(sv_2mortal(newSVnv((double)sdRows)));
	}else{
		h = ODBCError("No such ODBC connection.", (ODBC_TYPE*) 0, "ODBC_RowCount", "1");
			//	Report the error
		ReturnError(h);
	}
	PUTBACK;
}


XS(XS_WIN32__ODBC_Info) 
{
	dXSARGS;

	if(items > 0){
		CROAK("usage: ($ExtName, $Version, $Date, $Author, $CompileDate, $Credits, $Comments) = Info()\n");
	}
	
	PUSHMARK(sp);
	
	XPUSHs(sv_2mortal(newSVpv(VERNAME, strlen(VERNAME))));
	XPUSHs(sv_2mortal(newSVpv(VERSION, strlen(VERSION))));
	XPUSHs(sv_2mortal(newSVpv(VERDATE, strlen(VERDATE))));
	XPUSHs(sv_2mortal(newSVpv(VERAUTH, strlen(VERAUTH))));
	XPUSHs(sv_2mortal(newSVpv(VERDATE, strlen(VERDATE))));
	XPUSHs(sv_2mortal(newSVpv(VERTIME, strlen(VERTIME))));
	XPUSHs(sv_2mortal(newSVpv(VERCRED, strlen(VERCRED))));
	XPUSHs(sv_2mortal(newSVpv(VERCOMMENT, strlen(VERCOMMENT))));

	PUTBACK;
}

XS(XS_WIN32__ODBC_GetStmtCloseType) 
{
	dXSARGS;
	ODBC_TYPE * h;
	long	iSize;
	char	*szType;

	if(items != 1){
		CROAK("usage: ($Err, $Type) = ODBC_GetStmtCloseType($Connection)\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
	PUSHMARK(sp);
	
	if (!(szType = MapCloseType(h->uStmtCloseType))){
		ODBCError("Invalid Statment Close Type", h, "ODBC_GetStmtCloseType", "1");
	}			
	if (!h->Error->ErrNum){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		XPUSHs(sv_2mortal(newSVpv(szType, strlen(szType))));
	}else{
			//	Report the error
		ReturnError(h);
	}
	PUTBACK;
}

XS(XS_WIN32__ODBC_SetStmtCloseType) 
{
	dXSARGS;
	ODBC_TYPE * h;
	char	*szType;
	UWORD	uType;		
	
	if(items != 2){
		CROAK("usage: ($Err, $Type) = ODBC_SetStmtCloseType($Connection, $Type)\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
	uType = SvIV(ST(1));
	PUSHMARK(sp);

	switch(uType){
		case SQL_DONT_CLOSE:
		case SQL_DROP:
		case SQL_CLOSE:
		case SQL_UNBIND:
		case SQL_RESET_PARAMS:
			h->uStmtCloseType = uType;
			if (!(szType = MapCloseType(h->uStmtCloseType))){
				ODBCError("Invalid Statment Close Type", h, "ODBC_SetStmtCloseType", "1");
			}
			break;

		default:
			ODBCError("Not a valid Stmt Close Type", h, "ODBC_SetStmtCloseType", "2");
	}					
	if (!h->Error->ErrNum){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		XPUSHs(sv_2mortal(newSVpv(szType, strlen(szType))));
	}else{
			//	Report the error
		ReturnError(h);
	}


	PUTBACK;
}


XS(XS_WIN32__ODBC_SetConnectOption)
{
	dXSARGS;
	ODBC_TYPE * h;
    UWORD   uType;
    UDWORD  udValue;
    RETCODE rResult = 0;
    STRLEN  n_a;

    if(items != 3){
        CROAK("usage: ($Err, $Type) = ODBC_SetConnectOption($Connection, $Type, $Value)\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
    uType= SvIV(ST(1));
    if (SvIOKp(ST(2)) || SvNOKp(ST(2))){
    	udValue = SvIV(ST(2));
	}else{
		udValue = (UDWORD) SvPV(ST(2), n_a);
	}
	PUSHMARK(sp);

    if(!h->Error->ErrNum){
        rResult = SQLSetConnectOption(h->hdbc->hdbc, uType, udValue);
        if (rResult != SQL_SUCCESS){
 			_NT_ODBC_Error(h, "ODBC_SetConnectOption", "1");
        }
    }

	if (!h->Error->ErrNum){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		XPUSHs(sv_2mortal(newSVnv((double)1)));
	}else{
			//	Report the error
		ReturnError(h);
	}


	PUTBACK;
}

XS(XS_WIN32__ODBC_GetConnectOption)
{
	dXSARGS;
	ODBC_TYPE * h;
	UCHAR	ucValue[SQL_MAX_OPTION_STRING_LENGTH + 1];
	DWORD	*dValue = (DWORD *)ucValue;
    UWORD   uOption;
    RETCODE rResult = 0;
	
    if(items != 2){
        CROAK("usage: ($Err, $NumValue, $Value) = ODBC_GetConnectOption($Connection, $Type)\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
    uOption = SvIV(ST(1));
	PUSHMARK(sp);

    if(!h->Error->ErrNum){
		memset(ucValue, 255, SQL_MAX_OPTION_STRING_LENGTH + 1);
        rResult = SQLGetConnectOption(h->hdbc->hdbc, uOption, ucValue);
        if (rResult != SQL_SUCCESS){
 			_NT_ODBC_Error(h, "ODBC_GetConnectOption", "1");
        }
    }

	if (!h->Error->ErrNum){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		switch(uOption){
			case SQL_ACCESS_MODE:
			case SQL_AUTOCOMMIT:
			case SQL_LOGIN_TIMEOUT:
			case SQL_ODBC_CURSORS:		
			case SQL_OPT_TRACE:			
			case SQL_PACKET_SIZE: 			
			case SQL_QUIET_MODE:			
			case SQL_TRANSLATE_OPTION:
			case SQL_TXN_ISOLATION:		
					//	GetConnectOption returned a DWORD
				XPUSHs(sv_2mortal(newSVnv((double) *dValue)));
				break;

			default:
					//	GetConnectOption returned a string
				XPUSHs(sv_2mortal(newSVpv((char *)ucValue, strlen((char *) ucValue))));
				break;
		}
	}else{
			//	Report the error
		ReturnError(h);
	}

	
	PUTBACK;
}

XS(XS_WIN32__ODBC_StmtOption)
{
	dXSARGS;
	ODBC_TYPE * h;
	UCHAR	ucValue[SQL_MAX_OPTION_STRING_LENGTH + 1];
	DWORD	*dValue = (DWORD *)ucValue;
    UWORD   uOption;
	UDWORD	udValue;
	UCHAR	uType;
    RETCODE rResult = 0;
    STRLEN  n_a;

    if(items < 2 || items > 3){
        CROAK("usage: ($Err, $NumValue, $Value) = ODBC_StmtOption($Connection, $Type [,$Value])\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
    uOption = SvIV(ST(1));
	if (items > 2){
	    if (SvIOKp(ST(2)) || SvNOKp(ST(2))){
	    	udValue = SvIV(ST(2));
		}else{
			udValue = (UDWORD) SvPV(ST(2), n_a);
		}
	}

	PUSHMARK(sp);

    if(!h->Error->ErrNum){
		if(items < 3){
				//	Less than three parameters passed in so we are GETTING the stmt option
			memset(ucValue, 255, SQL_MAX_OPTION_STRING_LENGTH + 1);
	        rResult = SQLGetStmtOption(h->hstmt, uOption, &ucValue);
	        if (rResult != SQL_SUCCESS){
	 			_NT_ODBC_Error(h, "ODBC_StmtOption", "1");
	        }
	    }else{
				//	Three parameters passed in so we are SETTING the stmt option
	        rResult = SQLSetStmtOption(h->hstmt, uOption, udValue);
	        if (rResult != SQL_SUCCESS){
	 			_NT_ODBC_Error(h, "ODBC_StmtOption", "2");
	        }
	    }
	}
	if (!h->Error->ErrNum){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		if(items < 3){
			switch(uOption){
						/*	Even though this function is basically the same
						  	as the ODBC_GetConnectOption the default on the
							select() is a DWORD since so few (actually as of 
							now NONE) of the options return strings. So the 
							list of strings returns (case: xxx) will be small.
						*/
				case 0xf0f0:			//	Something toatly unreasonable since I can't find a real string return.
						//	GetStmtOption returned a string
					XPUSHs(sv_2mortal(newSVpv((char *)ucValue, strlen((char *) ucValue))));
					break;

				default:
						//	GetStmtOption returned a DWORD
					XPUSHs(sv_2mortal(newSVnv((double) *dValue)));
					break;
			}
		}else{
				XPUSHs(sv_2mortal(newSVnv((double)1)));
		}
	}else{
			//	Report the error
		ReturnError(h);
	}

	PUTBACK;
}


XS(XS_WIN32__ODBC_GetFunctions)
{
	dXSARGS;
	ODBC_TYPE * h;
	UWORD	uOutput[100];
    UWORD   uOption;
    RETCODE rResult = 0;
	int		iTemp, iTotal;
	
	iTemp = iTotal = 0;
    if(items < 1 || items > 101){
        CROAK("usage: ($Err, $NumValue, $Value) = ODBC_GetFunctions($Connection, ($Function1, $Function2 ... $Function100))\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
	PUSHMARK(sp);

	items--;

		//	We decremented items, so if it's < 1 assume we want ALL functions!
	if (items < 1){
		uOption = SQL_API_ALL_FUNCTIONS;
		iTotal = 100;
		items = 1;
	}else{
		uOption = SvIV(ST(1));
	}

	while(items--){
		rResult = SQLGetFunctions(h->hdbc->hdbc, uOption, &uOutput[iTemp]);
		if (rResult != SQL_SUCCESS){
			_NT_ODBC_Error(h, "ODBC_GetFunctions", "1");
			if(! strcmp((const char*) h->Error->szSqlState, "00000")){
					//	The requested function number does not exist.	
				CleanError(h->Error);
				uOutput[iTemp] = ~0;
			}else{
				items = 0;
			}
		}
		iTemp++;
		if (items){ 	//	If there are no more stack elements we will screw up
						//	trying to access ST(1 + iTemp)
			uOption = SvIV(ST(1 + iTemp));
		}
	}
	if (!h->Error->ErrNum){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		if (!iTotal){
			 iTotal = iTemp;
		}
		for (iTemp = 0; iTemp < iTotal; iTemp++){
			XPUSHs(sv_2mortal(newSVnv((double) uOutput[iTemp])));
		}
	}else{
			//	Report the error
		ReturnError(h);
	}

	
	PUTBACK;
}


XS(XS_WIN32__ODBC_Transact)
{
	dXSARGS;
	ODBC_TYPE * h;
    UWORD  uType;
    RETCODE rResult = 0;
	
    if(items != 2){
        CROAK("usage: ($Err, $Type) = ODBC_Transact($Connection, $Type)\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
    uType = SvIV(ST(1));
    PUSHMARK(sp);

	if(!h->Error->ErrNum){
		if(uType == SQL_ROLLBACK || uType == SQL_COMMIT){
			rResult = SQLTransact(h->henv, h->hdbc->hdbc, uType);
	        if (rResult != SQL_SUCCESS){
	 			_NT_ODBC_Error(h, "ODBC_Transact", "1");
	        }
		}else{
			ODBCError("Invalid Transaction Type", h, "ODBC_Transact", "2");
		}
    }

	if (!h->Error->ErrNum){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		XPUSHs(sv_2mortal(newSVnv((double)1)));
	}else{
			//	Report the error
		ReturnError(h);
	}

	PUTBACK;
}


XS(XS_WIN32__ODBC_ConfigDSN)
{
	dXSARGS;
	ODBC_TYPE * h;
    UWORD  uType;
	int		iStack = 0;
	char	*szDriver = 0;
	char	*szTemp = 0;
	char	*szTemp2 = 0;
	char	*szAttributes = 0;
    int		iResult = 0;
	int		iSize = 0;
	STRLEN  n_a;

    if(items < 3){
        CROAK("usage: ($Err, $Type) = ODBC_ConfigDSN($Connection, $Function, $Driver, @Attributes)\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(iStack)));
	CleanError(h->Error);
	iStack++;
    uType = SvIV(ST(iStack));
	iStack++;
	szDriver = SvPV(ST(iStack), n_a);
	if (strlen(szDriver) == 0){
		szDriver = 0;
	}
	iStack++;
	items -= iStack;		//	Reduce the # of items by 3 (compensate for connection, function and driver)

					//	Remember: when starting szAttributes = 0, not ""
	while (items--){
		szTemp = SvPV(ST(iStack), n_a);
		if(strcspn(szTemp, "[]{}(),?*!@;") < strlen(szTemp)){
			ODBCError("Illegal use of reserved characters []{}(),?*!@;", h, "ODBC_ConfigDSN", "1");
			break;
		}

		
		iStack++;
		iSize += strlen(szTemp) + 2;
		if (! (szTemp2 = new char [iSize])){
			ODBCError("Could not allocate memory for the attribute list", h, "ODBC_ConfigDSN", "2");
			items = 0;
		}else{
			if (szAttributes) {
				strcpy(szTemp2, szAttributes);
			}else{
				strcpy(szTemp2, "");
			}
			strcat(szTemp2, szTemp);
			strcat(szTemp2, ";");
			if (szAttributes){
				delete [] szAttributes;
			}
			szAttributes = szTemp2;
			szTemp2 = 0;
		}
	}

	PUSHMARK(sp);
		/*	
			As of now szAttributes contains the entire DSN
		*/

	if (! h->Error->ErrNum){
		if(strcspn(szAttributes, "[]{}(),?*!@") < strlen(szAttributes)){
			ODBCError("Illegal use of reserved characters []{}(),?*!@", h, "ODBC_ConfigDSN", "3");
		}

				//	Format Attribute list for the ConfigDataSource function...
				//	attrib1=value1\0attrib2=value2\0attrib3=value3\0\0
		for (;iSize > -1; iSize--){
			if (szAttributes[iSize] == ';'){
				szAttributes[iSize] = '\0';
			}
		}
		iResult = SQLConfigDataSource(0, (UINT) uType, (LPCSTR) szDriver, (LPCSTR) szAttributes);
	}
	
	if (!h->Error->ErrNum){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		XPUSHs(sv_2mortal(newSVnv((double)iResult)));
	}else{
			//	Report the error
		ReturnError(h);
	}

	if (szAttributes){
		delete [] szAttributes;
	}

	PUTBACK;
}


XS(XS_WIN32__ODBC_GetInfo)
{
	dXSARGS;
	ODBC_TYPE * h;
	UCHAR	*ucValue = 0;
	int		iSize = sizeof(DWORD) + 1;		//	Arbitrary size to allocate for result
	int		iFlag = 0;
	DWORD	*dValue;
	UWORD	*uValue;
    UWORD   uType;
	SWORD	swBytes = 0;
    RETCODE rResult = 0;
	
    if(items != 2){
        CROAK("usage: ($Err, $NumValue, $Value) = ODBC_GetInfo($Connection, $Type)\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
    uType = SvIV(ST(1));
	PUSHMARK(sp);

    if(!h->Error->ErrNum){
		while (!iFlag++){
			if (ucValue = (UCHAR *) new char [iSize]){
					//	Set the memory set to $FF so we can tell if the return value
					//	is a string. It will have a \0 at the end of it.
				memset(ucValue, 0xff, iSize);	
				dValue = (DWORD *) ucValue;
				uValue = (UWORD *) ucValue;
		        rResult = SQLGetInfo(h->hdbc->hdbc, uType, ucValue, (SWORD) iSize, &swBytes);
		        if (rResult != SQL_SUCCESS){
		 			_NT_ODBC_Error(h, "ODBC_GetInfo", "1");
		        }
			}else{
				ODBCError("Could not allocate memory for result string", h, "ODBC_GetInfo", "2");
				ucValue = 0;
			}
			if (iSize <= (int) swBytes){
				if(ucValue){delete [] ucValue;}
				iSize = (int) swBytes + 1;
				iFlag = 0;
				CleanError(h->Error);
			}
		} while (iSize <= (int) swBytes);
    }

	if (!h->Error->ErrNum){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		if (ucValue[swBytes] == 0){
				//	GetInfo() returned a string!
			XPUSHs(sv_2mortal(newSVpv((char *) ucValue, (int) swBytes)));
		}else{
			switch (swBytes){	
				case 2:			//	We must have a 16 bit value
					XPUSHs(sv_2mortal(newSVnv((double) *uValue)));
					break;

				case 4:			//	We must have a 32 bit value
					XPUSHs(sv_2mortal(newSVnv((double) *dValue)));
					break;
			}
		}
	}else{
			//	Report the error
		ReturnError(h);
	}

	if (ucValue){
		delete [] ucValue;
	}
	PUTBACK;
}

XS(XS_WIN32__ODBC_CleanError) 
{
	dXSARGS;
	ODBC_TYPE * h;
	
	if(items != 1){
		CROAK("usage: ($Err) = ODBC_CleanError($Connection)\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	
	PUSHMARK(sp);
   
	CleanError(h->Error);
	XPUSHs(sv_2mortal(newSVnv((double)0)));
	XPUSHs(sv_2mortal(newSVnv((double)1)));
	PUTBACK;
}

  
XS(XS_WIN32__ODBC_ColAttributes) 
{
	dXSARGS;
	ODBC_TYPE * h;
	UWORD	iCol = 0;
	UWORD	iType = 0;
	int		iTemp;
	UCHAR	*szName = 0;
	UCHAR	szBuff[ODBC_BUFF_SIZE];
	SWORD	dBuffLen = 0;
	SDWORD	dValue = 0;
	RETCODE	rResult;
	STRLEN  n_a;

	if(items != 3){
		CROAK("usage: ($Err) = ODBC_ColAttributes($Connection, $ColName, $Description)\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	szName = (unsigned char *) SvPV(ST(1), n_a);
	iType = SvIV(ST(2));
	
	PUSHMARK(sp);
   
	CleanError(h->Error);
	if (!h->Error->ErrNum){
	    if (iCol = ColNameToNum(h, (char *)szName)){
			memset(szBuff, '\0', ODBC_BUFF_SIZE);
			rResult = SQLColAttributes(h->hstmt, iCol, iType, szBuff, ODBC_BUFF_SIZE, (short *) &dBuffLen, (SDWORD *) &dValue);
			if (rResult == SQL_SUCCESS || rResult == SQL_SUCCESS_WITH_INFO){
				if (dBuffLen){
					XPUSHs(sv_2mortal(newSVnv((double)0)));
					XPUSHs(sv_2mortal(newSVpv((char *)szBuff, dBuffLen)));
				}else{
					XPUSHs(sv_2mortal(newSVnv((double)0)));
					XPUSHs(sv_2mortal(newSVnv((double)dValue)));
				}
			}else{
				ODBCError("Unable to determine Column Attribute", h, "ODBC_ColAttributes", "1");
			}
		}else{
			ODBCError("Field name does not exist", h, "ODBC_ColAttributes", "2");
		}
	}
	 		//	Process only an error state. If this has been successfull the values have
			//	already been processed.
	if (h->Error->ErrNum){	
			//	Report the error
		ReturnError(h);
	}
	PUTBACK;
}


 XS(XS_WIN32__ODBC_Debug) 
{
	dXSARGS;
	ODBC_TYPE * h;
	int	iDebug = 0;
	char	*szFile = 0;
	char	szBuff[286];
	DWORD	dCount;
	STRLEN  n_a;

	if(items < 1 || items > 3){
		CROAK("usage: $Debug = ODBC_Debug($Connection [, $Value])\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);

	if (items > 1 && !h->Error->ErrNum){
		iDebug = SvIV(ST(1));
		if (items > 2){
			szFile = SvPV(ST(2), n_a);
		}
			//	What about the output file?
		if (szFile){
			DEBUG_DUMP("ODBC_Debug: Entering Critical Section gDCS")
			EnterCriticalSection(&gDCS);
			if (ghFile){
			
			#ifdef _Debug	
				sprintf(szBuff, "Closing debug file \"%s\"", (gszFile)? gszFile:"none opened");
				DebugConnection(szBuff, h);
			#endif
				CloseHandle(ghFile);
				ghFile = 0;
			}
				//	unless a file is specified (not "") then use the default file
			if (strcmp(szFile, "") == 0){
				if (gszFile){
					delete [] gszFile;
				}
				gszFile = 0;
			}else{
				if (gszFile = new char [strlen(szFile) + 1]){
					strcpy(gszFile, szFile);
				}
			}
			LeaveCriticalSection(&gDCS);
			DEBUG_DUMP("ODBC_Debug: Left Critical Section gDCS    ")
		}
		if (iDebug){
			AddDebug(h);
			
		#ifdef _Debug
			if(ghDebug){
				sprintf(szBuff, "Debug mode set on by connection %i.\n", h->conn);
				DebugConnection(szBuff, h);
			}
		#endif

		}else{
		
		#ifdef _Debug
			if (ghDebug){
				sprintf(szBuff, "Debug mode set off by connection %i.\n", h->conn);
				DebugConnection(szBuff, h);
			}
		#endif

			RemoveDebug(h);
		}
		h->iDebug = (iDebug)? 1:0;
	}
	
	PUSHMARK(sp);
   
	if (!h->Error->ErrNum){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		XPUSHs(sv_2mortal(newSVnv((double)h->iDebug)));
	}else{
			//	Report the error
		ReturnError(h);
	}

	PUTBACK;
}

XS(XS_WIN32__ODBC_SetPos)
{
	dXSARGS;
	ODBC_TYPE * h;
    UWORD	uRow = 1;
    UWORD	uOption = SQL_POSITION;
    UWORD	uLock = SQL_LOCK_NO_CHANGE;
    RETCODE rResult = 0;
	
    if(items < 2 || items > 4){
        CROAK("usage: ($Err, $Type) = ODBC_SetPos($Connection, $Row [, $Option, $Lock})\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
    uRow 	= SvIV(ST(1));
	if (items > 2){
		uOption = SvIV(ST(2));
	}
	if (items > 3){
		uLock 	= SvIV(ST(3));
	}
    PUSHMARK(sp);

	if(!h->Error->ErrNum){
		rResult = SQLSetPos(h->hstmt, uRow, uOption, uLock);
	    if (rResult != SQL_SUCCESS){
			_NT_ODBC_Error(h, "ODBC_SetPos", "1");
				//	Set h->Error->EOR so that Fetch() knows the error is not because
				//	of itself.  What a hack!
			//h->Error->EOR = 1;
	    }
    }

	if (!h->Error->ErrNum){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		XPUSHs(sv_2mortal(newSVnv((double)1)));
	}else{
			//	Report the error
		ReturnError(h);
	}

	PUTBACK;
}

XS(XS_WIN32__ODBC_GetData)
{
	dXSARGS;
	ODBC_TYPE * h;
	RETCODE retcode;         // yet more ODBC garbage
	UCHAR  buff2[ODBC_BUFF_SIZE];
	UCHAR	*szBuf = 0;
	SDWORD	iBuf = DEFAULTCOLSIZE;
	SDWORD	bufflenout;
	SWORD	sSQLType;
	SWORD	sTemp;
	int lenn, iTotalPushed, iTemp;
	UWORD  x;
	int len;
	DWORD	dTemp;
	
    if(items != 1){
        CROAK("usage: ($Err, $Type) = ODBC_GetData($Connection)\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
    
    PUSHMARK(sp);
    
/*	HERE WE must figure whether to use SQLGetData() beause we are using
	SQLFetch() or since we are using SQLExtendedFetch() we are to just
	extract the data from h->szFetchBuffer[x].
	The flag to determine this is h->iFetchType (1=fetch;2=extended fetch)
*/


	if (!h->Error->ErrNum && !h->Error->EOR){
		int	iTempEnd;
			//	Allocate a default size to be used. This prevents the problem of ODBC 
			//	returning too small of a size for a regular numberical field.
			//	ALSO it saves some allocation time, hopefully the average field
			//	will fit into this size.
			//											rothd@roth.net	96.05.03	
						/*
							Needed to change SQL_COLUMN_LENGTH to SQL_COLUMN_DISPLAY_SIZE. The
							former found the # of bytes needed to represent the C data type
							in memory. The latter is used to find the # of bytes needed to store
							an ASCII version of the data. 
							Way big Thanks to Mike Knister <knister@sierra.net> 96.05.13

							Removed the ++bufflenout to prevent the wrap-around bug
							(when bufflenout = 2147483647, adding 1 yields -2147483648. Bad, very bad.
							A whopping big thanks to Jutta M. Klebe <jmk@exc.bybyte.de>	96.04.21

							Changed (iBuf < bufflenout) to (iBuf <= bufflenout). How silly! if we
							happen to have returning a field of size iBuf there will be no place 
							for the \0
								96.10.07 <rothd@roth.net>
						*/
		XPUSHs(sv_2mortal(newSVnv((double)0)));

		iTempEnd = h->Results->NumOfCols();
			//	Notice we start at 1 (don't include bookmarks (for now)
		for(iTemp = 1; iTemp <= iTempEnd; iTemp++){
			CResults *crTemp = h->Results;
			DWORD	dTemp = crTemp->ReturnSize(iTemp);
			SV	*sv = 0;
			if (dTemp){
				sv = sv_2mortal(newSVpv(crTemp->operator[](iTemp), dTemp));
			}else{
					//	If the data field has 0 size (is NULL or empty) then return undef.
				sv = &PL_sv_undef;
			}
			XPUSHs(sv);
		}
	}else{										
			//	Report the error
		ReturnError(h);
	}

	if (szBuf){
		delete [] szBuf;
	}
	PUTBACK;
}


XS(XS_WIN32__ODBC_DropCursor)
{
	dXSARGS;
	ODBC_TYPE * h;
	UWORD		uCloseType, uCloseSpecified = 0;
    RETCODE 	retcode = 0;
	
    if(items < 1 || items > 2){
        CROAK("usage: ($Err, $Type) = ODBC_DropCursor($Connection [, $CloseType])\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	if (items > 1){
		uCloseSpecified = SvIV(ST(1));	
	}
	CleanError(h->Error);
    PUSHMARK(sp);

	if(!h->Error->ErrNum){
		uCloseType = h->uStmtCloseType;
		h->uStmtCloseType = (items > 1)? uCloseSpecified:SQL_DROP;
		retcode = ResetStmt(h);
		h->uStmtCloseType = uCloseType;
	}
	if (!h->Error->ErrNum){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		XPUSHs(sv_2mortal(newSVnv((double)1)));
	}else{
			//	Report the error
		ReturnError(h);
	}

	PUTBACK;
}

XS(XS_WIN32__ODBC_CursorName)
{
	dXSARGS;
	ODBC_TYPE * h;
	char	*szName;
	SWORD	sSize = 0;
    RETCODE retcode = 0;
    STRLEN  n_a;

    if(items < 1 || items > 2){
        CROAK("usage: ($Err, $Type) = ODBC_CursorName($Connection [, $Name])\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);

	PUSHMARK(sp);

	if(!h->Error->ErrNum){
		if (items > 1){
			szName = SvPV(ST(1), n_a); 
			if (SQLSetCursorName(h->hstmt, (UCHAR *)szName, (SWORD)strlen(szName)) != SQL_SUCCESS){;
				_NT_ODBC_Error(h, "ODBC_CursorName", "1");
			}
		}
		szName = 0;
		if (!h->Error->ErrNum){
			if(SQL_SUCCESS == SQLGetCursorName(h->hstmt, (UCHAR *)szName, sSize, &sSize)){
				if(!(szName = new char [++sSize])){
					ODBCError("Could not allocate memory", h, "ODBC_CursorName", "2");
				}else{
					if(SQL_SUCCESS != SQLGetCursorName(h->hstmt, (UCHAR *)szName, sSize, &sSize)){
						_NT_ODBC_Error(h, "ODBC_CursorName", "3");
					}
				}
			}else{
				_NT_ODBC_Error(h, "ODBC_CursorName", "4");
			}
		}
	}else{
		szName = 0;
	}
	if (!h->Error->ErrNum){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		XPUSHs(sv_2mortal(newSVpv(szName, strlen(szName))));
	}else{
			//	Report the error
		ReturnError(h);
	}
	
	if (szName){
		delete [] szName;
	}

	PUTBACK;
}		


XS(XS_WIN32__ODBC_Clone) // ODBC_Connect(Connection string: input) returns connection #
{
	dXSARGS;
	ODBC_TYPE	*h, *hOld = 0;
	RETCODE retcode;           // Misc ODBC sh!t

	if(items != 1){
			//	We need at least 1 (DSN) entry. If more then we need them in pairs of
			//	two, hence (items & 1) make sure we have an odd number of entries...
			//	(dsn) + (ConnetOption, Value) [ + (ConnectOption, Value)] ...
		CROAK("usage: ($Connection, $Err, $ErrText) = ODBC_Clone($Num)\n");
	}
	hOld = _NT_ODBC_Verify(SvIV(ST(0)));

	PUSHMARK(sp);

 		 	//	Allocate new ODBC connection
	if (!(h = NewODBC())){
		h = ODBCError("Could not allocate memory of an ODBC connection\n", (ODBC_TYPE *)0, "ODBC_Clone", "1");
	}else{
			//	Copy info from the Cloned ODBC connection
		strcpy(h->szUserDSN, hOld->szUserDSN);
		h->henv = hOld->henv;
		h->hdbc = hOld->hdbc;
		h->hdbc->iCount++;	
		if (h->szDSN = new char [strlen((const char *)hOld->szDSN) + 1]){
			strcpy(h->szDSN, (char *) hOld->szDSN);
		}else{
			_NT_ODBC_Error(h, "ODBC_Clone", "2");
			h->Error->ErrNum = 0;
			DeleteConn(h->conn);
		}
	}

	if (!h->Error->ErrNum){
		retcode = ResetStmt(h);
		if (retcode != SQL_SUCCESS){
			DeleteConn(h->conn);
		}
	}
	if (!h->Error->ErrNum){ // everything is happy
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		XPUSHs(sv_2mortal(newSVnv((double)h->conn)));
			//	Report the szError ONLY because it may contain state info.
		XPUSHs(sv_2mortal(newSVpv(h->Error->szError, strlen(h->Error->szError))));
	}else{
			//	Report the error
		ReturnError(h);
	}
	PUTBACK;
} 

XS(XS_WIN32__ODBC_GetSQLState)
{
	dXSARGS;
	ODBC_TYPE * h;
	
    if(items != 1){
        CROAK("usage: ($Err, $Type) = ODBC_GetSQLState($Connection)\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));

	PUSHMARK(sp);

	if (h->Error->szSqlState){
		XPUSHs(sv_2mortal(newSVnv((double)0)));
		XPUSHs(sv_2mortal(newSVpv((char *)h->Error->szSqlState, strlen((const char *) h->Error->szSqlState))));
	}else{
			//	Report the error
		ReturnError(h);
	}

	PUTBACK;
}

XS(XS_WIN32__ODBC_GetStats)
{
	dXSARGS;
	ODBC_TYPE * h;
	int	iTemp = 0;
	CMom	*cDaughter = 0;
	char	*Keys[] = {  "TotalActiveThreads",			//	Total number of threads attached to this dll
						 "TotalActiveODBCThreads",		//	Total num of threads with ODBC connections
						 "TotalActiveDebugConnections",	//	Total number of active connections with active debugging
						 "TotalHistoryThreads",			//	Total history of threads that have connected to odbc connections
						 "TotalHistoryCurrentThread",	//	Total history of connections on current thread.
						 "TotalHistoryConnections",		//	Total history of all connections since dll initialization
						 "CurrentThreadID",				//	ID of current thread
						 "ConnectionErrNum", 			//	Connections Error Number (if any)
						 "ConnectionErrText", 			//	Connections Error Text (if any)
						 "ConnectionSQLState", 			//	Connections SQLState (if any)
						 "ConnectionFunction", 			//	Connections Function (if any)
						 "ConnectionLevel", 			//	Connections Function level (if any)
						 "ConnectionDebug"};			//	Connections Debug state (if any)
	
    if(items > 1){
        CROAK("usage: %Results = ODBC_GetStats([$Connection])\n");
	}

	h = _NT_ODBC_Verify( (items)? SvIV(ST(0)):0);

	PUSHMARK(sp);

	if (h && !(h->Error->ErrNum)){
		cDaughter = (CMom *) ::cMom->operator[](GetCurrentThreadId());
		
		XPUSHs(sv_2mortal(newSVpv((char *) Keys[iTemp], strlen((const char *) Keys[iTemp])) ));
		XPUSHs( sv_2mortal(newSVnv((double) giThread) ));

		iTemp++;
		XPUSHs(sv_2mortal(newSVpv((char *) Keys[iTemp], strlen((const char *) Keys[iTemp])) ));
		XPUSHs( sv_2mortal(newSVnv((double) ((::cMom)? ::cMom->Total():0)) ));
		
		iTemp++;
		XPUSHs(sv_2mortal(newSVpv((char *) Keys[iTemp], strlen((const char *) Keys[iTemp])) ));
		XPUSHs( sv_2mortal(newSVnv((double) giDebug) ));
		
		iTemp++;
		XPUSHs(sv_2mortal(newSVpv((char *) Keys[iTemp], strlen((const char *) Keys[iTemp])) ));
		XPUSHs( sv_2mortal(newSVnv((double) ((::cMom)? (::cMom->iHistory):0)) ));

		iTemp++;
		XPUSHs(sv_2mortal(newSVpv((char *) Keys[iTemp], strlen((const char *) Keys[iTemp])) ));
		XPUSHs( sv_2mortal(newSVnv((double) ((cDaughter)? (cDaughter->iHistory):0)) ));
	 	
	 	iTemp++;
		XPUSHs(sv_2mortal(newSVpv((char *) Keys[iTemp], strlen((const char *) Keys[iTemp])) ));
		XPUSHs( sv_2mortal(newSVnv((double) ((::cMom)? ::cMom->iTotalHistory:0)) ));
			
	 	iTemp++;
		XPUSHs(sv_2mortal(newSVpv((char *) Keys[iTemp], strlen((const char *) Keys[iTemp])) ));
		XPUSHs( sv_2mortal(newSVnv((double) GetCurrentThreadId())) );

		if (items){
			iTemp++;
			XPUSHs(sv_2mortal(newSVpv((char *) Keys[iTemp], strlen((const char *) Keys[iTemp])) ));
			XPUSHs(sv_2mortal(newSVnv((double) h->Error->ErrNum)) );	

			iTemp++;
			XPUSHs(sv_2mortal(newSVpv((char *) Keys[iTemp], strlen((const char *) Keys[iTemp])) ));
			XPUSHs(sv_2mortal(newSVpv((char *) h->Error->szSqlState, strlen((const char *) h->Error->szSqlState)) ));	
			
			iTemp++;
			XPUSHs(sv_2mortal(newSVpv((char *) Keys[iTemp], strlen((const char *) Keys[iTemp])) ));
			XPUSHs(sv_2mortal(newSVpv((char *) h->Error->szError, strlen((const char *) h->Error->szError)) ));	
			
			iTemp++;
			XPUSHs(sv_2mortal(newSVpv((char *) Keys[iTemp], strlen((const char *) Keys[iTemp])) ));
			XPUSHs(sv_2mortal(newSVpv((char *) h->Error->szFunction, strlen((const char *) h->Error->szFunction)) ));	
			
			iTemp++;
			XPUSHs(sv_2mortal(newSVpv((char *) Keys[iTemp], strlen((const char *) Keys[iTemp])) ));
			XPUSHs(sv_2mortal(newSVpv((char *) h->Error->szFunctionLevel, strlen((const char *) h->Error->szFunctionLevel)) ));	
			
			iTemp++;
			XPUSHs(sv_2mortal(newSVpv((char *) Keys[iTemp], strlen((const char *) Keys[iTemp])) ));
			XPUSHs(sv_2mortal(newSVnv((double) h->iDebug) ));	
		}
	}else{
			//	Report the error
		ReturnError(h);
	}

	PUTBACK;
}

XS(XS_WIN32__ODBC_GetTypeInfo)
{
	dXSARGS;
	ODBC_TYPE * h;
    SWORD   sType;
    RETCODE retcode= 0;
	char	*szCommand = "GetTypeInfo()";
	
    if(items != 2){
        CROAK("usage: ($Err, $NumValue, $Value) = ODBC_GetTypeInfo($Connection, $Type)\n");
	}

	h = _NT_ODBC_Verify(SvIV(ST(0)));
	CleanError(h->Error);
    sType = SvIV(ST(1));
	PUSHMARK(sp);

    if(!h->Error->ErrNum){
		if (ResetStmt(h) == SQL_SUCCESS){
			if (h->szCommand){
				delete [] h->szCommand;
				h->szCommand = 0;
			}
			if (h->szCommand = new char [strlen(szCommand) + 1]){
				strcpy(h->szCommand, szCommand);
			}
			retcode = SQLGetTypeInfo(h->hstmt, sType);
			if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO){
					_NT_ODBC_Error(h, "ODBC_GetTypeInfo", "1");
			}else{
				if(h->Results){
					delete h->Results;
				}
				h->Results = new CResults(h);
			}
		}
	}
	if (!h->Error->ErrNum){ // everything is happy
		UCHAR	buff2[ODBC_BUFF_SIZE];
		SDWORD	bufflenout;
		int		x;

		XPUSHs(sv_2mortal(newSVnv((double)0)));
		retcode = SQLNumResultCols(h->hstmt, (short *)&h->numcols);
		for(x=1; x<=h->numcols; x++){
			SQLColAttributes(h->hstmt, x, SQL_COLUMN_NAME, buff2, ODBC_BUFF_SIZE, (short *)&bufflenout, NULL);
			XPUSHs(sv_2mortal(newSVpv((char *)buff2, strlen((const char*)buff2))));
		}
	}else{										
			//	Report the error
		ReturnError(h);
	}
	PUTBACK;
}


XS(XS_WIN32__ODBC_ShutDown)
{
	//	dXSARGS;
	TerminateThread();
}

void TerminateThread(){
	char	szBuff[100];
	CMom	*cmDaughter;

	DEBUG_DUMP("TerminateThread(): Entering Critical Section gDCS")
#ifdef _Debug
	EnterCriticalSection(&gDCS);
	sprintf(szBuff, "Thread %05i (total threads: %03i) terminating.\n", GetCurrentThreadId(), giThread);
		//	Entered Debug CS so no other debug messages interrupt us...
		//	...since we are in DCS we *MUST* use DebugPrint() not DebugDump()
	DebugPrint(szBuff);
		//	If this thread has a CMom then delete it!
	DebugPrint("\t--> Checking for a daughter on this thread...\n");
#endif

	if (::cMom){
		if (cmDaughter = (CMom *) ::cMom->operator[](GetCurrentThreadId())){
		
		#ifdef _Debug
			DebugPrint("\t--> Thread daughter about to be deleted...\n");
		#endif

			delete cmDaughter;
		
		#ifdef _Debug
			DebugPrint("\t--> Thread daughter has been deleted.\n\n");	  
		#endif

		}
	#ifdef _Debug
		else{
			DebugPrint("\t--> No daughter on this thread.\n\n");
		}
	#endif
	}

#ifdef _Debug
	LeaveCriticalSection(&gDCS);
#endif

	DEBUG_DUMP("TerminateThread(): Left Critical Section gDCS    ")
	return;
}

XS(XS_WIN32__ODBC_InitExtension)
{
	InitExtension();
}

int InitExtension(){
	CMom	*cmDaughter = 0;
	ODBC_TYPE	*h = 0;
	int	iRetCode = 1;

	if (! ::cMom){
		::cMom = new CMom;
	}
	if (! ::cMom){
		iRetCode = FALSE;
	}else{
		if (cmDaughter = (CMom *) ::cMom->operator[](GetCurrentThreadId())){
			h = (ODBC_TYPE*) cmDaughter->operator[]((DWORD)0);
		}
		if(!h || !cmDaughter){
			
		#ifdef _Debug
			DebugConnection("     =========> Creating Gratuitous ODBC structure...", 0);
		#endif
			if (!(h = NewODBC())){
				iRetCode = FALSE;
			}else{
				strcpy(h->szUserDSN, "Gratuitous ODBC Structure");
			}

		#ifdef _Debug
			DebugConnection("     =========> Finished creating gratuitous ODBC structure.", 0);
		#endif

		}
	}
	return iRetCode;
}

#if defined(__cplusplus)
extern "C"
#endif
XS(boot_Win32__ODBC)
{
	dXSARGS;
	char* file = __FILE__;
	int i;
	RETCODE	iRetCode = 1;

#ifdef _Debug
	DebugConnection("==============\n\tRunning bootstrap code.", 0);
#endif
		//	This will force the creation of a daughter Mom and populate it with a
		//	default ODBC object.				   	
	if (iRetCode = InitExtension()){	
		newXS("Win32::ODBC::constant",				XS_WIN32__ODBC_Constant, file);
		newXS("Win32::ODBC::ODBCConnect",			XS_WIN32__ODBC_Connect,  file);
	    newXS("Win32::ODBC::ODBCExecute",			XS_WIN32__ODBC_Execute, file);
	    newXS("Win32::ODBC::ODBCFetch",				XS_WIN32__ODBC_Fetch, file);
	    newXS("Win32::ODBC::ODBCDisconnect",		XS_WIN32__ODBC_Disconnect, file);
		newXS("Win32::ODBC::ODBCGetError",			XS_WIN32__ODBC_GetError, file);
		newXS("Win32::ODBC::ODBCTableList",			XS_WIN32__ODBC_TableList, file);
		newXS("Win32::ODBC::ODBCColumnList",		XS_WIN32__ODBC_ColumnList, file);
		newXS("Win32::ODBC::ODBCMoreResults",		XS_WIN32__ODBC_MoreResults, file);
	
		newXS("Win32::ODBC::ODBCGetConnections",	XS_WIN32__ODBC_GetConnections, file);
		newXS("Win32::ODBC::ODBCGetMaxBufSize",		XS_WIN32__ODBC_MaxBufSize, file);
		newXS("Win32::ODBC::ODBCSetMaxBufSize",		XS_WIN32__ODBC_MaxBufSize, file);
		newXS("Win32::ODBC::ODBCGetStmtCloseType",	XS_WIN32__ODBC_GetStmtCloseType, file);
		newXS("Win32::ODBC::ODBCSetStmtCloseType",	XS_WIN32__ODBC_SetStmtCloseType, file);
	
		newXS("Win32::ODBC::ODBCDataSources",		XS_WIN32__ODBC_DataSources, file);
		newXS("Win32::ODBC::ODBCDrivers",			XS_WIN32__ODBC_Drivers, file);
	
		newXS("Win32::ODBC::ODBCGetStmtOption",		XS_WIN32__ODBC_StmtOption, file);
		newXS("Win32::ODBC::ODBCSetStmtOption",		XS_WIN32__ODBC_StmtOption, file);
	    newXS("Win32::ODBC::ODBCSetConnectOption",  XS_WIN32__ODBC_SetConnectOption, file);
	    newXS("Win32::ODBC::ODBCGetConnectOption",  XS_WIN32__ODBC_GetConnectOption, file);
	
		newXS("Win32::ODBC::ODBCRowCount",			XS_WIN32__ODBC_RowCount, file);
		newXS("Win32::ODBC::ODBCCleanError",		XS_WIN32__ODBC_CleanError, file);
		newXS("Win32::ODBC::Info",					XS_WIN32__ODBC_Info, file);
		newXS("Win32::ODBC::ODBCColAttributes",		XS_WIN32__ODBC_ColAttributes, file);									   
		newXS("Win32::ODBC::ODBCConfigDSN",			XS_WIN32__ODBC_ConfigDSN, file);									   
		newXS("Win32::ODBC::ODBCGetFunctions",  	XS_WIN32__ODBC_GetFunctions, file);
		newXS("Win32::ODBC::ODBCTransact",			XS_WIN32__ODBC_Transact, file);
		newXS("Win32::ODBC::ODBCGetDSN",			XS_WIN32__ODBC_GetDSN, file);
		newXS("Win32::ODBC::ODBCGetInfo",			XS_WIN32__ODBC_GetInfo, file);
		newXS("Win32::ODBC::ODBCDebug",				XS_WIN32__ODBC_Debug, file);
		newXS("Win32::ODBC::ODBCSetPos",			XS_WIN32__ODBC_SetPos, file);
		newXS("Win32::ODBC::ODBCGetData",			XS_WIN32__ODBC_GetData, file);
		newXS("Win32::ODBC::ODBCDropCursor",		XS_WIN32__ODBC_DropCursor, file);
		newXS("Win32::ODBC::ODBCSetCursorName",		XS_WIN32__ODBC_CursorName, file);
		newXS("Win32::ODBC::ODBCGetCursorName",		XS_WIN32__ODBC_CursorName, file);
		newXS("Win32::ODBC::ODBCClone",				XS_WIN32__ODBC_Clone, file);
		newXS("Win32::ODBC::ODBCGetSQLState",		XS_WIN32__ODBC_GetSQLState, file);
		newXS("Win32::ODBC::ODBCGetStats",			XS_WIN32__ODBC_GetStats, file);
		newXS("Win32::ODBC::ODBCGetTypeInfo",		XS_WIN32__ODBC_GetTypeInfo, file);

		newXS("Win32::ODBC::ODBCShutDown",			XS_WIN32__ODBC_ShutDown, file);
		newXS("Win32::ODBC::ODBCInit",				XS_WIN32__ODBC_InitExtension, file);

	}
#ifdef _Debug
	DebugConnection("Finished bootstrap code.\n\t==============", 0);
#endif

//	ST(0) = &sv_yes;
	XSRETURN(iRetCode);

}			

#ifdef	WIN32

	/* ===============  DLL Specific  Functions  ===================  */
	BOOL WINAPI
#  ifdef __BORLANDC__
	DllEntryPoint
#  else
	DllMain
#  endif
	(HINSTANCE  hinstDLL, DWORD fdwReason, LPVOID  lpvReserved){
		BOOL	bResult = TRUE;

		switch(fdwReason){
			case DLL_PROCESS_ATTACH:
			{
			
			#ifdef _Debug
				char	szBuff[100];
			#endif
				ghDLL    = hinstDLL;
				giDebug  = 0;
				ghDebug  = 0;
				giThread++;
				
						//	The Debug CS
				InitializeCriticalSection(&gDCS);

						//	The Global CS
				InitializeCriticalSection(&gCS);

			#ifdef _Debug
				DebugDump("DLL attaching to process.\n");
			#endif
			
				break;
			}
			case DLL_THREAD_ATTACH:
			{
			
			#ifdef _Debug
				char	szBuff[100];
			#endif
			
					/*	
						We will create a new CMom for this thread in the bootstrap
						function. This way we are not creating CMom & primary
						ODBC objects for every thread without reason
					*/

				giThread++;
				
					//	Since we moved the code to create the threads ODBC object there should
					//	no longer be a need to enter the critical section gCS so let's remark
					//	it out for now.
//				EnterCriticalSection(&gCS);
			#ifdef _Debug				
				DEBUG_DUMP("DLL_THREAD_ATTACH: Entering Critical Section gCS")
				sprintf(szBuff, "Thread %05i (total threads: %03i) starting.\n", GetCurrentThreadId(), giThread);
				DebugPrint(szBuff);
				DEBUG_DUMP("DLL_THREAD_ATTACH: Left Critical Section gCS    ")
			#endif
//				LeaveCriticalSection(&gCS);

				break;
			 
			}
			case DLL_THREAD_DETACH:
			{
				
			#ifdef _Debug
				char	szBuff[100];
			
					//	Is there a need for this section go enter the critical section gCS?
				DEBUG_DUMP("DLL_THREAD_DETACH: Entering Critical Section gCS")
				EnterCriticalSection(&gCS);
				sprintf(szBuff, "Thread %05i (total threads: %03i) stopping.\n", GetCurrentThreadId(), giThread);
				DebugPrint(szBuff);
				LeaveCriticalSection(&gCS);
				DEBUG_DUMP("DLL_THREAD_DETACH: Left Critical Section gCS    ")
			#endif

				//	TerminateThread();
				giThread--;
				break;
			}
			case DLL_PROCESS_DETACH:
			{
			
			#ifdef _Debug
				DebugDump("Unloading DLL.\n");
			#endif

				if (::cMom){
					delete ::cMom;
				}
				if (ghDebug){
					ghDebug = 0;
					FreeConsole();
				}

				DeleteCriticalSection(&gCS);
				DeleteCriticalSection(&gDCS);
				ghDLL = 0;
				if (ghFile){
					CloseHandle(ghFile);
				}
				break;
			}
			default:
				break;
		}
		return bResult;
	}

#endif
				 
/*
	To do list:
		-Convert this beast to OOP.
		done kinda -Create classes of reusable hEnv, hDbc, hStmt so we do not
		 waste memory and connections.
		done -Configure/alter DSNs.
		done -Return an array of values that describe the total connection 
		 string for a DSN. Similar to DataSources($DSN) but return the 
		 connection string not just the driver string.
		done -return values that can have embedded nulls (binary data).
*/
