#include "stdafx.h"
// MSSQL2000, added by PAF
#ifndef SQL_NVARCHAR
#define SQL_NVARCHAR (-9)
#endif
#ifndef SQL_NTEXT
#define SQL_NTEXT (-10)
#endif
#ifndef _WIN64
#ifdef AFX_DB_SEG
#pragma code_seg(AFX_DB_SEG)
#endif
#define new DEBUG_NEW
/////////////////////////////////////////////////////////////////////////////
// Global data
#ifdef _DEBUG
BOOL bTraceSql = FALSE;
#endif
AFX_STATIC_DATA const TCHAR _afxODBCTrail[] = _T("ODBC;");
AFX_STATIC_DATA const TCHAR _afxComma[] = _T(",");
AFX_STATIC_DATA const TCHAR _afxLiteralSeparator = '\'';
AFX_STATIC_DATA const TCHAR _afxExec[] = _T("EXEC ");
AFX_STATIC_DATA const TCHAR _afxCall[] = _T("{CALL "); //paf
AFX_STATIC_DATA const TCHAR _afxParamCall[] = _T("{?");
AFX_STATIC_DATA const TCHAR _afxSelect[] = _T("SELECT ");
AFX_STATIC_DATA const TCHAR _afxFrom[] = _T(" FROM ");
AFX_STATIC_DATA const TCHAR _afxWhere[] = _T(" WHERE ");
AFX_STATIC_DATA const TCHAR _afxOrderBy[] = _T(" ORDER BY ");
AFX_STATIC_DATA const TCHAR _afxForUpdate[] = _T(" FOR UPDATE ");
AFX_STATIC_DATA const TCHAR _afxRowFetch[] = _T("State:01S01");
AFX_STATIC_DATA const TCHAR _afxDataTruncated[] = _T("State:01004");
AFX_STATIC_DATA const TCHAR _afxInfoRange[] = _T("State:S1096");
AFX_STATIC_DATA const TCHAR _afxOutOfSequence[] = _T("State:S1010");
AFX_STATIC_DATA const TCHAR _afxDriverNotCapable[] = _T("State:S1C00");
AFX_STATIC_DATA const char _afxODBCDLL[] = "ODBC32.DLL";
/////////////////////////////////////////////////////////////////////////////
// for dynamic load of ODBC32.DLL
#pragma comment(lib, "odbc32.lib")
/////////////////////////////////////////////////////////////////////////////
// CDBException
void AFXAPI AfxThrowDBException(RETCODE nRetCode, CDatabase* pdb, HSTMT hstmt)
{
CDBException* pException = new CDBException(nRetCode);
if (nRetCode == SQL_ERROR && pdb != NULL)
pException->BuildErrorString(pdb, hstmt);
else if (nRetCode > AFX_SQL_ERROR && nRetCode < AFX_SQL_ERROR_MAX)
{
VERIFY(pException->m_strError.LoadString(
AFX_IDP_SQL_FIRST+(nRetCode-AFX_SQL_ERROR)));
TRACE(traceDatabase, 0, _T("%s\n"), pException->m_strError);
}
THROW(pException);
}
CDBException::CDBException(RETCODE nRetCode)
{
m_nRetCode = nRetCode;
}
CDBException::~CDBException()
{
}
void CDBException::BuildErrorString(CDatabase* pdb, HSTMT hstmt, BOOL bTrace)
{
ASSERT_VALID(this);
UNUSED(bTrace); // unused in release builds
if (pdb != NULL)
{
SWORD nOutlen;
RETCODE nRetCode;
TCHAR lpszMsg[SQL_MAX_MESSAGE_LENGTH];
TCHAR lpszState[SQL_SQLSTATE_SIZE+1];
CString strMsg;
CString strState;
SDWORD lNative;
_AFX_DB_STATE* pDbState = _afxDbState;
AFX_SQL_SYNC(::SQLError(pDbState->m_henvAllConnections, pdb->m_hdbc,
hstmt, reinterpret_cast<SQLTCHAR *>(lpszState), &lNative,
reinterpret_cast<SQLTCHAR *>(lpszMsg), SQL_MAX_MESSAGE_LENGTH-1, &nOutlen));
strState = lpszState;
// Skip non-errors
while ((nRetCode == SQL_SUCCESS || nRetCode == SQL_SUCCESS_WITH_INFO) &&
lstrcmp(strState, _T("00000")) != 0)
{
strMsg = lpszMsg;
TCHAR lpszNative[50];
wsprintf(lpszNative, _T(",Native:%ld,Origin:"), lNative);
strState += lpszNative;
// transfer [origin] from message string to StateNativeOrigin string
int nCloseBracket;
int nMsgLength;
while (!strMsg.IsEmpty() &&
strMsg[0] == '[' && (nCloseBracket = strMsg.Find(']')) >= 0)
{
// Skip ']'
nCloseBracket++;
strState += strMsg.Left(nCloseBracket);
nMsgLength = strMsg.GetLength();
// Skip ' ', if present
if (nCloseBracket < nMsgLength && strMsg[nCloseBracket] == ' ')
nCloseBracket++;
strMsg = strMsg.Right(nMsgLength - nCloseBracket);
}
strState += _T("\n");
m_strStateNativeOrigin += _T("State:") + strState;
m_strError += strMsg + _T("\n");
#ifdef _DEBUG
if (bTrace)
{
TraceErrorMessage(strMsg);
TraceErrorMessage(_T("State:") + strState);
}
#endif // _DEBUG
AFX_SQL_SYNC(::SQLError(pDbState->m_henvAllConnections,
pdb->m_hdbc, hstmt, reinterpret_cast<SQLTCHAR *>(lpszState), &lNative,
reinterpret_cast<SQLTCHAR *>(lpszMsg), SQL_MAX_MESSAGE_LENGTH-1, &nOutlen));
strState = lpszState;
}
}
}
BOOL CDBException::GetErrorMessage(LPTSTR lpszError, UINT nMaxError,
PUINT pnHelpContext /* = NULL */)
{
ASSERT(lpszError != NULL && AfxIsValidString(lpszError, nMaxError));
if (pnHelpContext != NULL)
*pnHelpContext = 0;
lstrcpyn(lpszError, m_strError, nMaxError-1);
lpszError[nMaxError-1] = '\0';
return TRUE;
}
#ifdef _DEBUG
void CDBException::TraceErrorMessage(LPCTSTR szTrace) const
{
CString strTrace = szTrace;
if (strTrace.GetLength() <= 80)
TRACE(traceDatabase, 0, _T("%s\n"), strTrace);
else
{
// Display 80 chars/line
while (strTrace.GetLength() > 80)
{
TRACE(traceDatabase, 0, _T("%s\n"), strTrace.Left(80));
strTrace = strTrace.Right(strTrace.GetLength() - 80);
}
TRACE(traceDatabase, 0, _T("%s\n"), strTrace);
}
}
#endif // _DEBUG
void CDBException::Empty()
{
m_strError.Empty();
m_strStateNativeOrigin.Empty();
}
/////////////////////////////////////////////////////////////////////////////
// Global helper
HENV AFXAPI AfxGetHENV()
{
_AFX_DB_STATE* pDbState = _afxDbState;
return pDbState->m_henvAllConnections;
}
/////////////////////////////////////////////////////////////////////////////
// CDatabase implementation
CDatabase::CDatabase()
{
m_hdbc = SQL_NULL_HDBC;
m_hstmt = SQL_NULL_HSTMT;
m_bUpdatable = FALSE;
m_bTransactions = FALSE;
DEBUG_ONLY(m_bTransactionPending = FALSE);
m_dwLoginTimeout = DEFAULT_LOGIN_TIMEOUT;
m_dwQueryTimeout = DEFAULT_QUERY_TIMEOUT;
m_bStripTrailingSpaces = FALSE;
m_bIncRecordCountOnAdd = FALSE;
m_bAddForUpdate = FALSE;
}
CDatabase::~CDatabase()
{
ASSERT_VALID(this);
Free();
}
BOOL CDatabase::Open(LPCTSTR lpszDSN, BOOL bExclusive,
BOOL bReadonly, LPCTSTR lpszConnect, BOOL bUseCursorLib)
{
ASSERT(lpszDSN == NULL || AfxIsValidString(lpszDSN));
ASSERT(lpszConnect == NULL || AfxIsValidString(lpszConnect));
CString strConnect;
if (lpszConnect != NULL)
strConnect = lpszConnect;
// if there is a "ODBC;" (or "odbc;") prefix in the connect string...
if (_tcsnicmp(strConnect, _afxODBCTrail, lstrlen(_afxODBCTrail)) == 0)
{
// Strip "ODBC;"
strConnect = strConnect.Right(strConnect.GetLength()
- lstrlen(_afxODBCTrail));
}
if (lpszDSN != NULL && lstrlen(lpszDSN) != 0)
{
// Append "DSN=" lpszDSN
strConnect += _T(";DSN=");
strConnect += lpszDSN;
}
DWORD dwOptions = 0;
if (bExclusive)
dwOptions |= openExclusive;
if (bReadonly)
dwOptions |= openReadOnly;
if (bUseCursorLib)
dwOptions |= useCursorLib;
return OpenEx(strConnect, dwOptions);
}
BOOL CDatabase::OpenEx(LPCTSTR lpszConnectString, DWORD dwOptions)
{
ASSERT_VALID(this);
ASSERT(lpszConnectString == NULL || AfxIsValidString(lpszConnectString));
ASSERT(!(dwOptions & noOdbcDialog && dwOptions & forceOdbcDialog));
// Exclusive access not supported.
ASSERT(!(dwOptions & openExclusive));
m_bUpdatable = !(dwOptions & openReadOnly);
TRY
{
m_strConnect = lpszConnectString;
// Allocate the HDBC and make connection
AllocConnect(dwOptions);
if(!Connect(dwOptions))
return FALSE;
// Verify support for required functionality and cache info
VerifyConnect();
GetConnectInfo();
}
CATCH_ALL(e)
{
Free();
THROW_LAST();
}
END_CATCH_ALL
return TRUE;
}
void CDatabase::ExecuteSQL(LPCTSTR lpszSQL)
{
RETCODE nRetCode;
HSTMT hstmt;
ASSERT_VALID(this);
ASSERT(AfxIsValidString(lpszSQL));
AFX_SQL_SYNC(::SQLAllocStmt(m_hdbc, &hstmt));
if (!CheckHstmt(nRetCode, hstmt))
AfxThrowDBException(nRetCode, this, hstmt);
TRY
{
OnSetOptions(hstmt);
// Give derived CDatabase classes option to use parameters
BindParameters(hstmt);
LPTSTR pszSQL = const_cast<LPTSTR>(lpszSQL);
AFX_ODBC_CALL(::SQLExecDirect(hstmt, reinterpret_cast<SQLTCHAR *>(pszSQL), SQL_NTS));
if (!CheckHstmt(nRetCode, hstmt))
AfxThrowDBException(nRetCode, this, hstmt);
SWORD nResultColumns;
do
{
AFX_ODBC_CALL(::SQLNumResultCols(hstmt, &nResultColumns));
if(!CheckHstmt(nRetCode, hstmt))
AfxThrowDBException(nRetCode, this, hstmt);
if (nResultColumns != 0)
do
{
AFX_ODBC_CALL(::SQLFetch(hstmt));
if(!CheckHstmt(nRetCode, hstmt))
AfxThrowDBException(nRetCode, this, hstmt);
} while (nRetCode != SQL_NO_DATA_FOUND);
AFX_ODBC_CALL(::SQLMoreResults(hstmt));
if(!CheckHstmt(nRetCode, hstmt))
AfxThrowDBException(nRetCode, this, hstmt);
} while (nRetCode != SQL_NO_DATA_FOUND);
}
CATCH_ALL(e)
{
::SQLCancel(hstmt);
AFX_SQL_SYNC(::SQLFreeStmt(hstmt, SQL_DROP));
THROW_LAST();
}
END_CATCH_ALL
AFX_SQL_SYNC(::SQLFreeStmt(hstmt, SQL_DROP));
}
// Shutdown pending query for CDatabase's private m_hstmt
void CDatabase::Cancel()
{
ASSERT_VALID(this);
ASSERT(m_hdbc != SQL_NULL_HDBC);
::SQLCancel(m_hstmt);
}
// Disconnect connection
void CDatabase::Close()
{
ASSERT_VALID(this);
// Close any open recordsets
AfxLockGlobals(CRIT_ODBC);
TRY
{
while (!m_listRecordsets.IsEmpty())
{
CRecordset* pSet = (CRecordset*)m_listRecordsets.GetHead();
pSet->Close(); // will implicitly remove from list
pSet->m_pDatabase = NULL;
}
}
CATCH_ALL(e)
{
AfxUnlockGlobals(CRIT_ODBC);
THROW_LAST();
}
END_CATCH_ALL
AfxUnlockGlobals(CRIT_ODBC);
if (m_hdbc != SQL_NULL_HDBC)
{
RETCODE nRetCode;
AFX_SQL_SYNC(::SQLDisconnect(m_hdbc));
AFX_SQL_SYNC(::SQLFreeConnect(m_hdbc));
m_hdbc = SQL_NULL_HDBC;
_AFX_DB_STATE* pDbState = _afxDbState;
AfxLockGlobals(CRIT_ODBC);
ASSERT(pDbState->m_nAllocatedConnections != 0);
pDbState->m_nAllocatedConnections--;
AfxUnlockGlobals(CRIT_ODBC);
}
}
// Silently disconnect and free all ODBC resources. Don't throw any exceptions
void CDatabase::Free()
{
ASSERT_VALID(this);
// Trap failures upon close
TRY
{
Close();
}
CATCH_ALL(e)
{
// Nothing we can do
TRACE(traceDatabase, 0, _T("Error: exception by CDatabase::Close() ignored in CDatabase::Free().\n"));
DELETE_EXCEPTION(e);
}
END_CATCH_ALL
// free henv if refcount goes to 0
_AFX_DB_STATE* pDbState = _afxDbState;
AfxLockGlobals(CRIT_ODBC);
if (pDbState->m_henvAllConnections != SQL_NULL_HENV)
{
ASSERT(pDbState->m_nAllocatedConnections >= 0);
if (pDbState->m_nAllocatedConnections == 0)
{
// free last connection - release HENV
RETCODE nRetCodeEnv = ::SQLFreeEnv(pDbState->m_henvAllConnections);
#ifdef _DEBUG
if (nRetCodeEnv != SQL_SUCCESS)
// Nothing we can do
TRACE(traceDatabase, 0, _T("Error: SQLFreeEnv failure ignored in CDatabase::Free().\n"));
#endif
pDbState->m_henvAllConnections = SQL_NULL_HENV;
}
}
AfxUnlockGlobals(CRIT_ODBC);
}
void CDatabase::OnSetOptions(HSTMT hstmt)
{
RETCODE nRetCode;
ASSERT_VALID(this);
ASSERT(m_hdbc != SQL_NULL_HDBC);
if (m_dwQueryTimeout != -1)
{
// Attempt to set query timeout. Ignore failure
AFX_SQL_SYNC(::SQLSetStmtOption(hstmt, SQL_QUERY_TIMEOUT,
m_dwQueryTimeout));
if (!Check(nRetCode))
// don't attempt it again
m_dwQueryTimeout = (DWORD)-1;
}
}
CString CDatabase::GetDatabaseName() const
{
ASSERT_VALID(this);
ASSERT(m_hdbc != SQL_NULL_HDBC);
CString str;
SWORD nResult;
RETCODE nRetCode;
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DATABASE_NAME, NULL, 0, &nResult));
if (Check(nRetCode))
{
LPTSTR pszName =
str.GetBufferSetLength(nResult / sizeof(CString::XCHAR));
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DATABASE_NAME, pszName,
SWORD(nResult + sizeof(CString::XCHAR)), &nResult));
str.ReleaseBuffer();
if(!Check(nRetCode))
str.Empty();
}
return str;
}
BOOL CDatabase::BeginTrans()
{
ASSERT_VALID(this);
ASSERT(m_hdbc != SQL_NULL_HDBC);
if (!m_bTransactions)
return FALSE;
// Only 1 level of transactions supported
#ifdef _DEBUG
ASSERT(!m_bTransactionPending);
#endif
RETCODE nRetCode;
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_AUTOCOMMIT,
SQL_AUTOCOMMIT_OFF));
DEBUG_ONLY(m_bTransactionPending = TRUE);
return Check(nRetCode);
}
BOOL CDatabase::CommitTrans()
{
ASSERT_VALID(this);
ASSERT(m_hdbc != SQL_NULL_HDBC);
if (!m_bTransactions)
return FALSE;
// BeginTrans must be called first
#ifdef _DEBUG
ASSERT(m_bTransactionPending);
#endif
_AFX_DB_STATE* pDbState = _afxDbState;
RETCODE nRetCode;
AFX_SQL_SYNC(::SQLTransact(pDbState->m_henvAllConnections, m_hdbc, SQL_COMMIT));
BOOL bSuccess = Check(nRetCode);
// Turn back on auto commit
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_AUTOCOMMIT,
SQL_AUTOCOMMIT_ON));
DEBUG_ONLY(m_bTransactionPending = FALSE);
return bSuccess;
}
BOOL CDatabase::Rollback()
{
ASSERT_VALID(this);
ASSERT(m_hdbc != SQL_NULL_HDBC);
if (!m_bTransactions)
return FALSE;
// BeginTrans must be called first
#ifdef _DEBUG
ASSERT(m_bTransactionPending);
#endif
_AFX_DB_STATE* pDbState = _afxDbState;
RETCODE nRetCode;
AFX_SQL_SYNC(::SQLTransact(pDbState->m_henvAllConnections, m_hdbc, SQL_ROLLBACK));
BOOL bSuccess = Check(nRetCode);
// Turn back on auto commit
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_AUTOCOMMIT,
SQL_AUTOCOMMIT_ON));
DEBUG_ONLY(m_bTransactionPending = FALSE);
return bSuccess;
}
// Screen for errors.
BOOL CDatabase::Check(RETCODE nRetCode) const
{
return CheckHstmt(nRetCode, SQL_NULL_HSTMT);
}
BOOL CDatabase::CheckHstmt(RETCODE nRetCode, HSTMT hstmt) const
{
ASSERT_VALID(this);
UNUSED(hstmt);
switch (nRetCode)
{
case SQL_SUCCESS_WITH_INFO:
#ifdef _DEBUG
{
CDBException e(nRetCode);
TRACE(traceDatabase, 0, _T("Warning: ODBC Success With Info, "));
e.BuildErrorString((CDatabase*)this, hstmt);
}
#endif // _DEBUG
// Fall through
case SQL_SUCCESS:
case SQL_NO_DATA_FOUND:
return TRUE;
}
return FALSE;
}
//////////////////////////////////////////////////////////////////////////////
// CDatabase internal functions
//Replace brackets in SQL string with SQL_IDENTIFIER_QUOTE_CHAR
void CDatabase::ReplaceBrackets(LPTSTR lpchSQL)
{
BOOL bInLiteral = FALSE;
LPTSTR lpchNewSQL = lpchSQL;
while (*lpchSQL)
{
if (*lpchSQL == _afxLiteralSeparator)
{
// Handle escaped literal
if (*_tcsinc(lpchSQL) == _afxLiteralSeparator)
{
*lpchNewSQL = *lpchSQL;
lpchSQL = _tcsinc(lpchSQL);
lpchNewSQL = _tcsinc(lpchNewSQL);
}
else
bInLiteral = !bInLiteral;
*lpchNewSQL = *lpchSQL;
}
else if (*lpchSQL == _T('[') && !bInLiteral)
{
if (*_tcsinc(lpchSQL) == _T('['))
{
// Handle escaped left bracket by inserting one '['
*lpchNewSQL = *lpchSQL;
lpchSQL = _tcsinc(lpchSQL);
}
else
*lpchNewSQL = m_chIDQuoteChar;
}
else if (*lpchSQL == _T(']') && !bInLiteral)
{
if (*_tcsinc(lpchSQL) == _T(']'))
{
// Handle escaped right bracket by inserting one ']'
*lpchNewSQL = *lpchSQL;
lpchSQL = _tcsinc(lpchSQL);
}
else
*lpchNewSQL = m_chIDQuoteChar;
}
else
*lpchNewSQL = *lpchSQL;
lpchSQL = _tcsinc(lpchSQL);
lpchNewSQL = _tcsinc(lpchNewSQL);
}
*lpchNewSQL = _T('\0');
}
// Allocate an henv (first time called) and hdbc
void CDatabase::AllocConnect(DWORD dwOptions)
{
ASSERT_VALID(this);
if (m_hdbc != SQL_NULL_HDBC)
return;
_AFX_DB_STATE* pDbState = _afxDbState;
RETCODE nRetCode;
AfxLockGlobals(CRIT_ODBC);
if (pDbState->m_henvAllConnections == SQL_NULL_HENV)
{
ASSERT(pDbState->m_nAllocatedConnections == 0);
// need to allocate an environment for first connection
AFX_SQL_SYNC(::SQLAllocEnv(&pDbState->m_henvAllConnections));
if (!Check(nRetCode))
{
AfxUnlockGlobals(CRIT_ODBC);
AfxThrowMemoryException(); // fatal
}
}
ASSERT(pDbState->m_henvAllConnections != SQL_NULL_HENV);
AFX_SQL_SYNC(::SQLAllocConnect(pDbState->m_henvAllConnections, &m_hdbc));
if (!Check(nRetCode))
{
AfxUnlockGlobals(CRIT_ODBC);
ThrowDBException(nRetCode); // fatal
}
pDbState->m_nAllocatedConnections++; // allocated at least
AfxUnlockGlobals(CRIT_ODBC);
#ifdef _DEBUG
if (bTraceSql)
{
::SQLSetConnectOption(m_hdbc, SQL_OPT_TRACEFILE,
(SQLULEN)(DWORD_PTR)"odbccall.txt");
::SQLSetConnectOption(m_hdbc, SQL_OPT_TRACE, 1);
}
#endif // _DEBUG
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_LOGIN_TIMEOUT,
m_dwLoginTimeout));
#ifdef _DEBUG
if (nRetCode != SQL_SUCCESS && nRetCode != SQL_SUCCESS_WITH_INFO)
TRACE(traceDatabase, 0, _T("Warning: Failure setting login timeout.\n"));
#endif
if (!m_bUpdatable)
{
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_ACCESS_MODE,
SQL_MODE_READ_ONLY));
#ifdef _DEBUG
if (nRetCode != SQL_SUCCESS && nRetCode != SQL_SUCCESS_WITH_INFO)
TRACE(traceDatabase, 0, _T("Warning: Failure setting read only access mode.\n"));
#endif
}
// Turn on cursor lib support
if (dwOptions & useCursorLib)
{
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc,
SQL_ODBC_CURSORS, SQL_CUR_USE_ODBC));
// With cursor library added records immediately in result set
m_bIncRecordCountOnAdd = TRUE;
}
}
BOOL CDatabase::Connect(DWORD dwOptions)
{
HWND hWndTop;
HWND hWnd = CWnd::GetSafeOwner_(NULL, &hWndTop);
if (hWnd == NULL)
hWnd = ::GetDesktopWindow();
TCHAR szConnectOutput[MAX_CONNECT_LEN];
TCHAR *pszConnectInput = const_cast<LPTSTR>(static_cast<LPCTSTR>(m_strConnect));
RETCODE nRetCode;
SWORD nResult;
UWORD wConnectOption = SQL_DRIVER_COMPLETE;
if (dwOptions & noOdbcDialog)
wConnectOption = SQL_DRIVER_NOPROMPT;
else if (dwOptions & forceOdbcDialog)
wConnectOption = SQL_DRIVER_PROMPT;
AFX_SQL_SYNC(::SQLDriverConnect(m_hdbc, hWnd, reinterpret_cast<SQLTCHAR *>(pszConnectInput),
SQL_NTS, reinterpret_cast<SQLTCHAR *>(szConnectOutput), sizeof(szConnectOutput),
&nResult, wConnectOption));
if (hWndTop != NULL)
::EnableWindow(hWndTop, TRUE);
// If user hit 'Cancel'
if (nRetCode == SQL_NO_DATA_FOUND)
{
Free();
return FALSE;
}
if (!Check(nRetCode))
{
#ifdef _DEBUG
if (hWnd == NULL)
TRACE(traceDatabase, 0, _T("Error: No default window (AfxGetApp()->m_pMainWnd) for SQLDriverConnect.\n"));
#endif
ThrowDBException(nRetCode);
}
// Connect strings must have "ODBC;"
m_strConnect = _afxODBCTrail;
// Save connect string returned from ODBC
m_strConnect += szConnectOutput;
return TRUE;
}
void CDatabase::VerifyConnect()
{
RETCODE nRetCode;
SWORD nResult;
SWORD nAPIConformance;
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_ODBC_API_CONFORMANCE,
&nAPIConformance, sizeof(nAPIConformance), &nResult));
if (!Check(nRetCode))
ThrowDBException(nRetCode);
if (nAPIConformance < SQL_OAC_LEVEL1)
ThrowDBException(AFX_SQL_ERROR_API_CONFORMANCE);
SWORD nSQLConformance;
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_ODBC_SQL_CONFORMANCE,
&nSQLConformance, sizeof(nSQLConformance), &nResult));
if (!Check(nRetCode))
ThrowDBException(nRetCode);
if (nSQLConformance < SQL_OSC_MINIMUM)
ThrowDBException(AFX_SQL_ERROR_SQL_CONFORMANCE);
}
void CDatabase::GetConnectInfo()
{
RETCODE nRetCode;
SWORD nResult;
// Reset the database update options
m_dwUpdateOptions = 0;
// Check for SQLSetPos support
UDWORD dwDriverPosOperations;
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_POS_OPERATIONS,
&dwDriverPosOperations, sizeof(dwDriverPosOperations), &nResult));
if (Check(nRetCode) &&
(dwDriverPosOperations & SQL_POS_UPDATE) &&
(dwDriverPosOperations & SQL_POS_DELETE) &&
(dwDriverPosOperations & SQL_POS_ADD))
m_dwUpdateOptions |= AFX_SQL_SETPOSUPDATES;
// Check for positioned update SQL support
UDWORD dwPositionedStatements;
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_POSITIONED_STATEMENTS,
&dwPositionedStatements, sizeof(dwPositionedStatements),
&nResult));
if (Check(nRetCode) &&
(dwPositionedStatements & SQL_PS_POSITIONED_DELETE) &&
(dwPositionedStatements & SQL_PS_POSITIONED_UPDATE))
m_dwUpdateOptions |= AFX_SQL_POSITIONEDSQL;
// Check for transaction support
SWORD nTxnCapable;
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_TXN_CAPABLE, &nTxnCapable,
sizeof(nTxnCapable), &nResult));
if (Check(nRetCode) && nTxnCapable != SQL_TC_NONE)
m_bTransactions = TRUE;
// Cache the effect of transactions on cursors
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_CURSOR_COMMIT_BEHAVIOR,
&m_nCursorCommitBehavior, sizeof(m_nCursorCommitBehavior),
&nResult));
if (!Check(nRetCode))
m_nCursorCommitBehavior = SQL_ERROR;
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_CURSOR_ROLLBACK_BEHAVIOR,
&m_nCursorRollbackBehavior, sizeof(m_nCursorRollbackBehavior),
&nResult));
if (!Check(nRetCode))
m_nCursorRollbackBehavior = SQL_ERROR;
// Cache bookmark attributes
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_BOOKMARK_PERSISTENCE,
&m_dwBookmarkAttributes, sizeof(m_dwBookmarkAttributes),
&nResult));
Check(nRetCode);
// Check for SQLGetData support req'd by RFX_LongBinary
UDWORD dwGetDataExtensions;
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_GETDATA_EXTENSIONS,
&dwGetDataExtensions, sizeof(dwGetDataExtensions),
&nResult));
if (!Check(nRetCode))
dwGetDataExtensions = 0;
if (dwGetDataExtensions & SQL_GD_BOUND)
m_dwUpdateOptions |= AFX_SQL_GDBOUND;
if (m_bUpdatable)
{
// Make sure data source is Updatable
TCHAR szReadOnly[10];
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DATA_SOURCE_READ_ONLY,
szReadOnly, sizeof(szReadOnly), &nResult));
if (Check(nRetCode) && nResult == sizeof(TCHAR))
m_bUpdatable = szReadOnly[0] == _T('N');
else
m_bUpdatable = FALSE;
#ifdef _DEBUG
if (!m_bUpdatable)
TRACE(traceDatabase, 0, _T("Warning: data source is readonly.\n"));
#endif
}
else
{
// Make data source is !Updatable
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc,
SQL_ACCESS_MODE, SQL_MODE_READ_ONLY));
}
// Cache the quote char to use when constructing SQL
TCHAR szIDQuoteChar[2];
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_IDENTIFIER_QUOTE_CHAR,
szIDQuoteChar, sizeof(szIDQuoteChar), &nResult));
if (Check(nRetCode) && nResult == sizeof(TCHAR))
m_chIDQuoteChar = szIDQuoteChar[0];
else
m_chIDQuoteChar = '\"';
#ifdef _DEBUG
TCHAR szInfo[64];
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DBMS_NAME,
szInfo, sizeof(szInfo), &nResult));
if (Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("DBMS: %s\n"), szInfo);
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DBMS_VER,
szInfo, sizeof(szInfo), &nResult));
if (Check(nRetCode))
TRACE(traceDatabase, 0, _T("Version: %s\n"), szInfo);
}
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_ODBC_VER,
szInfo, sizeof(szInfo), &nResult));
if(Check(nRetCode))
TRACE(traceDatabase, 0, _T("ODBC Driver Manager Version: %s\n"), szInfo);
#endif // _DEBUG
}
void CDatabase::BindParameters(HSTMT /* hstmt */)
{
// Must override and call SQLBindParameter directly
}
//////////////////////////////////////////////////////////////////////////////
// CDatabase diagnostics
#ifdef _DEBUG
void CDatabase::AssertValid() const
{
CObject::AssertValid();
}
void CDatabase::Dump(CDumpContext& dc) const
{
CObject::Dump(dc);
dc << _T("m_hdbc = ") << m_hdbc;
dc << _T("\nm_strConnect = ") << m_strConnect;
dc << _T("\nm_bUpdatable = ") << m_bUpdatable;
dc << _T("\nm_bTransactions = ") << m_bTransactions;
dc << _T("\nm_bTransactionPending = ") << m_bTransactionPending;
dc << _T("\nm_dwLoginTimeout = ") << m_dwLoginTimeout;
dc << _T("\nm_dwQueryTimeout = ") << m_dwQueryTimeout;
if (dc.GetDepth() > 0)
{
_AFX_DB_STATE* pDbState = _afxDbState;
dc << _T("\nwith env:");
dc << _T("\n\tnAllocated = ") << pDbState->m_nAllocatedConnections;
dc << _T("\n\thenvAllConnections = ") << pDbState->m_henvAllConnections;
}
dc << _T("\n");
}
#endif // _DEBUG
//////////////////////////////////////////////////////////////////////////////
// CRecordset helpers
void AFXAPI _AfxSetCurrentRecord(long* plCurrentRecord, long nRows, RETCODE nRetCode);
void AFXAPI _AfxSetRecordCount(long* plRecordCount, long lCurrentRecord,
BOOL bEOFSeen, RETCODE nRetCode);
//////////////////////////////////////////////////////////////////////////////
// CRecordset
CRecordset::CRecordset(CDatabase* pDatabase)
{
ASSERT(pDatabase == NULL || AfxIsValidAddress(pDatabase, sizeof(CDatabase)));
m_pDatabase = pDatabase;
m_nOpenType = snapshot;
m_lOpen = AFX_RECORDSET_STATUS_UNKNOWN;
m_nEditMode = noMode;
m_nDefaultType = snapshot;
m_dwOptions = none;
m_bAppendable = FALSE;
m_bUpdatable = FALSE;
m_bScrollable = FALSE;
m_bRecordsetDb = FALSE;
m_bRebindParams = FALSE;
m_bLongBinaryColumns = FALSE;
m_nLockMode = optimistic;
m_dwInitialGetDataLen = 0;
m_rgODBCFieldInfos = NULL;
m_rgFieldInfos = NULL;
m_rgRowStatus = NULL;
m_dwRowsetSize = 25;
m_dwAllocatedRowsetSize = 0;
m_nFields = 0;
m_nParams = 0;
m_nFieldsBound = 0;
m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
m_lRecordCount = 0;
m_bUseUpdateSQL = FALSE;
m_bUseODBCCursorLib = FALSE;
m_nResultCols = -1;
m_bCheckCacheForDirtyFields = TRUE;
m_pbFieldFlags = NULL;
m_pbParamFlags = NULL;
m_plParamLength = NULL;
m_pvFieldProxy = NULL;
m_pvParamProxy = NULL;
m_nProxyFields = 0;
m_nProxyParams = 0;
m_hstmtUpdate = SQL_NULL_HSTMT;
m_hstmt = SQL_NULL_HSTMT;
if (m_pDatabase != NULL && m_pDatabase->IsOpen())
{
ASSERT_VALID(m_pDatabase);
TRY
{
RETCODE nRetCode;
AFX_SQL_SYNC(::SQLAllocStmt(m_pDatabase->m_hdbc, &m_hstmt));
if (!Check(nRetCode))
ThrowDBException(SQL_INVALID_HANDLE);
// Add to list of CRecordsets with alloced hstmts
AfxLockGlobals(CRIT_ODBC);
TRY
{
m_pDatabase->m_listRecordsets.AddHead(this);
}
CATCH_ALL(e)
{
AfxUnlockGlobals(CRIT_ODBC);
THROW_LAST();
}
END_CATCH_ALL
AfxUnlockGlobals(CRIT_ODBC);
}
CATCH_ALL(e)
{
ASSERT(m_hstmt == SQL_NULL_HSTMT);
DELETE_EXCEPTION(e);
}
END_CATCH_ALL
}
}
CRecordset::~CRecordset()
{
ASSERT_VALID(this);
TRY
{
if (m_hstmt != NULL)
{
#ifdef _DEBUG
if (m_dwOptions & useMultiRowFetch)
{
TRACE(traceDatabase, 0, _T("\nWARNING: Close called implicitly from destructor."));
TRACE(traceDatabase, 0, _T("\nUse of multi row fetch requires explicit call"));
TRACE(traceDatabase, 0, _T("\nto Close or memory leaks will result.\n"));
}
#endif
Close();
}
if (m_bRecordsetDb)
delete m_pDatabase;
m_pDatabase = NULL;
}
CATCH_ALL(e)
{
// Nothing we can do
TRACE(traceDatabase, 0, _T("Error: Exception ignored in ~CRecordset().\n"));
DELETE_EXCEPTION(e);
}
END_CATCH_ALL
}
BOOL CRecordset::Open(UINT nOpenType, LPCTSTR lpszSQL, DWORD dwOptions)
{
ASSERT(!IsOpen());
ASSERT_VALID(this);
ASSERT(lpszSQL == NULL || AfxIsValidString(lpszSQL));
ASSERT(nOpenType == AFX_DB_USE_DEFAULT_TYPE ||
nOpenType == dynaset || nOpenType == snapshot ||
nOpenType == forwardOnly || nOpenType == dynamic);
ASSERT(!(dwOptions & readOnly && dwOptions & appendOnly));
// Can only use optimizeBulkAdd with appendOnly recordsets
ASSERT((dwOptions & optimizeBulkAdd && dwOptions & appendOnly) ||
!(dwOptions & optimizeBulkAdd));
// forwardOnly recordsets have limited functionality
ASSERT(!(nOpenType == forwardOnly && dwOptions & skipDeletedRecords));
// Cache state info and allocate hstmt
SetState(nOpenType, lpszSQL, dwOptions);
if(!AllocHstmt())
return FALSE;
// Check if bookmarks upported (CanBookmark depends on open DB)
ASSERT(dwOptions & useBookmarks ? CanBookmark() : TRUE);
TRY
{
OnSetOptions(m_hstmt);
// Allocate the field/param status arrays, if necessary
BOOL bUnbound = FALSE;
if (m_nFields > 0 || m_nParams > 0)
AllocStatusArrays();
else
bUnbound = TRUE;
// Build SQL and prep/execute or just execute direct
BuildSQL(lpszSQL);
PrepareAndExecute();
// Cache some field info and prepare the rowset
AllocAndCacheFieldInfo();
AllocRowset();
// If late binding, still need to allocate status arrays
if (bUnbound && (m_nFields > 0 || m_nParams > 0))
AllocStatusArrays();
// Give derived classes a call before binding
PreBindFields();
// Fetch the first row of data
MoveNext();
// If EOF, then result set empty, so set BOF as well
m_bBOF = m_bEOF;
}
CATCH_ALL(e)
{
Close();
THROW_LAST();
}
END_CATCH_ALL
return TRUE;
}
void CRecordset::Close()
{
ASSERT_VALID(this);
// Can't close if database has been deleted
ASSERT(m_pDatabase != NULL);
// This will force a requery for cursor name if reopened.
m_strCursorName.Empty();
if (m_rgFieldInfos != NULL &&
m_nFields > 0 && m_bCheckCacheForDirtyFields)
{
FreeDataCache();
}
FreeRowset();
m_nEditMode = noMode;
delete [] m_rgFieldInfos;
m_rgFieldInfos = NULL;
delete [] m_rgODBCFieldInfos;
m_rgODBCFieldInfos = NULL;
delete [] m_pbFieldFlags;
m_pbFieldFlags = NULL;
delete [] m_pbParamFlags;
m_pbParamFlags = NULL;
if (m_pvFieldProxy != NULL)
{
for (UINT nField = 0; nField < m_nProxyFields; nField++)
delete m_pvFieldProxy[nField];
delete [] m_pvFieldProxy;
m_pvFieldProxy = NULL;
m_nProxyFields = 0;
}
if (m_pvParamProxy != NULL)
{
for (UINT nParam = 0; nParam < m_nProxyParams; nParam++)
delete m_pvParamProxy[nParam];
delete [] m_pvParamProxy;
m_pvParamProxy = NULL;
m_nProxyParams = 0;
}
delete [] m_plParamLength;
m_plParamLength = NULL;
RETCODE nRetCode;
if (m_hstmt != SQL_NULL_HSTMT)
{
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmt, SQL_DROP));
m_hstmt = SQL_NULL_HSTMT;
}
if (m_hstmtUpdate != SQL_NULL_HSTMT)
{
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmtUpdate, SQL_DROP));
m_hstmtUpdate = SQL_NULL_HSTMT;
}
// Remove CRecordset from CDatabase's list
AfxLockGlobals(CRIT_ODBC);
TRY
{
POSITION pos = m_pDatabase->m_listRecordsets.Find(this);
if (pos != NULL)
m_pDatabase->m_listRecordsets.RemoveAt(pos);
#ifdef _DEBUG
else
TRACE(traceDatabase, 0, _T("WARNING: CRecordset not found in m_pDatabase->m_listRecordsets.\n"));
#endif
}
CATCH_ALL(e)
{
AfxUnlockGlobals(CRIT_ODBC);
THROW_LAST();
}
END_CATCH_ALL
AfxUnlockGlobals(CRIT_ODBC);
m_lOpen = AFX_RECORDSET_STATUS_CLOSED;
m_bBOF = TRUE;
m_bEOF = TRUE;
m_bDeleted = FALSE;
m_bAppendable = FALSE;
m_bUpdatable = FALSE;
m_bScrollable = FALSE;
m_bRebindParams = FALSE;
m_bLongBinaryColumns = FALSE;
m_nLockMode = optimistic;
m_nFieldsBound = 0;
m_nResultCols = -1;
}
BOOL CRecordset::IsOpen() const
// Note: assumes base class CRecordset::Close called
{
if (m_hstmt == NULL)
return FALSE;
if (m_lOpen == AFX_RECORDSET_STATUS_OPEN)
return TRUE;
RETCODE nRetCode;
SWORD nCols;
AFX_ODBC_CALL(::SQLNumResultCols(m_hstmt, &nCols));
if (!Check(nRetCode))
{
// If function sequence error, CRecordset not open
CDBException* e = new CDBException(nRetCode);
e->BuildErrorString(m_pDatabase, m_hstmt, FALSE);
if (e->m_strStateNativeOrigin.Find(_afxOutOfSequence) >= 0)
{
e->Delete();
return FALSE;
}
else
{
#ifdef _DEBUG
TRACE(traceDatabase, 0, _T("Error: SQLNumResultCols failed during IsOpen().\n"));
e->TraceErrorMessage(e->m_strError);
e->TraceErrorMessage(e->m_strStateNativeOrigin);
#endif
THROW(e);
}
}
BOOL bOpen = FALSE;
if (nCols != 0)
bOpen = TRUE;
return bOpen;
}
BOOL CRecordset::IsFieldDirty(void* pv)
{
ASSERT_VALID(this);
ASSERT(!(m_dwOptions & useMultiRowFetch));
if (m_nFields <= 0)
{
ASSERT(FALSE);
return FALSE;
}
// If not in update op fields can't be dirty
// must compare saved and current values
if (m_nEditMode == noMode)
return FALSE;
// Must compare values to find dirty fields if necessary
if (m_bCheckCacheForDirtyFields)
{
if (m_nEditMode == edit)
MarkForUpdate();
else
MarkForAddNew();
}
int nIndex = 0, nIndexEnd;
if (pv == NULL)
nIndexEnd = m_nFields - 1;
else
{
// GetBoundFieldIndex returns 1-based index
nIndex = nIndexEnd = GetBoundFieldIndex(pv) - 1;
// must be address of field member
ASSERT(nIndex >= 0);
}
BOOL bDirty = FALSE;
while (nIndex <= nIndexEnd && !bDirty)
bDirty = IsFieldStatusDirty(nIndex++);
return bDirty;
}
BOOL CRecordset::IsFieldNull(void* pv)
{
ASSERT_VALID(this);
ASSERT(!(m_dwOptions & useMultiRowFetch));
int nIndex;
BOOL bRetVal;
if (pv == NULL)
{
bRetVal = FALSE;
for (nIndex = 0; !bRetVal && nIndex <= int(m_nFields-1); nIndex++)
bRetVal = IsFieldStatusNull((DWORD) nIndex);
}
else
{
nIndex = GetBoundFieldIndex(pv) - 1;
if (nIndex < 0)
ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
bRetVal = IsFieldStatusNull((DWORD) nIndex);
}
return bRetVal;
}
BOOL CRecordset::IsFieldNullable(void* pv)
{
ASSERT_VALID(this);
if (pv == NULL)
{
// Must specify valid column name
ASSERT(FALSE);
return FALSE;
}
int nIndex = GetBoundFieldIndex(pv) - 1;
if (nIndex < 0)
ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
return IsFieldNullable((DWORD)nIndex);
}
BOOL CRecordset::CanBookmark() const
{
ASSERT_VALID(this);
ASSERT(m_pDatabase->IsOpen());
if (!(m_dwOptions & useBookmarks) ||
(m_nOpenType == forwardOnly && !(m_dwOptions & useExtendedFetch)))
return FALSE;
return m_pDatabase->GetBookmarkPersistence() & SQL_BP_SCROLL;
}
void CRecordset::Move(long nRows, WORD wFetchType)
{
ASSERT_VALID(this);
ASSERT(m_hstmt != SQL_NULL_HSTMT);
// First call - fields haven't been bound (m_nFieldsBound will change)
if (m_nFieldsBound == 0)
{
InitRecord();
ResetCursor();
}
if (m_nFieldsBound > 0)
{
// Reset field flags - mark all clean, all non-null
memset(m_pbFieldFlags, 0, m_nFields);
// Clear any edit mode that was set
m_nEditMode = noMode;
}
// Check scrollability, EOF/BOF status
CheckRowsetCurrencyStatus(wFetchType, nRows);
RETCODE nRetCode;
// Fetch the data, skipping deleted records if necessary
if ((wFetchType == SQL_FETCH_FIRST ||
wFetchType == SQL_FETCH_LAST ||
wFetchType == SQL_FETCH_NEXT ||
wFetchType == SQL_FETCH_PRIOR ||
wFetchType == SQL_FETCH_RELATIVE) &&
m_dwOptions & skipDeletedRecords)
{
SkipDeletedRecords(wFetchType, nRows, &m_dwRowsFetched, &nRetCode);
}
else
// Fetch the data and check for errors
nRetCode = FetchData(wFetchType, nRows, &m_dwRowsFetched);
// Set currency status and increment the record counters
SetRowsetCurrencyStatus(nRetCode, wFetchType, nRows, m_dwRowsFetched);
// Need to fixup bound fields in some cases
if (m_nFields > 0 && !IsEOF() && !IsBOF() &&
!(m_dwOptions & useMultiRowFetch))
{
Fixups();
}
}
void CRecordset::CheckRowsetError(RETCODE nRetCode)
{
if (nRetCode == SQL_SUCCESS_WITH_INFO)
{
CDBException e(nRetCode);
// Build the error string but don't send nuisance output to TRACE window
e.BuildErrorString(m_pDatabase, m_hstmt, FALSE);
if (e.m_strStateNativeOrigin.Find(_afxDataTruncated) >= 0)
{
// Ignore data truncated warning if binding long binary columns
// (may mask non-long binary truncation warnings or other warnings)
if (!((m_pDatabase->m_dwUpdateOptions & AFX_SQL_SETPOSUPDATES) &&
m_bLongBinaryColumns))
{
NO_CPP_EXCEPTION(e.Empty());
TRACE(traceDatabase, 0, _T("Error: field data truncated during data fetch.\n"));
ThrowDBException(AFX_SQL_ERROR_DATA_TRUNCATED);
}
}
else if (e.m_strStateNativeOrigin.Find(_afxRowFetch) >= 0)
{
#ifdef _DEBUG
TRACE(traceDatabase, 0, _T("Error: fetching row from server.\n"));
e.TraceErrorMessage(e.m_strError);
e.TraceErrorMessage(e.m_strStateNativeOrigin);
#endif
NO_CPP_EXCEPTION(e.Empty());
ThrowDBException(AFX_SQL_ERROR_ROW_FETCH);
}
#ifdef _DEBUG
else
{
// Not a truncation or row fetch warning so send debug output
TRACE(traceDatabase, 0, _T("Warning: ODBC Success With Info,\n"));
e.TraceErrorMessage(e.m_strError);
e.TraceErrorMessage(e.m_strStateNativeOrigin);
}
#endif // _DEBUG
}
else if (!Check(nRetCode))
ThrowDBException(nRetCode);
}
void CRecordset::GetBookmark(CDBVariant& varBookmark)
{
ASSERT_VALID(this);
// Validate bookmarks are usable
if (!(m_dwOptions & useBookmarks))
ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_ENABLED);
else if (!CanBookmark())
ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_SUPPORTED);
// Currently ODBC only supports 4 byte bookmarks
// Initialize the variant to a long
if (varBookmark.m_dwType != DBVT_LONG)
{
varBookmark.Clear();
varBookmark.m_dwType = DBVT_LONG;
varBookmark.m_lVal = 0;
}
RETCODE nRetCode;
SQLLEN nActualSize;
// Retrieve the bookmark (column 0) data
AFX_ODBC_CALL(::SQLGetData(m_hstmt, 0, SQL_C_BOOKMARK,
&varBookmark.m_lVal, sizeof(varBookmark.m_lVal), &nActualSize));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: GetBookmark operation failed.\n"));
ThrowDBException(nRetCode);
}
}
void CRecordset::SetBookmark(const CDBVariant& varBookmark)
{
ASSERT_VALID(this);
// Validate bookmarks are usable
if (!(m_dwOptions & useBookmarks))
ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_ENABLED);
else if (!CanBookmark())
ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_SUPPORTED);
// Currently ODBC only supports 4 byte bookmarks
ASSERT(varBookmark.m_dwType == DBVT_LONG);
Move(varBookmark.m_lVal, SQL_FETCH_BOOKMARK);
}
void CRecordset::SetRowsetSize(DWORD dwNewRowsetSize)
{
ASSERT_VALID(this);
ASSERT(dwNewRowsetSize > 0);
// If not yet open, only set expected length
if (!IsOpen())
{
m_dwRowsetSize = dwNewRowsetSize;
return;
}
if (!(m_dwOptions & useMultiRowFetch))
{
// Only works if bulk row fetching!
ASSERT(FALSE);
return;
}
// Need to reallocate some memory if rowset size grows
if (m_dwAllocatedRowsetSize == 0 ||
(m_dwAllocatedRowsetSize < dwNewRowsetSize))
{
// If rowset already allocated, delete old and reallocate
FreeRowset();
m_rgRowStatus = new WORD[dwNewRowsetSize];
// If not a user allocated buffer grow the data buffers
if (!(m_dwOptions & userAllocMultiRowBuffers))
{
// Allocate the rowset field buffers
m_dwRowsetSize = dwNewRowsetSize;
CFieldExchange fx(CFieldExchange::AllocMultiRowBuffer, this);
DoBulkFieldExchange(&fx);
m_dwAllocatedRowsetSize = dwNewRowsetSize;
// Set bound fields to zero, rebind and reset bound field count
int nOldFieldsBound = m_nFieldsBound;
m_nFieldsBound = 0;
InitRecord();
m_nFieldsBound = nOldFieldsBound;
}
}
else
{
// Just reset the new rowset size
m_dwRowsetSize = dwNewRowsetSize;
}
RETCODE nRetCode;
AFX_SQL_SYNC(::SQLSetStmtOption(m_hstmt, SQL_ROWSET_SIZE,
m_dwRowsetSize));
}
void CRecordset::AddNew()
{
ASSERT_VALID(this);
ASSERT(m_hstmt != SQL_NULL_HSTMT);
// we can't construct an INSERT statement w/o any columns
ASSERT(m_nFields != 0);
if (!m_bAppendable)
{
ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
}
if (m_dwOptions & useMultiRowFetch)
{
// Can't use update methods on multi-row rowset
ASSERT(FALSE);
return;
}
if (m_bCheckCacheForDirtyFields && m_nFields > 0)
{
if (m_nEditMode == noMode)
{
// First addnew call, cache record values
StoreFields();
}
else
{
// subsequent Edit/AddNew call. Restore values, save them again
LoadFields();
StoreFields();
}
}
SetFieldNull(NULL);
SetFieldDirty(NULL, FALSE);
m_nEditMode = addnew;
}
void CRecordset::Edit()
{
ASSERT_VALID(this);
ASSERT(m_hstmt != SQL_NULL_HSTMT);
// we can't construct an UPDATE statement w/o any columns
ASSERT(m_nFields != 0);
if (!m_bUpdatable)
ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
if (m_dwOptions & useMultiRowFetch)
{
// Can't use update methods on multi-row rowset
ASSERT(FALSE);
return;
}
if (m_bEOF || m_bBOF || m_bDeleted)
{
TRACE(traceDatabase, 0, _T("Error: Edit attempt failed - not on a record.\n"));
ThrowDBException(AFX_SQL_ERROR_NO_CURRENT_RECORD);
}
if ((m_nOpenType == dynaset || m_nOpenType == dynamic) &&
m_nLockMode == pessimistic)
{
RETCODE nRetCode;
AFX_ODBC_CALL(::SQLSetPos(m_hstmt, 1, SQL_POSITION,
SQL_LCK_EXCLUSIVE));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: attempt to lock record failed during Edit function.\n"));
ThrowDBException(nRetCode);
}
}
if (m_bCheckCacheForDirtyFields && m_nFields > 0)
{
if (m_nEditMode == noMode)
// First edit call, cache record values
StoreFields();
else
{
// subsequent Edit/AddNew call. Restore values, save them again
LoadFields();
StoreFields();
}
}
m_nEditMode = edit;
}
BOOL CRecordset::Update()
{
ASSERT_VALID(this);
ASSERT(m_hstmt != SQL_NULL_HSTMT);
if (m_dwOptions & useMultiRowFetch)
{
// Can't use update methods on multi-row rowset
ASSERT(FALSE);
return FALSE;
}
if (m_nEditMode != addnew && m_nEditMode != edit)
{
TRACE(traceDatabase, 0, _T("Error: must enter Edit or AddNew mode before updating.\n"));
ThrowDBException(AFX_SQL_ERROR_ILLEGAL_MODE);
}
return UpdateInsertDelete();
}
void CRecordset::Delete()
{
ASSERT_VALID(this);
ASSERT(m_hstmt != SQL_NULL_HSTMT);
if (m_dwOptions & useMultiRowFetch)
{
// Can't use update methods on multi-row rowset
ASSERT(FALSE);
return;
}
if (m_nEditMode != noMode)
{
TRACE(traceDatabase, 0, _T("Error: attempted to delete while still in Edit or AddNew mode.\n"));
ThrowDBException(AFX_SQL_ERROR_ILLEGAL_MODE);
}
UpdateInsertDelete(); // This call can't fail in delete mode (noMode)
}
void CRecordset::CancelUpdate()
{
ASSERT_VALID(this);
ASSERT(IsOpen());
if (m_nEditMode == noMode)
// Do nothing if not in edit mode
return;
else
// Reset the edit mode
m_nEditMode = noMode;
// Restore cache if necessary
if (m_bCheckCacheForDirtyFields && m_nFields > 0)
LoadFields();
}
BOOL CRecordset::FlushResultSet()
{
RETCODE nRetCode;
AFX_ODBC_CALL(::SQLMoreResults(m_hstmt));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: attempt FlushResultSet failed.\n"));
AfxThrowDBException(nRetCode, m_pDatabase, m_hstmt);
}
// Reset state of cursor
ResetCursor();
return nRetCode != SQL_NO_DATA_FOUND;
}
void CRecordset::GetODBCFieldInfo(LPCTSTR lpszName,
CODBCFieldInfo& fieldinfo)
{
ASSERT_VALID(this);
ASSERT(IsOpen());
ASSERT(lpszName != NULL);
// No data or no column info fetched yet
if (GetODBCFieldCount() <= 0)
{
ASSERT(FALSE);
return;
}
// Get the index of the field corresponding to name
short nField = GetFieldIndexByName(lpszName);
GetODBCFieldInfo(nField, fieldinfo);
}
void CRecordset::GetODBCFieldInfo(short nIndex,
CODBCFieldInfo& fieldinfo)
{
ASSERT_VALID(this);
ASSERT(IsOpen());
// No data or no column info fetched yet
if (GetODBCFieldCount() <= 0)
{
ASSERT(FALSE);
return;
}
// Just copy the data into the field info
CODBCFieldInfo* pInfo = &m_rgODBCFieldInfos[nIndex];
fieldinfo.m_strName = pInfo->m_strName;
fieldinfo.m_nSQLType = pInfo->m_nSQLType;
fieldinfo.m_nPrecision = pInfo->m_nPrecision;
fieldinfo.m_nScale = pInfo->m_nScale;
fieldinfo.m_nNullability = pInfo->m_nNullability;
}
void CRecordset::GetFieldValue(LPCTSTR lpszName,
CDBVariant& varValue, short nFieldType)
{
ASSERT_VALID(this);
ASSERT(IsOpen());
ASSERT(lpszName != NULL);
// No data or no column info fetched yet
if (GetODBCFieldCount() <= 0)
{
ASSERT(FALSE);
varValue.Clear();
return;
}
// Get the index of the field corresponding to name
short nField = GetFieldIndexByName(lpszName);
GetFieldValue(nField, varValue, nFieldType);
}
void CRecordset::GetFieldValue(short nIndex,
CDBVariant& varValue, short nFieldType)
{
ASSERT_VALID(this);
ASSERT(IsOpen());
// Clear the previous variant
varValue.Clear();
// No data or no column info fetched yet
if (GetODBCFieldCount() <= 0)
{
ASSERT(FALSE);
return;
}
// Convert index to 1-based and check range
nIndex++;
if (nIndex < 1 || nIndex > GetODBCFieldCount())
{
ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
}
void* pvData = NULL;
SQLLEN nLen = 0;
// Determine the default field type and get the data buffer
if (nFieldType == DEFAULT_FIELD_TYPE)
{
nFieldType =
GetDefaultFieldType(m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
}
pvData = GetDataBuffer(varValue, nFieldType, &nLen,
m_rgODBCFieldInfos[nIndex - 1].m_nSQLType,
m_rgODBCFieldInfos[nIndex - 1].m_nPrecision);
// Now can actually get the data
SQLLEN nActualSize = GetData(m_pDatabase, m_hstmt, nIndex,
nFieldType, pvData,
(SQLLEN) (varValue.m_dwType == DBVT_WSTRING ? nLen * sizeof(WCHAR) : nLen),
m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
// Handle NULL data separately
if (nActualSize == SQL_NULL_DATA)
{
// Clear value and set the value NULL
varValue.Clear();
}
else
{
// May need to cleanup and call SQLGetData again if LONG_VAR data
switch(nFieldType)
{
case SQL_C_CHAR:
GetLongCharDataAndCleanup(m_pDatabase, m_hstmt, nIndex,
nActualSize, &pvData, nLen, *varValue.m_pstringA,
m_rgODBCFieldInfos[nIndex - 1].m_nSQLType, SQL_C_CHAR);
break;
case SQL_C_WCHAR:
GetLongCharDataAndCleanup(m_pDatabase, m_hstmt, nIndex,
nActualSize, &pvData, nLen, *varValue.m_pstringW,
m_rgODBCFieldInfos[nIndex - 1].m_nSQLType, SQL_C_WCHAR);
break;
case SQL_C_BINARY:
GetLongBinaryDataAndCleanup(m_pDatabase, m_hstmt, nIndex,
nActualSize, &pvData, nLen, varValue,
m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
break;
}
}
}
void CRecordset::GetFieldValue(LPCTSTR lpszName, CStringW& strValue)
{
ASSERT_VALID(this);
ASSERT(IsOpen());
ASSERT(lpszName != NULL);
// No data or no column info fetched yet
if (GetODBCFieldCount() <= 0)
{
ASSERT(FALSE);
return;
}
// Get the index of the field corresponding to name
short nField = GetFieldIndexByName(lpszName);
GetFieldValue(nField, strValue);
}
void CRecordset::GetFieldValue(LPCTSTR lpszName, CStringA& strValue)
{
ASSERT_VALID(this);
ASSERT(IsOpen());
ASSERT(lpszName != NULL);
// No data or no column info fetched yet
if (GetODBCFieldCount() <= 0)
{
ASSERT(FALSE);
return;
}
// Get the index of the field corresponding to name
short nField = GetFieldIndexByName(lpszName);
GetFieldValue(nField, strValue);
}
void CRecordset::GetFieldValue(short nIndex, CStringW &strValue)
{
GetFieldValueEx(nIndex, strValue, SQL_C_WCHAR);
}
void CRecordset::GetFieldValue(short nIndex, CStringA &strValue)
{
GetFieldValueEx(nIndex, strValue, SQL_C_CHAR);
}
void CRecordset::SetFieldDirty(void* pv, BOOL bDirty)
{
ASSERT_VALID(this);
int nIndex, nIndexEnd;
// If not setting all NULL, check simple case
if (pv != NULL)
{
// GetBoundFieldIndex returns 1-based index
nIndex = GetBoundFieldIndex(pv) - 1;
if (nIndex < 0)
{
// pv must be address of field member
ASSERT(FALSE);
return;
}
else
{
nIndexEnd = nIndex;
}
}
else
{
nIndex = 0;
nIndexEnd = m_nFields - 1;
}
while (nIndex <= nIndexEnd)
{
if (bDirty)
SetDirtyFieldStatus((DWORD)nIndex);
else
ClearDirtyFieldStatus((DWORD)nIndex);
nIndex++;
}
}
void CRecordset::SetFieldNull(void* pv, BOOL bNull)
{
ASSERT_VALID(this);
ASSERT(IsOpen());
ASSERT(!(m_dwOptions & useMultiRowFetch));
// If not setting all fields NULL, check simple case (param) first
if (pv != NULL)
{
// Cached index is 1-based
int nIndex = GetBoundParamIndex(pv) - 1;
if (nIndex >= 0)
{
if (bNull)
SetNullParamStatus(nIndex);
else
ClearNullParamStatus(nIndex);
return;
}
}
// Not a param, must be a field
if (m_nFields <= 0)
{
TRACE(traceDatabase, 0, _T("Warning: SetFieldNull() called with no bound fields.\n"));
return;
}
// Need field exchange mechanism to set PSEUDO NULL values
// and to reset data lengths (especially for RFX_LongBinary)
CFieldExchange fx(CFieldExchange::SetFieldNull, this, pv);
fx.m_nFieldFound = 0;
fx.m_bField = bNull;
DoFieldExchange(&fx);
// If no field found, m_nFieldFound will still be zero
ASSERT(fx.m_nFieldFound != 0);
}
void CRecordset::SetParamNull(int nIndex, BOOL bNull)
{
ASSERT_VALID(this);
ASSERT((DWORD)nIndex < m_nParams);
// Can be called before Open, but need to alloc status arrays first
if (!IsOpen())
AllocStatusArrays();
if (bNull)
SetNullParamStatus(nIndex);
else
ClearNullParamStatus(nIndex);
return;
}
void CRecordset::SetLockingMode(UINT nLockMode)
{
if (nLockMode == pessimistic)
{
RETCODE nRetCode;
UDWORD dwTypes;
SWORD nResult;
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_LOCK_TYPES,
&dwTypes, sizeof(dwTypes), &nResult));
if (!Check(nRetCode) || !(dwTypes & SQL_LCK_EXCLUSIVE))
ThrowDBException(AFX_SQL_ERROR_LOCK_MODE_NOT_SUPPORTED);
}
m_nLockMode = nLockMode;
}
BOOL CRecordset::Requery()
{
RETCODE nRetCode;
ASSERT_VALID(this);
ASSERT(IsOpen());
// Can't requery if using direct execution
if (m_dwOptions & executeDirect)
return FALSE;
TRY
{
// Detect changes to filter and sort
if ((m_strFilter != m_strRequeryFilter) || (m_strSort != m_strRequerySort))
{
m_strRequeryFilter = m_strFilter;
m_strRequerySort = m_strSort;
Close();
if (m_strRequerySQL.IsEmpty())
return Open(m_nOpenType, NULL, m_dwOptions);
else
return Open(m_nOpenType, m_strRequerySQL, m_dwOptions);
}
else
{
// Shutdown current query, preserving buffers for performance
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmt, SQL_CLOSE));
m_lOpen = AFX_RECORDSET_STATUS_CLOSED;
// Rebind date/time parameters
RebindParams(m_hstmt);
// now attempt to re-execute the SQL Query
AFX_ODBC_CALL(::SQLExecute(m_hstmt));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: Requery attempt failed.\n"));
ThrowDBException(nRetCode);
}
m_lOpen = AFX_RECORDSET_STATUS_OPEN;
// Reset some cursor properties and fetch first record
ResetCursor();
MoveNext();
// If EOF, then result set empty, so set BOF as well
m_bBOF = m_bEOF;
}
}
CATCH_ALL(e)
{
Close();
THROW_LAST();
}
END_CATCH_ALL
return TRUE; // all set
}
// Shutdown any pending query for CRecordset's hstmt's
void CRecordset::Cancel()
{
ASSERT_VALID(this);
ASSERT(m_hstmt != SQL_NULL_HSTMT);
::SQLCancel(m_hstmt);
// If Update hstmt has been allocated, shut it down also
if (m_hstmtUpdate != SQL_NULL_HSTMT)
::SQLCancel(m_hstmtUpdate);
}
CString CRecordset::GetDefaultConnect()
{
ASSERT_VALID(this);
return _afxODBCTrail;
}
CString CRecordset::GetDefaultSQL()
{
ASSERT_VALID(this);
// Override and add table name or entire SQL SELECT statement
return _T("");
}
void CRecordset::DoFieldExchange(CFieldExchange* /* pFX */)
{
ASSERT_VALID(this);
// Do nothing if dynamically retrieving unbound fields,
// otherwise override CRecordset and add RFX calls
}
void CRecordset::DoBulkFieldExchange(CFieldExchange* /* pFX */)
{
ASSERT_VALID(this);
// To use multi-record data fetching, you must use
// a derived CRecordset class and call Close explicitly.
ASSERT(FALSE);
}
void CRecordset::OnSetUpdateOptions(HSTMT hstmt)
{
ASSERT_VALID(this);
ASSERT(hstmt != SQL_NULL_HSTMT);
UNUSED(hstmt);
// user will override
return;
}
void CRecordset::OnSetOptions(HSTMT hstmt)
{
ASSERT_VALID(this);
ASSERT(hstmt != SQL_NULL_HSTMT);
// Inherit options settings from CDatabase
m_pDatabase->OnSetOptions(hstmt);
// If fowardOnly recordset and not using SQLExtendedFetch, quit now
if (m_nOpenType == forwardOnly && !(m_dwOptions & useExtendedFetch))
return;
// Turn on bookmark support if necessary
EnableBookmarks();
// If using forwardOnly and extended fetch, quit now
if (m_nOpenType == forwardOnly)
return;
// Make sure driver supports extended fetch, ODBC 2.0 and requested cursor type
VerifyDriverBehavior();
DWORD dwScrollType = VerifyCursorSupport();
// Set the update method, concurrency and cursor type
SetUpdateMethod();
SetConcurrencyAndCursorType(hstmt, dwScrollType);
}
// Screen for errors.
BOOL CRecordset::Check(RETCODE nRetCode) const
{
ASSERT_VALID(this);
switch (nRetCode)
{
case SQL_SUCCESS_WITH_INFO:
#ifdef _DEBUG
{
CDBException e(nRetCode);
TRACE(traceDatabase, 0, _T("Warning: ODBC Success With Info, "));
e.BuildErrorString(m_pDatabase, m_hstmt);
}
#endif
// Fall through
case SQL_SUCCESS:
case SQL_NO_DATA_FOUND:
case SQL_NEED_DATA:
return TRUE;
}
return FALSE;
}
void CRecordset::PreBindFields()
{
// Do nothing
}
//////////////////////////////////////////////////////////////////////////////
// CRecordset internal functions
// Cache state information internally in CRecordset
void CRecordset::SetState(int nOpenType, LPCTSTR lpszSQL, DWORD dwOptions)
{
if (nOpenType == AFX_DB_USE_DEFAULT_TYPE)
m_nOpenType = m_nDefaultType;
else
m_nOpenType = nOpenType;
m_bAppendable = (dwOptions & appendOnly) != 0 ||
(dwOptions & readOnly) == 0;
m_bUpdatable = (dwOptions & readOnly) == 0 &&
(dwOptions & appendOnly) == 0;
// Can turn off dirty field checking via dwOptions
if (dwOptions & noDirtyFieldCheck || dwOptions & useMultiRowFetch)
m_bCheckCacheForDirtyFields = FALSE;
// Set recordset readOnly if forwardOnly
if (m_nOpenType == forwardOnly && !(dwOptions & readOnly))
{
TRACE(traceDatabase, 0, _T("Warning: Setting forwardOnly recordset readOnly.\n"));
dwOptions |= readOnly;
// If using multiRowFetch also set useExtendFetch
if (dwOptions & useMultiRowFetch)
dwOptions |= useExtendedFetch;
}
// Archive info for use in Requery
m_dwOptions = dwOptions;
m_strRequerySQL = lpszSQL;
m_strRequeryFilter = m_strFilter;
m_strRequerySort = m_strSort;
}
// Allocate the Hstmt and implicitly create and open Database if necessary
BOOL CRecordset::AllocHstmt()
{
RETCODE nRetCode;
if (m_hstmt == SQL_NULL_HSTMT)
{
CString strDefaultConnect;
TRY
{
if (m_pDatabase == NULL)
{
m_pDatabase = new CDatabase();
m_bRecordsetDb = TRUE;
}
strDefaultConnect = GetDefaultConnect();
// If not already opened, attempt to open
if (!m_pDatabase->IsOpen())
{
BOOL bUseCursorLib = m_bUseODBCCursorLib;
// If non-readOnly snapshot request must use cursor lib
if (m_nOpenType == snapshot && !(m_dwOptions & readOnly))
{
// This assumes drivers only support readOnly snapshots
bUseCursorLib = TRUE;
}
if (!m_pDatabase->Open(_T(""), FALSE, FALSE,
strDefaultConnect, bUseCursorLib))
{
return FALSE;
}
// If snapshot cursor requested and not supported, load cursor lib
if (m_nOpenType == snapshot && !bUseCursorLib)
{
// Get the supported cursor types
RETCODE nResult;
UDWORD dwDriverScrollOptions;
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_SCROLL_OPTIONS,
&dwDriverScrollOptions, sizeof(dwDriverScrollOptions), &nResult));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: ODBC failure checking for driver capabilities.\n"));
ThrowDBException(nRetCode);
}
// Check for STATIC cursor support and load cursor lib
if (!(dwDriverScrollOptions & SQL_SO_STATIC))
{
m_pDatabase->Close();
if (!m_pDatabase->Open(_T(""), FALSE, FALSE,
strDefaultConnect, TRUE))
return FALSE;
}
}
}
AFX_SQL_SYNC(::SQLAllocStmt(m_pDatabase->m_hdbc, &m_hstmt));
if (!Check(nRetCode))
ThrowDBException(SQL_INVALID_HANDLE);
// Add to list of CRecordsets with alloced hstmts
AfxLockGlobals(CRIT_ODBC);
TRY
{
m_pDatabase->m_listRecordsets.AddHead(this);
}
CATCH_ALL(e)
{
AfxUnlockGlobals(CRIT_ODBC);
THROW_LAST();
}
END_CATCH_ALL
AfxUnlockGlobals(CRIT_ODBC);
}
CATCH_ALL(e)
{
TRACE(traceDatabase, 0, _T("Error: CDatabase create for CRecordset failed.\n"));
NO_CPP_EXCEPTION(strDefaultConnect.Empty());
if (m_bRecordsetDb)
{
delete m_pDatabase;
m_pDatabase = NULL;
}
ASSERT(m_hstmt == SQL_NULL_HSTMT);
THROW_LAST();
}
END_CATCH_ALL
}
return TRUE;
}
// Initialize the status arrays and create the SQL
void CRecordset::BuildSQL(LPCTSTR lpszSQL)
{
if (lpszSQL == NULL)
m_strSQL = GetDefaultSQL();
else
m_strSQL = lpszSQL;
// Set any supplied params
if (m_nParams != 0)
{
UINT nParams = BindParams(m_hstmt);
ASSERT(nParams == m_nParams);
}
// Construct the SQL string
BuildSelectSQL();
AppendFilterAndSortSQL();
// Do some extra checking if trying to set recordset updatable or appendable
if ((m_bUpdatable || m_bAppendable) && !IsRecordsetUpdatable())
m_bUpdatable = m_bAppendable = FALSE;
if (m_bUpdatable && m_bUseUpdateSQL && m_pDatabase->m_bAddForUpdate)
m_strSQL += _afxForUpdate;
// Replace brackets with SQL_IDENTIFIER_QUOTE_CHAR
m_pDatabase->ReplaceBrackets(m_strSQL.GetBuffer(0));
m_strSQL.ReleaseBuffer();
}
// Prepare and Execute the SQL or simple call SQLExecDirect, resetting concurrency if necessary
void CRecordset::PrepareAndExecute()
{
RETCODE nRetCode = 0;
BOOL bConcurrency = FALSE;
TCHAR *pszSQL = const_cast<LPTSTR>(static_cast<LPCTSTR>(m_strSQL));
while (!bConcurrency)
{
// Prepare or execute the query
if (m_dwOptions & executeDirect)
AFX_ODBC_CALL(::SQLExecDirect(m_hstmt, reinterpret_cast<SQLTCHAR *>(pszSQL), SQL_NTS));
else
AFX_ODBC_CALL(::SQLPrepare(m_hstmt, reinterpret_cast<SQLTCHAR *>(pszSQL), SQL_NTS));
if (Check(nRetCode))
bConcurrency = TRUE;
else
{
// If "Driver Not Capable" error, assume cursor type doesn't
// support requested concurrency and try alternate concurrency.
CDBException* e = new CDBException(nRetCode);
e->BuildErrorString(m_pDatabase, m_hstmt);
if (m_dwConcurrency != SQL_CONCUR_READ_ONLY &&
e->m_strStateNativeOrigin.Find(_afxDriverNotCapable) >= 0)
{
TRACE(traceDatabase, 0, _T("Warning: Driver does not support requested concurrency.\n"));
// Don't need exception to persist while attempting to reset concurrency
e->Delete();
// ODBC will automatically attempt to set alternate concurrency if
// request fails, but it won't try LOCK even if driver supports it.
if ((m_dwDriverConcurrency & SQL_SCCO_LOCK) &&
(m_dwConcurrency == SQL_CONCUR_ROWVER ||
m_dwConcurrency == SQL_CONCUR_VALUES))
{
m_dwConcurrency = SQL_CONCUR_LOCK;
}
else
{
m_dwConcurrency = SQL_CONCUR_READ_ONLY;
m_bUpdatable = m_bAppendable = FALSE;
TRACE(traceDatabase, 0, _T("Warning: Setting recordset read only.\n"));
}
// Attempt to reset the concurrency model.
AFX_SQL_SYNC(::SQLSetStmtOption(m_hstmt, SQL_CONCURRENCY,
m_dwConcurrency));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: ODBC failure setting recordset concurrency.\n"));
ThrowDBException(nRetCode);
}
}
else
{
TRACE(traceDatabase, 0, _T("Error: ODBC failure on SQLPrepare or SQLExecDirect\n"));
THROW(e);
}
}
}
// now attempt to execute the SQL Query if not executed already
if (!(m_dwOptions & executeDirect))
{
AFX_ODBC_CALL(::SQLExecute(m_hstmt));
if (!Check(nRetCode))
ThrowDBException(nRetCode);
}
m_lOpen = AFX_RECORDSET_STATUS_OPEN;
// SQLExecute or SQLExecDirect may have changed an option value
if (nRetCode == SQL_SUCCESS_WITH_INFO)
{
// Check if concurrency was changed in order to mark
// recordset non-updatable if necessary
DWORD dwConcurrency;
AFX_SQL_SYNC(::SQLGetStmtOption(m_hstmt, SQL_CONCURRENCY, &dwConcurrency));
if (!Check(nRetCode))
ThrowDBException(nRetCode);
if (dwConcurrency == SQL_CONCUR_READ_ONLY && (m_bUpdatable || m_bAppendable))
{
m_bUpdatable = FALSE;
m_bAppendable = FALSE;
TRACE(traceDatabase, 0, _T("Warning: Concurrency changed by driver.\n"));
TRACE(traceDatabase, 0, _T("\tMarking CRecordset as not updatable.\n"));
}
}
}
// Ensure that driver supports extended fetch and ODBC 2.0 if necessary
void CRecordset::VerifyDriverBehavior()
{
RETCODE nRetCode;
UWORD wScrollable;
// If SQLExtendedFetch not supported, use SQLFetch
AFX_SQL_SYNC(::SQLGetFunctions(m_pDatabase->m_hdbc,
SQL_API_SQLEXTENDEDFETCH, &wScrollable));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: ODBC failure determining whether recordset is scrollable.\n"));
ThrowDBException(nRetCode);
}
m_bScrollable = wScrollable;
if (!m_bScrollable)
{
TRACE(traceDatabase, 0, _T("Warning: SQLExtendedFetch not supported by driver\n"));
TRACE(traceDatabase, 0, _T("and/or cursor library not loaded. Opening forwardOnly.\n"));
TRACE(traceDatabase, 0, _T("for use with SQLFetch.\n"));
m_bUpdatable = FALSE;
return;
}
TCHAR szResult[30];
SWORD nResult;
// require ODBC v2.0
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_ODBC_VER,
szResult, sizeof(szResult), &nResult));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: ODBC failure checking for driver capabilities.\n"));
ThrowDBException(nRetCode);
}
if (szResult[0] == '0' && szResult[1] < '2')
ThrowDBException(AFX_SQL_ERROR_ODBC_V2_REQUIRED);
}
// Check that driver supports requested cursor type
DWORD CRecordset::VerifyCursorSupport()
{
RETCODE nRetCode;
SWORD nResult;
UDWORD dwDriverScrollOptions;
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_SCROLL_OPTIONS,
&dwDriverScrollOptions, sizeof(dwDriverScrollOptions), &nResult));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: ODBC failure checking for driver capabilities.\n"));
ThrowDBException(nRetCode);
}
SDWORD dwScrollOptions = SQL_CURSOR_FORWARD_ONLY;
if (m_nOpenType == dynaset)
{
// Dynaset support requires ODBC's keyset driven cursor model
if (!(dwDriverScrollOptions & SQL_SO_KEYSET_DRIVEN))
ThrowDBException(AFX_SQL_ERROR_DYNASET_NOT_SUPPORTED);
dwScrollOptions = SQL_CURSOR_KEYSET_DRIVEN;
}
else if (m_nOpenType == snapshot)
{
// Snapshot support requires ODBC's static cursor model
if (!(dwDriverScrollOptions & SQL_SO_STATIC))
ThrowDBException(AFX_SQL_ERROR_SNAPSHOT_NOT_SUPPORTED);
dwScrollOptions = SQL_CURSOR_STATIC;
}
else
{
// Dynamic cursor support requires ODBC's dynamic cursor model
if (!(dwDriverScrollOptions & SQL_SO_DYNAMIC))
ThrowDBException(AFX_SQL_ERROR_DYNAMIC_CURSOR_NOT_SUPPORTED);
dwScrollOptions = SQL_CURSOR_DYNAMIC;
}
return dwScrollOptions;
}
void CRecordset::AllocAndCacheFieldInfo()
{
ASSERT(GetODBCFieldCount() < 0);
ASSERT(m_rgODBCFieldInfos == NULL);
RETCODE nRetCode;
SWORD nActualLen;
// Cache the number of result columns
AFX_ODBC_CALL(::SQLNumResultCols(m_hstmt, &m_nResultCols));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: Can't get field info.\n"));
ThrowDBException(nRetCode);
}
// If there are no fields quit now
if (m_nResultCols == 0)
return;
// Allocate buffer and get the ODBC meta data
m_rgODBCFieldInfos = new CODBCFieldInfo[m_nResultCols];
TCHAR *lpszFieldName;
// Get the field info each field
for (WORD n = 1; n <= GetODBCFieldCount(); n++)
{
// Reset the buffer to point to next element
lpszFieldName =
static_cast<LPTSTR>(m_rgODBCFieldInfos[n - 1].m_strName.GetBuffer(MAX_FNAME_LEN + 1));
AFX_ODBC_CALL(::SQLDescribeCol(m_hstmt, n,
reinterpret_cast<SQLTCHAR *>(lpszFieldName), MAX_FNAME_LEN, &nActualLen,
&m_rgODBCFieldInfos[n - 1].m_nSQLType,
&m_rgODBCFieldInfos[n - 1].m_nPrecision,
&m_rgODBCFieldInfos[n - 1].m_nScale,
&m_rgODBCFieldInfos[n - 1].m_nNullability));
m_rgODBCFieldInfos[n - 1].m_strName.ReleaseBuffer(nActualLen);
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: ODBC failure getting field #%d info.\n"), n);
ThrowDBException(nRetCode);
}
}
}
void CRecordset::AllocRowset()
{
if (m_dwOptions & useMultiRowFetch)
SetRowsetSize(m_dwRowsetSize);
else
{
// Not using bulk row fetch, set rowset size to 1
m_rgRowStatus = new WORD[1];
m_dwRowsetSize = 1;
}
}
void CRecordset::FreeRowset()
{
// Delete the rowset status
delete [] m_rgRowStatus;
m_rgRowStatus = NULL;
if (m_dwOptions & useMultiRowFetch &&
!(m_dwOptions & userAllocMultiRowBuffers))
{
// Calling virtual function, DoBulkFieldExchange, here is bad
// because Close then FreeRowset may get called from destructor.
// There is no simple choice however if RFX_Bulk functions do
// a memory allocation. The net result is that users MUST call
// Close explicitly (rather than relying on destructor) if
// using multi row fetches, otherwise they will get a memory leak.
// If rowset already allocated, delete old rowset buffers
if (m_dwAllocatedRowsetSize != 0)
{
CFieldExchange fx(CFieldExchange::DeleteMultiRowBuffer, this);
DoBulkFieldExchange(&fx);
}
}
m_dwAllocatedRowsetSize = 0;
}
void CRecordset::EnableBookmarks()
{
// Turn on bookmark support if necessary
if (m_dwOptions & useBookmarks)
{
RETCODE nRetCode;
// Set stmt option if bookmarks supported by driver
if (m_pDatabase->GetBookmarkPersistence() & SQL_BP_SCROLL)
{
AFX_SQL_SYNC(::SQLSetStmtOption(m_hstmt, SQL_USE_BOOKMARKS,
SQL_UB_ON));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: Can't enable bookmark support.\n"));
ThrowDBException(nRetCode);
}
}
}
}
// Determine whether to use SQLSetPos or positioned update SQL
void CRecordset::SetUpdateMethod()
{
// Determine update method
if (m_pDatabase->m_dwUpdateOptions & AFX_SQL_SETPOSUPDATES)
m_bUseUpdateSQL = FALSE;
else if (m_pDatabase->m_dwUpdateOptions & AFX_SQL_POSITIONEDSQL)
m_bUseUpdateSQL = TRUE;
else
m_bUpdatable = FALSE;
}
// Determine which type of concurrency to set, set it and cursor type
void CRecordset::SetConcurrencyAndCursorType(HSTMT hstmt, DWORD dwScrollOptions)
{
RETCODE nRetCode;
SWORD nResult;
m_dwConcurrency = SQL_CONCUR_READ_ONLY;
if ((m_bUpdatable || m_bAppendable) && m_pDatabase->m_bUpdatable)
{
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_SCROLL_CONCURRENCY,
&m_dwDriverConcurrency, sizeof(m_dwDriverConcurrency), &nResult));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: ODBC failure checking recordset updatability.\n"));
ThrowDBException(nRetCode);
}
if (m_nLockMode == pessimistic)
{
if (m_dwDriverConcurrency & SQL_SCCO_LOCK)
m_dwConcurrency = SQL_CONCUR_LOCK;
#ifdef _DEBUG
else
TRACE(traceDatabase, 0, _T("Warning: locking not supported, setting recordset read only.\n"));
#endif
}
else
{
// Use cheapest, most concurrent model
if (m_dwDriverConcurrency & SQL_SCCO_OPT_ROWVER)
m_dwConcurrency = SQL_CONCUR_ROWVER;
else if (m_dwDriverConcurrency & SQL_SCCO_OPT_VALUES)
m_dwConcurrency = SQL_CONCUR_VALUES;
else if (m_dwDriverConcurrency & SQL_SCCO_LOCK)
m_dwConcurrency = SQL_CONCUR_LOCK;
}
}
// Set cursor type (Let rowset size default to 1).
AFX_SQL_SYNC(::SQLSetStmtOption(hstmt, SQL_CURSOR_TYPE, dwScrollOptions));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: ODBC failure setting recordset cursor type.\n"));
ThrowDBException(nRetCode);
}
// Set the concurrency model (NOTE: may have to reset concurrency later).
AFX_SQL_SYNC(::SQLSetStmtOption(hstmt, SQL_CONCURRENCY, m_dwConcurrency));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: ODBC failure setting recordset concurrency.\n"));
ThrowDBException(nRetCode);
}
}
// Is there a join, stored proc call, GROUP BY, UNION or missing FROM?
BOOL CRecordset::IsSQLUpdatable(LPCTSTR lpszSQL)
{
// Parse for query procedure call keyword or return param
if (!(_tcsnicmp(lpszSQL, _afxCall, lstrlen(_afxCall)-1) == 0 ||
_tcsnicmp(lpszSQL, _afxParamCall, lstrlen(_afxParamCall)-1) == 0
|| _tcsnicmp(lpszSQL, _afxExec, lstrlen(_afxExec)-1) == 0))
// Assume this is a select query
return IsSelectQueryUpdatable(lpszSQL);
else
// Don't know the table name to update in procedure call
return FALSE;
}
BOOL CRecordset::IsSelectQueryUpdatable(LPCTSTR lpszSQL)
{
LPCTSTR lpchTokenFrom;
LPCTSTR lpchToken;
LPCTSTR lpchTokenNext;
LPTSTR lpszSQLStart;
CString strSQL = lpszSQL;
lpchTokenFrom = FindSQLToken(strSQL, _afxFrom);
if (lpchTokenFrom == NULL)
{
TRACE(traceDatabase, 0, _T("Warning: Missing ' FROM ', recordset not updatable \n"));
return FALSE;
}
lpchToken = FindSQLToken(strSQL, _T(" GROUP BY "));
if (lpchToken != NULL)
{
TRACE(traceDatabase, 0, _T("Warning: SQL contains ' GROUP BY ', recordset not updatable \n"));
return FALSE;
}
lpchToken = FindSQLToken(strSQL, _T(" UNION "));
if (lpchToken != NULL)
{
TRACE(traceDatabase, 0, _T("Warning: SQL contains ' UNION ', recordset not updatable \n"));
return FALSE;
}
// Find next token after FROM (can't have HAVING clause without GROUP BY)
lpchToken = FindSQLToken(strSQL, _afxWhere);
lpchTokenNext = FindSQLToken(strSQL, _afxOrderBy);
lpszSQLStart = strSQL.GetBuffer(0);
if (lpchTokenNext == NULL)
lpchTokenNext = lpchToken;
else if (lpchToken != NULL && lpchToken < lpchTokenNext)
lpchTokenNext = lpchToken;
if (lpchTokenNext != NULL)
{
//IA64: Assume max query length < 2G chars
int nFromLength = int(lpchTokenNext - lpchTokenFrom);
memmove(lpszSQLStart, lpchTokenFrom, nFromLength*sizeof(TCHAR));
lpszSQLStart[nFromLength] = '\0';
}
else
memmove(lpszSQLStart, lpchTokenFrom, (lstrlen(lpchTokenFrom) + 1) * sizeof(TCHAR));
strSQL.ReleaseBuffer();
if (IsJoin(strSQL))
{
TRACE(traceDatabase, 0, _T("Warning: SQL contains join, recordset not updatable \n"));
return FALSE;
}
// Cache table name (skip over " FROM ")
m_strTableName = strSQL.Right(strSQL.GetLength()-6);
return TRUE;
}
// Check FROM clause for join syntax
BOOL PASCAL CRecordset::IsJoin(LPCTSTR lpszJoinClause)
{
// Look for comma in join clause
if (FindSQLToken(lpszJoinClause, _afxComma) != NULL)
return TRUE;
// Look for outer join clause
if (FindSQLToken(lpszJoinClause, _T(" JOIN ")) != NULL)
return TRUE;
return FALSE;
}
// Searches string for given token not in single quotes or brackets
LPCTSTR PASCAL CRecordset::FindSQLToken(LPCTSTR lpszSQL, LPCTSTR lpszSQLToken)
{
BOOL bInLiteral;
BOOL bInBrackets;
int nLeftBrackets;
int nRightBrackets;
LPCTSTR lpch;
LPCTSTR lpchSQLStart;
LPCTSTR lpszFoundToken;
int nTokenOffset = 0;
CString strSQL = lpszSQL;
strSQL.MakeUpper();
lpszFoundToken = strSQL.GetBuffer(0);
lpchSQLStart = lpszFoundToken;
do
{
lpszFoundToken = _tcsstr(lpszFoundToken + nTokenOffset, lpszSQLToken);
if (lpszFoundToken == NULL)
{
strSQL.ReleaseBuffer();
return NULL;
}
bInLiteral = bInBrackets = FALSE;
nLeftBrackets = nRightBrackets = 0;
// Check if embedded in literal or brackets
for (lpch = lpchSQLStart; lpch < lpszFoundToken; lpch = _tcsinc(lpch))
{
if (*lpch == _afxLiteralSeparator)
{
// Skip if escape literal
if (*_tcsinc(lpch) == _afxLiteralSeparator)
lpch = _tcsinc(lpch);
else
bInLiteral = !bInLiteral;
}
else if (!bInLiteral && (*lpch == '['))
{
// Skip if escape left bracket
if (*_tcsinc(lpch) == '[')
lpch = _tcsinc(lpch);
else
{
nLeftBrackets++;
if ((nLeftBrackets - nRightBrackets) > 0)
bInBrackets = TRUE;
else
bInBrackets = FALSE;
}
}
else if (!bInLiteral && (*lpch == ']'))
{
// Skip if escape right bracket
if (*_tcsinc(lpch) == ']')
lpch = _tcsinc(lpch);
else
{
nRightBrackets++;
if ((nLeftBrackets - nRightBrackets) > 0)
bInBrackets = TRUE;
else
bInBrackets = FALSE;
}
}
}
// If first iteration, reset the offset to jump over found token
if (nTokenOffset == 0)
nTokenOffset = lstrlen(lpszSQLToken);
} while (bInLiteral || bInBrackets);
lpszFoundToken = lpszSQL + (lpszFoundToken - lpchSQLStart);
strSQL.ReleaseBuffer();
return lpszFoundToken;
}
// Bind fields (if not already bound), then retrieve 1st record
void CRecordset::InitRecord()
{
// fields to bind
if (m_nFields != 0)
{
m_nFieldsBound = BindFieldsToColumns();
// m_nFields doesn't reflect number of
// RFX_ output column calls in Do[Bulk]FieldExchange
ASSERT((int)m_nFields == m_nFieldsBound);
// Allocate the data cache if necessary
if (m_nFields > 0 && m_bCheckCacheForDirtyFields)
AllocDataCache();
}
else
// No fields to bind, don't attempt to bind again
m_nFieldsBound = -1;
}
void CRecordset::ResetCursor()
{
m_bEOFSeen = m_bBOF = m_bEOF = FALSE;
m_bDeleted = FALSE;
m_lCurrentRecord = AFX_CURRENT_RECORD_BOF;
m_lRecordCount = 0;
}
void CRecordset::CheckRowsetCurrencyStatus(UWORD wFetchType, long nRows)
{
if (!m_bScrollable && wFetchType != SQL_FETCH_NEXT)
{
TRACE(traceDatabase, 0, _T("Error: forward-only recordsets only support MoveNext.\n"));
ThrowDBException(AFX_SQL_ERROR_RECORDSET_FORWARD_ONLY);
}
if (IsEOF() && IsBOF())
{
// Can't position cursor if recordset empty
TRACE(traceDatabase, 0, _T("Error: attempted to position cursor on empty recordset.\n"));
ThrowDBException(AFX_SQL_ERROR_NO_DATA_FOUND);
}
if (m_nOpenType != dynamic)
{
if (IsEOF() && (wFetchType == SQL_FETCH_NEXT ||
(wFetchType == SQL_FETCH_RELATIVE && nRows > 0)))
{
// if already at EOF, throw an exception
TRACE(traceDatabase, 0, _T("Error: attempted to move past EOF.\n"));
ThrowDBException(AFX_SQL_ERROR_NO_DATA_FOUND);
}
else if (IsBOF() && (wFetchType == SQL_FETCH_PRIOR ||
(wFetchType == SQL_FETCH_RELATIVE && nRows < 0)))
{
// if already at BOF, throw an exception
TRACE(traceDatabase, 0, _T("Error: attempted to move before BOF.\n"));
ThrowDBException(AFX_SQL_ERROR_NO_DATA_FOUND);
}
}
}
RETCODE CRecordset::FetchData(UWORD wFetchType, SDWORD nRow,
DWORD* pdwRowsFetched)
{
RETCODE nRetCode;
if (m_nOpenType == forwardOnly && !(m_dwOptions & useExtendedFetch))
{
ASSERT(wFetchType == SQL_FETCH_NEXT);
AFX_ODBC_CALL(::SQLFetch(m_hstmt));
*pdwRowsFetched = 1;
m_bDeleted = FALSE;
}
else
{
AFX_ODBC_CALL(::SQLExtendedFetch(m_hstmt, wFetchType,
nRow, pdwRowsFetched, m_rgRowStatus));
// Set deleted status
m_bDeleted = GetRowStatus(1) == SQL_ROW_DELETED;
}
CheckRowsetError(nRetCode);
return nRetCode;
}
void CRecordset::SkipDeletedRecords(UWORD wFetchType, long nRows,
DWORD* pdwRowsFetched, RETCODE* pnRetCode)
{
ASSERT(!(m_dwOptions & useMultiRowFetch));
ASSERT(wFetchType == SQL_FETCH_RELATIVE ||
wFetchType == SQL_FETCH_FIRST ||
wFetchType == SQL_FETCH_NEXT ||
wFetchType == SQL_FETCH_LAST ||
wFetchType == SQL_FETCH_PRIOR);
ASSERT(nRows != 0);
UWORD wDeletedFetchType = wFetchType;
DWORD dwDeletedRows = abs(nRows);
BOOL m_bDone;
switch (wFetchType)
{
case SQL_FETCH_FIRST:
wDeletedFetchType = SQL_FETCH_NEXT;
break;
case SQL_FETCH_LAST:
wDeletedFetchType = SQL_FETCH_PRIOR;
break;
case SQL_FETCH_RELATIVE:
if (nRows > 0)
wDeletedFetchType = SQL_FETCH_NEXT;
else
wDeletedFetchType = SQL_FETCH_PRIOR;
break;
}
// First fetch is as expected unless relative fetch
if (wFetchType != SQL_FETCH_RELATIVE)
{
*pnRetCode = FetchData(wFetchType, 1, pdwRowsFetched);
m_bDone = !m_bDeleted;
}
else
{
// Since deleted records must be skipped Move(n)
// must be turned into n MoveNext/MovePrev calls
*pnRetCode = FetchData(wDeletedFetchType, 1, pdwRowsFetched);
if (!m_bDeleted)
{
dwDeletedRows--;
m_bDone = dwDeletedRows == 0;
}
else
m_bDone = FALSE;
}
// Continue fetching until all req'd deleted records skipped
while (*pnRetCode != SQL_NO_DATA_FOUND && !m_bDone)
{
*pnRetCode = FetchData(wDeletedFetchType, 1, pdwRowsFetched);
if (wFetchType == SQL_FETCH_RELATIVE)
{
if (!m_bDeleted)
{
dwDeletedRows--;
m_bDone = dwDeletedRows == 0;
}
else
m_bDone = FALSE;
}
else
m_bDone = !m_bDeleted;
}
}
void CRecordset::SetRowsetCurrencyStatus(RETCODE nRetCode,
UWORD wFetchType, long nRows, DWORD dwRowsFetched)
{
UNUSED_ALWAYS(dwRowsFetched);
// Set the fetch direction
int nDirection = 0;
switch (wFetchType)
{
case SQL_FETCH_FIRST:
nDirection = 1;
if (nRetCode == SQL_NO_DATA_FOUND)
{
m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
m_lRecordCount = 0;
}
else
m_lCurrentRecord = 0;
break;
case SQL_FETCH_NEXT:
nDirection = 1;
_AfxSetCurrentRecord(&m_lCurrentRecord, nRows, nRetCode);
_AfxSetRecordCount(&m_lRecordCount, m_lCurrentRecord,
m_bEOFSeen, nRetCode);
// This is the only way to know you've hit the end (m_bEOFSeen)
if (!m_bEOFSeen && nRetCode == SQL_NO_DATA_FOUND &&
m_lRecordCount == m_lCurrentRecord + 1)
{
m_bEOFSeen = TRUE;
}
break;
case SQL_FETCH_LAST:
nDirection = -1;
if (nRetCode == SQL_NO_DATA_FOUND)
{
m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
m_lRecordCount = 0;
}
else if (m_bEOFSeen)
m_lCurrentRecord = m_lRecordCount - 1;
else
m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
break;
case SQL_FETCH_PRIOR:
nDirection = -1;
// If doing MovePrev after m_bEOF, don't increment current rec
if (!m_bEOF)
_AfxSetCurrentRecord(&m_lCurrentRecord, nRows, nRetCode);
break;
case SQL_FETCH_RELATIVE:
nDirection = nRows;
_AfxSetCurrentRecord(&m_lCurrentRecord, nRows, nRetCode);
_AfxSetRecordCount(&m_lRecordCount, m_lCurrentRecord,
m_bEOFSeen, nRetCode);
break;
case SQL_FETCH_ABSOLUTE:
nDirection = nRows;
if (nRetCode != SQL_NO_DATA_FOUND)
{
if (nRows > 0)
m_lCurrentRecord = nRows - 1;
else if (m_bEOFSeen)
m_lCurrentRecord = m_lRecordCount + nRows;
else
m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
}
else
m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
_AfxSetRecordCount(&m_lRecordCount, m_lCurrentRecord,
m_bEOFSeen, nRetCode);
break;
case SQL_FETCH_BOOKMARK:
nDirection = 0;
m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
break;
}
// Set the BOF/EOF flags
if (nRetCode == SQL_NO_DATA_FOUND)
{
if (wFetchType == SQL_FETCH_FIRST || wFetchType == SQL_FETCH_LAST ||
wFetchType == SQL_FETCH_BOOKMARK)
{
// If MoveFirst/MoveLast fails, result set is empty
// If SetBookmark fails, currency undefined
m_bEOF = m_bBOF = TRUE;
}
else
{
m_bEOF = nDirection >= 0;
m_bBOF = !m_bEOF;
}
}
else
{
m_bEOF = m_bBOF = FALSE;
}
}
void CRecordset::RefreshRowset(WORD wRow, WORD wLockType)
{
ASSERT(IsOpen());
ASSERT(m_dwOptions & useMultiRowFetch);
RETCODE nRetCode;
AFX_ODBC_CALL(::SQLSetPos(m_hstmt, wRow, SQL_REFRESH, wLockType));
// Need to fixup bound fields in some cases
if (m_nFields > 0 && !IsEOF() && !IsBOF() &&
!(m_dwOptions & useMultiRowFetch))
{
Fixups();
}
}
void CRecordset::SetRowsetCursorPosition(WORD wRow, WORD wLockType)
{
ASSERT(IsOpen());
ASSERT(m_dwOptions & useMultiRowFetch);
RETCODE nRetCode;
AFX_ODBC_CALL(::SQLSetPos(m_hstmt, wRow, SQL_POSITION, wLockType));
}
// "SELECT <user column name list> FROM <table name>"
void CRecordset::BuildSelectSQL()
{
ASSERT_VALID(this);
ASSERT(m_hstmt != SQL_NULL_HSTMT);
// Ignore queries with procedure call keyword or output param
if (!(_tcsnicmp(m_strSQL, _afxCall, lstrlen(_afxCall)-1) == 0 ||
_tcsnicmp(m_strSQL, _afxParamCall, lstrlen(_afxParamCall)-1) == 0
|| _tcsnicmp(m_strSQL, _afxExec, lstrlen(_afxExec)-1) == 0))
{
// Ignore queries already built
if (_tcsnicmp(m_strSQL, _afxSelect, lstrlen(_afxSelect)-1) != 0)
{
// Assume m_strSQL specifies table name
ASSERT(m_nFields != 0);
CString strTableName;
strTableName = m_strSQL;
m_strSQL.Empty();
m_strSQL = _afxSelect;
// Set all fields dirty. AppendNames only outputs dirty field names
SetFieldDirty(NULL);
if (AppendNames(&m_strSQL, _T(",")) == 0)
{
TRACE(traceDatabase, 0, _T("Error: no field names - at least 1 required.\n"));
ThrowDBException(AFX_SQL_ERROR_EMPTY_COLUMN_LIST);
}
// Overwrite final ',' separator with ' '
ASSERT(m_strSQL[m_strSQL.GetLength()-1] == ',');
m_strSQL.SetAt(m_strSQL.GetLength()-1, ' ');
m_strSQL += _afxFrom;
m_strSQL += strTableName;
}
}
}
// Add the filter and sort strings to query SQL
void CRecordset::AppendFilterAndSortSQL()
{
if (!m_strFilter.IsEmpty())
{
m_strSQL += _afxWhere;
m_strSQL += m_strFilter;
}
if (!m_strSort.IsEmpty())
{
m_strSQL += _afxOrderBy;
m_strSQL += m_strSort;
}
}
// Check for required SQLGetData support and do limited SQL parsing
BOOL CRecordset::IsRecordsetUpdatable()
{
// Do limited SQL parsing to determine if SQL updatable
if (!IsSQLUpdatable(m_strSQL))
return FALSE;
// Updatable recordsets with long binary columns must support
// SQL_GD_BOUND to use SQLSetPos, otherwise must use SQL updates
BOOL bUpdatable = TRUE;
if (m_bLongBinaryColumns && !m_bUseUpdateSQL)
{
// Set non-updatable if you can't use SQLGetData on bound columns
if (!(m_pDatabase->m_dwUpdateOptions & AFX_SQL_GDBOUND))
{
// Okay can't use SetPos, try and use positioned update SQL
if (m_pDatabase->m_dwUpdateOptions & AFX_SQL_POSITIONEDSQL)
{
m_bUseUpdateSQL = TRUE;
TRACE(traceDatabase, 0, _T("Warning: Can't use SQLSetPos due to lack of SQLGetData support.\n"));
TRACE(traceDatabase, 0, _T("\tWill use positioned update SQL.\n"));
}
else
{
TRACE(traceDatabase, 0, _T("Warning: Setting recordset read only due to lack of SQLGetData support.\n"));
bUpdatable = FALSE;
}
}
}
return bUpdatable;
}
// Execute the update (or delete) using SQLSetPos
void CRecordset::ExecuteSetPosUpdate()
{
UWORD wExpectedRowStatus;
UWORD wPosOption;
if (m_nEditMode == noMode)
{
wPosOption = SQL_DELETE;
wExpectedRowStatus = SQL_ROW_DELETED;
}
else
{
if (m_nEditMode == edit)
{
wPosOption = SQL_UPDATE;
wExpectedRowStatus = SQL_ROW_UPDATED;
}
else
{
wPosOption = SQL_ADD;
wExpectedRowStatus = SQL_ROW_ADDED;
}
}
BindFieldsForUpdate();
RETCODE nRetCode;
AFX_ODBC_CALL(::SQLSetPos(m_hstmt, 1, wPosOption, SQL_LOCK_NO_CHANGE));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: failure updating record.\n"));
AfxThrowDBException(nRetCode, m_pDatabase, m_hstmt);
}
// Only have data-at-execution columns for CLongBinary columns
if (nRetCode == SQL_NEED_DATA)
SendLongBinaryData(m_hstmt);
// This should only fail if SQLSetPos returned SQL_SUCCESS_WITH_INFO explaining why
if (nRetCode == SQL_SUCCESS_WITH_INFO && GetRowStatus(1) != wExpectedRowStatus)
ThrowDBException(AFX_SQL_ERROR_UPDATE_DELETE_FAILED);
UnbindFieldsForUpdate();
}
// Prepare for sending update SQL by initializing m_hstmtUpdate
void CRecordset::PrepareUpdateHstmt()
{
RETCODE nRetCode;
if (m_hstmtUpdate == SQL_NULL_HSTMT)
{
AFX_SQL_SYNC(::SQLAllocStmt(m_pDatabase->m_hdbc, &m_hstmtUpdate));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: failure to allocate update statement.\n"));
AfxThrowDBException(nRetCode, m_pDatabase, m_hstmtUpdate);
}
OnSetUpdateOptions(m_hstmtUpdate);
}
else
{
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmtUpdate, SQL_CLOSE));
if (!Check(nRetCode))
goto LErrRetCode;
// Re-use (prepared) hstmt & param binding if optimizeBulkAdd option
if(!(m_dwOptions & optimizeBulkAdd))
{
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmtUpdate, SQL_RESET_PARAMS));
if (!Check(nRetCode))
{
LErrRetCode:
// Bad hstmt, free it and allocate another one
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmtUpdate, SQL_DROP));
m_hstmtUpdate = SQL_NULL_HSTMT;
AFX_SQL_SYNC(::SQLAllocStmt(m_pDatabase->m_hdbc, &m_hstmtUpdate));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: failure to allocate update statement.\n"));
AfxThrowDBException(nRetCode, m_pDatabase, m_hstmtUpdate);
}
OnSetUpdateOptions(m_hstmtUpdate);
}
}
}
}
// Build the UPDATE, INSERT or DELETE SQL
void CRecordset::BuildUpdateSQL()
{
switch (m_nEditMode)
{
case noMode:
// DELETE FROM <tablename> WHERE CURRENT OF
{
m_strUpdateSQL = _T("DELETE FROM ");
m_strUpdateSQL += m_strTableName;
}
break;
case addnew:
// INSERT INTO <tablename> (<colname1>[,<colname2>]) VALUES (?[,?])
{
m_strUpdateSQL = _T("INSERT INTO ");
m_strUpdateSQL += m_strTableName;
m_strUpdateSQL += _T(" (");
// Append column names
AppendNames(&m_strUpdateSQL, _afxComma);
// overwrite last ',' with ')'
ASSERT(m_strUpdateSQL[m_strUpdateSQL.GetLength()-1] == ',');
m_strUpdateSQL.SetAt(m_strUpdateSQL.GetLength()-1, ')');
// Append values
m_strUpdateSQL += _T(" VALUES (");
AppendValues(m_hstmtUpdate, &m_strUpdateSQL, _afxComma);
// overwrite last ',' with ')'
ASSERT(m_strUpdateSQL[m_strUpdateSQL.GetLength()-1] == ',');
m_strUpdateSQL.SetAt(m_strUpdateSQL.GetLength()-1, ')');
}
break;
case edit:
// UPDATE <tablename> SET <colname1>=?[,<colname2>=?] WHERE CURRENT OF
{
m_strUpdateSQL = _T("UPDATE ");
m_strUpdateSQL += m_strTableName;
m_strUpdateSQL += _T(" SET ");
AppendNamesValues(m_hstmtUpdate, &m_strUpdateSQL, _afxComma);
// overwrite last ',' with ' '
ASSERT(m_strUpdateSQL[m_strUpdateSQL.GetLength()-1] == ',');
m_strUpdateSQL.SetAt(m_strUpdateSQL.GetLength()-1, ' ');
}
break;
}
// Update and Delete need "WHERE CURRENT OF <cursorname>"
if (m_nEditMode == edit || m_nEditMode == noMode)
{
m_strUpdateSQL += _T(" WHERE CURRENT OF ");
// Cache cursor name assigned by ODBC
if (m_strCursorName.IsEmpty())
{
// Get predefined cursor name from datasource
RETCODE nRetCode;
TCHAR szCursorName[MAX_CURSOR_NAME+1];
SWORD nLength = sizeof(szCursorName)-1;
AFX_SQL_SYNC(::SQLGetCursorName(m_hstmt,
reinterpret_cast<SQLTCHAR *>(szCursorName), sizeof(szCursorName), &nLength));
if (!Check(nRetCode))
ThrowDBException(nRetCode);
m_strCursorName = szCursorName;
}
m_strUpdateSQL += m_strCursorName;
}
m_pDatabase->ReplaceBrackets(m_strUpdateSQL.GetBuffer(0));
m_strUpdateSQL.ReleaseBuffer();
// Must prepare the hstmt on first optimized bulk add
if(m_dwOptions & firstBulkAdd)
{
RETCODE nRetCode;
TCHAR *pszSQL = const_cast<LPTSTR>(static_cast<LPCTSTR>(m_strUpdateSQL));
AFX_ODBC_CALL(::SQLPrepare(m_hstmtUpdate, reinterpret_cast<SQLTCHAR *>(pszSQL), SQL_NTS));
if (!Check(nRetCode))
ThrowDBException(nRetCode, m_hstmtUpdate);
}
}
void CRecordset::ExecuteUpdateSQL()
{
RETCODE nRetCode;
if(!(m_dwOptions & optimizeBulkAdd))
{
TCHAR *pszSQL = const_cast<LPTSTR>(static_cast<LPCTSTR>(m_strUpdateSQL));
AFX_ODBC_CALL(::SQLExecDirect(m_hstmtUpdate, reinterpret_cast<SQLTCHAR *>(pszSQL), SQL_NTS));
if (!Check(nRetCode))
ThrowDBException(nRetCode, m_hstmtUpdate);
}
else
{
AFX_ODBC_CALL(::SQLExecute(m_hstmtUpdate));
if (!Check(nRetCode))
ThrowDBException(nRetCode, m_hstmtUpdate);
}
// Only have data-at-execution parameters for CLongBinary columns
if (nRetCode == SQL_NEED_DATA)
SendLongBinaryData(m_hstmtUpdate);
#ifdef _WIN64
SQLROWCOUNT lRowsAffected = 0;
#else
LONG lRowsAffected = 0;
#endif
AFX_SQL_SYNC(::SQLRowCount(m_hstmtUpdate, &lRowsAffected));
if (!Check(nRetCode) || lRowsAffected == -1)
{
// Assume 1 row affected if # rows affected can't be determined
lRowsAffected = 1;
}
else
{
if (lRowsAffected != 1)
{
TRACE(traceDatabase, 0, _T("Warning: %u rows affected by update operation (expected 1).\n"),
lRowsAffected);
ThrowDBException((RETCODE)(lRowsAffected == 0 ?
AFX_SQL_ERROR_NO_ROWS_AFFECTED :
AFX_SQL_ERROR_MULTIPLE_ROWS_AFFECTED));
}
}
m_strUpdateSQL.Empty();
}
void CRecordset::SendLongBinaryData(HSTMT hstmt)
{
RETCODE nRetCode;
void* pv;
AFX_ODBC_CALL(::SQLParamData(hstmt, &pv));
if (!Check(nRetCode))
{
// cache away error
CDBException* pException = new CDBException(nRetCode);
pException->BuildErrorString(m_pDatabase, hstmt);
// then cancel Execute operation
Cancel();
THROW(pException);
}
while (nRetCode == SQL_NEED_DATA)
{
CLongBinary* pLongBinary = (CLongBinary*)pv;
ASSERT_VALID(pLongBinary);
const BYTE* lpData = (const BYTE*)::GlobalLock(pLongBinary->m_hData);
ASSERT(lpData != NULL);
AFX_ODBC_CALL(::SQLPutData(hstmt, (PTR)lpData,
(SQLLEN)pLongBinary->m_dwDataLength));
::GlobalUnlock(pLongBinary->m_hData);
if (!Check(nRetCode))
{
// cache away error
CDBException* pException = new CDBException(nRetCode);
pException->BuildErrorString(m_pDatabase, hstmt);
// then cancel Execute operation
Cancel();
THROW(pException);
}
// Check for another DATA_AT_EXEC
AFX_ODBC_CALL(::SQLParamData(hstmt, &pv));
if (!Check(nRetCode))
{
TRACE(traceDatabase, 0, _T("Error: failure handling long binary value during update.\n"));
ThrowDBException(nRetCode, hstmt);
}
}
}
//////////////////////////////////////////////////////////////////////////////
// CRecordset RFX implementations
void CRecordset::AllocStatusArrays()
{
TRY
{
if (m_nFields != 0)
{
// Allocate buffers to hold field info
if (m_rgFieldInfos == NULL)
{
m_rgFieldInfos = new CFieldInfo[m_nFields];
memset(m_rgFieldInfos, 0, sizeof(CFieldInfo) * m_nFields);
}
if (m_pbFieldFlags == NULL)
{
m_pbFieldFlags = new BYTE[m_nFields];
memset(m_pbFieldFlags, 0, m_nFields);
}
}
if (m_nParams != 0)
{
// Allocate buffers to hold param info
if (m_pbParamFlags == NULL)
{
m_pbParamFlags = new BYTE[m_nParams];
memset(m_pbParamFlags, 0, m_nParams);
}
if (m_plParamLength == NULL)
{
m_plParamLength = new LONG_PTR[m_nParams];
memset(m_plParamLength, 0, m_nParams*sizeof(LONG_PTR));
}
}
}
CATCH_ALL(e)
{
Close();
THROW_LAST();
}
END_CATCH_ALL
}
int CRecordset::GetBoundFieldIndex(void* pv)
{
void* pvIndex;
if (!m_mapFieldIndex.Lookup(pv, pvIndex))
return -1;
else
// Cached value is short not ptr
return (int)(INT_PTR)pvIndex;
}
int CRecordset::GetBoundParamIndex(void* pv)
{
void* pvIndex;
if (!m_mapParamIndex.Lookup(pv, pvIndex))
return -1;
else
// Cached value in data not ptr
return (int)(INT_PTR)pvIndex;
}
short CRecordset::GetFieldIndexByName(LPCTSTR lpszFieldName)
{
short nIndex;
for (nIndex = 0; nIndex < GetODBCFieldCount(); nIndex++)
{
if (m_rgODBCFieldInfos[nIndex].m_strName == lpszFieldName)
break;
}
// Check if field name found
if (nIndex == GetODBCFieldCount())
ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
return nIndex;
}
LONG_PTR* CRecordset::GetFieldLengthBuffer(DWORD nField, int nFieldType)
{
if (nFieldType == CFieldExchange::outputColumn)
{
ASSERT(nField < m_nFields);
if(nField >= m_nFields)
AfxThrowUserException();
return &m_rgFieldInfos[nField].m_nLength;
}
else
{
ASSERT(nField < m_nParams);
if(nField >= m_nParams)
AfxThrowUserException();
return &m_plParamLength[nField];
}
}
BYTE CRecordset::GetFieldStatus(DWORD nField)
{
ASSERT(nField < m_nFields);
if(nField >= m_nFields)
AfxThrowUserException();
return m_pbFieldFlags[nField];
}
void CRecordset::SetFieldStatus(DWORD nField, BYTE bFlags)
{
ASSERT(nField < m_nFields);
if(nField >= m_nFields)
AfxThrowUserException();
m_pbFieldFlags[nField] |= bFlags;
}
void CRecordset::ClearFieldStatus()
{
memset(m_pbFieldFlags, 0, m_nFields);
}
BOOL CRecordset::IsFieldStatusDirty(DWORD nField) const
{
ASSERT(nField < m_nFields);
if(nField >= m_nFields)
AfxThrowUserException();
return m_pbFieldFlags[nField] & AFX_SQL_FIELD_FLAG_DIRTY;
}
void CRecordset::SetDirtyFieldStatus(DWORD nField)
{
ASSERT(nField < m_nFields);
if(nField >= m_nFields)
AfxThrowUserException();
m_pbFieldFlags[nField] |= AFX_SQL_FIELD_FLAG_DIRTY;
}
void CRecordset::ClearDirtyFieldStatus(DWORD nField)
{
ASSERT(nField < m_nFields);
if(nField >= m_nFields)
AfxThrowUserException();
m_pbFieldFlags[nField] &= ~AFX_SQL_FIELD_FLAG_DIRTY;
}
BOOL CRecordset::IsFieldStatusNull(DWORD nField) const
{
ASSERT(nField < m_nFields);
if(nField >= m_nFields)
AfxThrowUserException();
return m_pbFieldFlags[nField] & AFX_SQL_FIELD_FLAG_NULL;
}
void CRecordset::SetNullFieldStatus(DWORD nField)
{
ASSERT(nField < m_nFields);
if(nField >= m_nFields)
AfxThrowUserException();
m_pbFieldFlags[nField] |= AFX_SQL_FIELD_FLAG_NULL;
}
void CRecordset::ClearNullFieldStatus(DWORD nField)
{
ASSERT(nField < m_nFields);
if(nField >= m_nFields)
AfxThrowUserException();
m_pbFieldFlags[nField] &= ~AFX_SQL_FIELD_FLAG_NULL;
}
BOOL CRecordset::IsParamStatusNull(DWORD nParam) const
{
ASSERT(nParam < m_nParams);
if(nParam >= m_nParams)
AfxThrowUserException();
return m_pbParamFlags[nParam] & AFX_SQL_FIELD_FLAG_NULL;
}
void CRecordset::SetNullParamStatus(DWORD nParam)
{
ASSERT(nParam < m_nParams);
if(nParam >= m_nParams)
AfxThrowUserException();
m_pbParamFlags[nParam] |= AFX_SQL_FIELD_FLAG_NULL;
}
void CRecordset::ClearNullParamStatus(DWORD nParam)
{
ASSERT(nParam < m_nParams);
if(nParam >= m_nParams)
AfxThrowUserException();
m_pbParamFlags[nParam] &= ~AFX_SQL_FIELD_FLAG_NULL;
}
BOOL CRecordset::IsFieldNullable(DWORD nField) const
{
ASSERT(nField <= INT_MAX);
if(nField > INT_MAX)
AfxThrowUserException();
ASSERT((long)nField < GetODBCFieldCount());
if((long)nField >= GetODBCFieldCount())
AfxThrowUserException();
// return TRUE if nulls allowed or if not known
return m_rgODBCFieldInfos[nField].m_nNullability != SQL_NO_NULLS;
}
UINT CRecordset::BindParams(HSTMT hstmt)
{
ASSERT_VALID(this);
ASSERT(m_hstmt != SQL_NULL_HSTMT);
CFieldExchange fx(CFieldExchange::BindParam, this);
fx.m_hstmt = hstmt;
DoFieldExchange(&fx);
return fx.m_nParams;
}
void CRecordset::RebindParams(HSTMT hstmt)
{
ASSERT_VALID(this);
ASSERT(m_hstmt != SQL_NULL_HSTMT);
if (m_bRebindParams)
{
CFieldExchange fx(CFieldExchange::RebindParam, this);
fx.m_hstmt = hstmt;
DoFieldExchange(&fx);
}
}
UINT CRecordset::BindFieldsToColumns()
{
ASSERT_VALID(this);
ASSERT(m_hstmt != SQL_NULL_HSTMT);
ASSERT(m_nFieldsBound == 0);
ASSERT(m_nFields != 0 && m_nFields <= 255);
CFieldExchange fx(CFieldExchange::BindFieldToColumn, this);
fx.m_hstmt = m_hstmt;
// Binding depends on fetch type
if (m_dwOptions & useMultiRowFetch)
DoBulkFieldExchange(&fx);
else
DoFieldExchange(&fx);
return fx.m_nFields;
}
void CRecordset::BindFieldsForUpdate()
{
ASSERT_VALID(this);
if (m_nEditMode == edit || m_nEditMode == addnew)
{
CFieldExchange fx(CFieldExchange::BindFieldForUpdate, this);
fx.m_hstmt = m_hstmt;
DoFieldExchange(&fx);
}
}
void CRecordset::UnbindFieldsForUpdate()
{
ASSERT_VALID(this);
if (m_nEditMode == edit || m_nEditMode == addnew)
{
CFieldExchange fx(CFieldExchange::UnbindFieldForUpdate, this);
fx.m_hstmt = m_hstmt;
DoFieldExchange(&fx);
}
}
// After Move operation, reflect status and lengths of columns in RFX fields
void CRecordset::Fixups()
{
ASSERT_VALID(this);
ASSERT(m_hstmt != SQL_NULL_HSTMT);
ASSERT(m_nFieldsBound != 0);
CFieldExchange fx(CFieldExchange::Fixup, this);
fx.m_hstmt = m_hstmt;
DoFieldExchange(&fx);
}
UINT CRecordset::AppendNames(CString* pstr, LPCTSTR lpszSeparator)
{
ASSERT_VALID(this);
ASSERT(AfxIsValidAddress(pstr, sizeof(CString)));
ASSERT(AfxIsValidString(lpszSeparator));
ASSERT(m_hstmt != SQL_NULL_HSTMT);
CFieldExchange fx(CFieldExchange::Name, this);
fx.m_pstr = pstr;
fx.m_lpszSeparator = lpszSeparator;
if (m_dwOptions & useMultiRowFetch)
DoBulkFieldExchange(&fx);
else
DoFieldExchange(&fx);
return fx.m_nFields;
}
// For each "changed" column, append <column name>=<column value>,
UINT CRecordset::AppendNamesValues(HSTMT hstmt, CString* pstr,
LPCTSTR lpszSeparator)
{
ASSERT_VALID(this);
ASSERT(AfxIsValidAddress(pstr, sizeof(CString)));
ASSERT(AfxIsValidString(lpszSeparator));
ASSERT(m_hstmt != SQL_NULL_HSTMT);
ASSERT(hstmt != SQL_NULL_HSTMT);
CFieldExchange fx(CFieldExchange::NameValue, this);
fx.m_pstr = pstr;
fx.m_lpszSeparator = lpszSeparator;
fx.m_hstmt = hstmt;
DoFieldExchange(&fx);
return fx.m_nFields;
}
// For each "changed" column, append <column value>,
UINT CRecordset::AppendValues(HSTMT hstmt, CString* pstr,
LPCTSTR lpszSeparator)
{
ASSERT_VALID(this);
ASSERT(AfxIsValidAddress(pstr, sizeof(CString)));
ASSERT(AfxIsValidString(lpszSeparator));
ASSERT(m_hstmt != SQL_NULL_HSTMT);
ASSERT(hstmt != SQL_NULL_HSTMT);
CFieldExchange fx(CFieldExchange::Value, this);
fx.m_pstr = pstr;
fx.m_lpszSeparator = lpszSeparator;
fx.m_hstmt = hstmt;
DoFieldExchange(&fx);
return fx.m_nFields;
}
// Cache fields of copy buffer in a CMemFile with CArchive
void CRecordset::StoreFields()
{
ASSERT_VALID(this);
ASSERT(m_nFieldsBound != 0);
CFieldExchange fx(CFieldExchange::StoreField, this);
DoFieldExchange(&fx);
}
// Restore fields of copy buffer from archived memory file
void CRecordset::LoadFields()
{
ASSERT_VALID(this);
ASSERT(m_nFieldsBound != 0);
// Must clear out the old status
ClearFieldStatus();
CFieldExchange fx(CFieldExchange::LoadField, this);
DoFieldExchange(&fx);
}
void CRecordset::MarkForUpdate()
{
ASSERT_VALID(this);
CFieldExchange fx(CFieldExchange::MarkForUpdate, this);
DoFieldExchange(&fx);
}
void CRecordset::MarkForAddNew()
{
ASSERT_VALID(this);
CFieldExchange fx(CFieldExchange::MarkForAddNew, this);
DoFieldExchange(&fx);
}
void CRecordset::AllocDataCache()
{
ASSERT_VALID(this);
CFieldExchange fx(CFieldExchange::AllocCache, this);
DoFieldExchange(&fx);
}
void CRecordset::FreeDataCache()
{
ASSERT_VALID(this);
CFieldInfo* pInfo;
for (DWORD nField = 0; nField < m_nFields; nField++)
{
pInfo = &m_rgFieldInfos[nField];
switch(pInfo->m_nDataType)
{
default:
ASSERT(FALSE);
// fall through
// Data not cached
case AFX_RFX_NO_TYPE:
break;
// Types cached by value (sizeof(TYPE) <= sizeof(void*))
case AFX_RFX_BOOL:
case AFX_RFX_BYTE:
case AFX_RFX_INT:
case AFX_RFX_LONG:
case AFX_RFX_SINGLE:
pInfo->m_pvDataCache = NULL;
break;
case AFX_RFX_WTEXT:
delete reinterpret_cast<CStringW *>(pInfo->m_pvDataCache);
pInfo->m_pvDataCache = NULL;
break;
case AFX_RFX_ATEXT:
delete reinterpret_cast<CStringA *>(pInfo->m_pvDataCache);
pInfo->m_pvDataCache = NULL;
break;
case AFX_RFX_LPWSTR:
delete [] LPWSTR(pInfo->m_pvDataCache);
pInfo->m_pvDataCache = NULL;
break;
case AFX_RFX_LPASTR:
delete [] LPSTR(pInfo->m_pvDataCache);
pInfo->m_pvDataCache = NULL;
break;
case AFX_RFX_DOUBLE:
delete (double*)pInfo->m_pvDataCache;
pInfo->m_pvDataCache = NULL;
break;
case AFX_RFX_BIGINT:
delete (LONGLONG*)pInfo->m_pvDataCache;
pInfo->m_pvDataCache = NULL;
break;
case AFX_RFX_TIMESTAMP:
delete (TIMESTAMP_STRUCT*)pInfo->m_pvDataCache;
pInfo->m_pvDataCache = NULL;
break;
case AFX_RFX_OLEDATE:
delete (COleDateTime*)pInfo->m_pvDataCache;
pInfo->m_pvDataCache = NULL;
break;
case AFX_RFX_DATE:
delete (CTime*)pInfo->m_pvDataCache;
pInfo->m_pvDataCache = NULL;
break;
case AFX_RFX_BINARY:
delete (CByteArray*)pInfo->m_pvDataCache;
pInfo->m_pvDataCache = NULL;
break;
}
}
}
#ifdef _DEBUG
void CRecordset::DumpFields(CDumpContext& dc) const
{
CFieldExchange fx(CFieldExchange::DumpField, (CRecordset *)this);
fx.m_pdcDump = &dc;
((CRecordset *)this)->DoFieldExchange(&fx);
}
#endif // _DEBUG
// Perform Update (m_nModeEdit == edit), Insert (addnew),
// or Delete (noMode)
BOOL CRecordset::UpdateInsertDelete()
{
ASSERT_VALID(this);
ASSERT(m_hstmt != SQL_NULL_HSTMT);
// Delete mode
if (m_nEditMode == addnew)
{
if (!m_bAppendable)
{
TRACE(traceDatabase, 0, _T("Error: attempted to add a record to a read only recordset.\n"));
ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
}
}
else
{
if (!m_bUpdatable)
{
TRACE(traceDatabase, 0, _T("Error: attempted to update a read only recordset.\n"));
ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
}
// Requires currency
if (m_bEOF || m_bBOF || m_bDeleted)
{
TRACE(traceDatabase, 0, _T("Error: attempting to update recordset - but no record is current.\n"));
ThrowDBException(AFX_SQL_ERROR_NO_CURRENT_RECORD);
}
}
// Update or AddNew is NOP w/o at least 1 changed field
if (m_nEditMode != noMode && !IsFieldDirty(NULL))
return FALSE;
if (!m_bUseUpdateSQL)
{
// Most efficient update method
ExecuteSetPosUpdate();
}
else
{
BOOL bNullHstmt = (m_hstmtUpdate == NULL);
// Make sure m_hstmtUpdate allocated
PrepareUpdateHstmt();
// Build update SQL unless optimizing bulk adds and hstmt not NULL
if(!(m_dwOptions & optimizeBulkAdd) || bNullHstmt)
{
// Mark as first bulk add if optimizing
if(m_dwOptions & optimizeBulkAdd)
{
m_dwOptions &= ~optimizeBulkAdd;
m_dwOptions |= firstBulkAdd;
}
BuildUpdateSQL();
// Reset flag marking first optimization
if(m_dwOptions & firstBulkAdd)
{
m_dwOptions &= ~firstBulkAdd;
m_dwOptions |= optimizeBulkAdd;
}
}
else
{
// Just reset the data lengths and datetime proxies
AppendValues(m_hstmtUpdate, &m_strUpdateSQL, _afxComma);
}
ExecuteUpdateSQL();
}
TRY
{
// Delete
switch (m_nEditMode)
{
case noMode:
// Decrement record count
if (m_lCurrentRecord >= 0)
{
if (m_lRecordCount > 0)
m_lRecordCount--;
m_lCurrentRecord--;
}
// indicate on a deleted record
m_bDeleted = TRUE;
// Set all fields to NULL
SetFieldNull(NULL);
break;
case addnew:
// The recordset may no longer be empty (depending on driver behavior)
// reset m_bEOF/m_bBOF so Move can be called
m_bEOF = m_bBOF = FALSE;
if (m_pDatabase->m_bIncRecordCountOnAdd && m_lCurrentRecord >= 0)
{
if (m_lRecordCount != -1)
m_lRecordCount++;
m_lCurrentRecord++;
}
// Reset the data cache if necessary
if (m_bCheckCacheForDirtyFields && m_nFields > 0)
LoadFields();
break;
case edit:
break;
}
// Reset the edit mode
m_nEditMode = noMode;
}
END_TRY
// Unless doing a bulk AddNew, reset the dirty flags
if (m_bCheckCacheForDirtyFields && !(m_dwOptions & optimizeBulkAdd))
SetFieldDirty(NULL, FALSE);
// Must return TRUE since record updated
return TRUE;
}
// Fetch and alloc algorithms for CLongBinary data when length unknown
SQLLEN CRecordset::GetLBFetchSize(SQLLEN lOldSize)
{
// Make it twice as big
return lOldSize << 1;
}
SQLLEN CRecordset::GetLBReallocSize(SQLLEN lOldSize)
{
// Make it twice as big (no effect if less than fetch size)
return lOldSize << 1;
}
short PASCAL CRecordset::GetDefaultFieldType(short nSQLType)
{
short nFieldType = 0;
switch (nSQLType)
{
case SQL_BIT:
nFieldType = SQL_C_BIT;
break;
case SQL_TINYINT:
nFieldType = SQL_C_UTINYINT;
break;
case SQL_SMALLINT:
nFieldType = SQL_C_SSHORT;
break;
case SQL_INTEGER:
nFieldType = SQL_C_SLONG;
break;
case SQL_REAL:
nFieldType = SQL_C_FLOAT;
break;
case SQL_FLOAT:
case SQL_DOUBLE:
nFieldType = SQL_C_DOUBLE;
break;
case SQL_DATE:
case SQL_TIME:
case SQL_TIMESTAMP:
nFieldType = SQL_C_TIMESTAMP;
break;
case SQL_NUMERIC:
case SQL_DECIMAL:
case SQL_BIGINT:
nFieldType = SQL_C_TCHAR;
break;
case SQL_CHAR:
case SQL_VARCHAR:
case SQL_LONGVARCHAR:
nFieldType = SQL_C_CHAR;
break;
case SQL_WCHAR:
case SQL_WVARCHAR:
case SQL_WLONGVARCHAR:
nFieldType = SQL_C_WCHAR;
break;
case SQL_BINARY:
case SQL_VARBINARY:
case SQL_LONGVARBINARY:
nFieldType = SQL_C_BINARY;
break;
default:
//ASSERT(FALSE); // PAF here, ntext = SQL_NVARCHAR, and there is nvarchar...
nFieldType = SQL_C_CHAR; ///
break; ///
}
return nFieldType;
}
void* PASCAL CRecordset::GetDataBuffer(CDBVariant& varValue,
short nFieldType, SQLLEN* pnLen, short nSQLType, SQLULEN nPrecision)
{
void* pvData = NULL;
switch (nFieldType)
{
case SQL_C_BIT:
pvData = &varValue.m_boolVal;
varValue.m_dwType = DBVT_BOOL;
varValue.m_boolVal = 0; // SQL will only set the low byte, so we need
// to initialize the whole thing to zero.
*pnLen = sizeof(varValue.m_boolVal);
break;
case SQL_C_UTINYINT:
pvData = &varValue.m_chVal;
varValue.m_dwType = DBVT_UCHAR;
*pnLen = sizeof(varValue.m_chVal);
break;
case SQL_C_SSHORT:
pvData = &varValue.m_iVal;
varValue.m_dwType = DBVT_SHORT;
*pnLen = sizeof(varValue.m_iVal);
break;
case SQL_C_SLONG:
pvData = &varValue.m_lVal;
varValue.m_dwType = DBVT_LONG;
*pnLen = sizeof(varValue.m_lVal);
break;
case SQL_C_FLOAT:
pvData = &varValue.m_fltVal;
varValue.m_dwType = DBVT_SINGLE;
*pnLen = sizeof(varValue.m_fltVal);
break;
case SQL_C_DOUBLE:
pvData = &varValue.m_dblVal;
varValue.m_dwType = DBVT_DOUBLE;
*pnLen = sizeof(varValue.m_dblVal);
break;
case SQL_C_TIMESTAMP:
pvData = varValue.m_pdate = new TIMESTAMP_STRUCT;
varValue.m_dwType = DBVT_DATE;
*pnLen = sizeof(*varValue.m_pdate);
break;
case SQL_C_CHAR:
varValue.m_pstringA = new CStringA;
varValue.m_dwType = DBVT_ASTRING;
*pnLen = GetTextLen(nSQLType, nPrecision);
if (*pnLen > INT_MAX)
AfxThrowMemoryException();
pvData = varValue.m_pstringA->GetBufferSetLength(int(*pnLen));
break;
case SQL_C_WCHAR:
varValue.m_pstringW = new CStringW;
varValue.m_dwType = DBVT_WSTRING;
*pnLen = GetTextLen(nSQLType, nPrecision);
if (*pnLen > INT_MAX)
AfxThrowMemoryException();
pvData = varValue.m_pstringW->GetBufferSetLength(int(*pnLen));
break;
case SQL_C_BINARY:
varValue.m_pbinary = new CLongBinary;
varValue.m_dwType = DBVT_BINARY;
if (nSQLType == SQL_LONGVARBINARY)
{
// pvData can't be NULL, so nLen must be at least 1
*pnLen = 1;
}
else
{
// better know the length!
ASSERT(nPrecision != 0);
*pnLen = nPrecision;
}
varValue.m_pbinary->m_hData = ::GlobalAlloc(GMEM_MOVEABLE, *pnLen);
varValue.m_pbinary->m_dwDataLength = *pnLen;
pvData = ::GlobalLock(varValue.m_pbinary->m_hData);
break;
default:
ASSERT(FALSE);
}
return pvData;
}
SQLLEN PASCAL CRecordset::GetTextLen(short nSQLType, SQLULEN nPrecision)
{
SQLLEN nLen;
if (nSQLType == SQL_LONGVARCHAR || nSQLType == SQL_LONGVARBINARY
|| nSQLType == SQL_NVARCHAR /*PAF*/
|| nSQLType == SQL_NTEXT /*PAF */)
{
// Use a dummy length of 1 (will just get NULL terminator)
nLen = 1;
}
else if(nSQLType == SQL_WLONGVARCHAR)
{
// Use a dummy length of DBCS 1
nLen = sizeof(WCHAR);
}
else
{
// better know the length
//ASSERT(nPrecision >= 0);
nLen = nPrecision + 1;
/*
switch (nSQLType)
{
case SQL_INTEGER:
case SQL_SMALLINT:
nLen++; // '-' sign
break;
case SQL_NUMERIC:
case SQL_DECIMAL:
case SQL_FLOAT:
case SQL_REAL:
case SQL_DOUBLE:
// PAF&paul removed, because there are other types to check, and "more!=less"
*/
nLen += 2; // '-' sign and decimal point
/*break;
}*/
}
return nLen;
}
SQLLEN PASCAL CRecordset::GetData(CDatabase* pdb, HSTMT hstmt,
short nFieldIndex, short nFieldType, LPVOID pvData, SQLLEN nLen,
short nSQLType)
{
UNUSED(nSQLType);
SQLLEN nActualSize;
RETCODE nRetCode;
// Retrieve the column in question
AFX_ODBC_CALL(::SQLGetData(hstmt, nFieldIndex,
nFieldType, pvData, nLen, &nActualSize));
// Ignore data truncated warnings for long data
if (nRetCode == SQL_SUCCESS_WITH_INFO)
{
#ifdef _DEBUG
CDBException e(nRetCode);
// Build the error string but don't send nuisance output to TRACE window
e.BuildErrorString(pdb, hstmt, FALSE);
// If not a data truncated warning on long var column,
// then send debug output
if ((nSQLType != SQL_LONGVARCHAR &&
nSQLType != SQL_WLONGVARCHAR &&
nSQLType != SQL_LONGVARBINARY) ||
(e.m_strStateNativeOrigin.Find(_afxDataTruncated) < 0))
{
TRACE(traceDatabase, 0, _T("Warning: ODBC Success With Info on field %d.\n"),
nFieldIndex - 1);
e.TraceErrorMessage(e.m_strError);
e.TraceErrorMessage(e.m_strStateNativeOrigin);
}
#endif // _DEBUG
}
else if (nRetCode == SQL_NO_DATA_FOUND)
{
TRACE(traceDatabase, 0, _T("Error: GetFieldValue operation failed on field %d.\n"));
TRACE(traceDatabase, 0, _T("\tData already fetched for this field.\n"),
nFieldIndex - 1);
AfxThrowDBException(nRetCode, pdb, hstmt);
}
else if (nRetCode != SQL_SUCCESS)
{
TRACE(traceDatabase, 0, _T("Error: GetFieldValue operation failed on field %d.\n"),
nFieldIndex - 1);
AfxThrowDBException(nRetCode, pdb, hstmt);
}
return nActualSize;
}
template<typename StringType>
inline static void PASCAL GetLongCharDataAndCleanup(CDatabase* pdb,
HSTMT hstmt, short nFieldIndex, SQLLEN nActualSize, LPVOID* ppvData,
SQLLEN nLen, StringType& strValue, short nSQLType, short nSQLCType)
{
RETCODE nRetCode;
// Release the buffer now that data has been fetched
strValue.ReleaseBuffer((int)(nActualSize < nLen ? nActualSize : nLen));
// If long data, may need to call SQLGetData again
if (nLen <= nActualSize &&
(nSQLType == SQL_WLONGVARCHAR || nSQLType == SQL_LONGVARCHAR ||
nSQLType == SQL_LONGVARBINARY
|| nSQLType == SQL_NVARCHAR /*PAF*/
|| nSQLType == SQL_NTEXT /*PAF */))
{
// Reallocate the size (this will copy the data)
if (nActualSize > (INT_MAX-1))
AfxThrowMemoryException();
*ppvData = strValue.GetBufferSetLength((int)nActualSize + 1);
// Get pointer, skipping over original data, but not the NULL
SQLLEN nOldLen = nLen - 1;
*ppvData = (BYTE*)*ppvData + nOldLen;
nLen = nActualSize + 1 - nOldLen;
// Retrieve the column in question
AFX_ODBC_CALL(::SQLGetData(hstmt, nFieldIndex,
nSQLCType, *ppvData, nLen, &nActualSize));
if (nRetCode == SQL_SUCCESS_WITH_INFO)
{
#ifdef _DEBUG
TRACE(traceDatabase, 0, _T("Warning: ODBC Success With Info on field %d.\n"),
nFieldIndex - 1);
CDBException e(nRetCode);
e.BuildErrorString(pdb, hstmt);
#endif // _DEBUG
}
else if (nRetCode != SQL_SUCCESS)
{
TRACE(traceDatabase, 0, _T("Error: GetFieldValue operation failed on field %d.\n"),
nFieldIndex - 1);
AfxThrowDBException(nRetCode, pdb, hstmt);
}
// Release the buffer now that data has been fetched
strValue.ReleaseBuffer((int)(nActualSize + nOldLen));
}
}
void PASCAL CRecordset::GetLongCharDataAndCleanup(CDatabase* pdb, HSTMT hstmt,
short nFieldIndex, SQLLEN nActualSize, LPVOID* ppvData, SQLLEN nLen,
CStringW& strValue, short nSQLType, short nSQLCType)
{
::GetLongCharDataAndCleanup(pdb, hstmt, nFieldIndex, nActualSize, ppvData, nLen,
strValue, nSQLType, nSQLCType);
}
void PASCAL CRecordset::GetLongCharDataAndCleanup(CDatabase* pdb, HSTMT hstmt,
short nFieldIndex, SQLLEN nActualSize, LPVOID* ppvData, SQLLEN nLen,
CStringA& strValue, short nSQLType, short nSQLCType)
{
::GetLongCharDataAndCleanup(pdb, hstmt, nFieldIndex, nActualSize, ppvData, nLen,
strValue, nSQLType, nSQLCType);
}
void PASCAL CRecordset::GetLongBinaryDataAndCleanup(CDatabase* pdb, HSTMT hstmt,
short nFieldIndex, SQLLEN nActualSize, LPVOID* ppvData, SQLLEN nLen,
CDBVariant& varValue, short nSQLType)
{
RETCODE nRetCode;
::GlobalUnlock(varValue.m_pbinary->m_hData);
// If long data, may need to call SQLGetData again
if (nLen < nActualSize && nSQLType == SQL_LONGVARBINARY)
{
// Reallocate a bigger buffer
HGLOBAL hOldData = varValue.m_pbinary->m_hData;
varValue.m_pbinary->m_hData = ::GlobalReAlloc(hOldData,
nActualSize, GMEM_MOVEABLE);
// Validate the memory was allocated and can be locked
if (varValue.m_pbinary->m_hData == NULL)
{
// Restore the old handle (not NULL if Realloc failed)
varValue.m_pbinary->m_hData = hOldData;
AfxThrowMemoryException();
}
varValue.m_pbinary->m_dwDataLength = nActualSize;
// Get pointer, skipping over original data
*ppvData = (BYTE*)::GlobalLock(varValue.m_pbinary->m_hData) + nLen;
INT_PTR nOldLen = nLen;
nLen = nActualSize - nOldLen;
// Retrieve the column in question
AFX_ODBC_CALL(::SQLGetData(hstmt, nFieldIndex,
SQL_C_BINARY, *ppvData, nLen, &nActualSize));
if (nRetCode == SQL_SUCCESS_WITH_INFO)
{
#ifdef _DEBUG
TRACE(traceDatabase, 0, _T("Warning: ODBC Success With Info on field %d.\n"),
nFieldIndex - 1);
CDBException e(nRetCode);
e.BuildErrorString(pdb, hstmt);
#endif // _DEBUG
}
else if (nRetCode != SQL_SUCCESS)
{
TRACE(traceDatabase, 0, _T("Error: GetFieldValue operation failed on field %d.\n"),
nFieldIndex - 1);
AfxThrowDBException(nRetCode, pdb, hstmt);
}
ASSERT((int)varValue.m_pbinary->m_dwDataLength ==
nActualSize + nOldLen);
// Release the buffer now that data has been fetched
::GlobalUnlock(varValue.m_pbinary->m_hData);
}
}
//////////////////////////////////////////////////////////////////////////////
// CRecordset diagnostics
#ifdef _DEBUG
void CRecordset::AssertValid() const
{
CObject::AssertValid();
if (m_pDatabase != NULL)
m_pDatabase->AssertValid();
}
void CRecordset::Dump(CDumpContext& dc) const
{
CObject::Dump(dc);
dc << _T("m_nOpenType = ") << m_nOpenType;
dc << _T("\nm_strSQL = ") << m_strSQL;
dc << _T("\nm_hstmt = ") << m_hstmt;
dc << _T("\nm_bRecordsetDb = ") << m_bRecordsetDb;
dc << _T("\nm_lOpen = ") << m_lOpen;
dc << _T("\nm_bScrollable = ") << m_bScrollable;
dc << _T("\nm_bUpdatable = ") << m_bUpdatable;
dc << _T("\nm_bAppendable = ") << m_bAppendable;
dc << _T("\nm_nFields = ") << m_nFields;
dc << _T("\nm_nFieldsBound = ") << m_nFieldsBound;
dc << _T("\nm_nParams = ") << m_nParams;
dc << _T("\nm_bEOF = ") << m_bEOF;
dc << _T("\nm_bBOF = ") << m_bBOF;
dc << _T("\nm_bDeleted = ") << m_bEOF;
dc << _T("\nm_bLockMode = ") << m_nLockMode;
dc << _T("\nm_nEditMode = ") << m_nEditMode;
dc << _T("\nm_strCursorName = ") << m_strCursorName;
dc << _T("\nm_hstmtUpdate = ") << m_hstmtUpdate;
dc << _T("\nDump values for each field in current record.");
DumpFields(dc);
if (dc.GetDepth() > 0)
{
if (m_pDatabase == NULL)
dc << _T("with no database\n");
else
dc << _T("with database: ") << m_pDatabase;
}
}
#endif // _DEBUG
//////////////////////////////////////////////////////////////////////////////
// Helpers
void AFXAPI _AfxSetCurrentRecord(long* plCurrentRecord, long nRows, RETCODE nRetCode)
{
if (*plCurrentRecord != AFX_CURRENT_RECORD_UNDEFINED &&
nRetCode != SQL_NO_DATA_FOUND)
*plCurrentRecord += nRows;
}
void AFXAPI _AfxSetRecordCount(long* plRecordCount, long lCurrentRecord,
BOOL bEOFSeen, RETCODE nRetCode)
{
// If not at the end and haven't yet been to the end, incr count
if (nRetCode != SQL_NO_DATA_FOUND && !bEOFSeen &&
lCurrentRecord != AFX_CURRENT_RECORD_UNDEFINED)
{
// lCurrentRecord 0-based and it's already been set
*plRecordCount =
__max(*plRecordCount, lCurrentRecord + 1);
}
}
//////////////////////////////////////////////////////////////////////////////
// Inline function declarations expanded out-of-line
#ifndef _AFX_ENABLE_INLINES
#define _AFXDBCORE_INLINE
#include "afxdb.inl"
#endif
#ifdef AFX_INIT_SEG
#pragma code_seg(AFX_INIT_SEG)
#endif
IMPLEMENT_DYNAMIC(CDBException, CException)
IMPLEMENT_DYNAMIC(CDatabase, CObject)
IMPLEMENT_DYNAMIC(CRecordset, CObject)
#pragma warning(disable: 4074)
#pragma init_seg(lib)
PROCESS_LOCAL(_AFX_DB_STATE, _afxDbState)
/////////////////////////////////////////////////////////////////////////////
#endif // !_WIN64
E-mail: