/*
*	©Copyright 2002, Microsoft Corporation
*	Written by Sergey Vasilevskiy, 2002.
*/

#include "stdafx.h"

using namespace TPCE;

CDBConnection::CDBConnection(const char *szServer, const char *szDatabase, eClientDriverType eClientType)
	: m_henv(SQL_NULL_HENV), m_hdbc(SQL_NULL_HDBC), m_hstmt(SQL_NULL_HSTMT)
{
	RETCODE		rc;

	if ( SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv) != SQL_SUCCESS )
		ThrowError(CODBCERR::eAllocHandle, SQL_HANDLE_ENV, m_henv);

	if ( SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0 ) != SQL_SUCCESS )
		ThrowError(CODBCERR::eSetEnvAttr);

	if ( SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &m_hdbc) != SQL_SUCCESS )
		ThrowError(CODBCERR::eAllocHandle,SQL_HANDLE_DBC, m_henv);

	{
		char			szConnectStr[256];
		char			szOutStr[1024];
		SQLSMALLINT		iOutStrLen;

		if (eClientType == eMDAC)
		{
			sprintf( szConnectStr, "DRIVER=SQL Server;SERVER=%s;DATABASE=%s;Trusted_Connection=yes", 
					szServer, szDatabase );
		}
		else	// SNAC by default
		{
			sprintf( szConnectStr, "DRIVER=SQL Native Client;SERVER=%s;DATABASE=%s;Trusted_Connection=yes", 
					szServer, szDatabase );
		}

		rc = SQLDriverConnect(m_hdbc, NULL, (SQLCHAR*)szConnectStr, sizeof(szConnectStr), 
			(SQLCHAR*)szOutStr, sizeof(szOutStr), &iOutStrLen, SQL_DRIVER_NOPROMPT );

		if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
			ThrowError(CODBCERR::eConnect);
	}

	if (SQLAllocHandle(SQL_HANDLE_STMT, m_hdbc, &m_hstmt) != SQL_SUCCESS)
		ThrowError(CODBCERR::eAllocHandle, SQL_HANDLE_DBC, m_hdbc);

	InitConnection();
}

CDBConnection::~CDBConnection()
{
	// note: descriptors are automatically released when the connection is dropped
	if (m_hstmt != SQL_NULL_HSTMT)
	{
		SQLFreeHandle(SQL_HANDLE_STMT, m_hstmt);
		m_hstmt = SQL_NULL_HSTMT;
	}

	if (m_hdbc != SQL_NULL_HDBC)
	{
		// Rollback any non-complete transaction
		//
		SQLEndTran(SQL_HANDLE_DBC, m_hdbc, SQL_ROLLBACK);	// don't use Rollback as cannot throw exceptions here

		if (SQLDisconnect(m_hdbc) != SQL_ERROR)
		{
			SQLFreeHandle(SQL_HANDLE_DBC, m_hdbc);

			m_hdbc = SQL_NULL_HDBC;	
		}
	}

	if (m_henv != SQL_NULL_HENV)
	{
		SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
		m_henv = SQL_NULL_HENV;
	}
}

