/wcwp/bin/VC6CMD/MFC/SRC/DBCORE.CPP
http://wcwp.googlecode.com/ · C++ · 2662 lines · 2000 code · 415 blank · 247 comment · 457 complexity · 8caafa0b29c5d46086c9555a519dc9da MD5 · raw file
- // This is a part of the Microsoft Foundation Classes C++ library.
- // Copyright (C) 1992-1998 Microsoft Corporation
- // All rights reserved.
- //
- // This source code is only intended as a supplement to the
- // Microsoft Foundation Classes Reference and related
- // electronic documentation provided with the library.
- // See these sources for detailed information regarding the
- // Microsoft Foundation Classes product.
-
- #include "stdafx.h"
-
- #ifdef AFX_DB_SEG
- #pragma code_seg(AFX_DB_SEG)
- #endif
-
- #ifdef _DEBUG
- #undef THIS_FILE
- static char THIS_FILE[] = __FILE__;
- #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 _afxCall[] = _T("{CALL ");
- 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)));
- TRACE1("%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;
- UCHAR lpszMsg[SQL_MAX_MESSAGE_LENGTH];
- UCHAR lpszState[SQL_SQLSTATE_SIZE];
- CString strMsg;
- CString strState;
- SDWORD lNative;
-
- _AFX_DB_STATE* pDbState = _afxDbState;
- AFX_SQL_SYNC(::SQLError(pDbState->m_henvAllConnections, pdb->m_hdbc,
- hstmt, lpszState, &lNative,
- 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, lpszState, &lNative,
- 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)
- TRACE1("%s\n", strTrace);
- else
- {
- // Display 80 chars/line
- while (strTrace.GetLength() > 80)
- {
- TRACE1("%s\n", strTrace.Left(80));
- strTrace = strTrace.Right(strTrace.GetLength() - 80);
- }
- TRACE1("%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;
-
- // For VB/Access compatibility, require "ODBC;" (or "odbc;")
- // prefix to the connect string
- if (_tcsnicmp(strConnect, _afxODBCTrail, lstrlen(_afxODBCTrail)) != 0)
- {
- TRACE0("Error: Missing 'ODBC' prefix on connect string.\n");
- return FALSE;
- }
-
- // 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)
- {
- USES_CONVERSION;
- 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);
-
- AFX_ODBC_CALL(::SQLExecDirect(hstmt,
- (UCHAR*)T2A((LPTSTR)lpszSQL), SQL_NTS));
-
- if (!CheckHstmt(nRetCode, hstmt))
- AfxThrowDBException(nRetCode, this, hstmt);
- else
- {
- do
- {
- SWORD nResultColumns;
-
- AFX_ODBC_CALL(::SQLNumResultCols(hstmt, &nResultColumns));
- if (nResultColumns != 0)
- {
- do
- {
- AFX_ODBC_CALL(::SQLFetch(hstmt));
- } while (CheckHstmt(nRetCode, hstmt) &&
- nRetCode != SQL_NO_DATA_FOUND);
- }
- AFX_ODBC_CALL(::SQLMoreResults(hstmt));
- } while (CheckHstmt(nRetCode, hstmt) &&
- 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
- TRACE0("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
- TRACE0("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);
-
- char szName[MAX_TNAME_LEN];
- SWORD nResult;
- RETCODE nRetCode;
-
- AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DATABASE_NAME,
- szName, _countof(szName), &nResult));
- if (!Check(nRetCode))
- szName[0] = '\0';
-
- return szName;
- }
-
- 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
- if (afxTraceFlags & traceDatabase)
- {
- CDBException e(nRetCode);
- TRACE0("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 != '\0')
- {
- 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 (!bInLiteral && (*lpchSQL == '['))
- {
- if (*_tcsinc(lpchSQL) == '[')
- {
- // Handle escaped left bracket by inserting one '['
- *lpchNewSQL = *lpchSQL;
- lpchSQL = _tcsinc(lpchSQL);
- }
- else
- *lpchNewSQL = m_chIDQuoteChar;
- }
- else if (!bInLiteral && (*lpchSQL == ']'))
- {
- if (*_tcsinc(lpchSQL) == ']')
- {
- // 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);
- }
- }
-
- // 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,
- (DWORD)"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 &&
- (afxTraceFlags & traceDatabase))
- TRACE0("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 &&
- (afxTraceFlags & traceDatabase))
- TRACE0("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)
- {
- USES_CONVERSION;
-
- HWND hWndTop;
- HWND hWnd = CWnd::GetSafeOwner_(NULL, &hWndTop);
- if (hWnd == NULL)
- hWnd = ::GetDesktopWindow();
-
- UCHAR szConnectOutput[MAX_CONNECT_LEN];
- 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,
- (UCHAR*)T2A((LPTSTR)(LPCTSTR)m_strConnect), SQL_NTS,
- szConnectOutput, _countof(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)
- TRACE0("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 += (char*)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
- char szReadOnly[10];
- AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DATA_SOURCE_READ_ONLY,
- szReadOnly, _countof(szReadOnly), &nResult));
- if (Check(nRetCode) && nResult == 1)
- m_bUpdatable = !(lstrcmpA(szReadOnly, "Y") == 0);
- else
- m_bUpdatable = FALSE;
- #ifdef _DEBUG
- if (!m_bUpdatable && (afxTraceFlags & traceDatabase))
- TRACE0("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
- char szIDQuoteChar[2];
- AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_IDENTIFIER_QUOTE_CHAR,
- szIDQuoteChar, _countof(szIDQuoteChar), &nResult));
- if (Check(nRetCode) && nResult == 1)
- m_chIDQuoteChar = szIDQuoteChar[0];
- else
- m_chIDQuoteChar = ' ';
-
- #ifdef _DEBUG
- if (afxTraceFlags & traceDatabase)
- {
- char szInfo[64];
- AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DBMS_NAME,
- szInfo, _countof(szInfo), &nResult));
- if (Check(nRetCode))
- {
- CString strInfo = szInfo;
- TRACE1("DBMS: %s\n", strInfo);
- AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DBMS_VER,
- szInfo, _countof(szInfo), &nResult));
- if (Check(nRetCode))
- {
- strInfo = szInfo;
- TRACE1(", Version: %s\n", strInfo);
- }
- }
- }
- #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 << "m_hdbc = " << m_hdbc;
- dc << "\nm_strConnect = " << m_strConnect;
- dc << "\nm_bUpdatable = " << m_bUpdatable;
- dc << "\nm_bTransactions = " << m_bTransactions;
- dc << "\nm_bTransactionPending = " << m_bTransactionPending;
- dc << "\nm_dwLoginTimeout = " << m_dwLoginTimeout;
- dc << "\nm_dwQueryTimeout = " << m_dwQueryTimeout;
-
- if (dc.GetDepth() > 0)
- {
- _AFX_DB_STATE* pDbState = _afxDbState;
- dc << "\nwith env:";
- dc << "\n\tnAllocated = " << pDbState->m_nAllocatedConnections;
- dc << "\n\thenvAllConnections = " << pDbState->m_henvAllConnections;
- }
-
- dc << "\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 && afxTraceFlags & traceDatabase)
- {
- TRACE0("\nWARNING: Close called implicitly from destructor.");
- TRACE0("\nUse of multi row fetch requires explicit call");
- TRACE0("\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
- TRACE0("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 if (afxTraceFlags & traceDatabase)
- TRACE0("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
- TRACE0("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());
- TRACE0("Error: field data truncated during data fetch.\n");
- ThrowDBException(AFX_SQL_ERROR_DATA_TRUNCATED);
- }
- }
- else if (e.m_strStateNativeOrigin.Find(_afxRowFetch) >= 0)
- {
- #ifdef _DEBUG
- TRACE0("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);
- }
- else
- {
- #ifdef _DEBUG
- // Not a truncation or row fetch warning so send debug output
- if (afxTraceFlags & traceDatabase)
- {
- TRACE0("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;
- SDWORD 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))
- {
- TRACE0("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)
- {
- TRACE0("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))
- {
- TRACE0("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)
- {
- TRACE0("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)
- {
- TRACE0("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() const
- {
- RETCODE nRetCode;
- AFX_ODBC_CALL(::SQLMoreResults(m_hstmt));
-
- if (!Check(nRetCode))
- {
- TRACE0("Error: attempt FlushResultSet failed.\n");
- AfxThrowDBException(nRetCode, m_pDatabase, m_hstmt);
- }
-
- // Reset state of cursor
- ((CRecordset*)this)->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;
- int 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
- long nActualSize = GetData(m_pDatabase, m_hstmt, nIndex,
- nFieldType, pvData, 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
- if (nFieldType == SQL_C_CHAR)
- {
- GetLongCharDataAndCleanup(m_pDatabase, m_hstmt, nIndex,
- nActualSize, &pvData, nLen, *varValue.m_pstring,
- m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
-
- #ifdef _UNICODE
- // Now must convert string to UNICODE
- LPCSTR lpszOld = (LPCSTR)varValue.m_pstring->GetBuffer(0);
- CString* pStringNew = new CString(lpszOld);
- delete varValue.m_pstring;
- varValue.m_pstring = pStringNew;
- #endif // _UNICODE
- }
- else if (nFieldType == SQL_C_BINARY)
- {
- GetLongBinaryDataAndCleanup(m_pDatabase, m_hstmt, nIndex,
- nActualSize, &pvData, nLen, varValue,
- m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
- }
- }
- }
-
- void CRecordset::GetFieldValue(LPCTSTR lpszName, CString& 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, CString& strValue)
- {
- ASSERT_VALID(this);
- ASSERT(IsOpen());
-
- // 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);
- }
-
- int nLen = GetTextLen(m_rgODBCFieldInfos[nIndex - 1].m_nSQLType,
- m_rgODBCFieldInfos[nIndex - 1].m_nPrecision);
-
- #ifndef _UNICODE
- CString& strData = strValue;
- #else
- CString strProxy;
- CString& strData = strProxy;
- #endif
- void* pvData = strData.GetBufferSetLength(nLen);
-
- // Now can actually get the data
- long nActualSize = GetData(m_pDatabase, m_hstmt, nIndex,
- SQL_C_CHAR, pvData, nLen,
- m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
-
- // Handle NULL data separately
- if (nActualSize == SQL_NULL_DATA)
- {
- // Clear value
- strValue.Empty();
- }
- else
- {
- // May need to cleanup and call SQLGetData again if necessary
- GetLongCharDataAndCleanup(m_pDatabase, m_hstmt, nIndex,
- nActualSize, &pvData, nLen, strData,
- m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
-
- #ifdef _UNICODE
- // Now must convert string to UNICODE
- strValue = (LPCSTR)strData.GetBuffer(0);
- #endif // _UNIOCDE
- }
- }
-
- 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)
- {
- ASSERT(FALSE);
- 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))
- {
- TRACE0("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::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
- if (afxTraceFlags & traceDatabase)
- {
- CDBException e(nRetCode);
- TRACE0("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))
- {
- #ifdef _DEBUG
- if (afxTraceFlags & traceDatabase)
- TRACE0("Warning: Setting forwardOnly recordset readOnly.\n");
- #endif
- 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))
- {
- TRACE0("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)
- {
- #ifdef _DEBUG
- if (afxTraceFlags & traceDatabase)
- TRACE0("Error: CDatabase create for CRecordset failed.\n");
- #endif
- 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()
- {
- USES_CONVERSION;
- RETCODE nRetCode = 0;
- BOOL bConcurrency = FALSE;
- LPCSTR lpszWSQL = T2CA(m_strSQL);
-
- while (!bConcurrency)
- {
- // Prepare or execute the query
- if (m_dwOptions & executeDirect)
- {
- AFX_ODBC_CALL(::SQLExecDirect(m_hstmt,
- (UCHAR*)lpszWSQL, SQL_NTS));
- }
- else
- {
- AFX_ODBC_CALL(::SQLPrepare(m_hstmt,
- (UCHAR*)lpszWSQL, 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)
- {
- #ifdef _DEBUG
- if (afxTraceFlags & traceDatabase)
- TRACE0("Warning: Driver does not support requested concurrency.\n");
- #endif
-
- // 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;
- #ifdef _DEBUG
- if (afxTraceFlags & traceDatabase)
- TRACE0("Warning: Setting recordset read only.\n");
- #endif
- }
-
- // Attempt to reset the concurrency model.
- AFX_SQL_SYNC(::SQLSetStmtOption(m_hstmt, SQL_CONCURRENCY,
- m_dwConcurrency));
- if (!Check(nRetCode))
- {
- TRACE0("Error: ODBC failure setting recordset concurrency.\n");
- ThrowDBException(nRetCode);
- }
- }
- else
- {
- TRACE0("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;
-
- #ifdef _DEBUG
- if (afxTraceFlags & traceDatabase)
- {
- TRACE0("Warning: Concurrency changed by driver.\n");
- TRACE0("\tMarking CRecordset as not updatable.\n");
- }
- #endif // _DEBUG
- }
- }
- }
-
- // 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))
- {
- TRACE0("Error: ODBC failure determining whether recordset is scrollable.\n");
- ThrowDBException(nRetCode);
- }
- m_bScrollable = wScrollable;
- if (!m_bScrollable)
- {
- #ifdef _DEBUG
- if (afxTraceFlags & traceDatabase)
- {
- TRACE0("Warning: SQLExtendedFetch not supported by driver\n");
- TRACE0("and/or cursor library not loaded. Opening forwardOnly.\n");
- TRACE0("for use with SQLFetch.\n");
- }
- #endif
- m_bUpdatable = FALSE;
- return;
- }
-
- char szResult[30];
- SWORD nResult;
- // require ODBC v2.0
- AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_ODBC_VER,
- &szResult, _countof(szResult), &nResult));
- if (!Check(nRetCode))
- {
- TRACE0("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))
- {
- TRACE0("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))
- {
- TRACE0("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];
- LPSTR lpszFieldName;
-
- #ifdef _UNICODE
- // Need proxy to temporarily store non-UNICODE name
- lpszFieldName = new char[MAX_FNAME_LEN + 1];
- #endif
-
- // Get the field info each field
- for (WORD n = 1; n <= GetODBCFieldCount(); n++)
- {
- #ifndef _UNICODE
- // Reset the buffer to point to next element
- lpszFieldName =
- m_rgODBCFieldInfos[n - 1].m_strName.GetBuffer(MAX_FNAME_LEN + 1);
- #endif
-
- AFX_ODBC_CALL(::SQLDescribeCol(m_hstmt, n,
- (UCHAR*)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));
-
- #ifndef _UNICODE
- m_rgODBCFieldInfos[n - 1].m_strName.ReleaseBuffer(nActualLen);
- #else
- // Copy the proxy data to correct element
- m_rgODBCFieldInfos[n - 1].m_strName = lpszFieldName;
- #endif
-
- if (!Check(nRetCode))
- {
- TRACE1("Error: ODBC failure getting field #%d info.\n", n);
- ThrowDBException(nRetCode);
- }
- }
-
- #ifdef _UNICODE
- delete[] lpszFieldName;
- #endif
- }
-
- void CRecordset::AllocRowset()
- {
- if (m_dwOptions & useMultiRowFetch)
- SetRowsetSize(m_dwRowsetSize);