home *** CD-ROM | disk | FTP | other *** search
/ Supercompiler 1997 / SUPERCOMPILER97.iso / MS_VC.50 / VC / MFC / SRC / DBCORE.CPP < prev    next >
Encoding:
C/C++ Source or Header  |  1996-11-14  |  119.6 KB  |  4,969 lines

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