home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Windows Gam…ming Gurus (2nd Edition) / Disc2.iso / vc98 / mfc / src / dbcore.cpp < prev    next >
C/C++ Source or Header  |  1998-06-16  |  115KB  |  4,683 lines

  1. // This is a part of the Microsoft Foundation Classes C++ library.
  2. // Copyright (C) 1992-1998 Microsoft Corporation
  3. // All rights reserved.
  4. //
  5. // This source code is only intended as a supplement to the
  6. // Microsoft Foundation Classes Reference and related
  7. // electronic documentation provided with the library.
  8. // See these sources for detailed information regarding the
  9. // Microsoft Foundation Classes product.
  10.  
  11. #include "stdafx.h"
  12.  
  13. #ifdef AFX_DB_SEG
  14. #pragma code_seg(AFX_DB_SEG)
  15. #endif
  16.  
  17. #ifdef _DEBUG
  18. #undef THIS_FILE
  19. static char THIS_FILE[] = __FILE__;
  20. #endif
  21.  
  22. #define new DEBUG_NEW
  23.  
  24. /////////////////////////////////////////////////////////////////////////////
  25. // Global data
  26.  
  27. #ifdef _DEBUG
  28. BOOL bTraceSql = FALSE;
  29. #endif
  30.  
  31. AFX_STATIC_DATA const TCHAR _afxODBCTrail[] = _T("ODBC;");
  32. AFX_STATIC_DATA const TCHAR _afxComma[] = _T(",");
  33. AFX_STATIC_DATA const TCHAR _afxLiteralSeparator = '\'';
  34. AFX_STATIC_DATA const TCHAR _afxCall[] = _T("{CALL ");
  35. AFX_STATIC_DATA const TCHAR _afxParamCall[] = _T("{?");
  36. AFX_STATIC_DATA const TCHAR _afxSelect[] = _T("SELECT ");
  37. AFX_STATIC_DATA const TCHAR _afxFrom[] = _T(" FROM ");
  38. AFX_STATIC_DATA const TCHAR _afxWhere[] = _T(" WHERE ");
  39. AFX_STATIC_DATA const TCHAR _afxOrderBy[] = _T(" ORDER BY ");
  40. AFX_STATIC_DATA const TCHAR _afxForUpdate[] = _T(" FOR UPDATE ");
  41.  
  42. AFX_STATIC_DATA const TCHAR _afxRowFetch[] = _T("State:01S01");
  43. AFX_STATIC_DATA const TCHAR _afxDataTruncated[] = _T("State:01004");
  44. AFX_STATIC_DATA const TCHAR _afxInfoRange[] = _T("State:S1096");
  45. AFX_STATIC_DATA const TCHAR _afxOutOfSequence[] = _T("State:S1010");
  46. AFX_STATIC_DATA const TCHAR _afxDriverNotCapable[] = _T("State:S1C00");
  47.  
  48. AFX_STATIC_DATA const char _afxODBCDLL[] = "ODBC32.DLL";
  49.  
  50. /////////////////////////////////////////////////////////////////////////////
  51. // for dynamic load of ODBC32.DLL
  52.  
  53. #pragma comment(lib, "odbc32.lib")
  54.  
  55. /////////////////////////////////////////////////////////////////////////////
  56. // CDBException
  57.  
  58. void AFXAPI AfxThrowDBException(RETCODE nRetCode, CDatabase* pdb, HSTMT hstmt)
  59. {
  60.     CDBException* pException = new CDBException(nRetCode);
  61.     if (nRetCode == SQL_ERROR && pdb != NULL)
  62.         pException->BuildErrorString(pdb, hstmt);
  63.     else if (nRetCode > AFX_SQL_ERROR && nRetCode < AFX_SQL_ERROR_MAX)
  64.     {
  65.         VERIFY(pException->m_strError.LoadString(
  66.             AFX_IDP_SQL_FIRST+(nRetCode-AFX_SQL_ERROR)));
  67.         TRACE1("%s\n", pException->m_strError);
  68.     }
  69.     THROW(pException);
  70. }
  71.  
  72. CDBException::CDBException(RETCODE nRetCode)
  73. {
  74.     m_nRetCode = nRetCode;
  75. }
  76.  
  77. CDBException::~CDBException()
  78. {
  79. }
  80.  
  81. void CDBException::BuildErrorString(CDatabase* pdb, HSTMT hstmt, BOOL bTrace)
  82. {
  83.     ASSERT_VALID(this);
  84.     UNUSED(bTrace);  // unused in release builds
  85.  
  86.     if (pdb != NULL)
  87.     {
  88.         SWORD nOutlen;
  89.         RETCODE nRetCode;
  90.         UCHAR lpszMsg[SQL_MAX_MESSAGE_LENGTH];
  91.         UCHAR lpszState[SQL_SQLSTATE_SIZE];
  92.         CString strMsg;
  93.         CString strState;
  94.         SDWORD lNative;
  95.  
  96.         _AFX_DB_STATE* pDbState = _afxDbState;
  97.         AFX_SQL_SYNC(::SQLError(pDbState->m_henvAllConnections, pdb->m_hdbc,
  98.             hstmt, lpszState, &lNative,
  99.             lpszMsg, SQL_MAX_MESSAGE_LENGTH-1, &nOutlen));
  100.         strState = lpszState;
  101.  
  102.         // Skip non-errors
  103.         while ((nRetCode == SQL_SUCCESS || nRetCode == SQL_SUCCESS_WITH_INFO) &&
  104.             lstrcmp(strState, _T("00000")) != 0)
  105.         {
  106.             strMsg = lpszMsg;
  107.  
  108.             TCHAR lpszNative[50];
  109.             wsprintf(lpszNative, _T(",Native:%ld,Origin:"), lNative);
  110.             strState += lpszNative;
  111.  
  112.             // transfer [origin] from message string to StateNativeOrigin string
  113.             int nCloseBracket;
  114.             int nMsgLength;
  115.             while (!strMsg.IsEmpty() &&
  116.                 strMsg[0] == '[' && (nCloseBracket = strMsg.Find(']')) >= 0)
  117.             {
  118.                 // Skip ']'
  119.                 nCloseBracket++;
  120.                 strState += strMsg.Left(nCloseBracket);
  121.  
  122.                 nMsgLength = strMsg.GetLength();
  123.                 // Skip ' ', if present
  124.                 if (nCloseBracket < nMsgLength && strMsg[nCloseBracket] == ' ')
  125.                     nCloseBracket++;
  126.                 strMsg = strMsg.Right(nMsgLength - nCloseBracket);
  127.             }
  128.             strState += _T("\n");
  129.             m_strStateNativeOrigin += _T("State:") + strState;
  130.             m_strError += strMsg + _T("\n");
  131.  
  132. #ifdef _DEBUG
  133.             if (bTrace)
  134.             {
  135.                 TraceErrorMessage(strMsg);
  136.                 TraceErrorMessage(_T("State:") + strState);
  137.             }
  138. #endif // _DEBUG
  139.  
  140.             AFX_SQL_SYNC(::SQLError(pDbState->m_henvAllConnections,
  141.                 pdb->m_hdbc, hstmt, lpszState, &lNative,
  142.                 lpszMsg, SQL_MAX_MESSAGE_LENGTH-1, &nOutlen));
  143.             strState = lpszState;
  144.         }
  145.     }
  146. }
  147.  
  148.  
  149. BOOL CDBException::GetErrorMessage(LPTSTR lpszError, UINT nMaxError,
  150.         PUINT pnHelpContext /* = NULL */)
  151. {
  152.     ASSERT(lpszError != NULL && AfxIsValidString(lpszError, nMaxError));
  153.  
  154.     if (pnHelpContext != NULL)
  155.         *pnHelpContext = 0;
  156.  
  157.     lstrcpyn(lpszError, m_strError, nMaxError-1);
  158.     lpszError[nMaxError-1] = '\0';
  159.     return TRUE;
  160. }
  161.  
  162.  
  163. #ifdef _DEBUG
  164. void CDBException::TraceErrorMessage(LPCTSTR szTrace) const
  165. {
  166.     CString strTrace = szTrace;
  167.  
  168.     if (strTrace.GetLength() <= 80)
  169.         TRACE1("%s\n", strTrace);
  170.     else
  171.     {
  172.         // Display 80 chars/line
  173.         while (strTrace.GetLength() > 80)
  174.         {
  175.             TRACE1("%s\n", strTrace.Left(80));
  176.             strTrace = strTrace.Right(strTrace.GetLength() - 80);
  177.         }
  178.         TRACE1("%s\n", strTrace);
  179.     }
  180. }
  181. #endif // _DEBUG
  182.  
  183. void CDBException::Empty()
  184. {
  185.     m_strError.Empty();
  186.     m_strStateNativeOrigin.Empty();
  187. }
  188.  
  189. /////////////////////////////////////////////////////////////////////////////
  190. // Global helper
  191.  
  192. HENV AFXAPI AfxGetHENV()
  193. {
  194.     _AFX_DB_STATE* pDbState = _afxDbState;
  195.     return pDbState->m_henvAllConnections;
  196. }
  197.  
  198. /////////////////////////////////////////////////////////////////////////////
  199. // CDatabase implementation
  200.  
  201. CDatabase::CDatabase()
  202. {
  203.     m_hdbc = SQL_NULL_HDBC;
  204.     m_hstmt = SQL_NULL_HSTMT;
  205.  
  206.     m_bUpdatable = FALSE;
  207.     m_bTransactions = FALSE;
  208.     DEBUG_ONLY(m_bTransactionPending = FALSE);
  209.     m_dwLoginTimeout = DEFAULT_LOGIN_TIMEOUT;
  210.     m_dwQueryTimeout = DEFAULT_QUERY_TIMEOUT;
  211.  
  212.     m_bStripTrailingSpaces = FALSE;
  213.     m_bIncRecordCountOnAdd = FALSE;
  214.     m_bAddForUpdate = FALSE;
  215. }
  216.  
  217. CDatabase::~CDatabase()
  218. {
  219.     ASSERT_VALID(this);
  220.  
  221.     Free();
  222. }
  223.  
  224. BOOL CDatabase::Open(LPCTSTR lpszDSN, BOOL bExclusive,
  225.     BOOL bReadonly, LPCTSTR lpszConnect, BOOL bUseCursorLib)
  226. {
  227.     ASSERT(lpszDSN == NULL || AfxIsValidString(lpszDSN));
  228.     ASSERT(lpszConnect == NULL || AfxIsValidString(lpszConnect));
  229.  
  230.     CString strConnect;
  231.  
  232.     if (lpszConnect != NULL)
  233.         strConnect = lpszConnect;
  234.  
  235.     // For VB/Access compatibility, require "ODBC;" (or "odbc;")
  236.     // prefix to the connect string
  237.     if (_tcsnicmp(strConnect, _afxODBCTrail, lstrlen(_afxODBCTrail)) != 0)
  238.     {
  239.         TRACE0("Error: Missing 'ODBC' prefix on connect string.\n");
  240.         return FALSE;
  241.     }
  242.  
  243.     // Strip "ODBC;"
  244.     strConnect = strConnect.Right(strConnect.GetLength()
  245.         - lstrlen(_afxODBCTrail));
  246.  
  247.     if (lpszDSN != NULL && lstrlen(lpszDSN) != 0)
  248.     {
  249.         // Append "DSN=" lpszDSN
  250.         strConnect += _T(";DSN=");
  251.         strConnect += lpszDSN;
  252.     }
  253.  
  254.     DWORD dwOptions = 0;
  255.  
  256.     if (bExclusive)
  257.         dwOptions |= openExclusive;
  258.  
  259.     if (bReadonly)
  260.         dwOptions |= openReadOnly;
  261.  
  262.     if (bUseCursorLib)
  263.         dwOptions |= useCursorLib;
  264.  
  265.     return OpenEx(strConnect, dwOptions);
  266. }
  267.  
  268. BOOL CDatabase::OpenEx(LPCTSTR lpszConnectString, DWORD dwOptions)
  269. {
  270.     ASSERT_VALID(this);
  271.     ASSERT(lpszConnectString == NULL || AfxIsValidString(lpszConnectString));
  272.     ASSERT(!(dwOptions & noOdbcDialog && dwOptions & forceOdbcDialog));
  273.  
  274.     // Exclusive access not supported.
  275.     ASSERT(!(dwOptions & openExclusive));
  276.  
  277.     m_bUpdatable = !(dwOptions & openReadOnly);
  278.  
  279.     TRY
  280.     {
  281.         m_strConnect = lpszConnectString;
  282.  
  283.         // Allocate the HDBC and make connection
  284.         AllocConnect(dwOptions);
  285.         if(!Connect(dwOptions))
  286.             return FALSE;
  287.  
  288.         // Verify support for required functionality and cache info
  289.         VerifyConnect();
  290.         GetConnectInfo();
  291.     }
  292.     CATCH_ALL(e)
  293.     {
  294.         Free();
  295.         THROW_LAST();
  296.     }
  297.     END_CATCH_ALL
  298.  
  299.     return TRUE;
  300. }
  301.  
  302. void CDatabase::ExecuteSQL(LPCTSTR lpszSQL)
  303. {
  304.     USES_CONVERSION;
  305.     RETCODE nRetCode;
  306.     HSTMT hstmt;
  307.  
  308.     ASSERT_VALID(this);
  309.     ASSERT(AfxIsValidString(lpszSQL));
  310.  
  311.     AFX_SQL_SYNC(::SQLAllocStmt(m_hdbc, &hstmt));
  312.     if (!CheckHstmt(nRetCode, hstmt))
  313.         AfxThrowDBException(nRetCode, this, hstmt);
  314.  
  315.     TRY
  316.     {
  317.         OnSetOptions(hstmt);
  318.  
  319.         // Give derived CDatabase classes option to use parameters
  320.         BindParameters(hstmt);
  321.  
  322.         AFX_ODBC_CALL(::SQLExecDirect(hstmt,
  323.             (UCHAR*)T2A((LPTSTR)lpszSQL), SQL_NTS));
  324.  
  325.         if (!CheckHstmt(nRetCode, hstmt))
  326.             AfxThrowDBException(nRetCode, this, hstmt);
  327.         else
  328.         {
  329.             do
  330.             {
  331.                 SWORD nResultColumns;
  332.  
  333.                 AFX_ODBC_CALL(::SQLNumResultCols(hstmt, &nResultColumns));
  334.                 if (nResultColumns != 0)
  335.                 {
  336.                     do
  337.                     {
  338.                         AFX_ODBC_CALL(::SQLFetch(hstmt));
  339.                     } while (CheckHstmt(nRetCode, hstmt) &&
  340.                         nRetCode != SQL_NO_DATA_FOUND);
  341.                 }
  342.                 AFX_ODBC_CALL(::SQLMoreResults(hstmt));
  343.             } while (CheckHstmt(nRetCode, hstmt) &&
  344.                 nRetCode != SQL_NO_DATA_FOUND);
  345.         }
  346.     }
  347.     CATCH_ALL(e)
  348.     {
  349.         ::SQLCancel(hstmt);
  350.         AFX_SQL_SYNC(::SQLFreeStmt(hstmt, SQL_DROP));
  351.         THROW_LAST();
  352.     }
  353.     END_CATCH_ALL
  354.  
  355.     AFX_SQL_SYNC(::SQLFreeStmt(hstmt, SQL_DROP));
  356. }
  357.  
  358. // Shutdown pending query for CDatabase's private m_hstmt
  359. void CDatabase::Cancel()
  360. {
  361.     ASSERT_VALID(this);
  362.     ASSERT(m_hdbc != SQL_NULL_HDBC);
  363.  
  364.     ::SQLCancel(m_hstmt);
  365. }
  366.  
  367. // Disconnect connection
  368. void CDatabase::Close()
  369. {
  370.     ASSERT_VALID(this);
  371.  
  372.     // Close any open recordsets
  373.     AfxLockGlobals(CRIT_ODBC);
  374.     TRY
  375.     {
  376.         while (!m_listRecordsets.IsEmpty())
  377.         {
  378.             CRecordset* pSet = (CRecordset*)m_listRecordsets.GetHead();
  379.             pSet->Close();  // will implicitly remove from list
  380.             pSet->m_pDatabase = NULL;
  381.         }
  382.     }
  383.     CATCH_ALL(e)
  384.     {
  385.         AfxUnlockGlobals(CRIT_ODBC);
  386.         THROW_LAST();
  387.     }
  388.     END_CATCH_ALL
  389.     AfxUnlockGlobals(CRIT_ODBC);
  390.  
  391.     if (m_hdbc != SQL_NULL_HDBC)
  392.     {
  393.         RETCODE nRetCode;
  394.         AFX_SQL_SYNC(::SQLDisconnect(m_hdbc));
  395.         AFX_SQL_SYNC(::SQLFreeConnect(m_hdbc));
  396.         m_hdbc = SQL_NULL_HDBC;
  397.  
  398.         _AFX_DB_STATE* pDbState = _afxDbState;
  399.  
  400.         AfxLockGlobals(CRIT_ODBC);
  401.         ASSERT(pDbState->m_nAllocatedConnections != 0);
  402.         pDbState->m_nAllocatedConnections--;
  403.         AfxUnlockGlobals(CRIT_ODBC);
  404.     }
  405. }
  406.  
  407. // Silently disconnect and free all ODBC resources.  Don't throw any exceptions
  408. void CDatabase::Free()
  409. {
  410.     ASSERT_VALID(this);
  411.  
  412.     // Trap failures upon close
  413.     TRY
  414.     {
  415.         Close();
  416.     }
  417.     CATCH_ALL(e)
  418.     {
  419.         // Nothing we can do
  420.         TRACE0("Error: exception by CDatabase::Close() ignored in CDatabase::Free().\n");
  421.         DELETE_EXCEPTION(e);
  422.     }
  423.     END_CATCH_ALL
  424.  
  425.     // free henv if refcount goes to 0
  426.     _AFX_DB_STATE* pDbState = _afxDbState;
  427.     AfxLockGlobals(CRIT_ODBC);
  428.     if (pDbState->m_henvAllConnections != SQL_NULL_HENV)
  429.     {
  430.         ASSERT(pDbState->m_nAllocatedConnections >= 0);
  431.         if (pDbState->m_nAllocatedConnections == 0)
  432.         {
  433.             // free last connection - release HENV
  434.             RETCODE nRetCodeEnv = ::SQLFreeEnv(pDbState->m_henvAllConnections);
  435. #ifdef _DEBUG
  436.             if (nRetCodeEnv != SQL_SUCCESS)
  437.                 // Nothing we can do
  438.                 TRACE0("Error: SQLFreeEnv failure ignored in CDatabase::Free().\n");
  439. #endif
  440.             pDbState->m_henvAllConnections = SQL_NULL_HENV;
  441.         }
  442.     }
  443.     AfxUnlockGlobals(CRIT_ODBC);
  444. }
  445.  
  446. void CDatabase::OnSetOptions(HSTMT hstmt)
  447. {
  448.     RETCODE nRetCode;
  449.     ASSERT_VALID(this);
  450.     ASSERT(m_hdbc != SQL_NULL_HDBC);
  451.  
  452.     if (m_dwQueryTimeout != -1)
  453.     {
  454.         // Attempt to set query timeout.  Ignore failure
  455.         AFX_SQL_SYNC(::SQLSetStmtOption(hstmt, SQL_QUERY_TIMEOUT,
  456.             m_dwQueryTimeout));
  457.         if (!Check(nRetCode))
  458.             // don't attempt it again
  459.             m_dwQueryTimeout = (DWORD)-1;
  460.     }
  461. }
  462.  
  463. CString CDatabase::GetDatabaseName() const
  464. {
  465.     ASSERT_VALID(this);
  466.     ASSERT(m_hdbc != SQL_NULL_HDBC);
  467.  
  468.     char szName[MAX_TNAME_LEN];
  469.     SWORD nResult;
  470.     RETCODE nRetCode;
  471.  
  472.     AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DATABASE_NAME,
  473.         szName, _countof(szName), &nResult));
  474.     if (!Check(nRetCode))
  475.         szName[0] = '\0';
  476.  
  477.     return szName;
  478. }
  479.  
  480. BOOL CDatabase::BeginTrans()
  481. {
  482.     ASSERT_VALID(this);
  483.     ASSERT(m_hdbc != SQL_NULL_HDBC);
  484.  
  485.     if (!m_bTransactions)
  486.         return FALSE;
  487.  
  488.     // Only 1 level of transactions supported
  489. #ifdef _DEBUG
  490.     ASSERT(!m_bTransactionPending);
  491. #endif
  492.  
  493.     RETCODE nRetCode;
  494.     AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_AUTOCOMMIT,
  495.         SQL_AUTOCOMMIT_OFF));
  496.     DEBUG_ONLY(m_bTransactionPending = TRUE);
  497.  
  498.     return Check(nRetCode);
  499. }
  500.  
  501. BOOL CDatabase::CommitTrans()
  502. {
  503.     ASSERT_VALID(this);
  504.     ASSERT(m_hdbc != SQL_NULL_HDBC);
  505.  
  506.     if (!m_bTransactions)
  507.         return FALSE;
  508.  
  509.     // BeginTrans must be called first
  510. #ifdef _DEBUG
  511.     ASSERT(m_bTransactionPending);
  512. #endif
  513.  
  514.     _AFX_DB_STATE* pDbState = _afxDbState;
  515.     RETCODE nRetCode;
  516.     AFX_SQL_SYNC(::SQLTransact(pDbState->m_henvAllConnections, m_hdbc, SQL_COMMIT));
  517.     BOOL bSuccess = Check(nRetCode);
  518.  
  519.     // Turn back on auto commit
  520.     AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_AUTOCOMMIT,
  521.         SQL_AUTOCOMMIT_ON));
  522.     DEBUG_ONLY(m_bTransactionPending = FALSE);
  523.  
  524.     return bSuccess;
  525. }
  526.  
  527. BOOL CDatabase::Rollback()
  528. {
  529.     ASSERT_VALID(this);
  530.     ASSERT(m_hdbc != SQL_NULL_HDBC);
  531.  
  532.     if (!m_bTransactions)
  533.         return FALSE;
  534.  
  535.     // BeginTrans must be called first
  536. #ifdef _DEBUG
  537.     ASSERT(m_bTransactionPending);
  538. #endif
  539.  
  540.     _AFX_DB_STATE* pDbState = _afxDbState;
  541.     RETCODE nRetCode;
  542.     AFX_SQL_SYNC(::SQLTransact(pDbState->m_henvAllConnections, m_hdbc, SQL_ROLLBACK));
  543.     BOOL bSuccess = Check(nRetCode);
  544.  
  545.     // Turn back on auto commit
  546.     AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_AUTOCOMMIT,
  547.         SQL_AUTOCOMMIT_ON));
  548.     DEBUG_ONLY(m_bTransactionPending = FALSE);
  549.  
  550.     return bSuccess;
  551. }
  552.  
  553. // Screen for errors.
  554. BOOL CDatabase::Check(RETCODE nRetCode) const
  555. {
  556.     return CheckHstmt(nRetCode, SQL_NULL_HSTMT);
  557. }
  558.  
  559. BOOL CDatabase::CheckHstmt(RETCODE nRetCode, HSTMT hstmt) const
  560. {
  561.     ASSERT_VALID(this);
  562.     UNUSED(hstmt);
  563.  
  564.     switch (nRetCode)
  565.     {
  566.     case SQL_SUCCESS_WITH_INFO:
  567. #ifdef _DEBUG
  568.         if (afxTraceFlags & traceDatabase)
  569.         {
  570.             CDBException e(nRetCode);
  571.             TRACE0("Warning: ODBC Success With Info, ");
  572.             e.BuildErrorString((CDatabase*)this, hstmt);
  573.         }
  574. #endif // _DEBUG
  575.  
  576.         // Fall through
  577.  
  578.     case SQL_SUCCESS:
  579.     case SQL_NO_DATA_FOUND:
  580.         return TRUE;
  581.     }
  582.  
  583.     return FALSE;
  584. }
  585.  
  586. //////////////////////////////////////////////////////////////////////////////
  587. // CDatabase internal functions
  588.  
  589. //Replace brackets in SQL string with SQL_IDENTIFIER_QUOTE_CHAR
  590. void CDatabase::ReplaceBrackets(LPTSTR lpchSQL)
  591. {
  592.     BOOL bInLiteral = FALSE;
  593.     LPTSTR lpchNewSQL = lpchSQL;
  594.  
  595.     while (*lpchSQL != '\0')
  596.     {
  597.         if (*lpchSQL == _afxLiteralSeparator)
  598.             {
  599.                 // Handle escaped literal
  600.                 if (*_tcsinc(lpchSQL) == _afxLiteralSeparator)
  601.                 {
  602.                     *lpchNewSQL = *lpchSQL;
  603.                     lpchSQL = _tcsinc(lpchSQL);
  604.                     lpchNewSQL = _tcsinc(lpchNewSQL);
  605.                 }
  606.                 else
  607.                     bInLiteral = !bInLiteral;
  608.  
  609.                 *lpchNewSQL = *lpchSQL;
  610.             }
  611.         else if (!bInLiteral && (*lpchSQL == '['))
  612.         {
  613.             if (*_tcsinc(lpchSQL) == '[')
  614.             {
  615.                 // Handle escaped left bracket by inserting one '['
  616.                 *lpchNewSQL = *lpchSQL;
  617.                 lpchSQL = _tcsinc(lpchSQL);
  618.             }
  619.             else
  620.                 *lpchNewSQL = m_chIDQuoteChar;
  621.         }
  622.         else if (!bInLiteral && (*lpchSQL == ']'))
  623.         {
  624.             if (*_tcsinc(lpchSQL) == ']')
  625.             {
  626.                 // Handle escaped right bracket by inserting one ']'
  627.                 *lpchNewSQL = *lpchSQL;
  628.                 lpchSQL = _tcsinc(lpchSQL);
  629.             }
  630.             else
  631.                 *lpchNewSQL = m_chIDQuoteChar;
  632.         }
  633.         else
  634.             *lpchNewSQL = *lpchSQL;
  635.  
  636.         lpchSQL = _tcsinc(lpchSQL);
  637.         lpchNewSQL = _tcsinc(lpchNewSQL);
  638.     }
  639. }
  640.  
  641. // Allocate an henv (first time called) and hdbc
  642. void CDatabase::AllocConnect(DWORD dwOptions)
  643. {
  644.     ASSERT_VALID(this);
  645.  
  646.     if (m_hdbc != SQL_NULL_HDBC)
  647.         return;
  648.  
  649.     _AFX_DB_STATE* pDbState = _afxDbState;
  650.  
  651.     RETCODE nRetCode;
  652.  
  653.     AfxLockGlobals(CRIT_ODBC);
  654.     if (pDbState->m_henvAllConnections == SQL_NULL_HENV)
  655.     {
  656.         ASSERT(pDbState->m_nAllocatedConnections == 0);
  657.  
  658.         // need to allocate an environment for first connection
  659.         AFX_SQL_SYNC(::SQLAllocEnv(&pDbState->m_henvAllConnections));
  660.         if (!Check(nRetCode))
  661.         {
  662.             AfxUnlockGlobals(CRIT_ODBC);
  663.             AfxThrowMemoryException();  // fatal
  664.         }
  665.     }
  666.  
  667.     ASSERT(pDbState->m_henvAllConnections != SQL_NULL_HENV);
  668.     AFX_SQL_SYNC(::SQLAllocConnect(pDbState->m_henvAllConnections, &m_hdbc));
  669.     if (!Check(nRetCode))
  670.     {
  671.         AfxUnlockGlobals(CRIT_ODBC);
  672.         ThrowDBException(nRetCode); // fatal
  673.     }
  674.     pDbState->m_nAllocatedConnections++;    // allocated at least
  675.     AfxUnlockGlobals(CRIT_ODBC);
  676.  
  677. #ifdef _DEBUG
  678.     if (bTraceSql)
  679.     {
  680.         ::SQLSetConnectOption(m_hdbc, SQL_OPT_TRACEFILE,
  681.             (DWORD)"odbccall.txt");
  682.         ::SQLSetConnectOption(m_hdbc, SQL_OPT_TRACE, 1);
  683.     }
  684. #endif // _DEBUG
  685.  
  686.     AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_LOGIN_TIMEOUT,
  687.         m_dwLoginTimeout));
  688. #ifdef _DEBUG
  689.     if (nRetCode != SQL_SUCCESS && nRetCode != SQL_SUCCESS_WITH_INFO &&
  690.         (afxTraceFlags & traceDatabase))
  691.         TRACE0("Warning: Failure setting login timeout.\n");
  692. #endif
  693.  
  694.     if (!m_bUpdatable)
  695.     {
  696.         AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_ACCESS_MODE,
  697.             SQL_MODE_READ_ONLY));
  698. #ifdef _DEBUG
  699.         if (nRetCode != SQL_SUCCESS && nRetCode != SQL_SUCCESS_WITH_INFO &&
  700.             (afxTraceFlags & traceDatabase))
  701.             TRACE0("Warning: Failure setting read only access mode.\n");
  702. #endif
  703.     }
  704.  
  705.     // Turn on cursor lib support
  706.     if (dwOptions & useCursorLib)
  707.     {
  708.         AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc,
  709.             SQL_ODBC_CURSORS, SQL_CUR_USE_ODBC));
  710.         // With cursor library added records immediately in result set
  711.         m_bIncRecordCountOnAdd = TRUE;
  712.     }
  713. }
  714.  
  715. BOOL CDatabase::Connect(DWORD dwOptions)
  716. {
  717.     USES_CONVERSION;
  718.  
  719.     HWND hWndTop;
  720.     HWND hWnd = CWnd::GetSafeOwner_(NULL, &hWndTop);
  721.     if (hWnd == NULL)
  722.         hWnd = ::GetDesktopWindow();
  723.  
  724.     UCHAR szConnectOutput[MAX_CONNECT_LEN];
  725.     RETCODE nRetCode;
  726.     SWORD nResult;
  727.     UWORD wConnectOption = SQL_DRIVER_COMPLETE;
  728.     if (dwOptions & noOdbcDialog)
  729.         wConnectOption = SQL_DRIVER_NOPROMPT;
  730.     else if (dwOptions & forceOdbcDialog)
  731.         wConnectOption = SQL_DRIVER_PROMPT;
  732.     AFX_SQL_SYNC(::SQLDriverConnect(m_hdbc, hWnd,
  733.         (UCHAR*)T2A((LPTSTR)(LPCTSTR)m_strConnect), SQL_NTS,
  734.         szConnectOutput, _countof(szConnectOutput),
  735.         &nResult, wConnectOption));
  736.     if (hWndTop != NULL)
  737.         ::EnableWindow(hWndTop, TRUE);
  738.  
  739.     // If user hit 'Cancel'
  740.     if (nRetCode == SQL_NO_DATA_FOUND)
  741.     {
  742.         Free();
  743.         return FALSE;
  744.     }
  745.  
  746.     if (!Check(nRetCode))
  747.     {
  748. #ifdef _DEBUG
  749.         if (hWnd == NULL)
  750.             TRACE0("Error: No default window (AfxGetApp()->m_pMainWnd) for SQLDriverConnect.\n");
  751. #endif
  752.         ThrowDBException(nRetCode);
  753.     }
  754.  
  755.     // Connect strings must have "ODBC;"
  756.     m_strConnect = _afxODBCTrail;
  757.     // Save connect string returned from ODBC
  758.     m_strConnect += (char*)szConnectOutput;
  759.  
  760.     return TRUE;
  761. }
  762.  
  763. void CDatabase::VerifyConnect()
  764. {
  765.     RETCODE nRetCode;
  766.     SWORD nResult;
  767.  
  768.     SWORD nAPIConformance;
  769.     AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_ODBC_API_CONFORMANCE,
  770.         &nAPIConformance, sizeof(nAPIConformance), &nResult));
  771.     if (!Check(nRetCode))
  772.         ThrowDBException(nRetCode);
  773.  
  774.     if (nAPIConformance < SQL_OAC_LEVEL1)
  775.         ThrowDBException(AFX_SQL_ERROR_API_CONFORMANCE);
  776.  
  777.     SWORD nSQLConformance;
  778.     AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_ODBC_SQL_CONFORMANCE,
  779.         &nSQLConformance, sizeof(nSQLConformance), &nResult));
  780.     if (!Check(nRetCode))
  781.         ThrowDBException(nRetCode);
  782.  
  783.     if (nSQLConformance < SQL_OSC_MINIMUM)
  784.         ThrowDBException(AFX_SQL_ERROR_SQL_CONFORMANCE);
  785. }
  786.  
  787. void CDatabase::GetConnectInfo()
  788. {
  789.     RETCODE nRetCode;
  790.     SWORD nResult;
  791.  
  792.     // Reset the database update options
  793.     m_dwUpdateOptions = 0;
  794.  
  795.     // Check for SQLSetPos support
  796.     UDWORD dwDriverPosOperations;
  797.     AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_POS_OPERATIONS,
  798.         &dwDriverPosOperations, sizeof(dwDriverPosOperations), &nResult));
  799.     if (Check(nRetCode) &&
  800.         (dwDriverPosOperations & SQL_POS_UPDATE) &&
  801.         (dwDriverPosOperations & SQL_POS_DELETE) &&
  802.         (dwDriverPosOperations & SQL_POS_ADD))
  803.         m_dwUpdateOptions |= AFX_SQL_SETPOSUPDATES;
  804.  
  805.     // Check for positioned update SQL support
  806.     UDWORD dwPositionedStatements;
  807.     AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_POSITIONED_STATEMENTS,
  808.         &dwPositionedStatements, sizeof(dwPositionedStatements),
  809.         &nResult));
  810.     if (Check(nRetCode) &&
  811.         (dwPositionedStatements & SQL_PS_POSITIONED_DELETE) &&
  812.         (dwPositionedStatements & SQL_PS_POSITIONED_UPDATE))
  813.         m_dwUpdateOptions |= AFX_SQL_POSITIONEDSQL;
  814.  
  815.     // Check for transaction support
  816.     SWORD nTxnCapable;
  817.     AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_TXN_CAPABLE, &nTxnCapable,
  818.         sizeof(nTxnCapable), &nResult));
  819.     if (Check(nRetCode) && nTxnCapable != SQL_TC_NONE)
  820.         m_bTransactions = TRUE;
  821.  
  822.     // Cache the effect of transactions on cursors
  823.     AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_CURSOR_COMMIT_BEHAVIOR,
  824.         &m_nCursorCommitBehavior, sizeof(m_nCursorCommitBehavior),
  825.         &nResult));
  826.     if (!Check(nRetCode))
  827.         m_nCursorCommitBehavior = SQL_ERROR;
  828.  
  829.     AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_CURSOR_ROLLBACK_BEHAVIOR,
  830.         &m_nCursorRollbackBehavior, sizeof(m_nCursorRollbackBehavior),
  831.         &nResult));
  832.     if (!Check(nRetCode))
  833.         m_nCursorRollbackBehavior = SQL_ERROR;
  834.  
  835.     // Cache bookmark attributes
  836.     AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_BOOKMARK_PERSISTENCE,
  837.         &m_dwBookmarkAttributes, sizeof(m_dwBookmarkAttributes),
  838.         &nResult));
  839.     Check(nRetCode);
  840.  
  841.     // Check for SQLGetData support req'd by RFX_LongBinary
  842.     UDWORD dwGetDataExtensions;
  843.     AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_GETDATA_EXTENSIONS,
  844.         &dwGetDataExtensions, sizeof(dwGetDataExtensions),
  845.         &nResult));
  846.     if (!Check(nRetCode))
  847.         dwGetDataExtensions = 0;
  848.     if (dwGetDataExtensions & SQL_GD_BOUND)
  849.         m_dwUpdateOptions |= AFX_SQL_GDBOUND;
  850.  
  851.     if (m_bUpdatable)
  852.     {
  853.         // Make sure data source is Updatable
  854.         char szReadOnly[10];
  855.         AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DATA_SOURCE_READ_ONLY,
  856.             szReadOnly, _countof(szReadOnly), &nResult));
  857.         if (Check(nRetCode) && nResult == 1)
  858.             m_bUpdatable = !(lstrcmpA(szReadOnly, "Y") == 0);
  859.         else
  860.             m_bUpdatable = FALSE;
  861. #ifdef _DEBUG
  862.         if (!m_bUpdatable && (afxTraceFlags & traceDatabase))
  863.             TRACE0("Warning: data source is readonly.\n");
  864. #endif
  865.     }
  866.     else
  867.     {
  868.         // Make data source is !Updatable
  869.         AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc,
  870.             SQL_ACCESS_MODE, SQL_MODE_READ_ONLY));
  871.     }
  872.  
  873.     // Cache the quote char to use when constructing SQL
  874.     char szIDQuoteChar[2];
  875.     AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_IDENTIFIER_QUOTE_CHAR,
  876.         szIDQuoteChar, _countof(szIDQuoteChar), &nResult));
  877.     if (Check(nRetCode) && nResult == 1)
  878.         m_chIDQuoteChar = szIDQuoteChar[0];
  879.     else
  880.         m_chIDQuoteChar = ' ';
  881.  
  882. #ifdef _DEBUG
  883.     if (afxTraceFlags & traceDatabase)
  884.     {
  885.         char szInfo[64];
  886.         AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DBMS_NAME,
  887.             szInfo, _countof(szInfo), &nResult));
  888.         if (Check(nRetCode))
  889.         {
  890.             CString strInfo = szInfo;
  891.             TRACE1("DBMS: %s\n", strInfo);
  892.             AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DBMS_VER,
  893.                 szInfo, _countof(szInfo), &nResult));
  894.             if (Check(nRetCode))
  895.             {
  896.                 strInfo = szInfo;
  897.                 TRACE1(", Version: %s\n", strInfo);
  898.             }
  899.         }
  900.     }
  901. #endif // _DEBUG
  902. }
  903.  
  904. void CDatabase::BindParameters(HSTMT /* hstmt */)
  905. {
  906.     // Must override and call SQLBindParameter directly
  907. }
  908.  
  909. //////////////////////////////////////////////////////////////////////////////
  910. // CDatabase diagnostics
  911.  
  912. #ifdef _DEBUG
  913. void CDatabase::AssertValid() const
  914. {
  915.     CObject::AssertValid();
  916. }
  917.  
  918. void CDatabase::Dump(CDumpContext& dc) const
  919. {
  920.     CObject::Dump(dc);
  921.  
  922.     dc << "m_hdbc = " << m_hdbc;
  923.     dc << "\nm_strConnect = " << m_strConnect;
  924.     dc << "\nm_bUpdatable = " << m_bUpdatable;
  925.     dc << "\nm_bTransactions = " << m_bTransactions;
  926.     dc << "\nm_bTransactionPending = " << m_bTransactionPending;
  927.     dc << "\nm_dwLoginTimeout = " << m_dwLoginTimeout;
  928.     dc << "\nm_dwQueryTimeout = " << m_dwQueryTimeout;
  929.  
  930.     if (dc.GetDepth() > 0)
  931.     {
  932.         _AFX_DB_STATE* pDbState = _afxDbState;
  933.         dc << "\nwith env:";
  934.         dc << "\n\tnAllocated = " << pDbState->m_nAllocatedConnections;
  935.         dc << "\n\thenvAllConnections = " << pDbState->m_henvAllConnections;
  936.     }
  937.  
  938.     dc << "\n";
  939. }
  940.  
  941. #endif // _DEBUG
  942.  
  943.  
  944. //////////////////////////////////////////////////////////////////////////////
  945. // CRecordset helpers
  946.  
  947. void AFXAPI AfxSetCurrentRecord(long* plCurrentRecord, long nRows, RETCODE nRetCode);
  948. void AFXAPI AfxSetRecordCount(long* plRecordCount, long lCurrentRecord,
  949.     BOOL bEOFSeen, RETCODE nRetCode);
  950.  
  951. //////////////////////////////////////////////////////////////////////////////
  952. // CRecordset
  953.  
  954. CRecordset::CRecordset(CDatabase* pDatabase)
  955. {
  956.     ASSERT(pDatabase == NULL || AfxIsValidAddress(pDatabase, sizeof(CDatabase)));
  957.     m_pDatabase = pDatabase;
  958.  
  959.     m_nOpenType = snapshot;
  960.     m_lOpen = AFX_RECORDSET_STATUS_UNKNOWN;
  961.     m_nEditMode = noMode;
  962.     m_nDefaultType = snapshot;
  963.     m_dwOptions = none;
  964.  
  965.     m_bAppendable = FALSE;
  966.     m_bUpdatable = FALSE;
  967.     m_bScrollable = FALSE;
  968.     m_bRecordsetDb = FALSE;
  969.     m_bRebindParams = FALSE;
  970.     m_bLongBinaryColumns = FALSE;
  971.     m_nLockMode = optimistic;
  972.     m_dwInitialGetDataLen = 0;
  973.     m_rgODBCFieldInfos = NULL;
  974.     m_rgFieldInfos = NULL;
  975.     m_rgRowStatus = NULL;
  976.     m_dwRowsetSize = 25;
  977.     m_dwAllocatedRowsetSize = 0;
  978.  
  979.     m_nFields = 0;
  980.     m_nParams = 0;
  981.     m_nFieldsBound = 0;
  982.     m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
  983.     m_lRecordCount = 0;
  984.     m_bUseUpdateSQL = FALSE;
  985.     m_bUseODBCCursorLib = FALSE;
  986.     m_nResultCols = -1;
  987.     m_bCheckCacheForDirtyFields = TRUE;
  988.  
  989.     m_pbFieldFlags = NULL;
  990.     m_pbParamFlags = NULL;
  991.     m_plParamLength = NULL;
  992.     m_pvFieldProxy = NULL;
  993.     m_pvParamProxy = NULL;
  994.     m_nProxyFields = 0;
  995.     m_nProxyParams = 0;
  996.  
  997.     m_hstmtUpdate = SQL_NULL_HSTMT;
  998.     m_hstmt = SQL_NULL_HSTMT;
  999.     if (m_pDatabase != NULL && m_pDatabase->IsOpen())
  1000.     {
  1001.         ASSERT_VALID(m_pDatabase);
  1002.         TRY
  1003.         {
  1004.             RETCODE nRetCode;
  1005.             AFX_SQL_SYNC(::SQLAllocStmt(m_pDatabase->m_hdbc, &m_hstmt));
  1006.             if (!Check(nRetCode))
  1007.                 ThrowDBException(SQL_INVALID_HANDLE);
  1008.  
  1009.             // Add to list of CRecordsets with alloced hstmts
  1010.             AfxLockGlobals(CRIT_ODBC);
  1011.             TRY
  1012.             {
  1013.                 m_pDatabase->m_listRecordsets.AddHead(this);
  1014.             }
  1015.             CATCH_ALL(e)
  1016.             {
  1017.                 AfxUnlockGlobals(CRIT_ODBC);
  1018.                 THROW_LAST();
  1019.             }
  1020.             END_CATCH_ALL
  1021.             AfxUnlockGlobals(CRIT_ODBC);
  1022.         }
  1023.         CATCH_ALL(e)
  1024.         {
  1025.             ASSERT(m_hstmt == SQL_NULL_HSTMT);
  1026.             DELETE_EXCEPTION(e);
  1027.         }
  1028.         END_CATCH_ALL
  1029.     }
  1030. }
  1031.  
  1032. CRecordset::~CRecordset()
  1033. {
  1034.     ASSERT_VALID(this);
  1035.  
  1036.     TRY
  1037.     {
  1038.         if (m_hstmt != NULL)
  1039.         {
  1040. #ifdef _DEBUG
  1041.             if (m_dwOptions & useMultiRowFetch && afxTraceFlags & traceDatabase)
  1042.             {
  1043.                 TRACE0("\nWARNING: Close called implicitly from destructor.");
  1044.                 TRACE0("\nUse of multi row fetch requires explicit call");
  1045.                 TRACE0("\nto Close or memory leaks will result.\n");
  1046.             }
  1047. #endif
  1048.             Close();
  1049.         }
  1050.         if (m_bRecordsetDb)
  1051.             delete m_pDatabase;
  1052.         m_pDatabase = NULL;
  1053.     }
  1054.     CATCH_ALL(e)
  1055.     {
  1056.         // Nothing we can do
  1057.         TRACE0("Error: Exception ignored in ~CRecordset().\n");
  1058.         DELETE_EXCEPTION(e);
  1059.     }
  1060.     END_CATCH_ALL
  1061. }
  1062.  
  1063. BOOL CRecordset::Open(UINT nOpenType, LPCTSTR lpszSQL, DWORD dwOptions)
  1064. {
  1065.     ASSERT(!IsOpen());
  1066.     ASSERT_VALID(this);
  1067.     ASSERT(lpszSQL == NULL || AfxIsValidString(lpszSQL));
  1068.     ASSERT(nOpenType == AFX_DB_USE_DEFAULT_TYPE ||
  1069.         nOpenType == dynaset || nOpenType == snapshot ||
  1070.         nOpenType == forwardOnly || nOpenType == dynamic);
  1071.     ASSERT(!(dwOptions & readOnly && dwOptions & appendOnly));
  1072.  
  1073.     // Can only use optimizeBulkAdd with appendOnly recordsets
  1074.     ASSERT((dwOptions & optimizeBulkAdd && dwOptions & appendOnly) ||
  1075.         !(dwOptions & optimizeBulkAdd));
  1076.  
  1077.     // forwardOnly recordsets have limited functionality
  1078.     ASSERT(!(nOpenType == forwardOnly && dwOptions & skipDeletedRecords));
  1079.  
  1080.     // Cache state info and allocate hstmt
  1081.     SetState(nOpenType, lpszSQL, dwOptions);
  1082.     if(!AllocHstmt())
  1083.         return FALSE;
  1084.  
  1085.     // Check if bookmarks upported (CanBookmark depends on open DB)
  1086.     ASSERT(dwOptions & useBookmarks ? CanBookmark() : TRUE);
  1087.  
  1088.     TRY
  1089.     {
  1090.         OnSetOptions(m_hstmt);
  1091.  
  1092.         // Allocate the field/param status arrays, if necessary
  1093.         BOOL bUnbound = FALSE;
  1094.         if (m_nFields > 0 || m_nParams > 0)
  1095.             AllocStatusArrays();
  1096.         else
  1097.             bUnbound = TRUE;
  1098.  
  1099.         // Build SQL and prep/execute or just execute direct
  1100.         BuildSQL(lpszSQL);
  1101.         PrepareAndExecute();
  1102.  
  1103.         // Cache some field info and prepare the rowset
  1104.         AllocAndCacheFieldInfo();
  1105.         AllocRowset();
  1106.  
  1107.         // If late binding, still need to allocate status arrays
  1108.         if (bUnbound && (m_nFields > 0 || m_nParams > 0))
  1109.             AllocStatusArrays();
  1110.  
  1111.         // Give derived classes a call before binding
  1112.         PreBindFields();
  1113.  
  1114.         // Fetch the first row of data
  1115.         MoveNext();
  1116.  
  1117.         // If EOF, then result set empty, so set BOF as well
  1118.         m_bBOF = m_bEOF;
  1119.     }
  1120.     CATCH_ALL(e)
  1121.     {
  1122.         Close();
  1123.         THROW_LAST();
  1124.     }
  1125.     END_CATCH_ALL
  1126.  
  1127.     return TRUE;
  1128. }
  1129.  
  1130. void CRecordset::Close()
  1131. {
  1132.     ASSERT_VALID(this);
  1133.     // Can't close if database has been deleted
  1134.     ASSERT(m_pDatabase != NULL);
  1135.  
  1136.     // This will force a requery for cursor name if reopened.
  1137.     m_strCursorName.Empty();
  1138.  
  1139.     if (m_rgFieldInfos != NULL &&
  1140.         m_nFields > 0 && m_bCheckCacheForDirtyFields)
  1141.     {
  1142.         FreeDataCache();
  1143.     }
  1144.  
  1145.     FreeRowset();
  1146.  
  1147.     m_nEditMode = noMode;
  1148.  
  1149.     delete [] m_rgFieldInfos;
  1150.     m_rgFieldInfos = NULL;
  1151.  
  1152.     delete [] m_rgODBCFieldInfos;
  1153.     m_rgODBCFieldInfos = NULL;
  1154.  
  1155.     delete [] m_pbFieldFlags;
  1156.     m_pbFieldFlags = NULL;
  1157.  
  1158.     delete [] m_pbParamFlags;
  1159.     m_pbParamFlags = NULL;
  1160.  
  1161.     if (m_pvFieldProxy != NULL)
  1162.     {
  1163.         for (UINT nField = 0; nField < m_nProxyFields; nField++)
  1164.             delete m_pvFieldProxy[nField];
  1165.  
  1166.         delete [] m_pvFieldProxy;
  1167.         m_pvFieldProxy = NULL;
  1168.         m_nProxyFields = 0;
  1169.     }
  1170.  
  1171.     if (m_pvParamProxy != NULL)
  1172.     {
  1173.         for (UINT nParam = 0; nParam < m_nProxyParams; nParam++)
  1174.             delete m_pvParamProxy[nParam];
  1175.  
  1176.         delete [] m_pvParamProxy;
  1177.         m_pvParamProxy = NULL;
  1178.         m_nProxyParams = 0;
  1179.     }
  1180.  
  1181.     delete [] m_plParamLength;
  1182.     m_plParamLength = NULL;
  1183.  
  1184.     RETCODE nRetCode;
  1185.     if (m_hstmt != SQL_NULL_HSTMT)
  1186.     {
  1187.         AFX_SQL_SYNC(::SQLFreeStmt(m_hstmt, SQL_DROP));
  1188.         m_hstmt = SQL_NULL_HSTMT;
  1189.     }
  1190.  
  1191.     if (m_hstmtUpdate != SQL_NULL_HSTMT)
  1192.     {
  1193.         AFX_SQL_SYNC(::SQLFreeStmt(m_hstmtUpdate, SQL_DROP));
  1194.         m_hstmtUpdate = SQL_NULL_HSTMT;
  1195.     }
  1196.  
  1197.     // Remove CRecordset from CDatabase's list
  1198.     AfxLockGlobals(CRIT_ODBC);
  1199.     TRY
  1200.     {
  1201.         POSITION pos = m_pDatabase->m_listRecordsets.Find(this);
  1202.         if (pos != NULL)
  1203.             m_pDatabase->m_listRecordsets.RemoveAt(pos);
  1204. #ifdef _DEBUG
  1205.         else if (afxTraceFlags & traceDatabase)
  1206.             TRACE0("WARNING: CRecordset not found in m_pDatabase->m_listRecordsets.\n");
  1207. #endif
  1208.     }
  1209.     CATCH_ALL(e)
  1210.     {
  1211.         AfxUnlockGlobals(CRIT_ODBC);
  1212.         THROW_LAST();
  1213.     }
  1214.     END_CATCH_ALL
  1215.     AfxUnlockGlobals(CRIT_ODBC);
  1216.  
  1217.     m_lOpen = AFX_RECORDSET_STATUS_CLOSED;
  1218.     m_bBOF = TRUE;
  1219.     m_bEOF = TRUE;
  1220.     m_bDeleted = FALSE;
  1221.     m_bAppendable = FALSE;
  1222.     m_bUpdatable = FALSE;
  1223.     m_bScrollable = FALSE;
  1224.     m_bRebindParams = FALSE;
  1225.     m_bLongBinaryColumns = FALSE;
  1226.     m_nLockMode = optimistic;
  1227.  
  1228.     m_nFieldsBound = 0;
  1229.     m_nResultCols = -1;
  1230. }
  1231.  
  1232. BOOL CRecordset::IsOpen() const
  1233.     // Note: assumes base class CRecordset::Close called
  1234. {
  1235.     if (m_hstmt == NULL)
  1236.         return FALSE;
  1237.  
  1238.     if (m_lOpen == AFX_RECORDSET_STATUS_OPEN)
  1239.         return TRUE;
  1240.  
  1241.     RETCODE nRetCode;
  1242.     SWORD nCols;
  1243.  
  1244.     AFX_ODBC_CALL(::SQLNumResultCols(m_hstmt, &nCols));
  1245.  
  1246.     if (!Check(nRetCode))
  1247.     {
  1248.         // If function sequence error, CRecordset not open
  1249.         CDBException* e = new CDBException(nRetCode);
  1250.         e->BuildErrorString(m_pDatabase, m_hstmt, FALSE);
  1251.         if (e->m_strStateNativeOrigin.Find(_afxOutOfSequence) >= 0)
  1252.         {
  1253.             e->Delete();
  1254.             return FALSE;
  1255.         }
  1256.         else
  1257.         {
  1258. #ifdef _DEBUG
  1259.             TRACE0("Error: SQLNumResultCols failed during IsOpen().\n");
  1260.             e->TraceErrorMessage(e->m_strError);
  1261.             e->TraceErrorMessage(e->m_strStateNativeOrigin);
  1262. #endif
  1263.             THROW(e);
  1264.         }
  1265.     }
  1266.  
  1267.     BOOL bOpen = FALSE;
  1268.  
  1269.     if (nCols != 0)
  1270.         bOpen = TRUE;
  1271.  
  1272.     return bOpen;
  1273. }
  1274.  
  1275. BOOL CRecordset::IsFieldDirty(void* pv)
  1276. {
  1277.     ASSERT_VALID(this);
  1278.     ASSERT(!(m_dwOptions & useMultiRowFetch));
  1279.  
  1280.     if (m_nFields <= 0)
  1281.     {
  1282.         ASSERT(FALSE);
  1283.         return FALSE;
  1284.     }
  1285.  
  1286.     // If not in update op fields can't be dirty
  1287.     // must compare saved and current values
  1288.     if (m_nEditMode == noMode)
  1289.         return FALSE;
  1290.  
  1291.     // Must compare values to find dirty fields if necessary
  1292.     if (m_bCheckCacheForDirtyFields)
  1293.     {
  1294.         if (m_nEditMode == edit)
  1295.             MarkForUpdate();
  1296.         else
  1297.             MarkForAddNew();
  1298.     }
  1299.  
  1300.     int nIndex = 0, nIndexEnd;
  1301.  
  1302.     if (pv == NULL)
  1303.         nIndexEnd = m_nFields - 1;
  1304.     else
  1305.     {
  1306.         // GetBoundFieldIndex returns 1-based index
  1307.         nIndex = nIndexEnd = GetBoundFieldIndex(pv) - 1;
  1308.  
  1309.         // must be address of field member
  1310.         ASSERT(nIndex >= 0);
  1311.     }
  1312.  
  1313.     BOOL bDirty = FALSE;
  1314.  
  1315.     while (nIndex <= nIndexEnd && !bDirty)
  1316.         bDirty = IsFieldStatusDirty(nIndex++);
  1317.  
  1318.     return bDirty;
  1319. }
  1320.  
  1321. BOOL CRecordset::IsFieldNull(void* pv)
  1322. {
  1323.     ASSERT_VALID(this);
  1324.     ASSERT(!(m_dwOptions & useMultiRowFetch));
  1325.  
  1326.     int nIndex;
  1327.     BOOL bRetVal;
  1328.  
  1329.     if (pv == NULL)
  1330.     {
  1331.         bRetVal = FALSE;
  1332.         for (nIndex = 0; !bRetVal && nIndex <= int(m_nFields-1); nIndex++)
  1333.             bRetVal = IsFieldStatusNull((DWORD) nIndex);
  1334.     }
  1335.     else
  1336.     {
  1337.         nIndex = GetBoundFieldIndex(pv) - 1;
  1338.         if (nIndex < 0)
  1339.             ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
  1340.         bRetVal = IsFieldStatusNull((DWORD) nIndex);
  1341.     }
  1342.  
  1343.     return bRetVal;
  1344. }
  1345.  
  1346. BOOL CRecordset::IsFieldNullable(void* pv)
  1347. {
  1348.     ASSERT_VALID(this);
  1349.  
  1350.     if (pv == NULL)
  1351.     {
  1352.         // Must specify valid column name
  1353.         ASSERT(FALSE);
  1354.         return FALSE;
  1355.     }
  1356.  
  1357.     int nIndex = GetBoundFieldIndex(pv) - 1;
  1358.     if (nIndex < 0)
  1359.         ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
  1360.  
  1361.     return IsFieldNullable((DWORD)nIndex);
  1362. }
  1363.  
  1364. BOOL CRecordset::CanBookmark() const
  1365. {
  1366.     ASSERT_VALID(this);
  1367.     ASSERT(m_pDatabase->IsOpen());
  1368.  
  1369.     if (!(m_dwOptions & useBookmarks) ||
  1370.         (m_nOpenType == forwardOnly && !(m_dwOptions & useExtendedFetch)))
  1371.         return FALSE;
  1372.  
  1373.     return m_pDatabase->GetBookmarkPersistence() & SQL_BP_SCROLL;
  1374. }
  1375.  
  1376. void CRecordset::Move(long nRows, WORD wFetchType)
  1377. {
  1378.     ASSERT_VALID(this);
  1379.     ASSERT(m_hstmt != SQL_NULL_HSTMT);
  1380.  
  1381.     // First call - fields haven't been bound (m_nFieldsBound will change)
  1382.     if (m_nFieldsBound == 0)
  1383.     {
  1384.         InitRecord();
  1385.         ResetCursor();
  1386.     }
  1387.  
  1388.     if (m_nFieldsBound > 0)
  1389.     {
  1390.         // Reset field flags - mark all clean, all non-null
  1391.         memset(m_pbFieldFlags, 0, m_nFields);
  1392.  
  1393.         // Clear any edit mode that was set
  1394.         m_nEditMode = noMode;
  1395.     }
  1396.  
  1397.     // Check scrollability, EOF/BOF status
  1398.     CheckRowsetCurrencyStatus(wFetchType, nRows);
  1399.  
  1400.     RETCODE nRetCode;
  1401.  
  1402.     // Fetch the data, skipping deleted records if necessary
  1403.     if ((wFetchType == SQL_FETCH_FIRST ||
  1404.         wFetchType == SQL_FETCH_LAST ||
  1405.         wFetchType == SQL_FETCH_NEXT ||
  1406.         wFetchType == SQL_FETCH_PRIOR ||
  1407.         wFetchType == SQL_FETCH_RELATIVE) &&
  1408.         m_dwOptions & skipDeletedRecords)
  1409.     {
  1410.         SkipDeletedRecords(wFetchType, nRows, &m_dwRowsFetched, &nRetCode);
  1411.     }
  1412.     else
  1413.         // Fetch the data and check for errors
  1414.         nRetCode = FetchData(wFetchType, nRows, &m_dwRowsFetched);
  1415.  
  1416.     // Set currency status and increment the record counters
  1417.     SetRowsetCurrencyStatus(nRetCode, wFetchType, nRows, m_dwRowsFetched);
  1418.  
  1419.     // Need to fixup bound fields in some cases
  1420.     if (m_nFields > 0 && !IsEOF() && !IsBOF() &&
  1421.         !(m_dwOptions & useMultiRowFetch))
  1422.     {
  1423.         Fixups();
  1424.     }
  1425. }
  1426.  
  1427. void CRecordset::CheckRowsetError(RETCODE nRetCode)
  1428. {
  1429.     if (nRetCode == SQL_SUCCESS_WITH_INFO)
  1430.     {
  1431.         CDBException e(nRetCode);
  1432.         // Build the error string but don't send nuisance output to TRACE window
  1433.         e.BuildErrorString(m_pDatabase, m_hstmt, FALSE);
  1434.  
  1435.         if (e.m_strStateNativeOrigin.Find(_afxDataTruncated) >= 0)
  1436.         {
  1437.             // Ignore data truncated warning if binding long binary columns
  1438.             // (may mask non-long binary truncation warnings or other warnings)
  1439.             if (!((m_pDatabase->m_dwUpdateOptions & AFX_SQL_SETPOSUPDATES) &&
  1440.                 m_bLongBinaryColumns))
  1441.             {
  1442.                 NO_CPP_EXCEPTION(e.Empty());
  1443.                 TRACE0("Error: field data truncated during data fetch.\n");
  1444.                 ThrowDBException(AFX_SQL_ERROR_DATA_TRUNCATED);
  1445.             }
  1446.         }
  1447.         else if (e.m_strStateNativeOrigin.Find(_afxRowFetch) >= 0)
  1448.         {
  1449. #ifdef _DEBUG
  1450.             TRACE0("Error: fetching row from server.\n");
  1451.             e.TraceErrorMessage(e.m_strError);
  1452.             e.TraceErrorMessage(e.m_strStateNativeOrigin);
  1453. #endif
  1454.             NO_CPP_EXCEPTION(e.Empty());
  1455.             ThrowDBException(AFX_SQL_ERROR_ROW_FETCH);
  1456.         }
  1457.         else
  1458.         {
  1459. #ifdef _DEBUG
  1460.             // Not a truncation or row fetch warning so send debug output
  1461.             if (afxTraceFlags & traceDatabase)
  1462.             {
  1463.                 TRACE0("Warning: ODBC Success With Info,\n");
  1464.                 e.TraceErrorMessage(e.m_strError);
  1465.                 e.TraceErrorMessage(e.m_strStateNativeOrigin);
  1466.             }
  1467. #endif // _DEBUG
  1468.         }
  1469.     }
  1470.     else if (!Check(nRetCode))
  1471.         ThrowDBException(nRetCode);
  1472. }
  1473.  
  1474. void CRecordset::GetBookmark(CDBVariant& varBookmark)
  1475. {
  1476.     ASSERT_VALID(this);
  1477.  
  1478.     // Validate bookmarks are usable
  1479.     if (!(m_dwOptions & useBookmarks))
  1480.         ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_ENABLED);
  1481.     else if (!CanBookmark())
  1482.         ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_SUPPORTED);
  1483.  
  1484.     // Currently ODBC only supports 4 byte bookmarks
  1485.     // Initialize the variant to a long
  1486.     if (varBookmark.m_dwType != DBVT_LONG)
  1487.     {
  1488.         varBookmark.Clear();
  1489.         varBookmark.m_dwType = DBVT_LONG;
  1490.         varBookmark.m_lVal = 0;
  1491.     }
  1492.  
  1493.     RETCODE nRetCode;
  1494.     SDWORD nActualSize;
  1495.  
  1496.     // Retrieve the bookmark (column 0) data
  1497.     AFX_ODBC_CALL(::SQLGetData(m_hstmt, 0, SQL_C_BOOKMARK,
  1498.         &varBookmark.m_lVal, sizeof(varBookmark.m_lVal), &nActualSize));
  1499.     if (!Check(nRetCode))
  1500.     {
  1501.         TRACE0("Error: GetBookmark operation failed.\n");
  1502.         ThrowDBException(nRetCode);
  1503.     }
  1504. }
  1505.  
  1506. void CRecordset::SetBookmark(const CDBVariant& varBookmark)
  1507. {
  1508.     ASSERT_VALID(this);
  1509.  
  1510.     // Validate bookmarks are usable
  1511.     if (!(m_dwOptions & useBookmarks))
  1512.         ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_ENABLED);
  1513.     else if (!CanBookmark())
  1514.         ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_SUPPORTED);
  1515.  
  1516.     // Currently ODBC only supports 4 byte bookmarks
  1517.     ASSERT(varBookmark.m_dwType == DBVT_LONG);
  1518.  
  1519.     Move(varBookmark.m_lVal, SQL_FETCH_BOOKMARK);
  1520. }
  1521.  
  1522. void CRecordset::SetRowsetSize(DWORD dwNewRowsetSize)
  1523. {
  1524.     ASSERT_VALID(this);
  1525.     ASSERT(dwNewRowsetSize > 0);
  1526.  
  1527.     // If not yet open, only set expected length
  1528.     if (!IsOpen())
  1529.     {
  1530.         m_dwRowsetSize = dwNewRowsetSize;
  1531.         return;
  1532.     }
  1533.  
  1534.     if (!(m_dwOptions & useMultiRowFetch))
  1535.     {
  1536.         // Only works if bulk row fetching!
  1537.         ASSERT(FALSE);
  1538.         return;
  1539.     }
  1540.  
  1541.     // Need to reallocate some memory if rowset size grows
  1542.     if (m_dwAllocatedRowsetSize == 0 ||
  1543.         (m_dwAllocatedRowsetSize < dwNewRowsetSize))
  1544.     {
  1545.         // If rowset already allocated, delete old and reallocate
  1546.         FreeRowset();
  1547.         m_rgRowStatus = new WORD[dwNewRowsetSize];
  1548.  
  1549.         // If not a user allocated buffer grow the data buffers
  1550.         if (!(m_dwOptions & userAllocMultiRowBuffers))
  1551.         {
  1552.             // Allocate the rowset field buffers
  1553.             m_dwRowsetSize = dwNewRowsetSize;
  1554.             CFieldExchange fx(CFieldExchange::AllocMultiRowBuffer, this);
  1555.             DoBulkFieldExchange(&fx);
  1556.  
  1557.             m_dwAllocatedRowsetSize = dwNewRowsetSize;
  1558.  
  1559.             // Set bound fields to zero, rebind and reset bound field count
  1560.             int nOldFieldsBound = m_nFieldsBound;
  1561.             m_nFieldsBound = 0;
  1562.             InitRecord();
  1563.             m_nFieldsBound = nOldFieldsBound;
  1564.         }
  1565.     }
  1566.     else
  1567.     {
  1568.         // Just reset the new rowset size
  1569.         m_dwRowsetSize = dwNewRowsetSize;
  1570.     }
  1571.  
  1572.     RETCODE nRetCode;
  1573.     AFX_SQL_SYNC(::SQLSetStmtOption(m_hstmt, SQL_ROWSET_SIZE,
  1574.         m_dwRowsetSize));
  1575. }
  1576.  
  1577. void CRecordset::AddNew()
  1578. {
  1579.     ASSERT_VALID(this);
  1580.     ASSERT(m_hstmt != SQL_NULL_HSTMT);
  1581.     // we can't construct an INSERT statement w/o any columns
  1582.     ASSERT(m_nFields != 0);
  1583.  
  1584.     if (!m_bAppendable)
  1585.     {
  1586.         ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
  1587.     }
  1588.  
  1589.     if (m_dwOptions & useMultiRowFetch)
  1590.     {
  1591.         // Can't use update methods on multi-row rowset
  1592.         ASSERT(FALSE);
  1593.         return;
  1594.     }
  1595.  
  1596.     if (m_bCheckCacheForDirtyFields && m_nFields > 0)
  1597.     {
  1598.         if (m_nEditMode == noMode)
  1599.         {
  1600.             // First addnew call, cache record values
  1601.             StoreFields();
  1602.         }
  1603.         else
  1604.         {
  1605.             // subsequent Edit/AddNew call.  Restore values, save them again
  1606.             LoadFields();
  1607.             StoreFields();
  1608.         }
  1609.     }
  1610.  
  1611.     SetFieldNull(NULL);
  1612.     SetFieldDirty(NULL, FALSE);
  1613.  
  1614.     m_nEditMode = addnew;
  1615. }
  1616.  
  1617. void CRecordset::Edit()
  1618. {
  1619.     ASSERT_VALID(this);
  1620.     ASSERT(m_hstmt != SQL_NULL_HSTMT);
  1621.     // we can't construct an UPDATE statement w/o any columns
  1622.     ASSERT(m_nFields != 0);
  1623.  
  1624.     if (!m_bUpdatable)
  1625.         ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
  1626.  
  1627.     if (m_dwOptions & useMultiRowFetch)
  1628.     {
  1629.         // Can't use update methods on multi-row rowset
  1630.         ASSERT(FALSE);
  1631.         return;
  1632.     }
  1633.  
  1634.     if (m_bEOF || m_bBOF || m_bDeleted)
  1635.     {
  1636.         TRACE0("Error: Edit attempt failed - not on a record.\n");
  1637.         ThrowDBException(AFX_SQL_ERROR_NO_CURRENT_RECORD);
  1638.     }
  1639.  
  1640.     if ((m_nOpenType == dynaset || m_nOpenType == dynamic) &&
  1641.         m_nLockMode == pessimistic)
  1642.     {
  1643.         RETCODE nRetCode;
  1644.         AFX_ODBC_CALL(::SQLSetPos(m_hstmt, 1, SQL_POSITION,
  1645.             SQL_LCK_EXCLUSIVE));
  1646.         if (!Check(nRetCode))
  1647.         {
  1648.             TRACE0("Error: attempt to lock record failed during Edit function.\n");
  1649.             ThrowDBException(nRetCode);
  1650.         }
  1651.     }
  1652.  
  1653.     if (m_bCheckCacheForDirtyFields && m_nFields > 0)
  1654.     {
  1655.         if (m_nEditMode == noMode)
  1656.             // First edit call, cache record values
  1657.             StoreFields();
  1658.         else
  1659.         {
  1660.             // subsequent Edit/AddNew call.  Restore values, save them again
  1661.             LoadFields();
  1662.             StoreFields();
  1663.         }
  1664.     }
  1665.  
  1666.     m_nEditMode = edit;
  1667. }
  1668.  
  1669. BOOL CRecordset::Update()
  1670. {
  1671.     ASSERT_VALID(this);
  1672.     ASSERT(m_hstmt != SQL_NULL_HSTMT);
  1673.  
  1674.     if (m_dwOptions & useMultiRowFetch)
  1675.     {
  1676.         // Can't use update methods on multi-row rowset
  1677.         ASSERT(FALSE);
  1678.         return FALSE;
  1679.     }
  1680.  
  1681.     if (m_nEditMode != addnew && m_nEditMode != edit)
  1682.     {
  1683.         TRACE0("Error: must enter Edit or AddNew mode before updating.\n");
  1684.         ThrowDBException(AFX_SQL_ERROR_ILLEGAL_MODE);
  1685.     }
  1686.     return UpdateInsertDelete();
  1687. }
  1688.  
  1689. void CRecordset::Delete()
  1690. {
  1691.     ASSERT_VALID(this);
  1692.     ASSERT(m_hstmt != SQL_NULL_HSTMT);
  1693.  
  1694.     if (m_dwOptions & useMultiRowFetch)
  1695.     {
  1696.         // Can't use update methods on multi-row rowset
  1697.         ASSERT(FALSE);
  1698.         return;
  1699.     }
  1700.  
  1701.     if (m_nEditMode != noMode)
  1702.     {
  1703.         TRACE0("Error: attempted to delete while still in Edit or AddNew mode.\n");
  1704.         ThrowDBException(AFX_SQL_ERROR_ILLEGAL_MODE);
  1705.     }
  1706.     UpdateInsertDelete();   // This call can't fail in delete mode (noMode)
  1707. }
  1708.  
  1709. void CRecordset::CancelUpdate()
  1710. {
  1711.     ASSERT_VALID(this);
  1712.     ASSERT(IsOpen());
  1713.  
  1714.     if (m_nEditMode == noMode)
  1715.         // Do nothing if not in edit mode
  1716.         return;
  1717.     else
  1718.         // Reset the edit mode
  1719.         m_nEditMode = noMode;
  1720.  
  1721.     // Restore cache if necessary
  1722.     if (m_bCheckCacheForDirtyFields && m_nFields > 0)
  1723.         LoadFields();
  1724. }
  1725.  
  1726. BOOL CRecordset::FlushResultSet() const
  1727. {
  1728.     RETCODE nRetCode;
  1729.     AFX_ODBC_CALL(::SQLMoreResults(m_hstmt));
  1730.  
  1731.     if (!Check(nRetCode))
  1732.     {
  1733.         TRACE0("Error: attempt FlushResultSet failed.\n");
  1734.         AfxThrowDBException(nRetCode, m_pDatabase, m_hstmt);
  1735.     }
  1736.  
  1737.     // Reset state of cursor
  1738.     ((CRecordset*)this)->ResetCursor();
  1739.  
  1740.     return nRetCode != SQL_NO_DATA_FOUND;
  1741. }
  1742.  
  1743. void CRecordset::GetODBCFieldInfo(LPCTSTR lpszName,
  1744.     CODBCFieldInfo& fieldinfo)
  1745. {
  1746.     ASSERT_VALID(this);
  1747.     ASSERT(IsOpen());
  1748.     ASSERT(lpszName != NULL);
  1749.  
  1750.     // No data or no column info fetched yet
  1751.     if (GetODBCFieldCount() <= 0)
  1752.     {
  1753.         ASSERT(FALSE);
  1754.         return;
  1755.     }
  1756.  
  1757.     // Get the index of the field corresponding to name
  1758.     short nField = GetFieldIndexByName(lpszName);
  1759.  
  1760.     GetODBCFieldInfo(nField, fieldinfo);
  1761. }
  1762.  
  1763. void CRecordset::GetODBCFieldInfo(short nIndex,
  1764.     CODBCFieldInfo& fieldinfo)
  1765. {
  1766.     ASSERT_VALID(this);
  1767.     ASSERT(IsOpen());
  1768.  
  1769.     // No data or no column info fetched yet
  1770.     if (GetODBCFieldCount() <= 0)
  1771.     {
  1772.         ASSERT(FALSE);
  1773.         return;
  1774.     }
  1775.  
  1776.     // Just copy the data into the field info
  1777.     CODBCFieldInfo* pInfo = &m_rgODBCFieldInfos[nIndex];
  1778.     fieldinfo.m_strName = pInfo->m_strName;
  1779.     fieldinfo.m_nSQLType = pInfo->m_nSQLType;
  1780.     fieldinfo.m_nPrecision = pInfo->m_nPrecision;
  1781.     fieldinfo.m_nScale = pInfo->m_nScale;
  1782.     fieldinfo.m_nNullability = pInfo->m_nNullability;
  1783. }
  1784.  
  1785. void CRecordset::GetFieldValue(LPCTSTR lpszName,
  1786.     CDBVariant& varValue, short nFieldType)
  1787. {
  1788.     ASSERT_VALID(this);
  1789.     ASSERT(IsOpen());
  1790.     ASSERT(lpszName != NULL);
  1791.  
  1792.     // No data or no column info fetched yet
  1793.     if (GetODBCFieldCount() <= 0)
  1794.     {
  1795.         ASSERT(FALSE);
  1796.         varValue.Clear();
  1797.         return;
  1798.     }
  1799.  
  1800.     // Get the index of the field corresponding to name
  1801.     short nField = GetFieldIndexByName(lpszName);
  1802.  
  1803.     GetFieldValue(nField, varValue, nFieldType);
  1804. }
  1805.  
  1806. void CRecordset::GetFieldValue(short nIndex,
  1807.     CDBVariant& varValue, short nFieldType)
  1808. {
  1809.     ASSERT_VALID(this);
  1810.     ASSERT(IsOpen());
  1811.  
  1812.     // Clear the previous variant
  1813.     varValue.Clear();
  1814.  
  1815.     // No data or no column info fetched yet
  1816.     if (GetODBCFieldCount() <= 0)
  1817.     {
  1818.         ASSERT(FALSE);
  1819.         return;
  1820.     }
  1821.  
  1822.     // Convert index to 1-based and check range
  1823.     nIndex++;
  1824.     if (nIndex < 1 || nIndex > GetODBCFieldCount())
  1825.     {
  1826.         ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
  1827.     }
  1828.  
  1829.     void* pvData = NULL;
  1830.     int nLen = 0;
  1831.  
  1832.     // Determine the default field type and get the data buffer
  1833.     if (nFieldType == DEFAULT_FIELD_TYPE)
  1834.     {
  1835.         nFieldType =
  1836.             GetDefaultFieldType(m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
  1837.     }
  1838.     pvData = GetDataBuffer(varValue, nFieldType, &nLen,
  1839.         m_rgODBCFieldInfos[nIndex - 1].m_nSQLType,
  1840.         m_rgODBCFieldInfos[nIndex - 1].m_nPrecision);
  1841.  
  1842.     // Now can actually get the data
  1843.     long nActualSize = GetData(m_pDatabase, m_hstmt, nIndex,
  1844.         nFieldType, pvData, nLen,
  1845.         m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
  1846.  
  1847.     // Handle NULL data separately
  1848.     if (nActualSize == SQL_NULL_DATA)
  1849.     {
  1850.         // Clear value and set the value NULL
  1851.         varValue.Clear();
  1852.     }
  1853.     else
  1854.     {
  1855.         // May need to cleanup and call SQLGetData again if LONG_VAR data
  1856.         if (nFieldType == SQL_C_CHAR)
  1857.         {
  1858.             GetLongCharDataAndCleanup(m_pDatabase, m_hstmt, nIndex,
  1859.                 nActualSize, &pvData, nLen, *varValue.m_pstring,
  1860.                 m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
  1861.  
  1862. #ifdef _UNICODE
  1863.             // Now must convert string to UNICODE
  1864.             LPCSTR lpszOld = (LPCSTR)varValue.m_pstring->GetBuffer(0);
  1865.             CString* pStringNew = new CString(lpszOld);
  1866.             delete varValue.m_pstring;
  1867.             varValue.m_pstring = pStringNew;
  1868. #endif // _UNICODE
  1869.         }
  1870.         else if (nFieldType == SQL_C_BINARY)
  1871.         {
  1872.             GetLongBinaryDataAndCleanup(m_pDatabase, m_hstmt, nIndex,
  1873.                 nActualSize, &pvData, nLen, varValue,
  1874.                 m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
  1875.         }
  1876.     }
  1877. }
  1878.  
  1879. void CRecordset::GetFieldValue(LPCTSTR lpszName, CString& strValue)
  1880. {
  1881.     ASSERT_VALID(this);
  1882.     ASSERT(IsOpen());
  1883.     ASSERT(lpszName != NULL);
  1884.  
  1885.     // No data or no column info fetched yet
  1886.     if (GetODBCFieldCount() <= 0)
  1887.     {
  1888.         ASSERT(FALSE);
  1889.         return;
  1890.     }
  1891.  
  1892.     // Get the index of the field corresponding to name
  1893.     short nField = GetFieldIndexByName(lpszName);
  1894.  
  1895.     GetFieldValue(nField, strValue);
  1896. }
  1897.  
  1898. void CRecordset::GetFieldValue(short nIndex, CString& strValue)
  1899. {
  1900.     ASSERT_VALID(this);
  1901.     ASSERT(IsOpen());
  1902.  
  1903.     // No data or no column info fetched yet
  1904.     if (GetODBCFieldCount() <= 0)
  1905.     {
  1906.         ASSERT(FALSE);
  1907.         return;
  1908.     }
  1909.  
  1910.     // Convert index to 1-based and check range
  1911.     nIndex++;
  1912.     if (nIndex < 1 || nIndex > GetODBCFieldCount())
  1913.     {
  1914.         ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
  1915.     }
  1916.  
  1917.     int nLen = GetTextLen(m_rgODBCFieldInfos[nIndex - 1].m_nSQLType,
  1918.             m_rgODBCFieldInfos[nIndex - 1].m_nPrecision);
  1919.  
  1920. #ifndef _UNICODE
  1921.     CString& strData = strValue;
  1922. #else
  1923.     CString strProxy;
  1924.     CString& strData = strProxy;
  1925. #endif
  1926.     void* pvData = strData.GetBufferSetLength(nLen);
  1927.  
  1928.     // Now can actually get the data
  1929.     long nActualSize = GetData(m_pDatabase, m_hstmt, nIndex,
  1930.         SQL_C_CHAR, pvData, nLen,
  1931.         m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
  1932.  
  1933.     // Handle NULL data separately
  1934.     if (nActualSize == SQL_NULL_DATA)
  1935.     {
  1936.         // Clear value
  1937.         strValue.Empty();
  1938.     }
  1939.     else
  1940.     {
  1941.         // May need to cleanup and call SQLGetData again if necessary
  1942.         GetLongCharDataAndCleanup(m_pDatabase, m_hstmt, nIndex,
  1943.             nActualSize, &pvData, nLen, strData,
  1944.             m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
  1945.  
  1946. #ifdef _UNICODE
  1947.     // Now must convert string to UNICODE
  1948.     strValue = (LPCSTR)strData.GetBuffer(0);
  1949. #endif // _UNIOCDE
  1950.     }
  1951. }
  1952.  
  1953. void CRecordset::SetFieldDirty(void* pv, BOOL bDirty)
  1954. {
  1955.     ASSERT_VALID(this);
  1956.  
  1957.     int nIndex, nIndexEnd;
  1958.  
  1959.     // If not setting all NULL, check simple case
  1960.     if (pv != NULL)
  1961.     {
  1962.         // GetBoundFieldIndex returns 1-based index
  1963.         nIndex = GetBoundFieldIndex(pv) - 1;
  1964.  
  1965.         if (nIndex < 0)
  1966.         {
  1967.             // pv must be address of field member
  1968.             ASSERT(FALSE);
  1969.             return;
  1970.         }
  1971.         else
  1972.         {
  1973.             nIndexEnd = nIndex;
  1974.         }
  1975.     }
  1976.     else
  1977.     {
  1978.         nIndex = 0;
  1979.         nIndexEnd = m_nFields - 1;
  1980.     }
  1981.  
  1982.     while (nIndex <= nIndexEnd)
  1983.     {
  1984.         if (bDirty)
  1985.             SetDirtyFieldStatus((DWORD)nIndex);
  1986.         else
  1987.             ClearDirtyFieldStatus((DWORD)nIndex);
  1988.  
  1989.         nIndex++;
  1990.     }
  1991. }
  1992.  
  1993. void CRecordset::SetFieldNull(void* pv, BOOL bNull)
  1994. {
  1995.     ASSERT_VALID(this);
  1996.     ASSERT(IsOpen());
  1997.     ASSERT(!(m_dwOptions & useMultiRowFetch));
  1998.  
  1999.     // If not setting all fields NULL, check simple case (param) first
  2000.     if (pv != NULL)
  2001.     {
  2002.         // Cached index is 1-based
  2003.         int nIndex = GetBoundParamIndex(pv) - 1;
  2004.         if (nIndex >= 0)
  2005.         {
  2006.             if (bNull)
  2007.                 SetNullParamStatus(nIndex);
  2008.             else
  2009.                 ClearNullParamStatus(nIndex);
  2010.             return;
  2011.         }
  2012.     }
  2013.  
  2014.     // Not a param, must be a field
  2015.     if (m_nFields <= 0)
  2016.     {
  2017.         ASSERT(FALSE);
  2018.         return;
  2019.     }
  2020.  
  2021.     // Need field exchange mechanism to set PSEUDO NULL values
  2022.     // and to reset data lengths (especially for RFX_LongBinary)
  2023.     CFieldExchange fx(CFieldExchange::SetFieldNull, this, pv);
  2024.     fx.m_nFieldFound = 0;
  2025.     fx.m_bField = bNull;
  2026.     DoFieldExchange(&fx);
  2027.  
  2028.     // If no field found, m_nFieldFound will still be zero
  2029.     ASSERT(fx.m_nFieldFound != 0);
  2030. }
  2031.  
  2032. void CRecordset::SetParamNull(int nIndex, BOOL bNull)
  2033. {
  2034.     ASSERT_VALID(this);
  2035.     ASSERT((DWORD)nIndex < m_nParams);
  2036.  
  2037.     // Can be called before Open, but need to alloc status arrays first
  2038.     if (!IsOpen())
  2039.         AllocStatusArrays();
  2040.  
  2041.     if (bNull)
  2042.         SetNullParamStatus(nIndex);
  2043.     else
  2044.         ClearNullParamStatus(nIndex);
  2045.  
  2046.     return;
  2047. }
  2048.  
  2049. void CRecordset::SetLockingMode(UINT nLockMode)
  2050. {
  2051.     if (nLockMode == pessimistic)
  2052.     {
  2053.         RETCODE nRetCode;
  2054.         UDWORD dwTypes;
  2055.         SWORD nResult;
  2056.         AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_LOCK_TYPES,
  2057.             &dwTypes, sizeof(dwTypes), &nResult));
  2058.         if (!Check(nRetCode) || !(dwTypes & SQL_LCK_EXCLUSIVE))
  2059.             ThrowDBException(AFX_SQL_ERROR_LOCK_MODE_NOT_SUPPORTED);
  2060.     }
  2061.     m_nLockMode = nLockMode;
  2062. }
  2063.  
  2064. BOOL CRecordset::Requery()
  2065. {
  2066.     RETCODE nRetCode;
  2067.  
  2068.     ASSERT_VALID(this);
  2069.     ASSERT(IsOpen());
  2070.  
  2071.     // Can't requery if using direct execution
  2072.     if (m_dwOptions & executeDirect)
  2073.         return FALSE;
  2074.  
  2075.     TRY
  2076.     {
  2077.         // Detect changes to filter and sort
  2078.         if ((m_strFilter != m_strRequeryFilter) || (m_strSort != m_strRequerySort))
  2079.         {
  2080.             m_strRequeryFilter = m_strFilter;
  2081.             m_strRequerySort = m_strSort;
  2082.             Close();
  2083.             if (m_strRequerySQL.IsEmpty())
  2084.                 return Open(m_nOpenType, NULL, m_dwOptions);
  2085.             else
  2086.                 return Open(m_nOpenType, m_strRequerySQL, m_dwOptions);
  2087.         }
  2088.         else
  2089.         {
  2090.             // Shutdown current query, preserving buffers for performance
  2091.             AFX_SQL_SYNC(::SQLFreeStmt(m_hstmt, SQL_CLOSE));
  2092.             m_lOpen = AFX_RECORDSET_STATUS_CLOSED;
  2093.  
  2094.             // Rebind date/time parameters
  2095.             RebindParams(m_hstmt);
  2096.  
  2097.             // now attempt to re-execute the SQL Query
  2098.             AFX_ODBC_CALL(::SQLExecute(m_hstmt));
  2099.             if (!Check(nRetCode))
  2100.             {
  2101.                 TRACE0("Error: Requery attempt failed.\n");
  2102.                 ThrowDBException(nRetCode);
  2103.             }
  2104.  
  2105.             m_lOpen = AFX_RECORDSET_STATUS_OPEN;
  2106.  
  2107.             // Reset some cursor properties and fetch first record
  2108.             ResetCursor();
  2109.             MoveNext();
  2110.  
  2111.             // If EOF, then result set empty, so set BOF as well
  2112.             m_bBOF = m_bEOF;
  2113.         }
  2114.     }
  2115.     CATCH_ALL(e)
  2116.     {
  2117.         Close();
  2118.         THROW_LAST();
  2119.     }
  2120.     END_CATCH_ALL
  2121.  
  2122.     return TRUE;    // all set
  2123. }
  2124.  
  2125. // Shutdown any pending query for CRecordset's hstmt's
  2126. void CRecordset::Cancel()
  2127. {
  2128.     ASSERT_VALID(this);
  2129.     ASSERT(m_hstmt != SQL_NULL_HSTMT);
  2130.  
  2131.     ::SQLCancel(m_hstmt);
  2132.  
  2133.     // If Update hstmt has been allocated, shut it down also
  2134.     if (m_hstmtUpdate != SQL_NULL_HSTMT)
  2135.         ::SQLCancel(m_hstmtUpdate);
  2136. }
  2137.  
  2138. CString CRecordset::GetDefaultConnect()
  2139. {
  2140.     ASSERT_VALID(this);
  2141.  
  2142.     return _afxODBCTrail;
  2143. }
  2144.  
  2145. CString CRecordset::GetDefaultSQL()
  2146. {
  2147.     ASSERT_VALID(this);
  2148.  
  2149.     // Override and add table name or entire SQL SELECT statement
  2150.     return _T("");
  2151. }
  2152.  
  2153. void CRecordset::DoFieldExchange(CFieldExchange* /* pFX */)
  2154. {
  2155.     ASSERT_VALID(this);
  2156.  
  2157.     // Do nothing if dynamically retrieving unbound fields,
  2158.     // otherwise override CRecordset and add RFX calls
  2159. }
  2160.  
  2161. void CRecordset::DoBulkFieldExchange(CFieldExchange* /* pFX */)
  2162. {
  2163.     ASSERT_VALID(this);
  2164.  
  2165.     // To use multi-record data fetching, you must use
  2166.     // a derived CRecordset class and call Close explicitly.
  2167.     ASSERT(FALSE);
  2168. }
  2169.  
  2170. void CRecordset::OnSetOptions(HSTMT hstmt)
  2171. {
  2172.     ASSERT_VALID(this);
  2173.     ASSERT(hstmt != SQL_NULL_HSTMT);
  2174.  
  2175.     // Inherit options settings from CDatabase
  2176.     m_pDatabase->OnSetOptions(hstmt);
  2177.  
  2178.     // If fowardOnly recordset and not using SQLExtendedFetch, quit now
  2179.     if (m_nOpenType == forwardOnly && !(m_dwOptions & useExtendedFetch))
  2180.         return;
  2181.  
  2182.     // Turn on bookmark support if necessary
  2183.     EnableBookmarks();
  2184.  
  2185.     // If using forwardOnly and extended fetch, quit now
  2186.     if (m_nOpenType == forwardOnly)
  2187.         return;
  2188.  
  2189.     // Make sure driver supports extended fetch, ODBC 2.0 and requested cursor type
  2190.     VerifyDriverBehavior();
  2191.     DWORD dwScrollType = VerifyCursorSupport();
  2192.  
  2193.     // Set the update method, concurrency and cursor type
  2194.     SetUpdateMethod();
  2195.     SetConcurrencyAndCursorType(hstmt, dwScrollType);
  2196. }
  2197.  
  2198. // Screen for errors.
  2199. BOOL CRecordset::Check(RETCODE nRetCode) const
  2200. {
  2201.     ASSERT_VALID(this);
  2202.  
  2203.     switch (nRetCode)
  2204.     {
  2205.     case SQL_SUCCESS_WITH_INFO:
  2206. #ifdef _DEBUG
  2207.         if (afxTraceFlags & traceDatabase)
  2208.         {
  2209.             CDBException e(nRetCode);
  2210.             TRACE0("Warning: ODBC Success With Info, ");
  2211.             e.BuildErrorString(m_pDatabase, m_hstmt);
  2212.         }
  2213. #endif
  2214.  
  2215.         // Fall through
  2216.  
  2217.     case SQL_SUCCESS:
  2218.     case SQL_NO_DATA_FOUND:
  2219.     case SQL_NEED_DATA:
  2220.         return TRUE;
  2221.     }
  2222.  
  2223.     return FALSE;
  2224. }
  2225.  
  2226. void CRecordset::PreBindFields()
  2227. {
  2228.     // Do nothing
  2229. }
  2230.  
  2231. //////////////////////////////////////////////////////////////////////////////
  2232. // CRecordset internal functions
  2233.  
  2234. // Cache state information internally in CRecordset
  2235. void CRecordset::SetState(int nOpenType, LPCTSTR lpszSQL, DWORD dwOptions)
  2236. {
  2237.     if (nOpenType == AFX_DB_USE_DEFAULT_TYPE)
  2238.         m_nOpenType = m_nDefaultType;
  2239.     else
  2240.         m_nOpenType = nOpenType;
  2241.  
  2242.     m_bAppendable = (dwOptions & appendOnly) != 0 ||
  2243.         (dwOptions & readOnly) == 0;
  2244.     m_bUpdatable = (dwOptions & readOnly) == 0 &&
  2245.         (dwOptions & appendOnly) == 0;
  2246.  
  2247.     // Can turn off dirty field checking via dwOptions
  2248.     if (dwOptions & noDirtyFieldCheck || dwOptions & useMultiRowFetch)
  2249.         m_bCheckCacheForDirtyFields = FALSE;
  2250.  
  2251.     // Set recordset readOnly if forwardOnly
  2252.     if (m_nOpenType == forwardOnly && !(dwOptions & readOnly))
  2253.     {
  2254. #ifdef _DEBUG
  2255.         if (afxTraceFlags & traceDatabase)
  2256.             TRACE0("Warning: Setting forwardOnly recordset readOnly.\n");
  2257. #endif
  2258.         dwOptions |= readOnly;
  2259.  
  2260.         // If using multiRowFetch also set useExtendFetch
  2261.         if (dwOptions & useMultiRowFetch)
  2262.             dwOptions |= useExtendedFetch;
  2263.     }
  2264.  
  2265.     // Archive info for use in Requery
  2266.     m_dwOptions = dwOptions;
  2267.     m_strRequerySQL = lpszSQL;
  2268.     m_strRequeryFilter = m_strFilter;
  2269.     m_strRequerySort = m_strSort;
  2270. }
  2271.  
  2272. // Allocate the Hstmt and implicitly create and open Database if necessary
  2273. BOOL CRecordset::AllocHstmt()
  2274. {
  2275.     RETCODE nRetCode;
  2276.     if (m_hstmt == SQL_NULL_HSTMT)
  2277.     {
  2278.         CString strDefaultConnect;
  2279.         TRY
  2280.         {
  2281.             if (m_pDatabase == NULL)
  2282.             {
  2283.                 m_pDatabase = new CDatabase();
  2284.                 m_bRecordsetDb = TRUE;
  2285.             }
  2286.  
  2287.             strDefaultConnect = GetDefaultConnect();
  2288.  
  2289.             // If not already opened, attempt to open
  2290.             if (!m_pDatabase->IsOpen())
  2291.             {
  2292.                 BOOL bUseCursorLib = m_bUseODBCCursorLib;
  2293.  
  2294.                 // If non-readOnly snapshot request must use cursor lib
  2295.                 if (m_nOpenType == snapshot && !(m_dwOptions & readOnly))
  2296.                 {
  2297.                     // This assumes drivers only support readOnly snapshots
  2298.                     bUseCursorLib = TRUE;
  2299.                 }
  2300.  
  2301.                 if (!m_pDatabase->Open(_T(""), FALSE, FALSE,
  2302.                     strDefaultConnect, bUseCursorLib))
  2303.                 {
  2304.                     return FALSE;
  2305.                 }
  2306.  
  2307.                 // If snapshot cursor requested and not supported, load cursor lib
  2308.                 if (m_nOpenType == snapshot && !bUseCursorLib)
  2309.                 {
  2310.                     // Get the supported cursor types
  2311.                     RETCODE nResult;
  2312.                     UDWORD dwDriverScrollOptions;
  2313.                     AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_SCROLL_OPTIONS,
  2314.                         &dwDriverScrollOptions, sizeof(dwDriverScrollOptions), &nResult));
  2315.                     if (!Check(nRetCode))
  2316.                     {
  2317.                         TRACE0("Error: ODBC failure checking for driver capabilities.\n");
  2318.                         ThrowDBException(nRetCode);
  2319.                     }
  2320.  
  2321.                     // Check for STATIC cursor support and load cursor lib
  2322.                     if (!(dwDriverScrollOptions & SQL_SO_STATIC))
  2323.                     {
  2324.                         m_pDatabase->Close();
  2325.                         if (!m_pDatabase->Open(_T(""), FALSE, FALSE,
  2326.                             strDefaultConnect, TRUE))
  2327.                             return FALSE;
  2328.                     }
  2329.                 }
  2330.             }
  2331.  
  2332.             AFX_SQL_SYNC(::SQLAllocStmt(m_pDatabase->m_hdbc, &m_hstmt));
  2333.             if (!Check(nRetCode))
  2334.                 ThrowDBException(SQL_INVALID_HANDLE);
  2335.  
  2336.             // Add to list of CRecordsets with alloced hstmts
  2337.             AfxLockGlobals(CRIT_ODBC);
  2338.             TRY
  2339.             {
  2340.                 m_pDatabase->m_listRecordsets.AddHead(this);
  2341.             }
  2342.             CATCH_ALL(e)
  2343.             {
  2344.                 AfxUnlockGlobals(CRIT_ODBC);
  2345.                 THROW_LAST();
  2346.             }
  2347.             END_CATCH_ALL
  2348.             AfxUnlockGlobals(CRIT_ODBC);
  2349.         }
  2350.         CATCH_ALL(e)
  2351.         {
  2352. #ifdef _DEBUG
  2353.             if (afxTraceFlags & traceDatabase)
  2354.                 TRACE0("Error: CDatabase create for CRecordset failed.\n");
  2355. #endif
  2356.             NO_CPP_EXCEPTION(strDefaultConnect.Empty());
  2357.             if (m_bRecordsetDb)
  2358.             {
  2359.                 delete m_pDatabase;
  2360.                 m_pDatabase = NULL;
  2361.             }
  2362.             ASSERT(m_hstmt == SQL_NULL_HSTMT);
  2363.             THROW_LAST();
  2364.         }
  2365.         END_CATCH_ALL
  2366.     }
  2367.  
  2368.     return TRUE;
  2369. }
  2370.  
  2371. // Initialize the status arrays and create the SQL
  2372. void CRecordset::BuildSQL(LPCTSTR lpszSQL)
  2373. {
  2374.     if (lpszSQL == NULL)
  2375.         m_strSQL = GetDefaultSQL();
  2376.     else
  2377.         m_strSQL = lpszSQL;
  2378.  
  2379.     // Set any supplied params
  2380.     if (m_nParams != 0)
  2381.     {
  2382.         UINT nParams = BindParams(m_hstmt);
  2383.         ASSERT(nParams == m_nParams);
  2384.     }
  2385.  
  2386.     // Construct the SQL string
  2387.     BuildSelectSQL();
  2388.     AppendFilterAndSortSQL();
  2389.  
  2390.     // Do some extra checking if trying to set recordset updatable or appendable
  2391.     if ((m_bUpdatable || m_bAppendable) && !IsRecordsetUpdatable())
  2392.         m_bUpdatable = m_bAppendable = FALSE;
  2393.  
  2394.     if (m_bUpdatable && m_bUseUpdateSQL && m_pDatabase->m_bAddForUpdate)
  2395.         m_strSQL += _afxForUpdate;
  2396.  
  2397.     // Replace brackets with SQL_IDENTIFIER_QUOTE_CHAR
  2398.     m_pDatabase->ReplaceBrackets(m_strSQL.GetBuffer(0));
  2399.     m_strSQL.ReleaseBuffer();
  2400. }
  2401.  
  2402. // Prepare and Execute the SQL or simple call SQLExecDirect, resetting concurrency if necessary
  2403. void CRecordset::PrepareAndExecute()
  2404. {
  2405.     USES_CONVERSION;
  2406.     RETCODE nRetCode = 0;
  2407.     BOOL bConcurrency = FALSE;
  2408.     LPCSTR lpszWSQL = T2CA(m_strSQL);
  2409.  
  2410.     while (!bConcurrency)
  2411.     {
  2412.         // Prepare or execute the query
  2413.         if (m_dwOptions & executeDirect)
  2414.         {
  2415.             AFX_ODBC_CALL(::SQLExecDirect(m_hstmt,
  2416.                 (UCHAR*)lpszWSQL, SQL_NTS));
  2417.         }
  2418.         else
  2419.         {
  2420.             AFX_ODBC_CALL(::SQLPrepare(m_hstmt,
  2421.                 (UCHAR*)lpszWSQL, SQL_NTS));
  2422.         }
  2423.         if (Check(nRetCode))
  2424.             bConcurrency = TRUE;
  2425.         else
  2426.         {
  2427.             // If "Driver Not Capable" error, assume cursor type doesn't
  2428.             // support requested concurrency and try alternate concurrency.
  2429.             CDBException* e = new CDBException(nRetCode);
  2430.             e->BuildErrorString(m_pDatabase, m_hstmt);
  2431.             if (m_dwConcurrency != SQL_CONCUR_READ_ONLY &&
  2432.                 e->m_strStateNativeOrigin.Find(_afxDriverNotCapable) >= 0)
  2433.             {
  2434. #ifdef _DEBUG
  2435.                 if (afxTraceFlags & traceDatabase)
  2436.                     TRACE0("Warning: Driver does not support requested concurrency.\n");
  2437. #endif
  2438.  
  2439.                 // Don't need exception to persist while attempting to reset concurrency
  2440.                 e->Delete();
  2441.  
  2442.                 // ODBC will automatically attempt to set alternate concurrency if
  2443.                 // request fails, but it won't try LOCK even if driver supports it.
  2444.                 if ((m_dwDriverConcurrency & SQL_SCCO_LOCK) &&
  2445.                     (m_dwConcurrency == SQL_CONCUR_ROWVER ||
  2446.                     m_dwConcurrency == SQL_CONCUR_VALUES))
  2447.                 {
  2448.                     m_dwConcurrency = SQL_CONCUR_LOCK;
  2449.                 }
  2450.                 else
  2451.                 {
  2452.                     m_dwConcurrency = SQL_CONCUR_READ_ONLY;
  2453.                     m_bUpdatable = m_bAppendable = FALSE;
  2454. #ifdef _DEBUG
  2455.                     if (afxTraceFlags & traceDatabase)
  2456.                         TRACE0("Warning: Setting recordset read only.\n");
  2457. #endif
  2458.                 }
  2459.  
  2460.                 // Attempt to reset the concurrency model.
  2461.                 AFX_SQL_SYNC(::SQLSetStmtOption(m_hstmt, SQL_CONCURRENCY,
  2462.                     m_dwConcurrency));
  2463.                 if (!Check(nRetCode))
  2464.                 {
  2465.                     TRACE0("Error: ODBC failure setting recordset concurrency.\n");
  2466.                     ThrowDBException(nRetCode);
  2467.                 }
  2468.             }
  2469.             else
  2470.             {
  2471.                 TRACE0("Error: ODBC failure on SQLPrepare or SQLExecDirect\n");
  2472.                 THROW(e);
  2473.             }
  2474.         }
  2475.     }
  2476.  
  2477.  
  2478.     // now attempt to execute the SQL Query if not executed already
  2479.     if (!(m_dwOptions & executeDirect))
  2480.     {
  2481.         AFX_ODBC_CALL(::SQLExecute(m_hstmt));
  2482.         if (!Check(nRetCode))
  2483.             ThrowDBException(nRetCode);
  2484.     }
  2485.     m_lOpen = AFX_RECORDSET_STATUS_OPEN;
  2486.  
  2487.     // SQLExecute or SQLExecDirect may have changed an option value
  2488.     if (nRetCode == SQL_SUCCESS_WITH_INFO)
  2489.     {
  2490.         // Check if concurrency was changed in order to mark
  2491.         // recordset non-updatable if necessary
  2492.         DWORD dwConcurrency;
  2493.         AFX_SQL_SYNC(::SQLGetStmtOption(m_hstmt, SQL_CONCURRENCY, &dwConcurrency));
  2494.         if (!Check(nRetCode))
  2495.             ThrowDBException(nRetCode);
  2496.  
  2497.         if (dwConcurrency == SQL_CONCUR_READ_ONLY && (m_bUpdatable || m_bAppendable))
  2498.         {
  2499.             m_bUpdatable = FALSE;
  2500.             m_bAppendable = FALSE;
  2501.  
  2502. #ifdef _DEBUG
  2503.             if (afxTraceFlags & traceDatabase)
  2504.             {
  2505.                 TRACE0("Warning: Concurrency changed by driver.\n");
  2506.                 TRACE0("\tMarking CRecordset as not updatable.\n");
  2507.             }
  2508. #endif // _DEBUG
  2509.         }
  2510.     }
  2511. }
  2512.  
  2513. // Ensure that driver supports extended fetch and ODBC 2.0 if necessary
  2514. void CRecordset::VerifyDriverBehavior()
  2515. {
  2516.     RETCODE nRetCode;
  2517.     UWORD wScrollable;
  2518.     // If SQLExtendedFetch not supported, use SQLFetch
  2519.     AFX_SQL_SYNC(::SQLGetFunctions(m_pDatabase->m_hdbc,
  2520.         SQL_API_SQLEXTENDEDFETCH, &wScrollable));
  2521.     if (!Check(nRetCode))
  2522.     {
  2523.         TRACE0("Error: ODBC failure determining whether recordset is scrollable.\n");
  2524.         ThrowDBException(nRetCode);
  2525.     }
  2526.     m_bScrollable = wScrollable;
  2527.     if (!m_bScrollable)
  2528.     {
  2529. #ifdef _DEBUG
  2530.         if (afxTraceFlags & traceDatabase)
  2531.         {
  2532.             TRACE0("Warning: SQLExtendedFetch not supported by driver\n");
  2533.             TRACE0("and/or cursor library not loaded. Opening forwardOnly.\n");
  2534.             TRACE0("for use with SQLFetch.\n");
  2535.         }
  2536. #endif
  2537.         m_bUpdatable = FALSE;
  2538.         return;
  2539.     }
  2540.  
  2541.     char szResult[30];
  2542.     SWORD nResult;
  2543.     // require ODBC v2.0
  2544.     AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_ODBC_VER,
  2545.         &szResult, _countof(szResult), &nResult));
  2546.     if (!Check(nRetCode))
  2547.     {
  2548.         TRACE0("Error: ODBC failure checking for driver capabilities.\n");
  2549.         ThrowDBException(nRetCode);
  2550.     }
  2551.     if (szResult[0] == '0' && szResult[1] < '2')
  2552.         ThrowDBException(AFX_SQL_ERROR_ODBC_V2_REQUIRED);
  2553. }
  2554.  
  2555. // Check that driver supports requested cursor type
  2556. DWORD CRecordset::VerifyCursorSupport()
  2557. {
  2558.     RETCODE nRetCode;
  2559.     SWORD nResult;
  2560.     UDWORD dwDriverScrollOptions;
  2561.     AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_SCROLL_OPTIONS,
  2562.         &dwDriverScrollOptions, sizeof(dwDriverScrollOptions), &nResult));
  2563.     if (!Check(nRetCode))
  2564.     {
  2565.         TRACE0("Error: ODBC failure checking for driver capabilities.\n");
  2566.         ThrowDBException(nRetCode);
  2567.     }
  2568.  
  2569.     SDWORD dwScrollOptions = SQL_CURSOR_FORWARD_ONLY;
  2570.     if (m_nOpenType == dynaset)
  2571.     {
  2572.         // Dynaset support requires ODBC's keyset driven cursor model
  2573.         if (!(dwDriverScrollOptions & SQL_SO_KEYSET_DRIVEN))
  2574.             ThrowDBException(AFX_SQL_ERROR_DYNASET_NOT_SUPPORTED);
  2575.         dwScrollOptions = SQL_CURSOR_KEYSET_DRIVEN;
  2576.     }
  2577.     else if (m_nOpenType == snapshot)
  2578.     {
  2579.         // Snapshot support requires ODBC's static cursor model
  2580.         if (!(dwDriverScrollOptions & SQL_SO_STATIC))
  2581.             ThrowDBException(AFX_SQL_ERROR_SNAPSHOT_NOT_SUPPORTED);
  2582.         dwScrollOptions = SQL_CURSOR_STATIC;
  2583.     }
  2584.     else
  2585.     {
  2586.         // Dynamic cursor support requires ODBC's dynamic cursor model
  2587.         if (!(dwDriverScrollOptions & SQL_SO_DYNAMIC))
  2588.             ThrowDBException(AFX_SQL_ERROR_DYNAMIC_CURSOR_NOT_SUPPORTED);
  2589.         dwScrollOptions = SQL_CURSOR_DYNAMIC;
  2590.     }
  2591.  
  2592.     return dwScrollOptions;
  2593. }
  2594.  
  2595. void CRecordset::AllocAndCacheFieldInfo()
  2596. {
  2597.     ASSERT(GetODBCFieldCount() < 0);
  2598.     ASSERT(m_rgODBCFieldInfos == NULL);
  2599.  
  2600.     RETCODE nRetCode;
  2601.     SWORD nActualLen;
  2602.  
  2603.     // Cache the number of result columns
  2604.     AFX_ODBC_CALL(::SQLNumResultCols(m_hstmt, &m_nResultCols));
  2605.     if (!Check(nRetCode))
  2606.     {
  2607.         TRACE0("Error: Can't get field info.\n");
  2608.         ThrowDBException(nRetCode);
  2609.     }
  2610.  
  2611.     // If there are no fields quit now
  2612.     if (m_nResultCols == 0)
  2613.         return;
  2614.  
  2615.     // Allocate buffer and get the ODBC meta data
  2616.     m_rgODBCFieldInfos = new CODBCFieldInfo[m_nResultCols];
  2617.     LPSTR lpszFieldName;
  2618.  
  2619. #ifdef _UNICODE
  2620.     // Need proxy to temporarily store non-UNICODE name
  2621.     lpszFieldName = new char[MAX_FNAME_LEN + 1];
  2622. #endif
  2623.  
  2624.     // Get the field info each field
  2625.     for (WORD n = 1; n <= GetODBCFieldCount(); n++)
  2626.     {
  2627. #ifndef _UNICODE
  2628.         // Reset the buffer to point to next element
  2629.         lpszFieldName =
  2630.             m_rgODBCFieldInfos[n - 1].m_strName.GetBuffer(MAX_FNAME_LEN + 1);
  2631. #endif
  2632.  
  2633.         AFX_ODBC_CALL(::SQLDescribeCol(m_hstmt, n,
  2634.             (UCHAR*)lpszFieldName, MAX_FNAME_LEN, &nActualLen,
  2635.             &m_rgODBCFieldInfos[n - 1].m_nSQLType,
  2636.             &m_rgODBCFieldInfos[n - 1].m_nPrecision,
  2637.             &m_rgODBCFieldInfos[n - 1].m_nScale,
  2638.             &m_rgODBCFieldInfos[n - 1].m_nNullability));
  2639.  
  2640. #ifndef _UNICODE
  2641.         m_rgODBCFieldInfos[n - 1].m_strName.ReleaseBuffer(nActualLen);
  2642. #else
  2643.         // Copy the proxy data to correct element
  2644.         m_rgODBCFieldInfos[n - 1].m_strName = lpszFieldName;
  2645. #endif
  2646.  
  2647.         if (!Check(nRetCode))
  2648.         {
  2649.             TRACE1("Error: ODBC failure getting field #%d info.\n", n);
  2650.             ThrowDBException(nRetCode);
  2651.         }
  2652.     }
  2653.  
  2654. #ifdef _UNICODE
  2655.     delete[] lpszFieldName;
  2656. #endif
  2657. }
  2658.  
  2659. void CRecordset::AllocRowset()
  2660. {
  2661.     if (m_dwOptions & useMultiRowFetch)
  2662.         SetRowsetSize(m_dwRowsetSize);
  2663.     else
  2664.     {
  2665.         // Not using bulk row fetch, set rowset size to 1
  2666.         m_rgRowStatus = new WORD[1];
  2667.         m_dwRowsetSize = 1;
  2668.     }
  2669. }
  2670.  
  2671. void CRecordset::FreeRowset()
  2672. {
  2673.     // Delete the rowset status
  2674.     delete [] m_rgRowStatus;
  2675.     m_rgRowStatus = NULL;
  2676.  
  2677.     if (m_dwOptions & useMultiRowFetch &&
  2678.         !(m_dwOptions & userAllocMultiRowBuffers))
  2679.     {
  2680.         // Calling virtual function, DoBulkFieldExchange, here is bad
  2681.         // because Close then FreeRowset may get called from destructor.
  2682.         // There is no simple choice however if RFX_Bulk functions do
  2683.         // a memory allocation. The net result is that users MUST call
  2684.         // Close explicitly (rather than relying on destructor) if
  2685.         // using multi row fetches, otherwise they will get a memory leak.
  2686.         // If rowset already allocated, delete old rowset buffers
  2687.         if (m_dwAllocatedRowsetSize != 0)
  2688.         {
  2689.             CFieldExchange fx(CFieldExchange::DeleteMultiRowBuffer, this);
  2690.             DoBulkFieldExchange(&fx);
  2691.         }
  2692.     }
  2693.  
  2694.     m_dwAllocatedRowsetSize = 0;
  2695. }
  2696.  
  2697. void CRecordset::EnableBookmarks()
  2698. {
  2699.     // Turn on bookmark support if necessary
  2700.     if (m_dwOptions & useBookmarks)
  2701.     {
  2702.         RETCODE nRetCode;
  2703.  
  2704.         // Set stmt option if bookmarks supported by driver
  2705.         if (m_pDatabase->GetBookmarkPersistence() & SQL_BP_SCROLL)
  2706.         {
  2707.             AFX_SQL_SYNC(::SQLSetStmtOption(m_hstmt, SQL_USE_BOOKMARKS,
  2708.                 SQL_UB_ON));
  2709.             if (!Check(nRetCode))
  2710.             {
  2711.                 TRACE0("Error: Can't enable bookmark support.\n");
  2712.                 ThrowDBException(nRetCode);
  2713.             }
  2714.         }
  2715.     }
  2716. }
  2717.  
  2718. // Determine whether to use SQLSetPos or positioned update SQL
  2719. void CRecordset::SetUpdateMethod()
  2720. {
  2721.     // Determine update method
  2722.     if (m_pDatabase->m_dwUpdateOptions & AFX_SQL_SETPOSUPDATES)
  2723.         m_bUseUpdateSQL = FALSE;
  2724.     else if (m_pDatabase->m_dwUpdateOptions & AFX_SQL_POSITIONEDSQL)
  2725.         m_bUseUpdateSQL = TRUE;
  2726.     else
  2727.         m_bUpdatable = FALSE;
  2728.  }
  2729.  
  2730. // Determine which type of concurrency to set, set it and cursor type
  2731. void CRecordset::SetConcurrencyAndCursorType(HSTMT hstmt, DWORD dwScrollOptions)
  2732. {
  2733.     RETCODE nRetCode;
  2734.     SWORD nResult;
  2735.  
  2736.     m_dwConcurrency = SQL_CONCUR_READ_ONLY;
  2737.     if ((m_bUpdatable || m_bAppendable) && m_pDatabase->m_bUpdatable)
  2738.     {
  2739.         AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_SCROLL_CONCURRENCY,
  2740.             &m_dwDriverConcurrency, sizeof(m_dwDriverConcurrency), &nResult));
  2741.         if (!Check(nRetCode))
  2742.         {
  2743.             TRACE0("Error: ODBC failure checking recordset updatability.\n");
  2744.             ThrowDBException(nRetCode);
  2745.         }
  2746.  
  2747.         if (m_nLockMode == pessimistic)
  2748.         {
  2749.             if (m_dwDriverConcurrency & SQL_SCCO_LOCK)
  2750.                 m_dwConcurrency = SQL_CONCUR_LOCK;
  2751. #ifdef _DEBUG
  2752.             else
  2753.                 if (afxTraceFlags & traceDatabase)
  2754.                     TRACE0("Warning: locking not supported, setting recordset read only.\n");
  2755. #endif
  2756.         }
  2757.         else
  2758.         {
  2759.             // Use cheapest, most concurrent model
  2760.             if (m_dwDriverConcurrency & SQL_SCCO_OPT_ROWVER)
  2761.                 m_dwConcurrency = SQL_CONCUR_ROWVER;
  2762.             else if (m_dwDriverConcurrency & SQL_SCCO_OPT_VALUES)
  2763.                 m_dwConcurrency = SQL_CONCUR_VALUES;
  2764.             else if (m_dwDriverConcurrency & SQL_SCCO_LOCK)
  2765.                 m_dwConcurrency = SQL_CONCUR_LOCK;
  2766.         }
  2767.     }
  2768.  
  2769.     // Set cursor type (Let rowset size default to 1).
  2770.     AFX_SQL_SYNC(::SQLSetStmtOption(hstmt, SQL_CURSOR_TYPE, dwScrollOptions));
  2771.     if (!Check(nRetCode))
  2772.     {
  2773.         TRACE0("Error: ODBC failure setting recordset cursor type.\n");
  2774.         ThrowDBException(nRetCode);
  2775.     }
  2776.  
  2777.     // Set the concurrency model (NOTE: may have to reset concurrency later).
  2778.     AFX_SQL_SYNC(::SQLSetStmtOption(hstmt, SQL_CONCURRENCY, m_dwConcurrency));
  2779.     if (!Check(nRetCode))
  2780.     {
  2781.         TRACE0("Error: ODBC failure setting recordset concurrency.\n");
  2782.         ThrowDBException(nRetCode);
  2783.     }
  2784. }
  2785.  
  2786. // Is there a join, stored proc call, GROUP BY, UNION or missing FROM?
  2787. BOOL CRecordset::IsSQLUpdatable(LPCTSTR lpszSQL)
  2788. {
  2789.     // Parse for query procedure call keyword or return param
  2790.     if (!(_tcsnicmp(lpszSQL, _afxCall, lstrlen(_afxCall)-1) == 0 ||
  2791.         _tcsnicmp(lpszSQL, _afxParamCall, lstrlen(_afxParamCall)-1) == 0))
  2792.         // Assume this is a select query
  2793.         return IsSelectQueryUpdatable(lpszSQL);
  2794.     else
  2795.         // Don't know the table name to update in procedure call
  2796.         return FALSE;
  2797. }
  2798.  
  2799. BOOL CRecordset::IsSelectQueryUpdatable(LPCTSTR lpszSQL)
  2800. {
  2801.     LPCTSTR lpchTokenFrom;
  2802.     LPCTSTR lpchToken;
  2803.     LPCTSTR lpchTokenNext;
  2804.     LPTSTR lpszSQLStart;
  2805.     CString strSQL = lpszSQL;
  2806.  
  2807.     lpchTokenFrom = FindSQLToken(strSQL, _afxFrom);
  2808.     if (lpchTokenFrom == NULL)
  2809.     {
  2810. #ifdef _DEBUG
  2811.         if (afxTraceFlags & traceDatabase)
  2812.             TRACE0("Warning: Missing ' FROM ', recordset not updatable \n");
  2813. #endif
  2814.         return FALSE;
  2815.     }
  2816.  
  2817.     lpchToken = FindSQLToken(strSQL, _T(" GROUP BY "));
  2818.     if (lpchToken != NULL)
  2819.     {
  2820. #ifdef _DEBUG
  2821.         if (afxTraceFlags & traceDatabase)
  2822.             TRACE0("Warning: SQL contains ' GROUP BY ', recordset not updatable \n");
  2823. #endif
  2824.         return FALSE;
  2825.     }
  2826.  
  2827.     lpchToken = FindSQLToken(strSQL, _T(" UNION "));
  2828.     if (lpchToken != NULL)
  2829.     {
  2830. #ifdef _DEBUG
  2831.         if (afxTraceFlags & traceDatabase)
  2832.             TRACE0("Warning: SQL contains ' UNION ', recordset not updatable \n");
  2833. #endif
  2834.         return FALSE;
  2835.     }
  2836.  
  2837.     // Find next token after FROM (can't have HAVING clause without GROUP BY)
  2838.     lpchToken = FindSQLToken(strSQL, _afxWhere);
  2839.     lpchTokenNext = FindSQLToken(strSQL, _afxOrderBy);
  2840.  
  2841.     lpszSQLStart = strSQL.GetBuffer(0);
  2842.  
  2843.     if (lpchTokenNext == NULL)
  2844.         lpchTokenNext = lpchToken;
  2845.     else if (lpchToken != NULL && lpchToken < lpchTokenNext)
  2846.         lpchTokenNext = lpchToken;
  2847.  
  2848.     if (lpchTokenNext != NULL)
  2849.     {
  2850.         int nFromLength = lpchTokenNext - lpchTokenFrom;
  2851.         memcpy(lpszSQLStart, lpchTokenFrom, nFromLength*sizeof(TCHAR));
  2852.         lpszSQLStart[nFromLength] = '\0';
  2853.     }
  2854.     else
  2855.         lstrcpy(lpszSQLStart, lpchTokenFrom);
  2856.  
  2857.     strSQL.ReleaseBuffer();
  2858.  
  2859.     if (IsJoin(strSQL))
  2860.     {
  2861. #ifdef _DEBUG
  2862.         if (afxTraceFlags & traceDatabase)
  2863.             TRACE0("Warning: SQL contains join, recordset not updatable \n");
  2864. #endif
  2865.         return FALSE;
  2866.     }
  2867.  
  2868.     // Cache table name (skip over " FROM ")
  2869.     m_strTableName = strSQL.Right(strSQL.GetLength()-6);
  2870.  
  2871.     return TRUE;
  2872. }
  2873.  
  2874.  
  2875. // Check FROM clause for join syntax
  2876. BOOL PASCAL CRecordset::IsJoin(LPCTSTR lpszJoinClause)
  2877. {
  2878.     // Look for comma in join clause
  2879.     if (FindSQLToken(lpszJoinClause, _afxComma) != NULL)
  2880.         return TRUE;
  2881.  
  2882.     // Look for outer join clause
  2883.     if (FindSQLToken(lpszJoinClause, _T(" JOIN ")) != NULL)
  2884.         return TRUE;
  2885.  
  2886.     return FALSE;
  2887. }
  2888.  
  2889. // Searches string for given token not in single quotes or brackets
  2890. LPCTSTR PASCAL CRecordset::FindSQLToken(LPCTSTR lpszSQL, LPCTSTR lpszSQLToken)
  2891. {
  2892.     BOOL bInLiteral;
  2893.     BOOL bInBrackets;
  2894.     int nLeftBrackets;
  2895.     int nRightBrackets;
  2896.     LPCTSTR lpch;
  2897.     LPCTSTR lpchSQLStart;
  2898.     LPCTSTR lpszFoundToken;
  2899.     int nTokenOffset = 0;
  2900.     CString strSQL = lpszSQL;
  2901.  
  2902.     strSQL.MakeUpper();
  2903.     lpszFoundToken = strSQL.GetBuffer(0);
  2904.     lpchSQLStart = lpszFoundToken;
  2905.  
  2906.     do
  2907.     {
  2908.         lpszFoundToken = _tcsstr(lpszFoundToken + nTokenOffset, lpszSQLToken);
  2909.         if (lpszFoundToken == NULL)
  2910.         {
  2911.             strSQL.ReleaseBuffer();
  2912.             return NULL;
  2913.         }
  2914.  
  2915.         bInLiteral = bInBrackets = FALSE;
  2916.         nLeftBrackets = nRightBrackets = 0;
  2917.  
  2918.         // Check if embedded in literal or brackets
  2919.         for (lpch = lpchSQLStart; lpch < lpszFoundToken; lpch = _tcsinc(lpch))
  2920.         {
  2921.             if (*lpch == _afxLiteralSeparator)
  2922.             {
  2923.                 // Skip if escape literal
  2924.                 if (*_tcsinc(lpch) == _afxLiteralSeparator)
  2925.                     lpch = _tcsinc(lpch);
  2926.                 else
  2927.                     bInLiteral = !bInLiteral;
  2928.             }
  2929.             else if (!bInLiteral && (*lpch == '['))
  2930.             {
  2931.                 // Skip if escape left bracket
  2932.                 if (*_tcsinc(lpch) == '[')
  2933.                     lpch = _tcsinc(lpch);
  2934.                 else
  2935.                 {
  2936.                     nLeftBrackets++;
  2937.                     if ((nLeftBrackets - nRightBrackets) > 0)
  2938.                         bInBrackets = TRUE;
  2939.                     else
  2940.                         bInBrackets = FALSE;
  2941.                 }
  2942.             }
  2943.             else if (!bInLiteral && (*lpch == ']'))
  2944.             {
  2945.                 // Skip if escape right bracket
  2946.                 if (*_tcsinc(lpch) == ']')
  2947.                     lpch = _tcsinc(lpch);
  2948.                 else
  2949.                 {
  2950.                     nRightBrackets++;
  2951.                     if ((nLeftBrackets - nRightBrackets) > 0)
  2952.                         bInBrackets = TRUE;
  2953.                     else
  2954.                         bInBrackets = FALSE;
  2955.                 }
  2956.             }
  2957.         }
  2958.  
  2959.         // If first iteration, reset the offset to jump over found token
  2960.         if (nTokenOffset == 0)
  2961.             nTokenOffset = lstrlen(lpszSQLToken);
  2962.  
  2963.     } while (bInLiteral || bInBrackets);
  2964.  
  2965.     lpszFoundToken = lpszSQL + (lpszFoundToken - lpchSQLStart);
  2966.     strSQL.ReleaseBuffer();
  2967.     return lpszFoundToken;
  2968. }
  2969.  
  2970. // Bind fields (if not already bound), then retrieve 1st record
  2971. void CRecordset::InitRecord()
  2972. {
  2973.     // fields to bind
  2974.     if (m_nFields != 0)
  2975.     {
  2976.         m_nFieldsBound = BindFieldsToColumns();
  2977.         // m_nFields doesn't reflect number of
  2978.         // RFX_ output column calls in Do[Bulk]FieldExchange
  2979.         ASSERT((int)m_nFields == m_nFieldsBound);
  2980.  
  2981.         // Allocate the data cache if necessary
  2982.         if (m_nFields > 0 && m_bCheckCacheForDirtyFields)
  2983.             AllocDataCache();
  2984.     }
  2985.     else
  2986.         // No fields to bind, don't attempt to bind again
  2987.         m_nFieldsBound = -1;
  2988. }
  2989.  
  2990. void CRecordset::ResetCursor()
  2991. {
  2992.     m_bEOFSeen = m_bBOF = m_bEOF = FALSE;
  2993.     m_bDeleted = FALSE;
  2994.     m_lCurrentRecord = AFX_CURRENT_RECORD_BOF;
  2995.     m_lRecordCount = 0;
  2996. }
  2997.  
  2998. void CRecordset::CheckRowsetCurrencyStatus(UWORD wFetchType, long nRows)
  2999. {
  3000.     if (!m_bScrollable && wFetchType != SQL_FETCH_NEXT)
  3001.     {
  3002.         TRACE0("Error: forward-only recordsets only support MoveNext.\n");
  3003.         ThrowDBException(AFX_SQL_ERROR_RECORDSET_FORWARD_ONLY);
  3004.     }
  3005.  
  3006.     if (IsEOF() && IsBOF())
  3007.     {
  3008.         // Can't position cursor if recordset empty
  3009.         TRACE0("Error: attempted to position cursor on empty recordset.\n");
  3010.         ThrowDBException(AFX_SQL_ERROR_NO_DATA_FOUND);
  3011.     }
  3012.  
  3013.     if (m_nOpenType != dynamic)
  3014.     {
  3015.         if (IsEOF() && (wFetchType == SQL_FETCH_NEXT ||
  3016.         (wFetchType == SQL_FETCH_RELATIVE && nRows > 0)))
  3017.         {
  3018.             // if already at EOF, throw an exception
  3019.             TRACE0("Error: attempted to move past EOF.\n");
  3020.             ThrowDBException(AFX_SQL_ERROR_NO_DATA_FOUND);
  3021.         }
  3022.         else if (IsBOF() && (wFetchType == SQL_FETCH_PRIOR ||
  3023.         (wFetchType == SQL_FETCH_RELATIVE && nRows < 0)))
  3024.         {
  3025.             // if already at BOF, throw an exception
  3026.             TRACE0("Error: attempted to move before BOF.\n");
  3027.             ThrowDBException(AFX_SQL_ERROR_NO_DATA_FOUND);
  3028.         }
  3029.     }
  3030. }
  3031.  
  3032. RETCODE CRecordset::FetchData(UWORD wFetchType, SDWORD nRow,
  3033.     DWORD* pdwRowsFetched)
  3034. {
  3035.     RETCODE nRetCode;
  3036.  
  3037.     if (m_nOpenType == forwardOnly && !(m_dwOptions & useExtendedFetch))
  3038.     {
  3039.         ASSERT(wFetchType == SQL_FETCH_NEXT);
  3040.  
  3041.         AFX_ODBC_CALL(::SQLFetch(m_hstmt));
  3042.         *pdwRowsFetched = 1;
  3043.  
  3044.         m_bDeleted = FALSE;
  3045.     }
  3046.     else
  3047.     {
  3048.         AFX_ODBC_CALL(::SQLExtendedFetch(m_hstmt, wFetchType,
  3049.             nRow, pdwRowsFetched, m_rgRowStatus));
  3050.  
  3051.         // Set deleted status
  3052.         m_bDeleted = GetRowStatus(1) == SQL_ROW_DELETED;
  3053.     }
  3054.  
  3055.     CheckRowsetError(nRetCode);
  3056.  
  3057.     return nRetCode;
  3058. }
  3059.  
  3060. void CRecordset::SkipDeletedRecords(UWORD wFetchType, long nRows,
  3061.     DWORD* pdwRowsFetched, RETCODE* pnRetCode)
  3062. {
  3063.     ASSERT(!(m_dwOptions & useMultiRowFetch));
  3064.     ASSERT(wFetchType == SQL_FETCH_RELATIVE ||
  3065.         wFetchType == SQL_FETCH_FIRST ||
  3066.         wFetchType == SQL_FETCH_NEXT ||
  3067.         wFetchType == SQL_FETCH_LAST ||
  3068.         wFetchType == SQL_FETCH_PRIOR);
  3069.     ASSERT(nRows != 0);
  3070.  
  3071.     UWORD wDeletedFetchType = wFetchType;
  3072.     DWORD dwDeletedRows = abs(nRows);
  3073.     BOOL m_bDone;
  3074.  
  3075.     switch (wFetchType)
  3076.     {
  3077.     case SQL_FETCH_FIRST:
  3078.         wDeletedFetchType = SQL_FETCH_NEXT;
  3079.         break;
  3080.  
  3081.     case SQL_FETCH_LAST:
  3082.         wDeletedFetchType = SQL_FETCH_PRIOR;
  3083.         break;
  3084.  
  3085.     case SQL_FETCH_RELATIVE:
  3086.         if (nRows > 0)
  3087.             wDeletedFetchType = SQL_FETCH_NEXT;
  3088.         else
  3089.             wDeletedFetchType = SQL_FETCH_PRIOR;
  3090.         break;
  3091.     }
  3092.  
  3093.     // First fetch is as expected unless relative fetch
  3094.     if (wFetchType != SQL_FETCH_RELATIVE)
  3095.     {
  3096.         *pnRetCode = FetchData(wFetchType, 1, pdwRowsFetched);
  3097.         m_bDone = !m_bDeleted;
  3098.     }
  3099.     else
  3100.     {
  3101.         // Since deleted records must be skipped Move(n)
  3102.         // must be turned into n MoveNext/MovePrev calls
  3103.         *pnRetCode = FetchData(wDeletedFetchType, 1, pdwRowsFetched);
  3104.         if (!m_bDeleted)
  3105.         {
  3106.             dwDeletedRows--;
  3107.             m_bDone = dwDeletedRows == 0;
  3108.         }
  3109.         else
  3110.             m_bDone = FALSE;
  3111.     }
  3112.  
  3113.     // Continue fetching until all req'd deleted records skipped
  3114.     while (*pnRetCode != SQL_NO_DATA_FOUND && !m_bDone)
  3115.     {
  3116.         *pnRetCode = FetchData(wDeletedFetchType, 1, pdwRowsFetched);
  3117.  
  3118.         if (wFetchType == SQL_FETCH_RELATIVE)
  3119.         {
  3120.             if (!m_bDeleted)
  3121.             {
  3122.                 dwDeletedRows--;
  3123.                 m_bDone = dwDeletedRows == 0;
  3124.             }
  3125.             else
  3126.                 m_bDone = FALSE;
  3127.         }
  3128.         else
  3129.             m_bDone = !m_bDeleted;
  3130.     }
  3131. }
  3132.  
  3133. void CRecordset::SetRowsetCurrencyStatus(RETCODE nRetCode,
  3134.     UWORD wFetchType, long nRows, DWORD dwRowsFetched)
  3135. {
  3136.     UNUSED_ALWAYS(dwRowsFetched);
  3137.  
  3138.     // Set the fetch direction
  3139.     int nDirection = 0;
  3140.  
  3141.     switch (wFetchType)
  3142.     {
  3143.     case SQL_FETCH_FIRST:
  3144.         nDirection = 1;
  3145.         if (nRetCode == SQL_NO_DATA_FOUND)
  3146.         {
  3147.             m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
  3148.             m_lRecordCount = 0;
  3149.         }
  3150.         else
  3151.             m_lCurrentRecord = 0;
  3152.         break;
  3153.  
  3154.     case SQL_FETCH_NEXT:
  3155.         nDirection = 1;
  3156.         AfxSetCurrentRecord(&m_lCurrentRecord, nRows, nRetCode);
  3157.         AfxSetRecordCount(&m_lRecordCount, m_lCurrentRecord,
  3158.             m_bEOFSeen, nRetCode);
  3159.  
  3160.         // This is the only way to know you've hit the end (m_bEOFSeen)
  3161.         if (!m_bEOFSeen && nRetCode == SQL_NO_DATA_FOUND &&
  3162.             m_lRecordCount == m_lCurrentRecord + 1)
  3163.         {
  3164.             m_bEOFSeen = TRUE;
  3165.         }
  3166.         break;
  3167.  
  3168.     case SQL_FETCH_LAST:
  3169.         nDirection = -1;
  3170.         if (nRetCode == SQL_NO_DATA_FOUND)
  3171.         {
  3172.             m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
  3173.             m_lRecordCount = 0;
  3174.         }
  3175.         else if (m_bEOFSeen)
  3176.             m_lCurrentRecord = m_lRecordCount - 1;
  3177.         else
  3178.             m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
  3179.         break;
  3180.  
  3181.     case SQL_FETCH_PRIOR:
  3182.         nDirection = -1;
  3183.         // If doing MovePrev after m_bEOF, don't increment current rec
  3184.         if (!m_bEOF)
  3185.             AfxSetCurrentRecord(&m_lCurrentRecord, nRows, nRetCode);
  3186.         break;
  3187.  
  3188.     case SQL_FETCH_RELATIVE:
  3189.         nDirection = nRows;
  3190.         AfxSetCurrentRecord(&m_lCurrentRecord, nRows, nRetCode);
  3191.         AfxSetRecordCount(&m_lRecordCount, m_lCurrentRecord,
  3192.             m_bEOFSeen, nRetCode);
  3193.         break;
  3194.  
  3195.     case SQL_FETCH_ABSOLUTE:
  3196.         nDirection = nRows;
  3197.         if (nRetCode != SQL_NO_DATA_FOUND)
  3198.         {
  3199.             if (nRows > 0)
  3200.                 m_lCurrentRecord = nRows - 1;
  3201.             else if (m_bEOFSeen)
  3202.                 m_lCurrentRecord = m_lRecordCount + nRows;
  3203.             else
  3204.                 m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
  3205.         }
  3206.         else
  3207.             m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
  3208.  
  3209.         AfxSetRecordCount(&m_lRecordCount, m_lCurrentRecord,
  3210.             m_bEOFSeen, nRetCode);
  3211.         break;
  3212.  
  3213.     case SQL_FETCH_BOOKMARK:
  3214.         nDirection = 0;
  3215.         m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
  3216.         break;
  3217.     }
  3218.  
  3219.     // Set the BOF/EOF flags
  3220.     if (nRetCode == SQL_NO_DATA_FOUND)
  3221.     {
  3222.         if (wFetchType == SQL_FETCH_FIRST || wFetchType == SQL_FETCH_LAST ||
  3223.             wFetchType == SQL_FETCH_BOOKMARK)
  3224.         {
  3225.             // If MoveFirst/MoveLast fails, result set is empty
  3226.             // If SetBookmark fails, currency undefined
  3227.             m_bEOF = m_bBOF = TRUE;
  3228.         }
  3229.         else
  3230.         {
  3231.             m_bEOF = nDirection >= 0;
  3232.             m_bBOF = !m_bEOF;
  3233.         }
  3234.     }
  3235.     else
  3236.     {
  3237.         m_bEOF = m_bBOF = FALSE;
  3238.     }
  3239. }
  3240.  
  3241. void CRecordset::RefreshRowset(WORD wRow, WORD wLockType)
  3242. {
  3243.     ASSERT(IsOpen());
  3244.     ASSERT(m_dwOptions & useMultiRowFetch);
  3245.  
  3246.     RETCODE nRetCode;
  3247.  
  3248.     AFX_ODBC_CALL(::SQLSetPos(m_hstmt, wRow, SQL_REFRESH, wLockType));
  3249.  
  3250.     // Need to fixup bound fields in some cases
  3251.     if (m_nFields > 0 && !IsEOF() && !IsBOF() &&
  3252.         !(m_dwOptions & useMultiRowFetch))
  3253.     {
  3254.         Fixups();
  3255.     }
  3256. }
  3257.  
  3258. void CRecordset::SetRowsetCursorPosition(WORD wRow, WORD wLockType)
  3259. {
  3260.     ASSERT(IsOpen());
  3261.     ASSERT(m_dwOptions & useMultiRowFetch);
  3262.  
  3263.     RETCODE nRetCode;
  3264.  
  3265.     AFX_ODBC_CALL(::SQLSetPos(m_hstmt, wRow, SQL_POSITION, wLockType));
  3266. }
  3267.  
  3268. // "SELECT <user column name list> FROM <table name>"
  3269. void CRecordset::BuildSelectSQL()
  3270. {
  3271.     ASSERT_VALID(this);
  3272.     ASSERT(m_hstmt != SQL_NULL_HSTMT);
  3273.  
  3274.     // Ignore queries with procedure call keyword or output param
  3275.     if (!(_tcsnicmp(m_strSQL, _afxCall, lstrlen(_afxCall)-1) == 0 ||
  3276.         _tcsnicmp(m_strSQL, _afxParamCall, lstrlen(_afxParamCall)-1) == 0))
  3277.     {
  3278.         // Ignore queries already built
  3279.         if (_tcsnicmp(m_strSQL, _afxSelect, lstrlen(_afxSelect)-1) != 0)
  3280.         {
  3281.             // Assume m_strSQL specifies table name
  3282.             ASSERT(m_nFields != 0);
  3283.  
  3284.             CString strTableName;
  3285.             strTableName = m_strSQL;
  3286.             m_strSQL.Empty();
  3287.             m_strSQL = _afxSelect;
  3288.  
  3289.             // Set all fields dirty. AppendNames only outputs dirty field names
  3290.             SetFieldDirty(NULL);
  3291.             if (AppendNames(&m_strSQL, _T(",")) == 0)
  3292.             {
  3293.                 TRACE0("Error: no field names - at least 1 required.\n");
  3294.                 ThrowDBException(AFX_SQL_ERROR_EMPTY_COLUMN_LIST);
  3295.             }
  3296.  
  3297.             // Overwrite final ',' separator with ' '
  3298.             ASSERT(m_strSQL[m_strSQL.GetLength()-1] == ',');
  3299.             m_strSQL.SetAt(m_strSQL.GetLength()-1, ' ');
  3300.  
  3301.             m_strSQL += _afxFrom;
  3302.             m_strSQL += strTableName;
  3303.         }
  3304.     }
  3305. }
  3306.  
  3307. // Add the filter and sort strings to query SQL
  3308. void CRecordset::AppendFilterAndSortSQL()
  3309. {
  3310.     if (!m_strFilter.IsEmpty())
  3311.     {
  3312.         m_strSQL += _afxWhere;
  3313.         m_strSQL += m_strFilter;
  3314.     }
  3315.  
  3316.     if (!m_strSort.IsEmpty())
  3317.     {
  3318.         m_strSQL += _afxOrderBy;
  3319.         m_strSQL += m_strSort;
  3320.     }
  3321. }
  3322.  
  3323. // Check for required SQLGetData support and do limited SQL parsing
  3324. BOOL CRecordset::IsRecordsetUpdatable()
  3325. {
  3326.     // Do limited SQL parsing to determine if SQL updatable
  3327.     if (!IsSQLUpdatable(m_strSQL))
  3328.         return FALSE;
  3329.  
  3330.     // Updatable recordsets with long binary columns must support
  3331.     // SQL_GD_BOUND to use SQLSetPos, otherwise must use SQL updates
  3332.     BOOL bUpdatable = TRUE;
  3333.     if (m_bLongBinaryColumns && !m_bUseUpdateSQL)
  3334.     {
  3335.         // Set non-updatable if you can't use SQLGetData on bound columns
  3336.         if (!(m_pDatabase->m_dwUpdateOptions & AFX_SQL_GDBOUND))
  3337.         {
  3338.             // Okay can't use SetPos, try and use positioned update SQL
  3339.             if (m_pDatabase->m_dwUpdateOptions & AFX_SQL_POSITIONEDSQL)
  3340.             {
  3341.                 m_bUseUpdateSQL = TRUE;
  3342. #ifdef _DEBUG
  3343.                 if (afxTraceFlags & traceDatabase)
  3344.                 {
  3345.                     TRACE0("Warning: Can't use SQLSetPos due to lack of SQLGetData support.\n");
  3346.                     TRACE0("\tWill use positioned update SQL.\n");
  3347.                 }
  3348. #endif
  3349.             }
  3350.             else
  3351.             {
  3352. #ifdef _DEBUG
  3353.                 if (afxTraceFlags & traceDatabase)
  3354.                     TRACE0("Warning: Setting recordset read only due to lack of SQLGetData support.\n");
  3355. #endif
  3356.                 bUpdatable = FALSE;
  3357.             }
  3358.         }
  3359.     }
  3360.  
  3361.     return bUpdatable;
  3362. }
  3363.  
  3364. // Execute the update (or delete) using SQLSetPos
  3365. void CRecordset::ExecuteSetPosUpdate()
  3366. {
  3367.     UWORD wExpectedRowStatus;
  3368.     UWORD wPosOption;
  3369.     if (m_nEditMode == noMode)
  3370.     {
  3371.         wPosOption = SQL_DELETE;
  3372.         wExpectedRowStatus = SQL_ROW_DELETED;
  3373.     }
  3374.     else
  3375.     {
  3376.         if (m_nEditMode == edit)
  3377.         {
  3378.             wPosOption = SQL_UPDATE;
  3379.             wExpectedRowStatus = SQL_ROW_UPDATED;
  3380.         }
  3381.         else
  3382.         {
  3383.             wPosOption = SQL_ADD;
  3384.             wExpectedRowStatus = SQL_ROW_ADDED;
  3385.         }
  3386.     }
  3387.  
  3388.     BindFieldsForUpdate();
  3389.  
  3390.     RETCODE nRetCode;
  3391.     AFX_ODBC_CALL(::SQLSetPos(m_hstmt, 1, wPosOption, SQL_LOCK_NO_CHANGE));
  3392.     if (!Check(nRetCode))
  3393.     {
  3394.         TRACE0("Error: failure updating record.\n");
  3395.         AfxThrowDBException(nRetCode, m_pDatabase, m_hstmt);
  3396.     }
  3397.     // Only have data-at-execution columns for CLongBinary columns
  3398.     if (nRetCode == SQL_NEED_DATA)
  3399.         SendLongBinaryData(m_hstmt);
  3400.     // This should only fail if SQLSetPos returned SQL_SUCCESS_WITH_INFO explaining why
  3401.     if (nRetCode == SQL_SUCCESS_WITH_INFO && GetRowStatus(1) != wExpectedRowStatus)
  3402.         ThrowDBException(AFX_SQL_ERROR_UPDATE_DELETE_FAILED);
  3403.  
  3404.     UnbindFieldsForUpdate();
  3405. }
  3406.  
  3407. // Prepare for sending update SQL by initializing m_hstmtUpdate
  3408. void CRecordset::PrepareUpdateHstmt()
  3409. {
  3410.     RETCODE nRetCode;
  3411.     if (m_hstmtUpdate == SQL_NULL_HSTMT)
  3412.     {
  3413.         AFX_SQL_SYNC(::SQLAllocStmt(m_pDatabase->m_hdbc, &m_hstmtUpdate));
  3414.         if (!Check(nRetCode))
  3415.         {
  3416.             TRACE0("Error: failure to allocate update statement.\n");
  3417.             AfxThrowDBException(nRetCode, m_pDatabase, m_hstmtUpdate);
  3418.         }
  3419.     }
  3420.     else
  3421.     {
  3422.         AFX_SQL_SYNC(::SQLFreeStmt(m_hstmtUpdate, SQL_CLOSE));
  3423.         if (!Check(nRetCode))
  3424.             goto LErrRetCode;
  3425.  
  3426.         // Re-use (prepared) hstmt & param binding if optimizeBulkAdd option
  3427.         if(!(m_dwOptions & optimizeBulkAdd))
  3428.         {
  3429.             AFX_SQL_SYNC(::SQLFreeStmt(m_hstmtUpdate, SQL_RESET_PARAMS));
  3430.             if (!Check(nRetCode))
  3431.             {
  3432.     LErrRetCode:
  3433.                 // Bad hstmt, free it and allocate another one
  3434.                 AFX_SQL_SYNC(::SQLFreeStmt(m_hstmtUpdate, SQL_DROP));
  3435.                 m_hstmtUpdate = SQL_NULL_HSTMT;
  3436.  
  3437.                 AFX_SQL_SYNC(::SQLAllocStmt(m_pDatabase->m_hdbc, &m_hstmtUpdate));
  3438.                 if (!Check(nRetCode))
  3439.                 {
  3440.                     TRACE0("Error: failure to allocate update statement.\n");
  3441.                     AfxThrowDBException(nRetCode, m_pDatabase, m_hstmtUpdate);
  3442.                 }
  3443.             }
  3444.         }
  3445.     }
  3446. }
  3447.  
  3448. // Build the UPDATE, INSERT or DELETE SQL
  3449. void CRecordset::BuildUpdateSQL()
  3450. {
  3451.     switch (m_nEditMode)
  3452.     {
  3453.     case noMode:
  3454.         // DELETE FROM <tablename> WHERE CURRENT OF
  3455.         {
  3456.             m_strUpdateSQL = _T("DELETE FROM ");
  3457.             m_strUpdateSQL += m_strTableName;
  3458.         }
  3459.         break;
  3460.  
  3461.     case addnew:
  3462.         // INSERT INTO <tablename> (<colname1>[,<colname2>]) VALUES (?[,?])
  3463.         {
  3464.             m_strUpdateSQL = _T("INSERT INTO ");
  3465.             m_strUpdateSQL += m_strTableName;
  3466.  
  3467.             m_strUpdateSQL += _T(" (");
  3468.  
  3469.             // Append column names
  3470.             AppendNames(&m_strUpdateSQL, _afxComma);
  3471.  
  3472.             // overwrite last ',' with ')'
  3473.             ASSERT(m_strUpdateSQL[m_strUpdateSQL.GetLength()-1] == ',');
  3474.             m_strUpdateSQL.SetAt(m_strUpdateSQL.GetLength()-1, ')');
  3475.  
  3476.             // Append values
  3477.             m_strUpdateSQL += _T(" VALUES (");
  3478.             AppendValues(m_hstmtUpdate, &m_strUpdateSQL, _afxComma);
  3479.  
  3480.             // overwrite last ',' with ')'
  3481.             ASSERT(m_strUpdateSQL[m_strUpdateSQL.GetLength()-1] == ',');
  3482.             m_strUpdateSQL.SetAt(m_strUpdateSQL.GetLength()-1, ')');
  3483.         }
  3484.         break;
  3485.  
  3486.     case edit:
  3487.         // UPDATE <tablename> SET <colname1>=?[,<colname2>=?] WHERE CURRENT OF
  3488.         {
  3489.             m_strUpdateSQL = _T("UPDATE ");
  3490.             m_strUpdateSQL += m_strTableName;
  3491.  
  3492.             m_strUpdateSQL += _T(" SET ");
  3493.             AppendNamesValues(m_hstmtUpdate, &m_strUpdateSQL, _afxComma);
  3494.  
  3495.             // overwrite last ',' with ' '
  3496.             ASSERT(m_strUpdateSQL[m_strUpdateSQL.GetLength()-1] == ',');
  3497.             m_strUpdateSQL.SetAt(m_strUpdateSQL.GetLength()-1, ' ');
  3498.         }
  3499.         break;
  3500.     }
  3501.  
  3502.     // Update and Delete need "WHERE CURRENT OF <cursorname>"
  3503.     if (m_nEditMode == edit || m_nEditMode == noMode)
  3504.     {
  3505.         m_strUpdateSQL += _T(" WHERE CURRENT OF ");
  3506.  
  3507.         // Cache cursor name assigned by ODBC
  3508.         if (m_strCursorName.IsEmpty())
  3509.         {
  3510.             // Get predefined cursor name from datasource
  3511.             RETCODE nRetCode;
  3512.             UCHAR szCursorName[MAX_CURSOR_NAME+1];
  3513.             SWORD nLength = _countof(szCursorName)-1;
  3514.             AFX_SQL_SYNC(::SQLGetCursorName(m_hstmt,
  3515.                 szCursorName, _countof(szCursorName), &nLength));
  3516.             if (!Check(nRetCode))
  3517.                 ThrowDBException(nRetCode);
  3518.             m_strCursorName = (char*)szCursorName;
  3519.         }
  3520.         m_strUpdateSQL += m_strCursorName;
  3521.     }
  3522.  
  3523.     m_pDatabase->ReplaceBrackets(m_strUpdateSQL.GetBuffer(0));
  3524.     m_strUpdateSQL.ReleaseBuffer();
  3525.  
  3526.     // Must prepare the hstmt on first optimized bulk add
  3527.     if(m_dwOptions & firstBulkAdd)
  3528.     {
  3529.         RETCODE nRetCode;
  3530.  
  3531.         USES_CONVERSION;
  3532.         AFX_ODBC_CALL(::SQLPrepare(m_hstmtUpdate,
  3533.             (UCHAR*)T2A((LPTSTR)(LPCTSTR)m_strUpdateSQL), SQL_NTS));
  3534.         if (!Check(nRetCode))
  3535.             ThrowDBException(nRetCode, m_hstmtUpdate);
  3536.     }
  3537. }
  3538.  
  3539. void CRecordset::ExecuteUpdateSQL()
  3540. {
  3541.     RETCODE nRetCode;
  3542.  
  3543.     if(!(m_dwOptions & optimizeBulkAdd))
  3544.     {
  3545.         USES_CONVERSION;
  3546.         AFX_ODBC_CALL(::SQLExecDirect(m_hstmtUpdate,
  3547.             (UCHAR*)T2A((LPTSTR)(LPCTSTR)m_strUpdateSQL), SQL_NTS));
  3548.         if (!Check(nRetCode))
  3549.             ThrowDBException(nRetCode, m_hstmtUpdate);
  3550.     }
  3551.     else
  3552.     {
  3553.         AFX_ODBC_CALL(::SQLExecute(m_hstmtUpdate));
  3554.         if (!Check(nRetCode))
  3555.             ThrowDBException(nRetCode, m_hstmtUpdate);
  3556.     }
  3557.  
  3558.     // Only have data-at-execution parameters for CLongBinary columns
  3559.     if (nRetCode == SQL_NEED_DATA)
  3560.         SendLongBinaryData(m_hstmtUpdate);
  3561.  
  3562.     SDWORD lRowsAffected = 0;
  3563.  
  3564.     AFX_SQL_SYNC(::SQLRowCount(m_hstmtUpdate, &lRowsAffected));
  3565.     if (!Check(nRetCode) || lRowsAffected == -1)
  3566.     {
  3567.         // Assume 1 row affected if # rows affected can't be determined
  3568.         lRowsAffected = 1;
  3569.     }
  3570.     else
  3571.     {
  3572.         if (lRowsAffected != 1)
  3573.         {
  3574. #ifdef _DEBUG
  3575.             if (afxTraceFlags & traceDatabase)
  3576.                 TRACE1("Warning: %u rows affected by update operation (expected 1).\n",
  3577.                     lRowsAffected);
  3578. #endif
  3579.             ThrowDBException((RETCODE)(lRowsAffected == 0 ?
  3580.                 AFX_SQL_ERROR_NO_ROWS_AFFECTED :
  3581.                 AFX_SQL_ERROR_MULTIPLE_ROWS_AFFECTED));
  3582.         }
  3583.     }
  3584.     m_strUpdateSQL.Empty();
  3585. }
  3586.  
  3587.  
  3588. void CRecordset::SendLongBinaryData(HSTMT hstmt)
  3589. {
  3590.     RETCODE nRetCode;
  3591.     void* pv;
  3592.     AFX_ODBC_CALL(::SQLParamData(hstmt, &pv));
  3593.     if (!Check(nRetCode))
  3594.     {
  3595.         // cache away error
  3596.         CDBException* pException = new CDBException(nRetCode);
  3597.         pException->BuildErrorString(m_pDatabase, hstmt);
  3598.  
  3599.         // then cancel Execute operation
  3600.         Cancel();
  3601.         THROW(pException);
  3602.     }
  3603.  
  3604.     while (nRetCode == SQL_NEED_DATA)
  3605.     {
  3606.         CLongBinary* pLongBinary = (CLongBinary*)pv;
  3607.         ASSERT_VALID(pLongBinary);
  3608.  
  3609.         const BYTE* lpData = (const BYTE*)::GlobalLock(pLongBinary->m_hData);
  3610.         ASSERT(lpData != NULL);
  3611.  
  3612.         AFX_ODBC_CALL(::SQLPutData(hstmt, (PTR)lpData,
  3613.             pLongBinary->m_dwDataLength));
  3614.  
  3615.         ::GlobalUnlock(pLongBinary->m_hData);
  3616.  
  3617.         if (!Check(nRetCode))
  3618.         {
  3619.             // cache away error
  3620.             CDBException* pException = new CDBException(nRetCode);
  3621.             pException->BuildErrorString(m_pDatabase, hstmt);
  3622.  
  3623.             // then cancel Execute operation
  3624.             Cancel();
  3625.             THROW(pException);
  3626.         }
  3627.  
  3628.         // Check for another DATA_AT_EXEC
  3629.         AFX_ODBC_CALL(::SQLParamData(hstmt, &pv));
  3630.         if (!Check(nRetCode))
  3631.         {
  3632.             TRACE0("Error: failure handling long binary value during update.\n");
  3633.             ThrowDBException(nRetCode, hstmt);
  3634.         }
  3635.     }
  3636. }
  3637.  
  3638. //////////////////////////////////////////////////////////////////////////////
  3639. // CRecordset RFX implementations
  3640.  
  3641. void CRecordset::AllocStatusArrays()
  3642. {
  3643.     TRY
  3644.     {
  3645.         if (m_nFields != 0)
  3646.         {
  3647.             // Allocate buffers to hold field info
  3648.             if (m_rgFieldInfos == NULL)
  3649.             {
  3650.                 m_rgFieldInfos = new CFieldInfo[m_nFields];
  3651.                 memset(m_rgFieldInfos, 0, sizeof(CFieldInfo) * m_nFields);
  3652.             }
  3653.  
  3654.             if (m_pbFieldFlags == NULL)
  3655.             {
  3656.                 m_pbFieldFlags = new BYTE[m_nFields];
  3657.                 memset(m_pbFieldFlags, 0, m_nFields);
  3658.             }
  3659.         }
  3660.  
  3661.         if (m_nParams != 0)
  3662.         {
  3663.             // Allocate buffers to hold param info
  3664.             if (m_pbParamFlags == NULL)
  3665.             {
  3666.                 m_pbParamFlags = new BYTE[m_nParams];
  3667.                 memset(m_pbParamFlags, 0, m_nParams);
  3668.             }
  3669.  
  3670.             if (m_plParamLength == NULL)
  3671.             {
  3672.                 m_plParamLength = new LONG[m_nParams];
  3673.                 memset(m_plParamLength, 0, m_nParams*sizeof(LONG));
  3674.             }
  3675.         }
  3676.     }
  3677.     CATCH_ALL(e)
  3678.     {
  3679.         Close();
  3680.         THROW_LAST();
  3681.     }
  3682.     END_CATCH_ALL
  3683. }
  3684.  
  3685. int CRecordset::GetBoundFieldIndex(void* pv)
  3686. {
  3687.     void* pvIndex;
  3688.  
  3689.     if (!m_mapFieldIndex.Lookup(pv, pvIndex))
  3690.         return -1;
  3691.     else
  3692.         // Cached value is short not ptr
  3693.         return (int)pvIndex;
  3694. }
  3695.  
  3696. int CRecordset::GetBoundParamIndex(void* pv)
  3697. {
  3698.     void* pvIndex;
  3699.  
  3700.     if (!m_mapParamIndex.Lookup(pv, pvIndex))
  3701.         return -1;
  3702.     else
  3703.         // Cached value in data not ptr
  3704.         return (int)pvIndex;
  3705. }
  3706.  
  3707. short CRecordset::GetFieldIndexByName(LPCTSTR lpszFieldName)
  3708. {
  3709.     for (short nIndex = 0; nIndex < GetODBCFieldCount(); nIndex++)
  3710.     {
  3711.         if (m_rgODBCFieldInfos[nIndex].m_strName == lpszFieldName)
  3712.             break;
  3713.     }
  3714.  
  3715.     // Check if field name found
  3716.     if (nIndex == GetODBCFieldCount())
  3717.         ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
  3718.  
  3719.     return nIndex;
  3720. }
  3721.  
  3722. long* CRecordset::GetFieldLengthBuffer(DWORD nField, int nFieldType)
  3723. {
  3724.     if (nFieldType == CFieldExchange::outputColumn)
  3725.     {
  3726.         ASSERT(nField < m_nFields);
  3727.         return &m_rgFieldInfos[nField].m_nLength;
  3728.     }
  3729.     else
  3730.     {
  3731.         ASSERT(nField < m_nParams);
  3732.         return &m_plParamLength[nField];
  3733.     }
  3734. }
  3735.  
  3736. BYTE CRecordset::GetFieldStatus(DWORD nField)
  3737. {
  3738.     ASSERT(nField < m_nFields);
  3739.  
  3740.     return m_pbFieldFlags[nField];
  3741. }
  3742.  
  3743. void CRecordset::SetFieldStatus(DWORD nField, BYTE bFlags)
  3744. {
  3745.     ASSERT(nField < m_nFields);
  3746.  
  3747.     m_pbFieldFlags[nField] |= bFlags;
  3748. }
  3749.  
  3750. void CRecordset::ClearFieldStatus()
  3751. {
  3752.     memset(m_pbFieldFlags, 0, m_nFields);
  3753. }
  3754.  
  3755. BOOL CRecordset::IsFieldStatusDirty(DWORD nField) const
  3756. {
  3757.     ASSERT(nField < m_nFields);
  3758.  
  3759.     return m_pbFieldFlags[nField] & AFX_SQL_FIELD_FLAG_DIRTY;
  3760. }
  3761.  
  3762. void CRecordset::SetDirtyFieldStatus(DWORD nField)
  3763. {
  3764.     ASSERT(nField < m_nFields);
  3765.  
  3766.     m_pbFieldFlags[nField] |= AFX_SQL_FIELD_FLAG_DIRTY;
  3767. }
  3768.  
  3769. void CRecordset::ClearDirtyFieldStatus(DWORD nField)
  3770. {
  3771.     ASSERT(nField < m_nFields);
  3772.  
  3773.     m_pbFieldFlags[nField] &= ~AFX_SQL_FIELD_FLAG_DIRTY;
  3774. }
  3775.  
  3776. BOOL CRecordset::IsFieldStatusNull(DWORD nField) const
  3777. {
  3778.     ASSERT(nField < m_nFields);
  3779.  
  3780.     return m_pbFieldFlags[nField] & AFX_SQL_FIELD_FLAG_NULL;
  3781. }
  3782.  
  3783. void CRecordset::SetNullFieldStatus(DWORD nField)
  3784. {
  3785.     ASSERT(nField < m_nFields);
  3786.  
  3787.     m_pbFieldFlags[nField] |= AFX_SQL_FIELD_FLAG_NULL;
  3788. }
  3789.  
  3790. void CRecordset::ClearNullFieldStatus(DWORD nField)
  3791. {
  3792.     ASSERT(nField < m_nFields);
  3793.  
  3794.     m_pbFieldFlags[nField] &= ~AFX_SQL_FIELD_FLAG_NULL;
  3795. }
  3796.  
  3797. BOOL CRecordset::IsParamStatusNull(DWORD nParam) const
  3798. {
  3799.     ASSERT(nParam < m_nParams);
  3800.  
  3801.     return m_pbParamFlags[nParam] & AFX_SQL_FIELD_FLAG_NULL;
  3802. }
  3803.  
  3804. void CRecordset::SetNullParamStatus(DWORD nParam)
  3805. {
  3806.     ASSERT(nParam < m_nParams);
  3807.  
  3808.     m_pbParamFlags[nParam] |= AFX_SQL_FIELD_FLAG_NULL;
  3809. }
  3810.  
  3811. void CRecordset::ClearNullParamStatus(DWORD nParam)
  3812. {
  3813.     ASSERT(nParam < m_nParams);
  3814.  
  3815.     m_pbParamFlags[nParam] &= ~AFX_SQL_FIELD_FLAG_NULL;
  3816. }
  3817.  
  3818. BOOL CRecordset::IsFieldNullable(DWORD nField) const
  3819. {
  3820.     ASSERT(nField <= INT_MAX);
  3821.     ASSERT((long)nField < GetODBCFieldCount());
  3822.  
  3823.     // return TRUE if nulls allowed or if not known
  3824.     return m_rgODBCFieldInfos[nField].m_nNullability != SQL_NO_NULLS;
  3825. }
  3826.  
  3827. UINT CRecordset::BindParams(HSTMT hstmt)
  3828. {
  3829.     ASSERT_VALID(this);
  3830.     ASSERT(m_hstmt != SQL_NULL_HSTMT);
  3831.  
  3832.     CFieldExchange fx(CFieldExchange::BindParam, this);
  3833.     fx.m_hstmt = hstmt;
  3834.  
  3835.     DoFieldExchange(&fx);
  3836.  
  3837.     return fx.m_nParams;
  3838. }
  3839.  
  3840. void CRecordset::RebindParams(HSTMT hstmt)
  3841. {
  3842.     ASSERT_VALID(this);
  3843.     ASSERT(m_hstmt != SQL_NULL_HSTMT);
  3844.  
  3845.     if (m_bRebindParams)
  3846.     {
  3847.         CFieldExchange fx(CFieldExchange::RebindParam, this);
  3848.         fx.m_hstmt = hstmt;
  3849.  
  3850.         DoFieldExchange(&fx);
  3851.     }
  3852. }
  3853.  
  3854. UINT CRecordset::BindFieldsToColumns()
  3855. {
  3856.     ASSERT_VALID(this);
  3857.     ASSERT(m_hstmt != SQL_NULL_HSTMT);
  3858.  
  3859.     ASSERT(m_nFieldsBound == 0);
  3860.     ASSERT(m_nFields != 0 && m_nFields <= 255);
  3861.  
  3862.     CFieldExchange fx(CFieldExchange::BindFieldToColumn, this);
  3863.     fx.m_hstmt = m_hstmt;
  3864.  
  3865.     // Binding depends on fetch type
  3866.     if (m_dwOptions & useMultiRowFetch)
  3867.         DoBulkFieldExchange(&fx);
  3868.     else
  3869.         DoFieldExchange(&fx);
  3870.  
  3871.     return fx.m_nFields;
  3872. }
  3873.  
  3874. void CRecordset::BindFieldsForUpdate()
  3875. {
  3876.     ASSERT_VALID(this);
  3877.  
  3878.     if (m_nEditMode == edit || m_nEditMode == addnew)
  3879.     {
  3880.         CFieldExchange fx(CFieldExchange::BindFieldForUpdate, this);
  3881.         fx.m_hstmt = m_hstmt;
  3882.         DoFieldExchange(&fx);
  3883.     }
  3884. }
  3885.  
  3886. void CRecordset::UnbindFieldsForUpdate()
  3887. {
  3888.     ASSERT_VALID(this);
  3889.  
  3890.     if (m_nEditMode == edit || m_nEditMode == addnew)
  3891.     {
  3892.         CFieldExchange fx(CFieldExchange::UnbindFieldForUpdate, this);
  3893.         fx.m_hstmt = m_hstmt;
  3894.         DoFieldExchange(&fx);
  3895.     }
  3896. }
  3897.  
  3898. // After Move operation, reflect status and lengths of columns in RFX fields
  3899. void CRecordset::Fixups()
  3900. {
  3901.     ASSERT_VALID(this);
  3902.     ASSERT(m_hstmt != SQL_NULL_HSTMT);
  3903.     ASSERT(m_nFieldsBound != 0);
  3904.  
  3905.     CFieldExchange fx(CFieldExchange::Fixup, this);
  3906.     fx.m_hstmt = m_hstmt;
  3907.     DoFieldExchange(&fx);
  3908. }
  3909.  
  3910. UINT CRecordset::AppendNames(CString* pstr, LPCTSTR lpszSeparator)
  3911. {
  3912.     ASSERT_VALID(this);
  3913.     ASSERT(AfxIsValidAddress(pstr, sizeof(CString)));
  3914.     ASSERT(AfxIsValidString(lpszSeparator));
  3915.     ASSERT(m_hstmt != SQL_NULL_HSTMT);
  3916.  
  3917.     CFieldExchange fx(CFieldExchange::Name, this);
  3918.     fx.m_pstr = pstr;
  3919.     fx.m_lpszSeparator = lpszSeparator;
  3920.  
  3921.     if (m_dwOptions & useMultiRowFetch)
  3922.         DoBulkFieldExchange(&fx);
  3923.     else
  3924.         DoFieldExchange(&fx);
  3925.  
  3926.     return fx.m_nFields;
  3927. }
  3928.  
  3929. // For each "changed" column, append <column name>=<column value>,
  3930. UINT CRecordset::AppendNamesValues(HSTMT hstmt, CString* pstr,
  3931.     LPCTSTR lpszSeparator)
  3932. {
  3933.     ASSERT_VALID(this);
  3934.     ASSERT(AfxIsValidAddress(pstr, sizeof(CString)));
  3935.     ASSERT(AfxIsValidString(lpszSeparator));
  3936.     ASSERT(m_hstmt != SQL_NULL_HSTMT);
  3937.     ASSERT(hstmt != SQL_NULL_HSTMT);
  3938.  
  3939.     CFieldExchange fx(CFieldExchange::NameValue, this);
  3940.     fx.m_pstr = pstr;
  3941.     fx.m_lpszSeparator = lpszSeparator;
  3942.     fx.m_hstmt = hstmt;
  3943.  
  3944.     DoFieldExchange(&fx);
  3945.  
  3946.     return fx.m_nFields;
  3947. }
  3948.  
  3949. // For each "changed" column, append <column value>,
  3950. UINT CRecordset::AppendValues(HSTMT hstmt, CString* pstr,
  3951.     LPCTSTR lpszSeparator)
  3952. {
  3953.     ASSERT_VALID(this);
  3954.     ASSERT(AfxIsValidAddress(pstr, sizeof(CString)));
  3955.     ASSERT(AfxIsValidString(lpszSeparator));
  3956.     ASSERT(m_hstmt != SQL_NULL_HSTMT);
  3957.     ASSERT(hstmt != SQL_NULL_HSTMT);
  3958.  
  3959.     CFieldExchange fx(CFieldExchange::Value, this);
  3960.     fx.m_pstr = pstr;
  3961.     fx.m_lpszSeparator = lpszSeparator;
  3962.     fx.m_hstmt = hstmt;
  3963.  
  3964.     DoFieldExchange(&fx);
  3965.  
  3966.     return fx.m_nFields;
  3967. }
  3968.  
  3969.  
  3970. // Cache fields of copy buffer in a CMemFile with CArchive
  3971. void CRecordset::StoreFields()
  3972. {
  3973.     ASSERT_VALID(this);
  3974.     ASSERT(m_nFieldsBound != 0);
  3975.  
  3976.     CFieldExchange fx(CFieldExchange::StoreField, this);
  3977.     DoFieldExchange(&fx);
  3978. }
  3979.  
  3980. // Restore fields of copy buffer from archived memory file
  3981. void CRecordset::LoadFields()
  3982. {
  3983.     ASSERT_VALID(this);
  3984.     ASSERT(m_nFieldsBound != 0);
  3985.  
  3986.     // Must clear out the old status
  3987.     ClearFieldStatus();
  3988.  
  3989.     CFieldExchange fx(CFieldExchange::LoadField, this);
  3990.     DoFieldExchange(&fx);
  3991. }
  3992.  
  3993. void CRecordset::MarkForUpdate()
  3994. {
  3995.     ASSERT_VALID(this);
  3996.  
  3997.     CFieldExchange fx(CFieldExchange::MarkForUpdate, this);
  3998.     DoFieldExchange(&fx);
  3999. }
  4000.  
  4001. void CRecordset::MarkForAddNew()
  4002. {
  4003.     ASSERT_VALID(this);
  4004.  
  4005.     CFieldExchange fx(CFieldExchange::MarkForAddNew, this);
  4006.     DoFieldExchange(&fx);
  4007. }
  4008.  
  4009. void CRecordset::AllocDataCache()
  4010. {
  4011.     ASSERT_VALID(this);
  4012.  
  4013.     CFieldExchange fx(CFieldExchange::AllocCache, this);
  4014.     DoFieldExchange(&fx);
  4015. }
  4016.  
  4017. void CRecordset::FreeDataCache()
  4018. {
  4019.     ASSERT_VALID(this);
  4020.  
  4021.     CFieldInfo* pInfo;
  4022.  
  4023.     for (DWORD nField = 0; nField < m_nFields; nField++)
  4024.     {
  4025.         pInfo = &m_rgFieldInfos[nField];
  4026.  
  4027.         switch(pInfo->m_nDataType)
  4028.         {
  4029.         default:
  4030.             ASSERT(FALSE);
  4031.             // fall through
  4032.  
  4033.         // Data not cached
  4034.         case AFX_RFX_NO_TYPE:
  4035.             break;
  4036.  
  4037.         // Types cached by value (sizeof(TYPE) <= sizeof(void*))
  4038.         case AFX_RFX_BOOL:
  4039.         case AFX_RFX_BYTE:
  4040.         case AFX_RFX_INT:
  4041.         case AFX_RFX_LONG:
  4042.         case AFX_RFX_SINGLE:
  4043.             pInfo->m_pvDataCache = NULL;
  4044.             break;
  4045.  
  4046.         case AFX_RFX_TEXT:
  4047.             delete (CString*)pInfo->m_pvDataCache;
  4048.             pInfo->m_pvDataCache = NULL;
  4049.             break;
  4050.  
  4051.         case AFX_RFX_LPTSTR:
  4052.             delete [] LPTSTR(pInfo->m_pvDataCache);
  4053.             pInfo->m_pvDataCache = NULL;
  4054.             break;
  4055.  
  4056.         case AFX_RFX_DOUBLE:
  4057.             delete (double*)pInfo->m_pvDataCache;
  4058.             pInfo->m_pvDataCache = NULL;
  4059.             break;
  4060.  
  4061.         case AFX_RFX_TIMESTAMP:
  4062.             delete (TIMESTAMP_STRUCT*)pInfo->m_pvDataCache;
  4063.             pInfo->m_pvDataCache = NULL;
  4064.             break;
  4065.  
  4066.         case AFX_RFX_OLEDATE:
  4067.             delete (COleDateTime*)pInfo->m_pvDataCache;
  4068.             pInfo->m_pvDataCache = NULL;
  4069.             break;
  4070.  
  4071.         case AFX_RFX_DATE:
  4072.             delete (CTime*)pInfo->m_pvDataCache;
  4073.             pInfo->m_pvDataCache = NULL;
  4074.             break;
  4075.  
  4076.         case AFX_RFX_BINARY:
  4077.             delete (CByteArray*)pInfo->m_pvDataCache;
  4078.             pInfo->m_pvDataCache = NULL;
  4079.             break;
  4080.         }
  4081.     }
  4082. }
  4083.  
  4084. #ifdef _DEBUG
  4085. void CRecordset::DumpFields(CDumpContext& dc) const
  4086. {
  4087.     CFieldExchange fx(CFieldExchange::DumpField, (CRecordset *)this);
  4088.     fx.m_pdcDump = &dc;
  4089.     ((CRecordset *)this)->DoFieldExchange(&fx);
  4090. }
  4091. #endif // _DEBUG
  4092.  
  4093.  
  4094. // Perform Update (m_nModeEdit == edit), Insert (addnew),
  4095. // or Delete (noMode)
  4096. BOOL CRecordset::UpdateInsertDelete()
  4097. {
  4098.     ASSERT_VALID(this);
  4099.     ASSERT(m_hstmt != SQL_NULL_HSTMT);
  4100.  
  4101.     // Delete mode
  4102.     if (m_nEditMode == addnew)
  4103.     {
  4104.         if (!m_bAppendable)
  4105.         {
  4106.             TRACE0("Error: attempted to add a record to a read only recordset.\n");
  4107.             ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
  4108.         }
  4109.     }
  4110.     else
  4111.     {
  4112.         if (!m_bUpdatable)
  4113.         {
  4114.             TRACE0("Error: attempted to update a read only recordset.\n");
  4115.             ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
  4116.         }
  4117.  
  4118.         // Requires currency
  4119.         if (m_bEOF || m_bBOF || m_bDeleted)
  4120.         {
  4121.             TRACE0("Error: attempting to update recordset - but no record is current.\n");
  4122.             ThrowDBException(AFX_SQL_ERROR_NO_CURRENT_RECORD);
  4123.         }
  4124.     }
  4125.  
  4126.     // Update or AddNew is NOP w/o at least 1 changed field
  4127.     if (m_nEditMode != noMode && !IsFieldDirty(NULL))
  4128.         return FALSE;
  4129.  
  4130.     if (!m_bUseUpdateSQL)
  4131.     {
  4132.         // Most efficient update method
  4133.         ExecuteSetPosUpdate();
  4134.     }
  4135.     else
  4136.     {
  4137.  
  4138.         BOOL bNullHstmt = (m_hstmtUpdate == NULL);
  4139.  
  4140.         // Make sure m_hstmtUpdate allocated
  4141.         PrepareUpdateHstmt();
  4142.  
  4143.         // Build update SQL unless optimizing bulk adds and hstmt not NULL
  4144.         if(!(m_dwOptions & optimizeBulkAdd) || bNullHstmt)
  4145.         {
  4146.             // Mark as first bulk add if optimizing
  4147.             if(m_dwOptions & optimizeBulkAdd)
  4148.             {
  4149.                 m_dwOptions &= ~optimizeBulkAdd;
  4150.                 m_dwOptions |= firstBulkAdd;
  4151.             }
  4152.             BuildUpdateSQL();
  4153.  
  4154.             // Reset flag marking first optimization
  4155.             if(m_dwOptions & firstBulkAdd)
  4156.             {
  4157.                 m_dwOptions &= ~firstBulkAdd;
  4158.                 m_dwOptions |= optimizeBulkAdd;
  4159.             }
  4160.         }
  4161.         else
  4162.         {
  4163.             // Just reset the data lengths and datetime proxies
  4164.             AppendValues(m_hstmtUpdate, &m_strUpdateSQL, _afxComma);
  4165.         }
  4166.  
  4167.         ExecuteUpdateSQL();
  4168.     }
  4169.  
  4170.     TRY
  4171.     {
  4172.         // Delete
  4173.         switch (m_nEditMode)
  4174.         {
  4175.         case noMode:
  4176.             // Decrement record count
  4177.             if (m_lCurrentRecord >= 0)
  4178.             {
  4179.                 if (m_lRecordCount > 0)
  4180.                     m_lRecordCount--;
  4181.                 m_lCurrentRecord--;
  4182.             }
  4183.  
  4184.             // indicate on a deleted record
  4185.             m_bDeleted = TRUE;
  4186.             // Set all fields to NULL
  4187.             SetFieldNull(NULL);
  4188.             break;
  4189.  
  4190.         case addnew:
  4191.             // The recordset may no longer be empty (depending on driver behavior)
  4192.             // reset m_bEOF/m_bBOF so Move can be called
  4193.             m_bEOF = m_bBOF = FALSE;
  4194.  
  4195.             if (m_pDatabase->m_bIncRecordCountOnAdd && m_lCurrentRecord >= 0)
  4196.             {
  4197.                 if (m_lRecordCount != -1)
  4198.                     m_lRecordCount++;
  4199.                 m_lCurrentRecord++;
  4200.             }
  4201.  
  4202.             // Reset the data cache if necessary
  4203.             if (m_bCheckCacheForDirtyFields && m_nFields > 0)
  4204.                 LoadFields();
  4205.             break;
  4206.  
  4207.         case edit:
  4208.             break;
  4209.         }
  4210.  
  4211.         // Reset the edit mode
  4212.         m_nEditMode = noMode;
  4213.     }
  4214.     END_TRY
  4215.  
  4216.     // Unless doing a bulk AddNew, reset the dirty flags
  4217.     if (m_bCheckCacheForDirtyFields && !(m_dwOptions & optimizeBulkAdd))
  4218.         SetFieldDirty(NULL, FALSE);
  4219.  
  4220.     // Must return TRUE since record updated
  4221.     return TRUE;
  4222. }
  4223.  
  4224. // Fetch and alloc algorithms for CLongBinary data when length unknown
  4225. long CRecordset::GetLBFetchSize(long lOldSize)
  4226. {
  4227.     // Make it twice as big
  4228.     return lOldSize << 1;
  4229. }
  4230.  
  4231. long CRecordset::GetLBReallocSize(long lOldSize)
  4232. {
  4233.     // Make it twice as big (no effect if less than fetch size)
  4234.     return lOldSize << 1;
  4235. }
  4236.  
  4237. short PASCAL CRecordset::GetDefaultFieldType(short nSQLType)
  4238. {
  4239.     short nFieldType = 0;
  4240.  
  4241.     switch (nSQLType)
  4242.     {
  4243.     case SQL_BIT:
  4244.         nFieldType = SQL_C_BIT;
  4245.         break;
  4246.  
  4247.     case SQL_TINYINT:
  4248.         nFieldType = SQL_C_UTINYINT;
  4249.         break;
  4250.  
  4251.     case SQL_SMALLINT:
  4252.         nFieldType = SQL_C_SSHORT;
  4253.         break;
  4254.  
  4255.     case SQL_INTEGER:
  4256.         nFieldType = SQL_C_SLONG;
  4257.         break;
  4258.  
  4259.     case SQL_REAL:
  4260.         nFieldType = SQL_C_FLOAT;
  4261.         break;
  4262.  
  4263.     case SQL_FLOAT:
  4264.     case SQL_DOUBLE:
  4265.         nFieldType = SQL_C_DOUBLE;
  4266.         break;
  4267.  
  4268.     case SQL_DATE:
  4269.     case SQL_TIME:
  4270.     case SQL_TIMESTAMP:
  4271.         nFieldType = SQL_C_TIMESTAMP;
  4272.         break;
  4273.  
  4274.     case SQL_NUMERIC:
  4275.     case SQL_DECIMAL:
  4276.     case SQL_BIGINT:
  4277.     case SQL_CHAR:
  4278.     case SQL_VARCHAR:
  4279.     case SQL_LONGVARCHAR:
  4280.         nFieldType = SQL_C_CHAR;
  4281.         break;
  4282.  
  4283.     case SQL_BINARY:
  4284.     case SQL_VARBINARY:
  4285.     case SQL_LONGVARBINARY:
  4286.         nFieldType = SQL_C_BINARY;
  4287.         break;
  4288.  
  4289.     default:
  4290.         ASSERT(FALSE);
  4291.     }
  4292.  
  4293.     return nFieldType;
  4294. }
  4295.  
  4296. void* PASCAL CRecordset::GetDataBuffer(CDBVariant& varValue,
  4297.     short nFieldType, int* pnLen, short nSQLType, UDWORD nPrecision)
  4298. {
  4299.     void* pvData = NULL;
  4300.  
  4301.     switch (nFieldType)
  4302.     {
  4303.     case SQL_C_BIT:
  4304.         pvData = &varValue.m_boolVal;
  4305.         varValue.m_dwType = DBVT_BOOL;
  4306.         *pnLen = sizeof(varValue.m_boolVal);
  4307.         break;
  4308.  
  4309.     case SQL_C_UTINYINT:
  4310.         pvData = &varValue.m_chVal;
  4311.         varValue.m_dwType = DBVT_UCHAR;
  4312.         *pnLen = sizeof(varValue.m_chVal);
  4313.         break;
  4314.  
  4315.     case SQL_C_SSHORT:
  4316.         pvData = &varValue.m_iVal;
  4317.         varValue.m_dwType = DBVT_SHORT;
  4318.         *pnLen = sizeof(varValue.m_iVal);
  4319.         break;
  4320.  
  4321.     case SQL_C_SLONG:
  4322.         pvData = &varValue.m_lVal;
  4323.         varValue.m_dwType = DBVT_LONG;
  4324.         *pnLen = sizeof(varValue.m_lVal);
  4325.         break;
  4326.  
  4327.     case SQL_C_FLOAT:
  4328.         pvData = &varValue.m_fltVal;
  4329.         varValue.m_dwType = DBVT_SINGLE;
  4330.         *pnLen = sizeof(varValue.m_fltVal);
  4331.         break;
  4332.  
  4333.     case SQL_C_DOUBLE:
  4334.         pvData = &varValue.m_dblVal;
  4335.         varValue.m_dwType = DBVT_DOUBLE;
  4336.         *pnLen = sizeof(varValue.m_dblVal);
  4337.         break;
  4338.  
  4339.     case SQL_C_TIMESTAMP:
  4340.         pvData = varValue.m_pdate = new TIMESTAMP_STRUCT;
  4341.         varValue.m_dwType = DBVT_DATE;
  4342.         *pnLen = sizeof(*varValue.m_pdate);
  4343.         break;
  4344.  
  4345.     case SQL_C_CHAR:
  4346.         varValue.m_pstring = new CString;
  4347.         varValue.m_dwType = DBVT_STRING;
  4348.  
  4349.         *pnLen = GetTextLen(nSQLType, nPrecision);
  4350.  
  4351.         pvData = varValue.m_pstring->GetBufferSetLength(*pnLen);
  4352.         break;
  4353.  
  4354.  
  4355.     case SQL_C_BINARY:
  4356.         varValue.m_pbinary = new CLongBinary;
  4357.         varValue.m_dwType = DBVT_BINARY;
  4358.  
  4359.         if (nSQLType == SQL_LONGVARBINARY)
  4360.         {
  4361.             // pvData can't be NULL, so nLen must be at least 1
  4362.             *pnLen = 1;
  4363.         }
  4364.         else
  4365.         {
  4366.             // better know the length!
  4367.             ASSERT(nPrecision != 0);
  4368.             *pnLen = nPrecision;
  4369.         }
  4370.  
  4371.         varValue.m_pbinary->m_hData = ::GlobalAlloc(GMEM_MOVEABLE, *pnLen);
  4372.         varValue.m_pbinary->m_dwDataLength = *pnLen;
  4373.  
  4374.         pvData = ::GlobalLock(varValue.m_pbinary->m_hData);
  4375.         break;
  4376.  
  4377.     default:
  4378.         ASSERT(FALSE);
  4379.     }
  4380.  
  4381.     return pvData;
  4382. }
  4383.  
  4384. int PASCAL CRecordset::GetTextLen(short nSQLType, UDWORD nPrecision)
  4385. {
  4386.     int nLen;
  4387.  
  4388.     if (nSQLType == SQL_LONGVARCHAR || nSQLType == SQL_LONGVARBINARY)
  4389.     {
  4390.         // Use a dummy length of 1 (will just get NULL terminator)
  4391.         nLen = 1;
  4392.     }
  4393.     else
  4394.     {
  4395.         // better know the length
  4396.         ASSERT(nPrecision >= 0);
  4397.  
  4398.         nLen = nPrecision + 1;
  4399.  
  4400.         // If converting Numeric or Decimal to text need
  4401.         // room for decimal point and sign in string
  4402.         if (nSQLType == SQL_NUMERIC || nSQLType == SQL_DECIMAL)
  4403.             nLen += 2;
  4404.     }
  4405.  
  4406.     return nLen;
  4407. }
  4408.  
  4409. long PASCAL CRecordset::GetData(CDatabase* pdb, HSTMT hstmt,
  4410.     short nFieldIndex, short nFieldType, LPVOID pvData, int nLen,
  4411.     short nSQLType)
  4412. {
  4413.     UNUSED(nSQLType);
  4414.  
  4415.     long nActualSize;
  4416.     RETCODE nRetCode;
  4417.  
  4418.     // Retrieve the column in question
  4419.     AFX_ODBC_CALL(::SQLGetData(hstmt, nFieldIndex,
  4420.         nFieldType, pvData, nLen, &nActualSize));
  4421.  
  4422.     // Ignore data truncated warnings for long data
  4423.     if (nRetCode == SQL_SUCCESS_WITH_INFO)
  4424.     {
  4425. #ifdef _DEBUG
  4426.         CDBException e(nRetCode);
  4427.  
  4428.         if (afxTraceFlags & traceDatabase)
  4429.         {
  4430.             CDBException e(nRetCode);
  4431.             // Build the error string but don't send nuisance output to TRACE window
  4432.             e.BuildErrorString(pdb, hstmt, FALSE);
  4433.  
  4434.             // If not a data truncated warning on long var column,
  4435.             // then send debug output
  4436.             if ((nSQLType != SQL_LONGVARCHAR &&
  4437.                 nSQLType != SQL_LONGVARBINARY) ||
  4438.                 (e.m_strStateNativeOrigin.Find(_afxDataTruncated) < 0))
  4439.             {
  4440.                 TRACE1("Warning: ODBC Success With Info on field %d.\n",
  4441.                     nFieldIndex - 1);
  4442.                 e.TraceErrorMessage(e.m_strError);
  4443.                 e.TraceErrorMessage(e.m_strStateNativeOrigin);
  4444.             }
  4445.         }
  4446. #endif // _DEBUG
  4447.     }
  4448.     else if (nRetCode == SQL_NO_DATA_FOUND)
  4449.     {
  4450.         TRACE0("Error: GetFieldValue operation failed on field %d.\n");
  4451.         TRACE1("\tData already fetched for this field.\n",
  4452.             nFieldIndex - 1);
  4453.         AfxThrowDBException(nRetCode, pdb, hstmt);
  4454.     }
  4455.     else if (nRetCode != SQL_SUCCESS)
  4456.     {
  4457.         TRACE1("Error: GetFieldValue operation failed on field %d.\n",
  4458.             nFieldIndex - 1);
  4459.         AfxThrowDBException(nRetCode, pdb, hstmt);
  4460.     }
  4461.  
  4462.     return nActualSize;
  4463. }
  4464.  
  4465. void PASCAL CRecordset::GetLongCharDataAndCleanup(CDatabase* pdb,
  4466.     HSTMT hstmt, short nFieldIndex, long nActualSize, LPVOID* ppvData,
  4467.     int nLen, CString& strValue, short nSQLType)
  4468. {
  4469.     RETCODE nRetCode;
  4470.  
  4471.     // Release the buffer now that data has been fetched
  4472.     strValue.ReleaseBuffer(nActualSize < nLen ? nActualSize : nLen);
  4473.  
  4474.     // If long data, may need to call SQLGetData again
  4475.     if (nLen <= nActualSize &&
  4476.         (nSQLType == SQL_LONGVARCHAR || nSQLType == SQL_LONGVARBINARY))
  4477.     {
  4478.         // Reallocate the size (this will copy the data)
  4479.         *ppvData = strValue.GetBufferSetLength(nActualSize + 1);
  4480.  
  4481.         // Get pointer, skipping over original data, but not the NULL
  4482.         int nOldLen = nLen - 1;
  4483.         *ppvData = (BYTE*)*ppvData + nOldLen;
  4484.         nLen = nActualSize + 1 - nOldLen;
  4485.  
  4486.         // Retrieve the column in question
  4487.         AFX_ODBC_CALL(::SQLGetData(hstmt, nFieldIndex,
  4488.             SQL_C_CHAR, *ppvData, nLen, &nActualSize));
  4489.         if (nRetCode == SQL_SUCCESS_WITH_INFO)
  4490.         {
  4491. #ifdef _DEBUG
  4492.             if (afxTraceFlags & traceDatabase)
  4493.             {
  4494.                 TRACE1("Warning: ODBC Success With Info on field %d.\n",
  4495.                     nFieldIndex - 1);
  4496.                 CDBException e(nRetCode);
  4497.                 e.BuildErrorString(pdb, hstmt);
  4498.             }
  4499. #endif // _DEBUG
  4500.         }
  4501.         else if (nRetCode != SQL_SUCCESS)
  4502.         {
  4503.             TRACE1("Error: GetFieldValue operation failed on field %d.\n",
  4504.                 nFieldIndex - 1);
  4505.             AfxThrowDBException(nRetCode, pdb, hstmt);
  4506.         }
  4507.  
  4508.         // Release the buffer now that data has been fetched
  4509.         strValue.ReleaseBuffer(nActualSize + nOldLen);
  4510.     }
  4511. }
  4512.  
  4513. void PASCAL CRecordset::GetLongBinaryDataAndCleanup(CDatabase* pdb, HSTMT hstmt,
  4514.     short nFieldIndex, long nActualSize, LPVOID* ppvData, int nLen,
  4515.     CDBVariant& varValue, short nSQLType)
  4516. {
  4517.     RETCODE nRetCode;
  4518.  
  4519.     ::GlobalUnlock(varValue.m_pbinary->m_hData);
  4520.  
  4521.     // If long data, may need to call SQLGetData again
  4522.     if (nLen < nActualSize && nSQLType == SQL_LONGVARBINARY)
  4523.     {
  4524.         // Reallocate a bigger buffer
  4525.         HGLOBAL hOldData = varValue.m_pbinary->m_hData;
  4526.         varValue.m_pbinary->m_hData = ::GlobalReAlloc(hOldData,
  4527.             nActualSize, GMEM_MOVEABLE);
  4528.  
  4529.         // Validate the memory was allocated and can be locked
  4530.         if (varValue.m_pbinary->m_hData == NULL)
  4531.         {
  4532.             // Restore the old handle (not NULL if Realloc failed)
  4533.             varValue.m_pbinary->m_hData = hOldData;
  4534.             AfxThrowMemoryException();
  4535.         }
  4536.         varValue.m_pbinary->m_dwDataLength = nActualSize;
  4537.  
  4538.         // Get pointer, skipping over original data
  4539.         *ppvData = (BYTE*)::GlobalLock(varValue.m_pbinary->m_hData) + nLen;
  4540.         int nOldLen = nLen;
  4541.         nLen = nActualSize - nOldLen;
  4542.  
  4543.         // Retrieve the column in question
  4544.         AFX_ODBC_CALL(::SQLGetData(hstmt, nFieldIndex,
  4545.             SQL_C_BINARY, *ppvData, nLen, &nActualSize));
  4546.         if (nRetCode == SQL_SUCCESS_WITH_INFO)
  4547.         {
  4548. #ifdef _DEBUG
  4549.             if (afxTraceFlags & traceDatabase)
  4550.             {
  4551.                 TRACE1("Warning: ODBC Success With Info on field %d.\n",
  4552.                     nFieldIndex - 1);
  4553.                 CDBException e(nRetCode);
  4554.                 e.BuildErrorString(pdb, hstmt);
  4555.             }
  4556. #endif // _DEBUG
  4557.         }
  4558.         else if (nRetCode != SQL_SUCCESS)
  4559.         {
  4560.             TRACE1("Error: GetFieldValue operation failed on field %d.\n",
  4561.                 nFieldIndex - 1);
  4562.             AfxThrowDBException(nRetCode, pdb, hstmt);
  4563.         }
  4564.  
  4565.         ASSERT((int)varValue.m_pbinary->m_dwDataLength ==
  4566.             nActualSize + nOldLen);
  4567.  
  4568.         // Release the buffer now that data has been fetched
  4569.         ::GlobalUnlock(varValue.m_pbinary->m_hData);
  4570.     }
  4571. }
  4572.  
  4573. //////////////////////////////////////////////////////////////////////////////
  4574. // CRecordset diagnostics
  4575.  
  4576. #ifdef _DEBUG
  4577. void CRecordset::AssertValid() const
  4578. {
  4579.     CObject::AssertValid();
  4580.     if (m_pDatabase != NULL)
  4581.         m_pDatabase->AssertValid();
  4582. }
  4583.  
  4584. void CRecordset::Dump(CDumpContext& dc) const
  4585. {
  4586.     CObject::Dump(dc);
  4587.  
  4588.     dc << "m_nOpenType = " << m_nOpenType;
  4589.     dc << "\nm_strSQL = " << m_strSQL;
  4590.     dc << "\nm_hstmt = " << m_hstmt;
  4591.     dc << "\nm_bRecordsetDb = " << m_bRecordsetDb;
  4592.  
  4593.     dc << "\nm_lOpen = " << m_lOpen;
  4594.     dc << "\nm_bScrollable = " << m_bScrollable;
  4595.     dc << "\nm_bUpdatable = " << m_bUpdatable;
  4596.     dc << "\nm_bAppendable = " << m_bAppendable;
  4597.  
  4598.     dc << "\nm_nFields = " << m_nFields;
  4599.     dc << "\nm_nFieldsBound = " << m_nFieldsBound;
  4600.     dc << "\nm_nParams = " << m_nParams;
  4601.  
  4602.     dc << "\nm_bEOF = " << m_bEOF;
  4603.     dc << "\nm_bBOF = " << m_bBOF;
  4604.     dc << "\nm_bDeleted = " << m_bEOF;
  4605.  
  4606.     dc << "\nm_bLockMode = " << m_nLockMode;
  4607.     dc << "\nm_nEditMode = " << m_nEditMode;
  4608.     dc << "\nm_strCursorName = " << m_strCursorName;
  4609.     dc << "\nm_hstmtUpdate = " << m_hstmtUpdate;
  4610.  
  4611.     dc << "\nDump values for each field in current record.";
  4612.     DumpFields(dc);
  4613.  
  4614.     if (dc.GetDepth() > 0)
  4615.     {
  4616.         if (m_pDatabase == NULL)
  4617.             dc << "with no database\n";
  4618.         else
  4619.             dc << "with database: " << m_pDatabase;
  4620.     }
  4621. }
  4622. #endif // _DEBUG
  4623.  
  4624. //////////////////////////////////////////////////////////////////////////////
  4625. // Helpers
  4626.  
  4627. void AFXAPI AfxSetCurrentRecord(long* plCurrentRecord, long nRows, RETCODE nRetCode)
  4628. {
  4629.     if (*plCurrentRecord != AFX_CURRENT_RECORD_UNDEFINED &&
  4630.         nRetCode != SQL_NO_DATA_FOUND)
  4631.         *plCurrentRecord += nRows;
  4632. }
  4633.  
  4634. void AFXAPI AfxSetRecordCount(long* plRecordCount, long lCurrentRecord,
  4635.     long nRows, BOOL bEOFSeen, RETCODE nRetCode)
  4636. {
  4637.     // This function provided for backward binary compatibility
  4638.     UNUSED(nRows); // not used in release build
  4639.     ASSERT(nRows == 1);
  4640.     AfxSetRecordCount(plRecordCount, lCurrentRecord, bEOFSeen, nRetCode);
  4641. }
  4642.  
  4643. void AFXAPI AfxSetRecordCount(long* plRecordCount, long lCurrentRecord,
  4644.     BOOL bEOFSeen, RETCODE nRetCode)
  4645. {
  4646.     // If not at the end and haven't yet been to the end, incr count
  4647.     if (nRetCode != SQL_NO_DATA_FOUND && !bEOFSeen &&
  4648.         lCurrentRecord != AFX_CURRENT_RECORD_UNDEFINED)
  4649.     {
  4650.         // lCurrentRecord 0-based and it's already been set
  4651.         *plRecordCount =
  4652.             __max(*plRecordCount, lCurrentRecord + 1);
  4653.     }
  4654. }
  4655.  
  4656. //////////////////////////////////////////////////////////////////////////////
  4657. // Inline function declarations expanded out-of-line
  4658.  
  4659. #ifndef _AFX_ENABLE_INLINES
  4660.  
  4661. static char _szAfxDbInl[] = "afxdb.inl";
  4662. #undef THIS_FILE
  4663. #define THIS_FILE _szAfxDbInl
  4664. #define _AFXDBCORE_INLINE
  4665. #include "afxdb.inl"
  4666.  
  4667. #endif
  4668.  
  4669. #ifdef AFX_INIT_SEG
  4670. #pragma code_seg(AFX_INIT_SEG)
  4671. #endif
  4672.  
  4673. IMPLEMENT_DYNAMIC(CDBException, CException)
  4674. IMPLEMENT_DYNAMIC(CDatabase, CObject)
  4675. IMPLEMENT_DYNAMIC(CRecordset, CObject)
  4676.  
  4677. #pragma warning(disable: 4074)
  4678. #pragma init_seg(lib)
  4679.  
  4680. PROCESS_LOCAL(_AFX_DB_STATE, _afxDbState)
  4681.  
  4682. /////////////////////////////////////////////////////////////////////////////
  4683.