home *** CD-ROM | disk | FTP | other *** search
- // 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);
- 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))
- {
- TRACE0("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))
- {
- TRACE0("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
- if (afxTraceFlags & traceDatabase)
- TRACE0("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))
- {
- TRACE0("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))
- {
- TRACE0("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))
- // 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)
- {
- #ifdef _DEBUG
- if (afxTraceFlags & traceDatabase)
- TRACE0("Warning: Missing ' FROM ', recordset not updatable \n");
- #endif
- return FALSE;
- }
-
- lpchToken = FindSQLToken(strSQL, _T(" GROUP BY "));
- if (lpchToken != NULL)
- {
- #ifdef _DEBUG
- if (afxTraceFlags & traceDatabase)
- TRACE0("Warning: SQL contains ' GROUP BY ', recordset not updatable \n");
- #endif
- return FALSE;
- }
-
- lpchToken = FindSQLToken(strSQL, _T(" UNION "));
- if (lpchToken != NULL)
- {
- #ifdef _DEBUG
- if (afxTraceFlags & traceDatabase)
- TRACE0("Warning: SQL contains ' UNION ', recordset not updatable \n");
- #endif
- 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)
- {
- int nFromLength = lpchTokenNext - lpchTokenFrom;
- memcpy(lpszSQLStart, lpchTokenFrom, nFromLength*sizeof(TCHAR));
- lpszSQLStart[nFromLength] = '\0';
- }
- else
- lstrcpy(lpszSQLStart, lpchTokenFrom);
-
- strSQL.ReleaseBuffer();
-
- if (IsJoin(strSQL))
- {
- #ifdef _DEBUG
- if (afxTraceFlags & traceDatabase)
- TRACE0("Warning: SQL contains join, recordset not updatable \n");
- #endif
- 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)
- {
- TRACE0("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
- TRACE0("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
- TRACE0("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
- TRACE0("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))
- {
- // 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)
- {
- TRACE0("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;
- #ifdef _DEBUG
- if (afxTraceFlags & traceDatabase)
- {
- TRACE0("Warning: Can't use SQLSetPos due to lack of SQLGetData support.\n");
- TRACE0("\tWill use positioned update SQL.\n");
- }
- #endif
- }
- else
- {
- #ifdef _DEBUG
- if (afxTraceFlags & traceDatabase)
- TRACE0("Warning: Setting recordset read only due to lack of SQLGetData support.\n");
- #endif
- 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))
- {
- TRACE0("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))
- {
- TRACE0("Error: failure to allocate update statement.\n");
- AfxThrowDBException(nRetCode, m_pDatabase, 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))
- {
- TRACE0("Error: failure to allocate update statement.\n");
- AfxThrowDBException(nRetCode, m_pDatabase, 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;
- UCHAR szCursorName[MAX_CURSOR_NAME+1];
- SWORD nLength = _countof(szCursorName)-1;
- AFX_SQL_SYNC(::SQLGetCursorName(m_hstmt,
- szCursorName, _countof(szCursorName), &nLength));
- if (!Check(nRetCode))
- ThrowDBException(nRetCode);
- m_strCursorName = (char*)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;
-
- USES_CONVERSION;
- AFX_ODBC_CALL(::SQLPrepare(m_hstmtUpdate,
- (UCHAR*)T2A((LPTSTR)(LPCTSTR)m_strUpdateSQL), SQL_NTS));
- if (!Check(nRetCode))
- ThrowDBException(nRetCode, m_hstmtUpdate);
- }
- }
-
- void CRecordset::ExecuteUpdateSQL()
- {
- RETCODE nRetCode;
-
- if(!(m_dwOptions & optimizeBulkAdd))
- {
- USES_CONVERSION;
- AFX_ODBC_CALL(::SQLExecDirect(m_hstmtUpdate,
- (UCHAR*)T2A((LPTSTR)(LPCTSTR)m_strUpdateSQL), 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);
-
- SDWORD lRowsAffected = 0;
-
- 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)
- {
- #ifdef _DEBUG
- if (afxTraceFlags & traceDatabase)
- TRACE1("Warning: %u rows affected by update operation (expected 1).\n",
- lRowsAffected);
- #endif
- 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,
- 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))
- {
- TRACE0("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[m_nParams];
- memset(m_plParamLength, 0, m_nParams*sizeof(LONG));
- }
- }
- }
- 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)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)pvIndex;
- }
-
- short CRecordset::GetFieldIndexByName(LPCTSTR lpszFieldName)
- {
- for (short 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* CRecordset::GetFieldLengthBuffer(DWORD nField, int nFieldType)
- {
- if (nFieldType == CFieldExchange::outputColumn)
- {
- ASSERT(nField < m_nFields);
- return &m_rgFieldInfos[nField].m_nLength;
- }
- else
- {
- ASSERT(nField < m_nParams);
- return &m_plParamLength[nField];
- }
- }
-
- BYTE CRecordset::GetFieldStatus(DWORD nField)
- {
- ASSERT(nField < m_nFields);
-
- return m_pbFieldFlags[nField];
- }
-
- void CRecordset::SetFieldStatus(DWORD nField, BYTE bFlags)
- {
- ASSERT(nField < m_nFields);
-
- m_pbFieldFlags[nField] |= bFlags;
- }
-
- void CRecordset::ClearFieldStatus()
- {
- memset(m_pbFieldFlags, 0, m_nFields);
- }
-
- BOOL CRecordset::IsFieldStatusDirty(DWORD nField) const
- {
- ASSERT(nField < m_nFields);
-
- return m_pbFieldFlags[nField] & AFX_SQL_FIELD_FLAG_DIRTY;
- }
-
- void CRecordset::SetDirtyFieldStatus(DWORD nField)
- {
- ASSERT(nField < m_nFields);
-
- m_pbFieldFlags[nField] |= AFX_SQL_FIELD_FLAG_DIRTY;
- }
-
- void CRecordset::ClearDirtyFieldStatus(DWORD nField)
- {
- ASSERT(nField < m_nFields);
-
- m_pbFieldFlags[nField] &= ~AFX_SQL_FIELD_FLAG_DIRTY;
- }
-
- BOOL CRecordset::IsFieldStatusNull(DWORD nField) const
- {
- ASSERT(nField < m_nFields);
-
- return m_pbFieldFlags[nField] & AFX_SQL_FIELD_FLAG_NULL;
- }
-
- void CRecordset::SetNullFieldStatus(DWORD nField)
- {
- ASSERT(nField < m_nFields);
-
- m_pbFieldFlags[nField] |= AFX_SQL_FIELD_FLAG_NULL;
- }
-
- void CRecordset::ClearNullFieldStatus(DWORD nField)
- {
- ASSERT(nField < m_nFields);
-
- m_pbFieldFlags[nField] &= ~AFX_SQL_FIELD_FLAG_NULL;
- }
-
- BOOL CRecordset::IsParamStatusNull(DWORD nParam) const
- {
- ASSERT(nParam < m_nParams);
-
- return m_pbParamFlags[nParam] & AFX_SQL_FIELD_FLAG_NULL;
- }
-
- void CRecordset::SetNullParamStatus(DWORD nParam)
- {
- ASSERT(nParam < m_nParams);
-
- m_pbParamFlags[nParam] |= AFX_SQL_FIELD_FLAG_NULL;
- }
-
- void CRecordset::ClearNullParamStatus(DWORD nParam)
- {
- ASSERT(nParam < m_nParams);
-
- m_pbParamFlags[nParam] &= ~AFX_SQL_FIELD_FLAG_NULL;
- }
-
- BOOL CRecordset::IsFieldNullable(DWORD nField) const
- {
- ASSERT(nField <= INT_MAX);
- ASSERT((long)nField < GetODBCFieldCount());
-
- // 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_TEXT:
- delete (CString*)pInfo->m_pvDataCache;
- pInfo->m_pvDataCache = NULL;
- break;
-
- case AFX_RFX_LPTSTR:
- delete [] LPTSTR(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_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)
- {
- TRACE0("Error: attempted to add a record to a read only recordset.\n");
- ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
- }
- }
- else
- {
- if (!m_bUpdatable)
- {
- TRACE0("Error: attempted to update a read only recordset.\n");
- ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
- }
-
- // Requires currency
- if (m_bEOF || m_bBOF || m_bDeleted)
- {
- TRACE0("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
- long CRecordset::GetLBFetchSize(long lOldSize)
- {
- // Make it twice as big
- return lOldSize << 1;
- }
-
- long CRecordset::GetLBReallocSize(long 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:
- case SQL_CHAR:
- case SQL_VARCHAR:
- case SQL_LONGVARCHAR:
- nFieldType = SQL_C_CHAR;
- break;
-
- case SQL_BINARY:
- case SQL_VARBINARY:
- case SQL_LONGVARBINARY:
- nFieldType = SQL_C_BINARY;
- break;
-
- default:
- ASSERT(FALSE);
- }
-
- return nFieldType;
- }
-
- void* PASCAL CRecordset::GetDataBuffer(CDBVariant& varValue,
- short nFieldType, int* pnLen, short nSQLType, UDWORD nPrecision)
- {
- void* pvData = NULL;
-
- switch (nFieldType)
- {
- case SQL_C_BIT:
- pvData = &varValue.m_boolVal;
- varValue.m_dwType = DBVT_BOOL;
- *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_pstring = new CString;
- varValue.m_dwType = DBVT_STRING;
-
- *pnLen = GetTextLen(nSQLType, nPrecision);
-
- pvData = varValue.m_pstring->GetBufferSetLength(*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;
- }
-
- int PASCAL CRecordset::GetTextLen(short nSQLType, UDWORD nPrecision)
- {
- int nLen;
-
- if (nSQLType == SQL_LONGVARCHAR || nSQLType == SQL_LONGVARBINARY)
- {
- // Use a dummy length of 1 (will just get NULL terminator)
- nLen = 1;
- }
- else
- {
- // better know the length
- ASSERT(nPrecision >= 0);
-
- nLen = nPrecision + 1;
-
- // If converting Numeric or Decimal to text need
- // room for decimal point and sign in string
- if (nSQLType == SQL_NUMERIC || nSQLType == SQL_DECIMAL)
- nLen += 2;
- }
-
- return nLen;
- }
-
- long PASCAL CRecordset::GetData(CDatabase* pdb, HSTMT hstmt,
- short nFieldIndex, short nFieldType, LPVOID pvData, int nLen,
- short nSQLType)
- {
- UNUSED(nSQLType);
-
- long 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);
-
- if (afxTraceFlags & traceDatabase)
- {
- 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_LONGVARBINARY) ||
- (e.m_strStateNativeOrigin.Find(_afxDataTruncated) < 0))
- {
- TRACE1("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)
- {
- TRACE0("Error: GetFieldValue operation failed on field %d.\n");
- TRACE1("\tData already fetched for this field.\n",
- nFieldIndex - 1);
- AfxThrowDBException(nRetCode, pdb, hstmt);
- }
- else if (nRetCode != SQL_SUCCESS)
- {
- TRACE1("Error: GetFieldValue operation failed on field %d.\n",
- nFieldIndex - 1);
- AfxThrowDBException(nRetCode, pdb, hstmt);
- }
-
- return nActualSize;
- }
-
- void PASCAL CRecordset::GetLongCharDataAndCleanup(CDatabase* pdb,
- HSTMT hstmt, short nFieldIndex, long nActualSize, LPVOID* ppvData,
- int nLen, CString& strValue, short nSQLType)
- {
- RETCODE nRetCode;
-
- // Release the buffer now that data has been fetched
- strValue.ReleaseBuffer(nActualSize < nLen ? nActualSize : nLen);
-
- // If long data, may need to call SQLGetData again
- if (nLen <= nActualSize &&
- (nSQLType == SQL_LONGVARCHAR || nSQLType == SQL_LONGVARBINARY))
- {
- // Reallocate the size (this will copy the data)
- *ppvData = strValue.GetBufferSetLength(nActualSize + 1);
-
- // Get pointer, skipping over original data, but not the NULL
- int nOldLen = nLen - 1;
- *ppvData = (BYTE*)*ppvData + nOldLen;
- nLen = nActualSize + 1 - nOldLen;
-
- // Retrieve the column in question
- AFX_ODBC_CALL(::SQLGetData(hstmt, nFieldIndex,
- SQL_C_CHAR, *ppvData, nLen, &nActualSize));
- if (nRetCode == SQL_SUCCESS_WITH_INFO)
- {
- #ifdef _DEBUG
- if (afxTraceFlags & traceDatabase)
- {
- TRACE1("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)
- {
- TRACE1("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(nActualSize + nOldLen);
- }
- }
-
- void PASCAL CRecordset::GetLongBinaryDataAndCleanup(CDatabase* pdb, HSTMT hstmt,
- short nFieldIndex, long nActualSize, LPVOID* ppvData, int 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 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
- if (afxTraceFlags & traceDatabase)
- {
- TRACE1("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)
- {
- TRACE1("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 << "m_nOpenType = " << m_nOpenType;
- dc << "\nm_strSQL = " << m_strSQL;
- dc << "\nm_hstmt = " << m_hstmt;
- dc << "\nm_bRecordsetDb = " << m_bRecordsetDb;
-
- dc << "\nm_lOpen = " << m_lOpen;
- dc << "\nm_bScrollable = " << m_bScrollable;
- dc << "\nm_bUpdatable = " << m_bUpdatable;
- dc << "\nm_bAppendable = " << m_bAppendable;
-
- dc << "\nm_nFields = " << m_nFields;
- dc << "\nm_nFieldsBound = " << m_nFieldsBound;
- dc << "\nm_nParams = " << m_nParams;
-
- dc << "\nm_bEOF = " << m_bEOF;
- dc << "\nm_bBOF = " << m_bBOF;
- dc << "\nm_bDeleted = " << m_bEOF;
-
- dc << "\nm_bLockMode = " << m_nLockMode;
- dc << "\nm_nEditMode = " << m_nEditMode;
- dc << "\nm_strCursorName = " << m_strCursorName;
- dc << "\nm_hstmtUpdate = " << m_hstmtUpdate;
-
- dc << "\nDump values for each field in current record.";
- DumpFields(dc);
-
- if (dc.GetDepth() > 0)
- {
- if (m_pDatabase == NULL)
- dc << "with no database\n";
- else
- dc << "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,
- long nRows, BOOL bEOFSeen, RETCODE nRetCode)
- {
- // This function provided for backward binary compatibility
- UNUSED(nRows); // not used in release build
- ASSERT(nRows == 1);
- AfxSetRecordCount(plRecordCount, lCurrentRecord, bEOFSeen, nRetCode);
- }
-
- 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
-
- static char _szAfxDbInl[] = "afxdb.inl";
- #undef THIS_FILE
- #define THIS_FILE _szAfxDbInl
- #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)
-
- /////////////////////////////////////////////////////////////////////////////
-