void CDBConnection::ThrowError( CODBCERR::ACTION eAction, SQLSMALLINT HandleType, SQLHANDLE Handle, const char* szLoc)
{
	RETCODE		rc;
	SDWORD		lNativeError;
    SQLSMALLINT iMessageLen;
	char		szState[6];
	char		szMsg[SQL_MAX_MESSAGE_LENGTH];
	char		szTmp[6*SQL_MAX_MESSAGE_LENGTH];
	CODBCERR   *pODBCErr;			// not allocated until needed (maybe never)

    if (NULL == szLoc) // cannot pass NULL location, because strlen in CBaseErr c'tor AVs
    { 
        pODBCErr = new CODBCERR("unknown");
    }
    else
    {
	    pODBCErr = new CODBCERR(szLoc);
    }

	pODBCErr->m_NativeError = 0;
	pODBCErr->m_eAction = eAction;
	pODBCErr->m_bDeadLock = FALSE;	

	if (Handle == SQL_NULL_HANDLE)
	{
		switch (eAction)
		{
		case CODBCERR::eSetEnvAttr:
			HandleType = SQL_HANDLE_ENV;
			Handle = m_henv;
			break;

		case CODBCERR::eBcpBind:
		case CODBCERR::eBcpControl:
		case CODBCERR::eBcpBatch:
		case CODBCERR::eBcpDone:
		case CODBCERR::eBcpInit:
		case CODBCERR::eBcpSendrow:
		case CODBCERR::eConnect:
		case CODBCERR::eSetConnectAttr:		
		case CODBCERR::eEndTxn:
			HandleType = SQL_HANDLE_DBC;
			Handle = m_hdbc;
			break;	
		case CODBCERR::eBindParam:
		case CODBCERR::eExecDirect:
		case CODBCERR::eBindCol:
		case CODBCERR::eFetch:
		case CODBCERR::eSetCursorName:
		case CODBCERR::eSQLSetPos:
		case CODBCERR::eNumResultCols:
		case CODBCERR::eCloseCursor:
			HandleType = SQL_HANDLE_STMT;
			Handle = m_hstmt;
			break;
		}
	}

	szTmp[0] = 0;
	int i = 0;
	while (TRUE)
	{

		rc = SQLGetDiagRec( HandleType, Handle, ++i, (BYTE *)&szState, &lNativeError, 
					  (BYTE *)&szMsg, sizeof(szMsg), &iMessageLen);
		if (rc == SQL_NO_DATA)
			break;

		// check for deadlock
//		if (lNativeError == 1205 || (lNativeError == iErrOleDbProvider && 
//			strstr(szMsg, sErrTimeoutExpired) != NULL))
//			pODBCErr->m_bDeadLock = TRUE;

		// capture the (first) database error
		if (pODBCErr->m_NativeError == 0 && lNativeError != 0)
		{
			pODBCErr->m_NativeError = lNativeError;

			// Check for deadlock error code and set the deadlock flag
			//
			if (lNativeError == 1205)
			{
				pODBCErr->m_bDeadLock = TRUE;
			}
		}

		// quit if there isn't enough room to concatenate error text
		if ( (strlen(szMsg) + 2) > (sizeof(szTmp) - strlen(szTmp)) )
			break;

		// include line break after first error msg
		if (szTmp[0] != 0)
			strcat( szTmp, "\n");
		//sprintf(szTmp, "%sSQLState=%s %s", szTmp, szState, szMsg );
		strcat(szTmp, szMsg);
	}

	if (pODBCErr->m_odbcerrstr != NULL)
	{
		delete [] pODBCErr->m_odbcerrstr;
		pODBCErr->m_odbcerrstr = NULL;
	}

	if (strlen(szTmp) > 0)
	{
		pODBCErr->m_odbcerrstr = new char[ strlen(szTmp)+1 ];
		strcpy( pODBCErr->m_odbcerrstr, szTmp );
	}

	SQLFreeStmt(m_hstmt, SQL_CLOSE);

	throw pODBCErr;
}

void CDBConnection::StartTransaction(char *szIsoLevel)
{
	int rc;
	char	szQuery[256];

	_snprintf(szQuery, sizeof(szQuery), "SET TRANSACTION ISOLATION LEVEL %s", szIsoLevel);
	szQuery[sizeof(szQuery)-1] = '\0';	//for the extreme case

	rc = SQLExecDirect(m_hstmt, (SQLCHAR*) szQuery, SQL_NTS);
	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
		ThrowError(CODBCERR::eExecDirect);
	rc = SQLExecDirect(m_hstmt, (SQLCHAR*)"BEGIN TRANSACTION", SQL_NTS);
	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
		ThrowError(CODBCERR::eExecDirect);
}

void CDBConnection::StartTransactionReadCommitted()
{
	StartTransaction("READ COMMITTED");
}

void CDBConnection::Rollback()
{
	int rc;

	// Note: with autocommit off SQLEndTran actually doesn't do anything
	// for either SQL_COMMIT or SQL_ROLLBACK.
	// Therefore need to manually send the statement to the server.
	//
	rc = SQLExecDirect(m_hstmt, 
			(SQLCHAR*)"rollback transaction",
			SQL_NTS);
	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
		ThrowError(CODBCERR::eExecDirect);

	CleanupStmt();

	//Rollback the transaction on this connection
	/*rc = SQLEndTran(SQL_HANDLE_DBC, m_hdbc, SQL_ROLLBACK);
	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
		ThrowError(CODBCERR::eEndTxn);*/
}


void CDBConnection::CommitTransaction()
{
	int rc;

	// Note: with autocommit off SQLEndTran actually doesn't do anything
	// for either SQL_COMMIT or SQL_ROLLBACK.
	// Therefore need to manually send the statement to the server.
	//
	rc = SQLExecDirect(m_hstmt, 
			(SQLCHAR*)"COMMIT TRANSACTION",
			SQL_NTS);
	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
		ThrowError(CODBCERR::eExecDirect);

	CleanupStmt();

	//Commit the transaction on this connection
	/*rc = SQLEndTran(SQL_HANDLE_DBC, m_hdbc, SQL_COMMIT);		
	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
		ThrowError(CODBCERR::eEndTxn);*/
}

void CDBConnection::InitConnection()
{
	int rc;

	//Set the NOCOUNT and XACT_ABORT options on the connection level
	// Set ARITHABORT option for the materialized views to work
	rc = SQLExecDirect(m_hstmt, (SQLCHAR *)"SET NOCOUNT ON SET XACT_ABORT ON SET ARITHABORT ON "
											, SQL_NTS);	
	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
		ThrowError(CODBCERR::eExecDirect);

	CleanupStmt();

	//Set commit mode (autocommit ON is the default).
	//rc = SQLSetConnectAttr(m_hdbc, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, SQL_IS_UINTEGER);
	//rc = SQLSetConnectAttr(m_hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_UINTEGER);
	//if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
	//	ThrowError(CODBCERR::eSetConnectAttr);
}
