/wcwp/bin/VC6CMD/MFC/SRC/DBCORE.CPP

http://wcwp.googlecode.com/ · C++ · 2662 lines · 2000 code · 415 blank · 247 comment · 457 complexity · 8caafa0b29c5d46086c9555a519dc9da MD5 · raw file

  1. // This is a part of the Microsoft Foundation Classes C++ library.
  2. // Copyright (C) 1992-1998 Microsoft Corporation
  3. // All rights reserved.
  4. //
  5. // This source code is only intended as a supplement to the
  6. // Microsoft Foundation Classes Reference and related
  7. // electronic documentation provided with the library.
  8. // See these sources for detailed information regarding the
  9. // Microsoft Foundation Classes product.
  10. #include "stdafx.h"
  11. #ifdef AFX_DB_SEG
  12. #pragma code_seg(AFX_DB_SEG)
  13. #endif
  14. #ifdef _DEBUG
  15. #undef THIS_FILE
  16. static char THIS_FILE[] = __FILE__;
  17. #endif
  18. #define new DEBUG_NEW
  19. /////////////////////////////////////////////////////////////////////////////
  20. // Global data
  21. #ifdef _DEBUG
  22. BOOL bTraceSql = FALSE;
  23. #endif
  24. AFX_STATIC_DATA const TCHAR _afxODBCTrail[] = _T("ODBC;");
  25. AFX_STATIC_DATA const TCHAR _afxComma[] = _T(",");
  26. AFX_STATIC_DATA const TCHAR _afxLiteralSeparator = '\'';
  27. AFX_STATIC_DATA const TCHAR _afxCall[] = _T("{CALL ");
  28. AFX_STATIC_DATA const TCHAR _afxParamCall[] = _T("{?");
  29. AFX_STATIC_DATA const TCHAR _afxSelect[] = _T("SELECT ");
  30. AFX_STATIC_DATA const TCHAR _afxFrom[] = _T(" FROM ");
  31. AFX_STATIC_DATA const TCHAR _afxWhere[] = _T(" WHERE ");
  32. AFX_STATIC_DATA const TCHAR _afxOrderBy[] = _T(" ORDER BY ");
  33. AFX_STATIC_DATA const TCHAR _afxForUpdate[] = _T(" FOR UPDATE ");
  34. AFX_STATIC_DATA const TCHAR _afxRowFetch[] = _T("State:01S01");
  35. AFX_STATIC_DATA const TCHAR _afxDataTruncated[] = _T("State:01004");
  36. AFX_STATIC_DATA const TCHAR _afxInfoRange[] = _T("State:S1096");
  37. AFX_STATIC_DATA const TCHAR _afxOutOfSequence[] = _T("State:S1010");
  38. AFX_STATIC_DATA const TCHAR _afxDriverNotCapable[] = _T("State:S1C00");
  39. AFX_STATIC_DATA const char _afxODBCDLL[] = "ODBC32.DLL";
  40. /////////////////////////////////////////////////////////////////////////////
  41. // for dynamic load of ODBC32.DLL
  42. #pragma comment(lib, "odbc32.lib")
  43. /////////////////////////////////////////////////////////////////////////////
  44. // CDBException
  45. void AFXAPI AfxThrowDBException(RETCODE nRetCode, CDatabase* pdb, HSTMT hstmt)
  46. {
  47. CDBException* pException = new CDBException(nRetCode);
  48. if (nRetCode == SQL_ERROR && pdb != NULL)
  49. pException->BuildErrorString(pdb, hstmt);
  50. else if (nRetCode > AFX_SQL_ERROR && nRetCode < AFX_SQL_ERROR_MAX)
  51. {
  52. VERIFY(pException->m_strError.LoadString(
  53. AFX_IDP_SQL_FIRST+(nRetCode-AFX_SQL_ERROR)));
  54. TRACE1("%s\n", pException->m_strError);
  55. }
  56. THROW(pException);
  57. }
  58. CDBException::CDBException(RETCODE nRetCode)
  59. {
  60. m_nRetCode = nRetCode;
  61. }
  62. CDBException::~CDBException()
  63. {
  64. }
  65. void CDBException::BuildErrorString(CDatabase* pdb, HSTMT hstmt, BOOL bTrace)
  66. {
  67. ASSERT_VALID(this);
  68. UNUSED(bTrace); // unused in release builds
  69. if (pdb != NULL)
  70. {
  71. SWORD nOutlen;
  72. RETCODE nRetCode;
  73. UCHAR lpszMsg[SQL_MAX_MESSAGE_LENGTH];
  74. UCHAR lpszState[SQL_SQLSTATE_SIZE];
  75. CString strMsg;
  76. CString strState;
  77. SDWORD lNative;
  78. _AFX_DB_STATE* pDbState = _afxDbState;
  79. AFX_SQL_SYNC(::SQLError(pDbState->m_henvAllConnections, pdb->m_hdbc,
  80. hstmt, lpszState, &lNative,
  81. lpszMsg, SQL_MAX_MESSAGE_LENGTH-1, &nOutlen));
  82. strState = lpszState;
  83. // Skip non-errors
  84. while ((nRetCode == SQL_SUCCESS || nRetCode == SQL_SUCCESS_WITH_INFO) &&
  85. lstrcmp(strState, _T("00000")) != 0)
  86. {
  87. strMsg = lpszMsg;
  88. TCHAR lpszNative[50];
  89. wsprintf(lpszNative, _T(",Native:%ld,Origin:"), lNative);
  90. strState += lpszNative;
  91. // transfer [origin] from message string to StateNativeOrigin string
  92. int nCloseBracket;
  93. int nMsgLength;
  94. while (!strMsg.IsEmpty() &&
  95. strMsg[0] == '[' && (nCloseBracket = strMsg.Find(']')) >= 0)
  96. {
  97. // Skip ']'
  98. nCloseBracket++;
  99. strState += strMsg.Left(nCloseBracket);
  100. nMsgLength = strMsg.GetLength();
  101. // Skip ' ', if present
  102. if (nCloseBracket < nMsgLength && strMsg[nCloseBracket] == ' ')
  103. nCloseBracket++;
  104. strMsg = strMsg.Right(nMsgLength - nCloseBracket);
  105. }
  106. strState += _T("\n");
  107. m_strStateNativeOrigin += _T("State:") + strState;
  108. m_strError += strMsg + _T("\n");
  109. #ifdef _DEBUG
  110. if (bTrace)
  111. {
  112. TraceErrorMessage(strMsg);
  113. TraceErrorMessage(_T("State:") + strState);
  114. }
  115. #endif // _DEBUG
  116. AFX_SQL_SYNC(::SQLError(pDbState->m_henvAllConnections,
  117. pdb->m_hdbc, hstmt, lpszState, &lNative,
  118. lpszMsg, SQL_MAX_MESSAGE_LENGTH-1, &nOutlen));
  119. strState = lpszState;
  120. }
  121. }
  122. }
  123. BOOL CDBException::GetErrorMessage(LPTSTR lpszError, UINT nMaxError,
  124. PUINT pnHelpContext /* = NULL */)
  125. {
  126. ASSERT(lpszError != NULL && AfxIsValidString(lpszError, nMaxError));
  127. if (pnHelpContext != NULL)
  128. *pnHelpContext = 0;
  129. lstrcpyn(lpszError, m_strError, nMaxError-1);
  130. lpszError[nMaxError-1] = '\0';
  131. return TRUE;
  132. }
  133. #ifdef _DEBUG
  134. void CDBException::TraceErrorMessage(LPCTSTR szTrace) const
  135. {
  136. CString strTrace = szTrace;
  137. if (strTrace.GetLength() <= 80)
  138. TRACE1("%s\n", strTrace);
  139. else
  140. {
  141. // Display 80 chars/line
  142. while (strTrace.GetLength() > 80)
  143. {
  144. TRACE1("%s\n", strTrace.Left(80));
  145. strTrace = strTrace.Right(strTrace.GetLength() - 80);
  146. }
  147. TRACE1("%s\n", strTrace);
  148. }
  149. }
  150. #endif // _DEBUG
  151. void CDBException::Empty()
  152. {
  153. m_strError.Empty();
  154. m_strStateNativeOrigin.Empty();
  155. }
  156. /////////////////////////////////////////////////////////////////////////////
  157. // Global helper
  158. HENV AFXAPI AfxGetHENV()
  159. {
  160. _AFX_DB_STATE* pDbState = _afxDbState;
  161. return pDbState->m_henvAllConnections;
  162. }
  163. /////////////////////////////////////////////////////////////////////////////
  164. // CDatabase implementation
  165. CDatabase::CDatabase()
  166. {
  167. m_hdbc = SQL_NULL_HDBC;
  168. m_hstmt = SQL_NULL_HSTMT;
  169. m_bUpdatable = FALSE;
  170. m_bTransactions = FALSE;
  171. DEBUG_ONLY(m_bTransactionPending = FALSE);
  172. m_dwLoginTimeout = DEFAULT_LOGIN_TIMEOUT;
  173. m_dwQueryTimeout = DEFAULT_QUERY_TIMEOUT;
  174. m_bStripTrailingSpaces = FALSE;
  175. m_bIncRecordCountOnAdd = FALSE;
  176. m_bAddForUpdate = FALSE;
  177. }
  178. CDatabase::~CDatabase()
  179. {
  180. ASSERT_VALID(this);
  181. Free();
  182. }
  183. BOOL CDatabase::Open(LPCTSTR lpszDSN, BOOL bExclusive,
  184. BOOL bReadonly, LPCTSTR lpszConnect, BOOL bUseCursorLib)
  185. {
  186. ASSERT(lpszDSN == NULL || AfxIsValidString(lpszDSN));
  187. ASSERT(lpszConnect == NULL || AfxIsValidString(lpszConnect));
  188. CString strConnect;
  189. if (lpszConnect != NULL)
  190. strConnect = lpszConnect;
  191. // For VB/Access compatibility, require "ODBC;" (or "odbc;")
  192. // prefix to the connect string
  193. if (_tcsnicmp(strConnect, _afxODBCTrail, lstrlen(_afxODBCTrail)) != 0)
  194. {
  195. TRACE0("Error: Missing 'ODBC' prefix on connect string.\n");
  196. return FALSE;
  197. }
  198. // Strip "ODBC;"
  199. strConnect = strConnect.Right(strConnect.GetLength()
  200. - lstrlen(_afxODBCTrail));
  201. if (lpszDSN != NULL && lstrlen(lpszDSN) != 0)
  202. {
  203. // Append "DSN=" lpszDSN
  204. strConnect += _T(";DSN=");
  205. strConnect += lpszDSN;
  206. }
  207. DWORD dwOptions = 0;
  208. if (bExclusive)
  209. dwOptions |= openExclusive;
  210. if (bReadonly)
  211. dwOptions |= openReadOnly;
  212. if (bUseCursorLib)
  213. dwOptions |= useCursorLib;
  214. return OpenEx(strConnect, dwOptions);
  215. }
  216. BOOL CDatabase::OpenEx(LPCTSTR lpszConnectString, DWORD dwOptions)
  217. {
  218. ASSERT_VALID(this);
  219. ASSERT(lpszConnectString == NULL || AfxIsValidString(lpszConnectString));
  220. ASSERT(!(dwOptions & noOdbcDialog && dwOptions & forceOdbcDialog));
  221. // Exclusive access not supported.
  222. ASSERT(!(dwOptions & openExclusive));
  223. m_bUpdatable = !(dwOptions & openReadOnly);
  224. TRY
  225. {
  226. m_strConnect = lpszConnectString;
  227. // Allocate the HDBC and make connection
  228. AllocConnect(dwOptions);
  229. if(!Connect(dwOptions))
  230. return FALSE;
  231. // Verify support for required functionality and cache info
  232. VerifyConnect();
  233. GetConnectInfo();
  234. }
  235. CATCH_ALL(e)
  236. {
  237. Free();
  238. THROW_LAST();
  239. }
  240. END_CATCH_ALL
  241. return TRUE;
  242. }
  243. void CDatabase::ExecuteSQL(LPCTSTR lpszSQL)
  244. {
  245. USES_CONVERSION;
  246. RETCODE nRetCode;
  247. HSTMT hstmt;
  248. ASSERT_VALID(this);
  249. ASSERT(AfxIsValidString(lpszSQL));
  250. AFX_SQL_SYNC(::SQLAllocStmt(m_hdbc, &hstmt));
  251. if (!CheckHstmt(nRetCode, hstmt))
  252. AfxThrowDBException(nRetCode, this, hstmt);
  253. TRY
  254. {
  255. OnSetOptions(hstmt);
  256. // Give derived CDatabase classes option to use parameters
  257. BindParameters(hstmt);
  258. AFX_ODBC_CALL(::SQLExecDirect(hstmt,
  259. (UCHAR*)T2A((LPTSTR)lpszSQL), SQL_NTS));
  260. if (!CheckHstmt(nRetCode, hstmt))
  261. AfxThrowDBException(nRetCode, this, hstmt);
  262. else
  263. {
  264. do
  265. {
  266. SWORD nResultColumns;
  267. AFX_ODBC_CALL(::SQLNumResultCols(hstmt, &nResultColumns));
  268. if (nResultColumns != 0)
  269. {
  270. do
  271. {
  272. AFX_ODBC_CALL(::SQLFetch(hstmt));
  273. } while (CheckHstmt(nRetCode, hstmt) &&
  274. nRetCode != SQL_NO_DATA_FOUND);
  275. }
  276. AFX_ODBC_CALL(::SQLMoreResults(hstmt));
  277. } while (CheckHstmt(nRetCode, hstmt) &&
  278. nRetCode != SQL_NO_DATA_FOUND);
  279. }
  280. }
  281. CATCH_ALL(e)
  282. {
  283. ::SQLCancel(hstmt);
  284. AFX_SQL_SYNC(::SQLFreeStmt(hstmt, SQL_DROP));
  285. THROW_LAST();
  286. }
  287. END_CATCH_ALL
  288. AFX_SQL_SYNC(::SQLFreeStmt(hstmt, SQL_DROP));
  289. }
  290. // Shutdown pending query for CDatabase's private m_hstmt
  291. void CDatabase::Cancel()
  292. {
  293. ASSERT_VALID(this);
  294. ASSERT(m_hdbc != SQL_NULL_HDBC);
  295. ::SQLCancel(m_hstmt);
  296. }
  297. // Disconnect connection
  298. void CDatabase::Close()
  299. {
  300. ASSERT_VALID(this);
  301. // Close any open recordsets
  302. AfxLockGlobals(CRIT_ODBC);
  303. TRY
  304. {
  305. while (!m_listRecordsets.IsEmpty())
  306. {
  307. CRecordset* pSet = (CRecordset*)m_listRecordsets.GetHead();
  308. pSet->Close(); // will implicitly remove from list
  309. pSet->m_pDatabase = NULL;
  310. }
  311. }
  312. CATCH_ALL(e)
  313. {
  314. AfxUnlockGlobals(CRIT_ODBC);
  315. THROW_LAST();
  316. }
  317. END_CATCH_ALL
  318. AfxUnlockGlobals(CRIT_ODBC);
  319. if (m_hdbc != SQL_NULL_HDBC)
  320. {
  321. RETCODE nRetCode;
  322. AFX_SQL_SYNC(::SQLDisconnect(m_hdbc));
  323. AFX_SQL_SYNC(::SQLFreeConnect(m_hdbc));
  324. m_hdbc = SQL_NULL_HDBC;
  325. _AFX_DB_STATE* pDbState = _afxDbState;
  326. AfxLockGlobals(CRIT_ODBC);
  327. ASSERT(pDbState->m_nAllocatedConnections != 0);
  328. pDbState->m_nAllocatedConnections--;
  329. AfxUnlockGlobals(CRIT_ODBC);
  330. }
  331. }
  332. // Silently disconnect and free all ODBC resources. Don't throw any exceptions
  333. void CDatabase::Free()
  334. {
  335. ASSERT_VALID(this);
  336. // Trap failures upon close
  337. TRY
  338. {
  339. Close();
  340. }
  341. CATCH_ALL(e)
  342. {
  343. // Nothing we can do
  344. TRACE0("Error: exception by CDatabase::Close() ignored in CDatabase::Free().\n");
  345. DELETE_EXCEPTION(e);
  346. }
  347. END_CATCH_ALL
  348. // free henv if refcount goes to 0
  349. _AFX_DB_STATE* pDbState = _afxDbState;
  350. AfxLockGlobals(CRIT_ODBC);
  351. if (pDbState->m_henvAllConnections != SQL_NULL_HENV)
  352. {
  353. ASSERT(pDbState->m_nAllocatedConnections >= 0);
  354. if (pDbState->m_nAllocatedConnections == 0)
  355. {
  356. // free last connection - release HENV
  357. RETCODE nRetCodeEnv = ::SQLFreeEnv(pDbState->m_henvAllConnections);
  358. #ifdef _DEBUG
  359. if (nRetCodeEnv != SQL_SUCCESS)
  360. // Nothing we can do
  361. TRACE0("Error: SQLFreeEnv failure ignored in CDatabase::Free().\n");
  362. #endif
  363. pDbState->m_henvAllConnections = SQL_NULL_HENV;
  364. }
  365. }
  366. AfxUnlockGlobals(CRIT_ODBC);
  367. }
  368. void CDatabase::OnSetOptions(HSTMT hstmt)
  369. {
  370. RETCODE nRetCode;
  371. ASSERT_VALID(this);
  372. ASSERT(m_hdbc != SQL_NULL_HDBC);
  373. if (m_dwQueryTimeout != -1)
  374. {
  375. // Attempt to set query timeout. Ignore failure
  376. AFX_SQL_SYNC(::SQLSetStmtOption(hstmt, SQL_QUERY_TIMEOUT,
  377. m_dwQueryTimeout));
  378. if (!Check(nRetCode))
  379. // don't attempt it again
  380. m_dwQueryTimeout = (DWORD)-1;
  381. }
  382. }
  383. CString CDatabase::GetDatabaseName() const
  384. {
  385. ASSERT_VALID(this);
  386. ASSERT(m_hdbc != SQL_NULL_HDBC);
  387. char szName[MAX_TNAME_LEN];
  388. SWORD nResult;
  389. RETCODE nRetCode;
  390. AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DATABASE_NAME,
  391. szName, _countof(szName), &nResult));
  392. if (!Check(nRetCode))
  393. szName[0] = '\0';
  394. return szName;
  395. }
  396. BOOL CDatabase::BeginTrans()
  397. {
  398. ASSERT_VALID(this);
  399. ASSERT(m_hdbc != SQL_NULL_HDBC);
  400. if (!m_bTransactions)
  401. return FALSE;
  402. // Only 1 level of transactions supported
  403. #ifdef _DEBUG
  404. ASSERT(!m_bTransactionPending);
  405. #endif
  406. RETCODE nRetCode;
  407. AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_AUTOCOMMIT,
  408. SQL_AUTOCOMMIT_OFF));
  409. DEBUG_ONLY(m_bTransactionPending = TRUE);
  410. return Check(nRetCode);
  411. }
  412. BOOL CDatabase::CommitTrans()
  413. {
  414. ASSERT_VALID(this);
  415. ASSERT(m_hdbc != SQL_NULL_HDBC);
  416. if (!m_bTransactions)
  417. return FALSE;
  418. // BeginTrans must be called first
  419. #ifdef _DEBUG
  420. ASSERT(m_bTransactionPending);
  421. #endif
  422. _AFX_DB_STATE* pDbState = _afxDbState;
  423. RETCODE nRetCode;
  424. AFX_SQL_SYNC(::SQLTransact(pDbState->m_henvAllConnections, m_hdbc, SQL_COMMIT));
  425. BOOL bSuccess = Check(nRetCode);
  426. // Turn back on auto commit
  427. AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_AUTOCOMMIT,
  428. SQL_AUTOCOMMIT_ON));
  429. DEBUG_ONLY(m_bTransactionPending = FALSE);
  430. return bSuccess;
  431. }
  432. BOOL CDatabase::Rollback()
  433. {
  434. ASSERT_VALID(this);
  435. ASSERT(m_hdbc != SQL_NULL_HDBC);
  436. if (!m_bTransactions)
  437. return FALSE;
  438. // BeginTrans must be called first
  439. #ifdef _DEBUG
  440. ASSERT(m_bTransactionPending);
  441. #endif
  442. _AFX_DB_STATE* pDbState = _afxDbState;
  443. RETCODE nRetCode;
  444. AFX_SQL_SYNC(::SQLTransact(pDbState->m_henvAllConnections, m_hdbc, SQL_ROLLBACK));
  445. BOOL bSuccess = Check(nRetCode);
  446. // Turn back on auto commit
  447. AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_AUTOCOMMIT,
  448. SQL_AUTOCOMMIT_ON));
  449. DEBUG_ONLY(m_bTransactionPending = FALSE);
  450. return bSuccess;
  451. }
  452. // Screen for errors.
  453. BOOL CDatabase::Check(RETCODE nRetCode) const
  454. {
  455. return CheckHstmt(nRetCode, SQL_NULL_HSTMT);
  456. }
  457. BOOL CDatabase::CheckHstmt(RETCODE nRetCode, HSTMT hstmt) const
  458. {
  459. ASSERT_VALID(this);
  460. UNUSED(hstmt);
  461. switch (nRetCode)
  462. {
  463. case SQL_SUCCESS_WITH_INFO:
  464. #ifdef _DEBUG
  465. if (afxTraceFlags & traceDatabase)
  466. {
  467. CDBException e(nRetCode);
  468. TRACE0("Warning: ODBC Success With Info, ");
  469. e.BuildErrorString((CDatabase*)this, hstmt);
  470. }
  471. #endif // _DEBUG
  472. // Fall through
  473. case SQL_SUCCESS:
  474. case SQL_NO_DATA_FOUND:
  475. return TRUE;
  476. }
  477. return FALSE;
  478. }
  479. //////////////////////////////////////////////////////////////////////////////
  480. // CDatabase internal functions
  481. //Replace brackets in SQL string with SQL_IDENTIFIER_QUOTE_CHAR
  482. void CDatabase::ReplaceBrackets(LPTSTR lpchSQL)
  483. {
  484. BOOL bInLiteral = FALSE;
  485. LPTSTR lpchNewSQL = lpchSQL;
  486. while (*lpchSQL != '\0')
  487. {
  488. if (*lpchSQL == _afxLiteralSeparator)
  489. {
  490. // Handle escaped literal
  491. if (*_tcsinc(lpchSQL) == _afxLiteralSeparator)
  492. {
  493. *lpchNewSQL = *lpchSQL;
  494. lpchSQL = _tcsinc(lpchSQL);
  495. lpchNewSQL = _tcsinc(lpchNewSQL);
  496. }
  497. else
  498. bInLiteral = !bInLiteral;
  499. *lpchNewSQL = *lpchSQL;
  500. }
  501. else if (!bInLiteral && (*lpchSQL == '['))
  502. {
  503. if (*_tcsinc(lpchSQL) == '[')
  504. {
  505. // Handle escaped left bracket by inserting one '['
  506. *lpchNewSQL = *lpchSQL;
  507. lpchSQL = _tcsinc(lpchSQL);
  508. }
  509. else
  510. *lpchNewSQL = m_chIDQuoteChar;
  511. }
  512. else if (!bInLiteral && (*lpchSQL == ']'))
  513. {
  514. if (*_tcsinc(lpchSQL) == ']')
  515. {
  516. // Handle escaped right bracket by inserting one ']'
  517. *lpchNewSQL = *lpchSQL;
  518. lpchSQL = _tcsinc(lpchSQL);
  519. }
  520. else
  521. *lpchNewSQL = m_chIDQuoteChar;
  522. }
  523. else
  524. *lpchNewSQL = *lpchSQL;
  525. lpchSQL = _tcsinc(lpchSQL);
  526. lpchNewSQL = _tcsinc(lpchNewSQL);
  527. }
  528. }
  529. // Allocate an henv (first time called) and hdbc
  530. void CDatabase::AllocConnect(DWORD dwOptions)
  531. {
  532. ASSERT_VALID(this);
  533. if (m_hdbc != SQL_NULL_HDBC)
  534. return;
  535. _AFX_DB_STATE* pDbState = _afxDbState;
  536. RETCODE nRetCode;
  537. AfxLockGlobals(CRIT_ODBC);
  538. if (pDbState->m_henvAllConnections == SQL_NULL_HENV)
  539. {
  540. ASSERT(pDbState->m_nAllocatedConnections == 0);
  541. // need to allocate an environment for first connection
  542. AFX_SQL_SYNC(::SQLAllocEnv(&pDbState->m_henvAllConnections));
  543. if (!Check(nRetCode))
  544. {
  545. AfxUnlockGlobals(CRIT_ODBC);
  546. AfxThrowMemoryException(); // fatal
  547. }
  548. }
  549. ASSERT(pDbState->m_henvAllConnections != SQL_NULL_HENV);
  550. AFX_SQL_SYNC(::SQLAllocConnect(pDbState->m_henvAllConnections, &m_hdbc));
  551. if (!Check(nRetCode))
  552. {
  553. AfxUnlockGlobals(CRIT_ODBC);
  554. ThrowDBException(nRetCode); // fatal
  555. }
  556. pDbState->m_nAllocatedConnections++; // allocated at least
  557. AfxUnlockGlobals(CRIT_ODBC);
  558. #ifdef _DEBUG
  559. if (bTraceSql)
  560. {
  561. ::SQLSetConnectOption(m_hdbc, SQL_OPT_TRACEFILE,
  562. (DWORD)"odbccall.txt");
  563. ::SQLSetConnectOption(m_hdbc, SQL_OPT_TRACE, 1);
  564. }
  565. #endif // _DEBUG
  566. AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_LOGIN_TIMEOUT,
  567. m_dwLoginTimeout));
  568. #ifdef _DEBUG
  569. if (nRetCode != SQL_SUCCESS && nRetCode != SQL_SUCCESS_WITH_INFO &&
  570. (afxTraceFlags & traceDatabase))
  571. TRACE0("Warning: Failure setting login timeout.\n");
  572. #endif
  573. if (!m_bUpdatable)
  574. {
  575. AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_ACCESS_MODE,
  576. SQL_MODE_READ_ONLY));
  577. #ifdef _DEBUG
  578. if (nRetCode != SQL_SUCCESS && nRetCode != SQL_SUCCESS_WITH_INFO &&
  579. (afxTraceFlags & traceDatabase))
  580. TRACE0("Warning: Failure setting read only access mode.\n");
  581. #endif
  582. }
  583. // Turn on cursor lib support
  584. if (dwOptions & useCursorLib)
  585. {
  586. AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc,
  587. SQL_ODBC_CURSORS, SQL_CUR_USE_ODBC));
  588. // With cursor library added records immediately in result set
  589. m_bIncRecordCountOnAdd = TRUE;
  590. }
  591. }
  592. BOOL CDatabase::Connect(DWORD dwOptions)
  593. {
  594. USES_CONVERSION;
  595. HWND hWndTop;
  596. HWND hWnd = CWnd::GetSafeOwner_(NULL, &hWndTop);
  597. if (hWnd == NULL)
  598. hWnd = ::GetDesktopWindow();
  599. UCHAR szConnectOutput[MAX_CONNECT_LEN];
  600. RETCODE nRetCode;
  601. SWORD nResult;
  602. UWORD wConnectOption = SQL_DRIVER_COMPLETE;
  603. if (dwOptions & noOdbcDialog)
  604. wConnectOption = SQL_DRIVER_NOPROMPT;
  605. else if (dwOptions & forceOdbcDialog)
  606. wConnectOption = SQL_DRIVER_PROMPT;
  607. AFX_SQL_SYNC(::SQLDriverConnect(m_hdbc, hWnd,
  608. (UCHAR*)T2A((LPTSTR)(LPCTSTR)m_strConnect), SQL_NTS,
  609. szConnectOutput, _countof(szConnectOutput),
  610. &nResult, wConnectOption));
  611. if (hWndTop != NULL)
  612. ::EnableWindow(hWndTop, TRUE);
  613. // If user hit 'Cancel'
  614. if (nRetCode == SQL_NO_DATA_FOUND)
  615. {
  616. Free();
  617. return FALSE;
  618. }
  619. if (!Check(nRetCode))
  620. {
  621. #ifdef _DEBUG
  622. if (hWnd == NULL)
  623. TRACE0("Error: No default window (AfxGetApp()->m_pMainWnd) for SQLDriverConnect.\n");
  624. #endif
  625. ThrowDBException(nRetCode);
  626. }
  627. // Connect strings must have "ODBC;"
  628. m_strConnect = _afxODBCTrail;
  629. // Save connect string returned from ODBC
  630. m_strConnect += (char*)szConnectOutput;
  631. return TRUE;
  632. }
  633. void CDatabase::VerifyConnect()
  634. {
  635. RETCODE nRetCode;
  636. SWORD nResult;
  637. SWORD nAPIConformance;
  638. AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_ODBC_API_CONFORMANCE,
  639. &nAPIConformance, sizeof(nAPIConformance), &nResult));
  640. if (!Check(nRetCode))
  641. ThrowDBException(nRetCode);
  642. if (nAPIConformance < SQL_OAC_LEVEL1)
  643. ThrowDBException(AFX_SQL_ERROR_API_CONFORMANCE);
  644. SWORD nSQLConformance;
  645. AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_ODBC_SQL_CONFORMANCE,
  646. &nSQLConformance, sizeof(nSQLConformance), &nResult));
  647. if (!Check(nRetCode))
  648. ThrowDBException(nRetCode);
  649. if (nSQLConformance < SQL_OSC_MINIMUM)
  650. ThrowDBException(AFX_SQL_ERROR_SQL_CONFORMANCE);
  651. }
  652. void CDatabase::GetConnectInfo()
  653. {
  654. RETCODE nRetCode;
  655. SWORD nResult;
  656. // Reset the database update options
  657. m_dwUpdateOptions = 0;
  658. // Check for SQLSetPos support
  659. UDWORD dwDriverPosOperations;
  660. AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_POS_OPERATIONS,
  661. &dwDriverPosOperations, sizeof(dwDriverPosOperations), &nResult));
  662. if (Check(nRetCode) &&
  663. (dwDriverPosOperations & SQL_POS_UPDATE) &&
  664. (dwDriverPosOperations & SQL_POS_DELETE) &&
  665. (dwDriverPosOperations & SQL_POS_ADD))
  666. m_dwUpdateOptions |= AFX_SQL_SETPOSUPDATES;
  667. // Check for positioned update SQL support
  668. UDWORD dwPositionedStatements;
  669. AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_POSITIONED_STATEMENTS,
  670. &dwPositionedStatements, sizeof(dwPositionedStatements),
  671. &nResult));
  672. if (Check(nRetCode) &&
  673. (dwPositionedStatements & SQL_PS_POSITIONED_DELETE) &&
  674. (dwPositionedStatements & SQL_PS_POSITIONED_UPDATE))
  675. m_dwUpdateOptions |= AFX_SQL_POSITIONEDSQL;
  676. // Check for transaction support
  677. SWORD nTxnCapable;
  678. AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_TXN_CAPABLE, &nTxnCapable,
  679. sizeof(nTxnCapable), &nResult));
  680. if (Check(nRetCode) && nTxnCapable != SQL_TC_NONE)
  681. m_bTransactions = TRUE;
  682. // Cache the effect of transactions on cursors
  683. AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_CURSOR_COMMIT_BEHAVIOR,
  684. &m_nCursorCommitBehavior, sizeof(m_nCursorCommitBehavior),
  685. &nResult));
  686. if (!Check(nRetCode))
  687. m_nCursorCommitBehavior = SQL_ERROR;
  688. AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_CURSOR_ROLLBACK_BEHAVIOR,
  689. &m_nCursorRollbackBehavior, sizeof(m_nCursorRollbackBehavior),
  690. &nResult));
  691. if (!Check(nRetCode))
  692. m_nCursorRollbackBehavior = SQL_ERROR;
  693. // Cache bookmark attributes
  694. AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_BOOKMARK_PERSISTENCE,
  695. &m_dwBookmarkAttributes, sizeof(m_dwBookmarkAttributes),
  696. &nResult));
  697. Check(nRetCode);
  698. // Check for SQLGetData support req'd by RFX_LongBinary
  699. UDWORD dwGetDataExtensions;
  700. AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_GETDATA_EXTENSIONS,
  701. &dwGetDataExtensions, sizeof(dwGetDataExtensions),
  702. &nResult));
  703. if (!Check(nRetCode))
  704. dwGetDataExtensions = 0;
  705. if (dwGetDataExtensions & SQL_GD_BOUND)
  706. m_dwUpdateOptions |= AFX_SQL_GDBOUND;
  707. if (m_bUpdatable)
  708. {
  709. // Make sure data source is Updatable
  710. char szReadOnly[10];
  711. AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DATA_SOURCE_READ_ONLY,
  712. szReadOnly, _countof(szReadOnly), &nResult));
  713. if (Check(nRetCode) && nResult == 1)
  714. m_bUpdatable = !(lstrcmpA(szReadOnly, "Y") == 0);
  715. else
  716. m_bUpdatable = FALSE;
  717. #ifdef _DEBUG
  718. if (!m_bUpdatable && (afxTraceFlags & traceDatabase))
  719. TRACE0("Warning: data source is readonly.\n");
  720. #endif
  721. }
  722. else
  723. {
  724. // Make data source is !Updatable
  725. AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc,
  726. SQL_ACCESS_MODE, SQL_MODE_READ_ONLY));
  727. }
  728. // Cache the quote char to use when constructing SQL
  729. char szIDQuoteChar[2];
  730. AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_IDENTIFIER_QUOTE_CHAR,
  731. szIDQuoteChar, _countof(szIDQuoteChar), &nResult));
  732. if (Check(nRetCode) && nResult == 1)
  733. m_chIDQuoteChar = szIDQuoteChar[0];
  734. else
  735. m_chIDQuoteChar = ' ';
  736. #ifdef _DEBUG
  737. if (afxTraceFlags & traceDatabase)
  738. {
  739. char szInfo[64];
  740. AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DBMS_NAME,
  741. szInfo, _countof(szInfo), &nResult));
  742. if (Check(nRetCode))
  743. {
  744. CString strInfo = szInfo;
  745. TRACE1("DBMS: %s\n", strInfo);
  746. AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DBMS_VER,
  747. szInfo, _countof(szInfo), &nResult));
  748. if (Check(nRetCode))
  749. {
  750. strInfo = szInfo;
  751. TRACE1(", Version: %s\n", strInfo);
  752. }
  753. }
  754. }
  755. #endif // _DEBUG
  756. }
  757. void CDatabase::BindParameters(HSTMT /* hstmt */)
  758. {
  759. // Must override and call SQLBindParameter directly
  760. }
  761. //////////////////////////////////////////////////////////////////////////////
  762. // CDatabase diagnostics
  763. #ifdef _DEBUG
  764. void CDatabase::AssertValid() const
  765. {
  766. CObject::AssertValid();
  767. }
  768. void CDatabase::Dump(CDumpContext& dc) const
  769. {
  770. CObject::Dump(dc);
  771. dc << "m_hdbc = " << m_hdbc;
  772. dc << "\nm_strConnect = " << m_strConnect;
  773. dc << "\nm_bUpdatable = " << m_bUpdatable;
  774. dc << "\nm_bTransactions = " << m_bTransactions;
  775. dc << "\nm_bTransactionPending = " << m_bTransactionPending;
  776. dc << "\nm_dwLoginTimeout = " << m_dwLoginTimeout;
  777. dc << "\nm_dwQueryTimeout = " << m_dwQueryTimeout;
  778. if (dc.GetDepth() > 0)
  779. {
  780. _AFX_DB_STATE* pDbState = _afxDbState;
  781. dc << "\nwith env:";
  782. dc << "\n\tnAllocated = " << pDbState->m_nAllocatedConnections;
  783. dc << "\n\thenvAllConnections = " << pDbState->m_henvAllConnections;
  784. }
  785. dc << "\n";
  786. }
  787. #endif // _DEBUG
  788. //////////////////////////////////////////////////////////////////////////////
  789. // CRecordset helpers
  790. void AFXAPI AfxSetCurrentRecord(long* plCurrentRecord, long nRows, RETCODE nRetCode);
  791. void AFXAPI AfxSetRecordCount(long* plRecordCount, long lCurrentRecord,
  792. BOOL bEOFSeen, RETCODE nRetCode);
  793. //////////////////////////////////////////////////////////////////////////////
  794. // CRecordset
  795. CRecordset::CRecordset(CDatabase* pDatabase)
  796. {
  797. ASSERT(pDatabase == NULL || AfxIsValidAddress(pDatabase, sizeof(CDatabase)));
  798. m_pDatabase = pDatabase;
  799. m_nOpenType = snapshot;
  800. m_lOpen = AFX_RECORDSET_STATUS_UNKNOWN;
  801. m_nEditMode = noMode;
  802. m_nDefaultType = snapshot;
  803. m_dwOptions = none;
  804. m_bAppendable = FALSE;
  805. m_bUpdatable = FALSE;
  806. m_bScrollable = FALSE;
  807. m_bRecordsetDb = FALSE;
  808. m_bRebindParams = FALSE;
  809. m_bLongBinaryColumns = FALSE;
  810. m_nLockMode = optimistic;
  811. m_dwInitialGetDataLen = 0;
  812. m_rgODBCFieldInfos = NULL;
  813. m_rgFieldInfos = NULL;
  814. m_rgRowStatus = NULL;
  815. m_dwRowsetSize = 25;
  816. m_dwAllocatedRowsetSize = 0;
  817. m_nFields = 0;
  818. m_nParams = 0;
  819. m_nFieldsBound = 0;
  820. m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
  821. m_lRecordCount = 0;
  822. m_bUseUpdateSQL = FALSE;
  823. m_bUseODBCCursorLib = FALSE;
  824. m_nResultCols = -1;
  825. m_bCheckCacheForDirtyFields = TRUE;
  826. m_pbFieldFlags = NULL;
  827. m_pbParamFlags = NULL;
  828. m_plParamLength = NULL;
  829. m_pvFieldProxy = NULL;
  830. m_pvParamProxy = NULL;
  831. m_nProxyFields = 0;
  832. m_nProxyParams = 0;
  833. m_hstmtUpdate = SQL_NULL_HSTMT;
  834. m_hstmt = SQL_NULL_HSTMT;
  835. if (m_pDatabase != NULL && m_pDatabase->IsOpen())
  836. {
  837. ASSERT_VALID(m_pDatabase);
  838. TRY
  839. {
  840. RETCODE nRetCode;
  841. AFX_SQL_SYNC(::SQLAllocStmt(m_pDatabase->m_hdbc, &m_hstmt));
  842. if (!Check(nRetCode))
  843. ThrowDBException(SQL_INVALID_HANDLE);
  844. // Add to list of CRecordsets with alloced hstmts
  845. AfxLockGlobals(CRIT_ODBC);
  846. TRY
  847. {
  848. m_pDatabase->m_listRecordsets.AddHead(this);
  849. }
  850. CATCH_ALL(e)
  851. {
  852. AfxUnlockGlobals(CRIT_ODBC);
  853. THROW_LAST();
  854. }
  855. END_CATCH_ALL
  856. AfxUnlockGlobals(CRIT_ODBC);
  857. }
  858. CATCH_ALL(e)
  859. {
  860. ASSERT(m_hstmt == SQL_NULL_HSTMT);
  861. DELETE_EXCEPTION(e);
  862. }
  863. END_CATCH_ALL
  864. }
  865. }
  866. CRecordset::~CRecordset()
  867. {
  868. ASSERT_VALID(this);
  869. TRY
  870. {
  871. if (m_hstmt != NULL)
  872. {
  873. #ifdef _DEBUG
  874. if (m_dwOptions & useMultiRowFetch && afxTraceFlags & traceDatabase)
  875. {
  876. TRACE0("\nWARNING: Close called implicitly from destructor.");
  877. TRACE0("\nUse of multi row fetch requires explicit call");
  878. TRACE0("\nto Close or memory leaks will result.\n");
  879. }
  880. #endif
  881. Close();
  882. }
  883. if (m_bRecordsetDb)
  884. delete m_pDatabase;
  885. m_pDatabase = NULL;
  886. }
  887. CATCH_ALL(e)
  888. {
  889. // Nothing we can do
  890. TRACE0("Error: Exception ignored in ~CRecordset().\n");
  891. DELETE_EXCEPTION(e);
  892. }
  893. END_CATCH_ALL
  894. }
  895. BOOL CRecordset::Open(UINT nOpenType, LPCTSTR lpszSQL, DWORD dwOptions)
  896. {
  897. ASSERT(!IsOpen());
  898. ASSERT_VALID(this);
  899. ASSERT(lpszSQL == NULL || AfxIsValidString(lpszSQL));
  900. ASSERT(nOpenType == AFX_DB_USE_DEFAULT_TYPE ||
  901. nOpenType == dynaset || nOpenType == snapshot ||
  902. nOpenType == forwardOnly || nOpenType == dynamic);
  903. ASSERT(!(dwOptions & readOnly && dwOptions & appendOnly));
  904. // Can only use optimizeBulkAdd with appendOnly recordsets
  905. ASSERT((dwOptions & optimizeBulkAdd && dwOptions & appendOnly) ||
  906. !(dwOptions & optimizeBulkAdd));
  907. // forwardOnly recordsets have limited functionality
  908. ASSERT(!(nOpenType == forwardOnly && dwOptions & skipDeletedRecords));
  909. // Cache state info and allocate hstmt
  910. SetState(nOpenType, lpszSQL, dwOptions);
  911. if(!AllocHstmt())
  912. return FALSE;
  913. // Check if bookmarks upported (CanBookmark depends on open DB)
  914. ASSERT(dwOptions & useBookmarks ? CanBookmark() : TRUE);
  915. TRY
  916. {
  917. OnSetOptions(m_hstmt);
  918. // Allocate the field/param status arrays, if necessary
  919. BOOL bUnbound = FALSE;
  920. if (m_nFields > 0 || m_nParams > 0)
  921. AllocStatusArrays();
  922. else
  923. bUnbound = TRUE;
  924. // Build SQL and prep/execute or just execute direct
  925. BuildSQL(lpszSQL);
  926. PrepareAndExecute();
  927. // Cache some field info and prepare the rowset
  928. AllocAndCacheFieldInfo();
  929. AllocRowset();
  930. // If late binding, still need to allocate status arrays
  931. if (bUnbound && (m_nFields > 0 || m_nParams > 0))
  932. AllocStatusArrays();
  933. // Give derived classes a call before binding
  934. PreBindFields();
  935. // Fetch the first row of data
  936. MoveNext();
  937. // If EOF, then result set empty, so set BOF as well
  938. m_bBOF = m_bEOF;
  939. }
  940. CATCH_ALL(e)
  941. {
  942. Close();
  943. THROW_LAST();
  944. }
  945. END_CATCH_ALL
  946. return TRUE;
  947. }
  948. void CRecordset::Close()
  949. {
  950. ASSERT_VALID(this);
  951. // Can't close if database has been deleted
  952. ASSERT(m_pDatabase != NULL);
  953. // This will force a requery for cursor name if reopened.
  954. m_strCursorName.Empty();
  955. if (m_rgFieldInfos != NULL &&
  956. m_nFields > 0 && m_bCheckCacheForDirtyFields)
  957. {
  958. FreeDataCache();
  959. }
  960. FreeRowset();
  961. m_nEditMode = noMode;
  962. delete [] m_rgFieldInfos;
  963. m_rgFieldInfos = NULL;
  964. delete [] m_rgODBCFieldInfos;
  965. m_rgODBCFieldInfos = NULL;
  966. delete [] m_pbFieldFlags;
  967. m_pbFieldFlags = NULL;
  968. delete [] m_pbParamFlags;
  969. m_pbParamFlags = NULL;
  970. if (m_pvFieldProxy != NULL)
  971. {
  972. for (UINT nField = 0; nField < m_nProxyFields; nField++)
  973. delete m_pvFieldProxy[nField];
  974. delete [] m_pvFieldProxy;
  975. m_pvFieldProxy = NULL;
  976. m_nProxyFields = 0;
  977. }
  978. if (m_pvParamProxy != NULL)
  979. {
  980. for (UINT nParam = 0; nParam < m_nProxyParams; nParam++)
  981. delete m_pvParamProxy[nParam];
  982. delete [] m_pvParamProxy;
  983. m_pvParamProxy = NULL;
  984. m_nProxyParams = 0;
  985. }
  986. delete [] m_plParamLength;
  987. m_plParamLength = NULL;
  988. RETCODE nRetCode;
  989. if (m_hstmt != SQL_NULL_HSTMT)
  990. {
  991. AFX_SQL_SYNC(::SQLFreeStmt(m_hstmt, SQL_DROP));
  992. m_hstmt = SQL_NULL_HSTMT;
  993. }
  994. if (m_hstmtUpdate != SQL_NULL_HSTMT)
  995. {
  996. AFX_SQL_SYNC(::SQLFreeStmt(m_hstmtUpdate, SQL_DROP));
  997. m_hstmtUpdate = SQL_NULL_HSTMT;
  998. }
  999. // Remove CRecordset from CDatabase's list
  1000. AfxLockGlobals(CRIT_ODBC);
  1001. TRY
  1002. {
  1003. POSITION pos = m_pDatabase->m_listRecordsets.Find(this);
  1004. if (pos != NULL)
  1005. m_pDatabase->m_listRecordsets.RemoveAt(pos);
  1006. #ifdef _DEBUG
  1007. else if (afxTraceFlags & traceDatabase)
  1008. TRACE0("WARNING: CRecordset not found in m_pDatabase->m_listRecordsets.\n");
  1009. #endif
  1010. }
  1011. CATCH_ALL(e)
  1012. {
  1013. AfxUnlockGlobals(CRIT_ODBC);
  1014. THROW_LAST();
  1015. }
  1016. END_CATCH_ALL
  1017. AfxUnlockGlobals(CRIT_ODBC);
  1018. m_lOpen = AFX_RECORDSET_STATUS_CLOSED;
  1019. m_bBOF = TRUE;
  1020. m_bEOF = TRUE;
  1021. m_bDeleted = FALSE;
  1022. m_bAppendable = FALSE;
  1023. m_bUpdatable = FALSE;
  1024. m_bScrollable = FALSE;
  1025. m_bRebindParams = FALSE;
  1026. m_bLongBinaryColumns = FALSE;
  1027. m_nLockMode = optimistic;
  1028. m_nFieldsBound = 0;
  1029. m_nResultCols = -1;
  1030. }
  1031. BOOL CRecordset::IsOpen() const
  1032. // Note: assumes base class CRecordset::Close called
  1033. {
  1034. if (m_hstmt == NULL)
  1035. return FALSE;
  1036. if (m_lOpen == AFX_RECORDSET_STATUS_OPEN)
  1037. return TRUE;
  1038. RETCODE nRetCode;
  1039. SWORD nCols;
  1040. AFX_ODBC_CALL(::SQLNumResultCols(m_hstmt, &nCols));
  1041. if (!Check(nRetCode))
  1042. {
  1043. // If function sequence error, CRecordset not open
  1044. CDBException* e = new CDBException(nRetCode);
  1045. e->BuildErrorString(m_pDatabase, m_hstmt, FALSE);
  1046. if (e->m_strStateNativeOrigin.Find(_afxOutOfSequence) >= 0)
  1047. {
  1048. e->Delete();
  1049. return FALSE;
  1050. }
  1051. else
  1052. {
  1053. #ifdef _DEBUG
  1054. TRACE0("Error: SQLNumResultCols failed during IsOpen().\n");
  1055. e->TraceErrorMessage(e->m_strError);
  1056. e->TraceErrorMessage(e->m_strStateNativeOrigin);
  1057. #endif
  1058. THROW(e);
  1059. }
  1060. }
  1061. BOOL bOpen = FALSE;
  1062. if (nCols != 0)
  1063. bOpen = TRUE;
  1064. return bOpen;
  1065. }
  1066. BOOL CRecordset::IsFieldDirty(void* pv)
  1067. {
  1068. ASSERT_VALID(this);
  1069. ASSERT(!(m_dwOptions & useMultiRowFetch));
  1070. if (m_nFields <= 0)
  1071. {
  1072. ASSERT(FALSE);
  1073. return FALSE;
  1074. }
  1075. // If not in update op fields can't be dirty
  1076. // must compare saved and current values
  1077. if (m_nEditMode == noMode)
  1078. return FALSE;
  1079. // Must compare values to find dirty fields if necessary
  1080. if (m_bCheckCacheForDirtyFields)
  1081. {
  1082. if (m_nEditMode == edit)
  1083. MarkForUpdate();
  1084. else
  1085. MarkForAddNew();
  1086. }
  1087. int nIndex = 0, nIndexEnd;
  1088. if (pv == NULL)
  1089. nIndexEnd = m_nFields - 1;
  1090. else
  1091. {
  1092. // GetBoundFieldIndex returns 1-based index
  1093. nIndex = nIndexEnd = GetBoundFieldIndex(pv) - 1;
  1094. // must be address of field member
  1095. ASSERT(nIndex >= 0);
  1096. }
  1097. BOOL bDirty = FALSE;
  1098. while (nIndex <= nIndexEnd && !bDirty)
  1099. bDirty = IsFieldStatusDirty(nIndex++);
  1100. return bDirty;
  1101. }
  1102. BOOL CRecordset::IsFieldNull(void* pv)
  1103. {
  1104. ASSERT_VALID(this);
  1105. ASSERT(!(m_dwOptions & useMultiRowFetch));
  1106. int nIndex;
  1107. BOOL bRetVal;
  1108. if (pv == NULL)
  1109. {
  1110. bRetVal = FALSE;
  1111. for (nIndex = 0; !bRetVal && nIndex <= int(m_nFields-1); nIndex++)
  1112. bRetVal = IsFieldStatusNull((DWORD) nIndex);
  1113. }
  1114. else
  1115. {
  1116. nIndex = GetBoundFieldIndex(pv) - 1;
  1117. if (nIndex < 0)
  1118. ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
  1119. bRetVal = IsFieldStatusNull((DWORD) nIndex);
  1120. }
  1121. return bRetVal;
  1122. }
  1123. BOOL CRecordset::IsFieldNullable(void* pv)
  1124. {
  1125. ASSERT_VALID(this);
  1126. if (pv == NULL)
  1127. {
  1128. // Must specify valid column name
  1129. ASSERT(FALSE);
  1130. return FALSE;
  1131. }
  1132. int nIndex = GetBoundFieldIndex(pv) - 1;
  1133. if (nIndex < 0)
  1134. ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
  1135. return IsFieldNullable((DWORD)nIndex);
  1136. }
  1137. BOOL CRecordset::CanBookmark() const
  1138. {
  1139. ASSERT_VALID(this);
  1140. ASSERT(m_pDatabase->IsOpen());
  1141. if (!(m_dwOptions & useBookmarks) ||
  1142. (m_nOpenType == forwardOnly && !(m_dwOptions & useExtendedFetch)))
  1143. return FALSE;
  1144. return m_pDatabase->GetBookmarkPersistence() & SQL_BP_SCROLL;
  1145. }
  1146. void CRecordset::Move(long nRows, WORD wFetchType)
  1147. {
  1148. ASSERT_VALID(this);
  1149. ASSERT(m_hstmt != SQL_NULL_HSTMT);
  1150. // First call - fields haven't been bound (m_nFieldsBound will change)
  1151. if (m_nFieldsBound == 0)
  1152. {
  1153. InitRecord();
  1154. ResetCursor();
  1155. }
  1156. if (m_nFieldsBound > 0)
  1157. {
  1158. // Reset field flags - mark all clean, all non-null
  1159. memset(m_pbFieldFlags, 0, m_nFields);
  1160. // Clear any edit mode that was set
  1161. m_nEditMode = noMode;
  1162. }
  1163. // Check scrollability, EOF/BOF status
  1164. CheckRowsetCurrencyStatus(wFetchType, nRows);
  1165. RETCODE nRetCode;
  1166. // Fetch the data, skipping deleted records if necessary
  1167. if ((wFetchType == SQL_FETCH_FIRST ||
  1168. wFetchType == SQL_FETCH_LAST ||
  1169. wFetchType == SQL_FETCH_NEXT ||
  1170. wFetchType == SQL_FETCH_PRIOR ||
  1171. wFetchType == SQL_FETCH_RELATIVE) &&
  1172. m_dwOptions & skipDeletedRecords)
  1173. {
  1174. SkipDeletedRecords(wFetchType, nRows, &m_dwRowsFetched, &nRetCode);
  1175. }
  1176. else
  1177. // Fetch the data and check for errors
  1178. nRetCode = FetchData(wFetchType, nRows, &m_dwRowsFetched);
  1179. // Set currency status and increment the record counters
  1180. SetRowsetCurrencyStatus(nRetCode, wFetchType, nRows, m_dwRowsFetched);
  1181. // Need to fixup bound fields in some cases
  1182. if (m_nFields > 0 && !IsEOF() && !IsBOF() &&
  1183. !(m_dwOptions & useMultiRowFetch))
  1184. {
  1185. Fixups();
  1186. }
  1187. }
  1188. void CRecordset::CheckRowsetError(RETCODE nRetCode)
  1189. {
  1190. if (nRetCode == SQL_SUCCESS_WITH_INFO)
  1191. {
  1192. CDBException e(nRetCode);
  1193. // Build the error string but don't send nuisance output to TRACE window
  1194. e.BuildErrorString(m_pDatabase, m_hstmt, FALSE);
  1195. if (e.m_strStateNativeOrigin.Find(_afxDataTruncated) >= 0)
  1196. {
  1197. // Ignore data truncated warning if binding long binary columns
  1198. // (may mask non-long binary truncation warnings or other warnings)
  1199. if (!((m_pDatabase->m_dwUpdateOptions & AFX_SQL_SETPOSUPDATES) &&
  1200. m_bLongBinaryColumns))
  1201. {
  1202. NO_CPP_EXCEPTION(e.Empty());
  1203. TRACE0("Error: field data truncated during data fetch.\n");
  1204. ThrowDBException(AFX_SQL_ERROR_DATA_TRUNCATED);
  1205. }
  1206. }
  1207. else if (e.m_strStateNativeOrigin.Find(_afxRowFetch) >= 0)
  1208. {
  1209. #ifdef _DEBUG
  1210. TRACE0("Error: fetching row from server.\n");
  1211. e.TraceErrorMessage(e.m_strError);
  1212. e.TraceErrorMessage(e.m_strStateNativeOrigin);
  1213. #endif
  1214. NO_CPP_EXCEPTION(e.Empty());
  1215. ThrowDBException(AFX_SQL_ERROR_ROW_FETCH);
  1216. }
  1217. else
  1218. {
  1219. #ifdef _DEBUG
  1220. // Not a truncation or row fetch warning so send debug output
  1221. if (afxTraceFlags & traceDatabase)
  1222. {
  1223. TRACE0("Warning: ODBC Success With Info,\n");
  1224. e.TraceErrorMessage(e.m_strError);
  1225. e.TraceErrorMessage(e.m_strStateNativeOrigin);
  1226. }
  1227. #endif // _DEBUG
  1228. }
  1229. }
  1230. else if (!Check(nRetCode))
  1231. ThrowDBException(nRetCode);
  1232. }
  1233. void CRecordset::GetBookmark(CDBVariant& varBookmark)
  1234. {
  1235. ASSERT_VALID(this);
  1236. // Validate bookmarks are usable
  1237. if (!(m_dwOptions & useBookmarks))
  1238. ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_ENABLED);
  1239. else if (!CanBookmark())
  1240. ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_SUPPORTED);
  1241. // Currently ODBC only supports 4 byte bookmarks
  1242. // Initialize the variant to a long
  1243. if (varBookmark.m_dwType != DBVT_LONG)
  1244. {
  1245. varBookmark.Clear();
  1246. varBookmark.m_dwType = DBVT_LONG;
  1247. varBookmark.m_lVal = 0;
  1248. }
  1249. RETCODE nRetCode;
  1250. SDWORD nActualSize;
  1251. // Retrieve the bookmark (column 0) data
  1252. AFX_ODBC_CALL(::SQLGetData(m_hstmt, 0, SQL_C_BOOKMARK,
  1253. &varBookmark.m_lVal, sizeof(varBookmark.m_lVal), &nActualSize));
  1254. if (!Check(nRetCode))
  1255. {
  1256. TRACE0("Error: GetBookmark operation failed.\n");
  1257. ThrowDBException(nRetCode);
  1258. }
  1259. }
  1260. void CRecordset::SetBookmark(const CDBVariant& varBookmark)
  1261. {
  1262. ASSERT_VALID(this);
  1263. // Validate bookmarks are usable
  1264. if (!(m_dwOptions & useBookmarks))
  1265. ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_ENABLED);
  1266. else if (!CanBookmark())
  1267. ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_SUPPORTED);
  1268. // Currently ODBC only supports 4 byte bookmarks
  1269. ASSERT(varBookmark.m_dwType == DBVT_LONG);
  1270. Move(varBookmark.m_lVal, SQL_FETCH_BOOKMARK);
  1271. }
  1272. void CRecordset::SetRowsetSize(DWORD dwNewRowsetSize)
  1273. {
  1274. ASSERT_VALID(this);
  1275. ASSERT(dwNewRowsetSize > 0);
  1276. // If not yet open, only set expected length
  1277. if (!IsOpen())
  1278. {
  1279. m_dwRowsetSize = dwNewRowsetSize;
  1280. return;
  1281. }
  1282. if (!(m_dwOptions & useMultiRowFetch))
  1283. {
  1284. // Only works if bulk row fetching!
  1285. ASSERT(FALSE);
  1286. return;
  1287. }
  1288. // Need to reallocate some memory if rowset size grows
  1289. if (m_dwAllocatedRowsetSize == 0 ||
  1290. (m_dwAllocatedRowsetSize < dwNewRowsetSize))
  1291. {
  1292. // If rowset already allocated, delete old and reallocate
  1293. FreeRowset();
  1294. m_rgRowStatus = new WORD[dwNewRowsetSize];
  1295. // If not a user allocated buffer grow the data buffers
  1296. if (!(m_dwOptions & userAllocMultiRowBuffers))
  1297. {
  1298. // Allocate the rowset field buffers
  1299. m_dwRowsetSize = dwNewRowsetSize;
  1300. CFieldExchange fx(CFieldExchange::AllocMultiRowBuffer, this);
  1301. DoBulkFieldExchange(&fx);
  1302. m_dwAllocatedRowsetSize = dwNewRowsetSize;
  1303. // Set bound fields to zero, rebind and reset bound field count
  1304. int nOldFieldsBound = m_nFieldsBound;
  1305. m_nFieldsBound = 0;
  1306. InitRecord();
  1307. m_nFieldsBound = nOldFieldsBound;
  1308. }
  1309. }
  1310. else
  1311. {
  1312. // Just reset the new rowset size
  1313. m_dwRowsetSize = dwNewRowsetSize;
  1314. }
  1315. RETCODE nRetCode;
  1316. AFX_SQL_SYNC(::SQLSetStmtOption(m_hstmt, SQL_ROWSET_SIZE,
  1317. m_dwRowsetSize));
  1318. }
  1319. void CRecordset::AddNew()
  1320. {
  1321. ASSERT_VALID(this);
  1322. ASSERT(m_hstmt != SQL_NULL_HSTMT);
  1323. // we can't construct an INSERT statement w/o any columns
  1324. ASSERT(m_nFields != 0);
  1325. if (!m_bAppendable)
  1326. {
  1327. ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
  1328. }
  1329. if (m_dwOptions & useMultiRowFetch)
  1330. {
  1331. // Can't use update methods on multi-row rowset
  1332. ASSERT(FALSE);
  1333. return;
  1334. }
  1335. if (m_bCheckCacheForDirtyFields && m_nFields > 0)
  1336. {
  1337. if (m_nEditMode == noMode)
  1338. {
  1339. // First addnew call, cache record values
  1340. StoreFields();
  1341. }
  1342. else
  1343. {
  1344. // subsequent Edit/AddNew call. Restore values, save them again
  1345. LoadFields();
  1346. StoreFields();
  1347. }
  1348. }
  1349. SetFieldNull(NULL);
  1350. SetFieldDirty(NULL, FALSE);
  1351. m_nEditMode = addnew;
  1352. }
  1353. void CRecordset::Edit()
  1354. {
  1355. ASSERT_VALID(this);
  1356. ASSERT(m_hstmt != SQL_NULL_HSTMT);
  1357. // we can't construct an UPDATE statement w/o any columns
  1358. ASSERT(m_nFields != 0);
  1359. if (!m_bUpdatable)
  1360. ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
  1361. if (m_dwOptions & useMultiRowFetch)
  1362. {
  1363. // Can't use update methods on multi-row rowset
  1364. ASSERT(FALSE);
  1365. return;
  1366. }
  1367. if (m_bEOF || m_bBOF || m_bDeleted)
  1368. {
  1369. TRACE0("Error: Edit attempt failed - not on a record.\n");
  1370. ThrowDBException(AFX_SQL_ERROR_NO_CURRENT_RECORD);
  1371. }
  1372. if ((m_nOpenType == dynaset || m_nOpenType == dynamic) &&
  1373. m_nLockMode == pessimistic)
  1374. {
  1375. RETCODE nRetCode;
  1376. AFX_ODBC_CALL(::SQLSetPos(m_hstmt, 1, SQL_POSITION,
  1377. SQL_LCK_EXCLUSIVE));
  1378. if (!Check(nRetCode))
  1379. {
  1380. TRACE0("Error: attempt to lock record failed during Edit function.\n");
  1381. ThrowDBException(nRetCode);
  1382. }
  1383. }
  1384. if (m_bCheckCacheForDirtyFields && m_nFields > 0)
  1385. {
  1386. if (m_nEditMode == noMode)
  1387. // First edit call, cache record values
  1388. StoreFields();
  1389. else
  1390. {
  1391. // subsequent Edit/AddNew call. Restore values, save them again
  1392. LoadFields();
  1393. StoreFields();
  1394. }
  1395. }
  1396. m_nEditMode = edit;
  1397. }
  1398. BOOL CRecordset::Update()
  1399. {
  1400. ASSERT_VALID(this);
  1401. ASSERT(m_hstmt != SQL_NULL_HSTMT);
  1402. if (m_dwOptions & useMultiRowFetch)
  1403. {
  1404. // Can't use update methods on multi-row rowset
  1405. ASSERT(FALSE);
  1406. return FALSE;
  1407. }
  1408. if (m_nEditMode != addnew && m_nEditMode != edit)
  1409. {
  1410. TRACE0("Error: must enter Edit or AddNew mode before updating.\n");
  1411. ThrowDBException(AFX_SQL_ERROR_ILLEGAL_MODE);
  1412. }
  1413. return UpdateInsertDelete();
  1414. }
  1415. void CRecordset::Delete()
  1416. {
  1417. ASSERT_VALID(this);
  1418. ASSERT(m_hstmt != SQL_NULL_HSTMT);
  1419. if (m_dwOptions & useMultiRowFetch)
  1420. {
  1421. // Can't use update methods on multi-row rowset
  1422. ASSERT(FALSE);
  1423. return;
  1424. }
  1425. if (m_nEditMode != noMode)
  1426. {
  1427. TRACE0("Error: attempted to delete while still in Edit or AddNew mode.\n");
  1428. ThrowDBException(AFX_SQL_ERROR_ILLEGAL_MODE);
  1429. }
  1430. UpdateInsertDelete(); // This call can't fail in delete mode (noMode)
  1431. }
  1432. void CRecordset::CancelUpdate()
  1433. {
  1434. ASSERT_VALID(this);
  1435. ASSERT(IsOpen());
  1436. if (m_nEditMode == noMode)
  1437. // Do nothing if not in edit mode
  1438. return;
  1439. else
  1440. // Reset the edit mode
  1441. m_nEditMode = noMode;
  1442. // Restore cache if necessary
  1443. if (m_bCheckCacheForDirtyFields && m_nFields > 0)
  1444. LoadFields();
  1445. }
  1446. BOOL CRecordset::FlushResultSet() const
  1447. {
  1448. RETCODE nRetCode;
  1449. AFX_ODBC_CALL(::SQLMoreResults(m_hstmt));
  1450. if (!Check(nRetCode))
  1451. {
  1452. TRACE0("Error: attempt FlushResultSet failed.\n");
  1453. AfxThrowDBException(nRetCode, m_pDatabase, m_hstmt);
  1454. }
  1455. // Reset state of cursor
  1456. ((CRecordset*)this)->ResetCursor();
  1457. return nRetCode != SQL_NO_DATA_FOUND;
  1458. }
  1459. void CRecordset::GetODBCFieldInfo(LPCTSTR lpszName,
  1460. CODBCFieldInfo& fieldinfo)
  1461. {
  1462. ASSERT_VALID(this);
  1463. ASSERT(IsOpen());
  1464. ASSERT(lpszName != NULL);
  1465. // No data or no column info fetched yet
  1466. if (GetODBCFieldCount() <= 0)
  1467. {
  1468. ASSERT(FALSE);
  1469. return;
  1470. }
  1471. // Get the index of the field corresponding to name
  1472. short nField = GetFieldIndexByName(lpszName);
  1473. GetODBCFieldInfo(nField, fieldinfo);
  1474. }
  1475. void CRecordset::GetODBCFieldInfo(short nIndex,
  1476. CODBCFieldInfo& fieldinfo)
  1477. {
  1478. ASSERT_VALID(this);
  1479. ASSERT(IsOpen());
  1480. // No data or no column info fetched yet
  1481. if (GetODBCFieldCount() <= 0)
  1482. {
  1483. ASSERT(FALSE);
  1484. return;
  1485. }
  1486. // Just copy the data into the field info
  1487. CODBCFieldInfo* pInfo = &m_rgODBCFieldInfos[nIndex];
  1488. fieldinfo.m_strName = pInfo->m_strName;
  1489. fieldinfo.m_nSQLType = pInfo->m_nSQLType;
  1490. fieldinfo.m_nPrecision = pInfo->m_nPrecision;
  1491. fieldinfo.m_nScale = pInfo->m_nScale;
  1492. fieldinfo.m_nNullability = pInfo->m_nNullability;
  1493. }
  1494. void CRecordset::GetFieldValue(LPCTSTR lpszName,
  1495. CDBVariant& varValue, short nFieldType)
  1496. {
  1497. ASSERT_VALID(this);
  1498. ASSERT(IsOpen());
  1499. ASSERT(lpszName != NULL);
  1500. // No data or no column info fetched yet
  1501. if (GetODBCFieldCount() <= 0)
  1502. {
  1503. ASSERT(FALSE);
  1504. varValue.Clear();
  1505. return;
  1506. }
  1507. // Get the index of the field corresponding to name
  1508. short nField = GetFieldIndexByName(lpszName);
  1509. GetFieldValue(nField, varValue, nFieldType);
  1510. }
  1511. void CRecordset::GetFieldValue(short nIndex,
  1512. CDBVariant& varValue, short nFieldType)
  1513. {
  1514. ASSERT_VALID(this);
  1515. ASSERT(IsOpen());
  1516. // Clear the previous variant
  1517. varValue.Clear();
  1518. // No data or no column info fetched yet
  1519. if (GetODBCFieldCount() <= 0)
  1520. {
  1521. ASSERT(FALSE);
  1522. return;
  1523. }
  1524. // Convert index to 1-based and check range
  1525. nIndex++;
  1526. if (nIndex < 1 || nIndex > GetODBCFieldCount())
  1527. {
  1528. ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
  1529. }
  1530. void* pvData = NULL;
  1531. int nLen = 0;
  1532. // Determine the default field type and get the data buffer
  1533. if (nFieldType == DEFAULT_FIELD_TYPE)
  1534. {
  1535. nFieldType =
  1536. GetDefaultFieldType(m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
  1537. }
  1538. pvData = GetDataBuffer(varValue, nFieldType, &nLen,
  1539. m_rgODBCFieldInfos[nIndex - 1].m_nSQLType,
  1540. m_rgODBCFieldInfos[nIndex - 1].m_nPrecision);
  1541. // Now can actually get the data
  1542. long nActualSize = GetData(m_pDatabase, m_hstmt, nIndex,
  1543. nFieldType, pvData, nLen,
  1544. m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
  1545. // Handle NULL data separately
  1546. if (nActualSize == SQL_NULL_DATA)
  1547. {
  1548. // Clear value and set the value NULL
  1549. varValue.Clear();
  1550. }
  1551. else
  1552. {
  1553. // May need to cleanup and call SQLGetData again if LONG_VAR data
  1554. if (nFieldType == SQL_C_CHAR)
  1555. {
  1556. GetLongCharDataAndCleanup(m_pDatabase, m_hstmt, nIndex,
  1557. nActualSize, &pvData, nLen, *varValue.m_pstring,
  1558. m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
  1559. #ifdef _UNICODE
  1560. // Now must convert string to UNICODE
  1561. LPCSTR lpszOld = (LPCSTR)varValue.m_pstring->GetBuffer(0);
  1562. CString* pStringNew = new CString(lpszOld);
  1563. delete varValue.m_pstring;
  1564. varValue.m_pstring = pStringNew;
  1565. #endif // _UNICODE
  1566. }
  1567. else if (nFieldType == SQL_C_BINARY)
  1568. {
  1569. GetLongBinaryDataAndCleanup(m_pDatabase, m_hstmt, nIndex,
  1570. nActualSize, &pvData, nLen, varValue,
  1571. m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
  1572. }
  1573. }
  1574. }
  1575. void CRecordset::GetFieldValue(LPCTSTR lpszName, CString& strValue)
  1576. {
  1577. ASSERT_VALID(this);
  1578. ASSERT(IsOpen());
  1579. ASSERT(lpszName != NULL);
  1580. // No data or no column info fetched yet
  1581. if (GetODBCFieldCount() <= 0)
  1582. {
  1583. ASSERT(FALSE);
  1584. return;
  1585. }
  1586. // Get the index of the field corresponding to name
  1587. short nField = GetFieldIndexByName(lpszName);
  1588. GetFieldValue(nField, strValue);
  1589. }
  1590. void CRecordset::GetFieldValue(short nIndex, CString& strValue)
  1591. {
  1592. ASSERT_VALID(this);
  1593. ASSERT(IsOpen());
  1594. // No data or no column info fetched yet
  1595. if (GetODBCFieldCount() <= 0)
  1596. {
  1597. ASSERT(FALSE);
  1598. return;
  1599. }
  1600. // Convert index to 1-based and check range
  1601. nIndex++;
  1602. if (nIndex < 1 || nIndex > GetODBCFieldCount())
  1603. {
  1604. ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
  1605. }
  1606. int nLen = GetTextLen(m_rgODBCFieldInfos[nIndex - 1].m_nSQLType,
  1607. m_rgODBCFieldInfos[nIndex - 1].m_nPrecision);
  1608. #ifndef _UNICODE
  1609. CString& strData = strValue;
  1610. #else
  1611. CString strProxy;
  1612. CString& strData = strProxy;
  1613. #endif
  1614. void* pvData = strData.GetBufferSetLength(nLen);
  1615. // Now can actually get the data
  1616. long nActualSize = GetData(m_pDatabase, m_hstmt, nIndex,
  1617. SQL_C_CHAR, pvData, nLen,
  1618. m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
  1619. // Handle NULL data separately
  1620. if (nActualSize == SQL_NULL_DATA)
  1621. {
  1622. // Clear value
  1623. strValue.Empty();
  1624. }
  1625. else
  1626. {
  1627. // May need to cleanup and call SQLGetData again if necessary
  1628. GetLongCharDataAndCleanup(m_pDatabase, m_hstmt, nIndex,
  1629. nActualSize, &pvData, nLen, strData,
  1630. m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
  1631. #ifdef _UNICODE
  1632. // Now must convert string to UNICODE
  1633. strValue = (LPCSTR)strData.GetBuffer(0);
  1634. #endif // _UNIOCDE
  1635. }
  1636. }
  1637. void CRecordset::SetFieldDirty(void* pv, BOOL bDirty)
  1638. {
  1639. ASSERT_VALID(this);
  1640. int nIndex, nIndexEnd;
  1641. // If not setting all NULL, check simple case
  1642. if (pv != NULL)
  1643. {
  1644. // GetBoundFieldIndex returns 1-based index
  1645. nIndex = GetBoundFieldIndex(pv) - 1;
  1646. if (nIndex < 0)
  1647. {
  1648. // pv must be address of field member
  1649. ASSERT(FALSE);
  1650. return;
  1651. }
  1652. else
  1653. {
  1654. nIndexEnd = nIndex;
  1655. }
  1656. }
  1657. else
  1658. {
  1659. nIndex = 0;
  1660. nIndexEnd = m_nFields - 1;
  1661. }
  1662. while (nIndex <= nIndexEnd)
  1663. {
  1664. if (bDirty)
  1665. SetDirtyFieldStatus((DWORD)nIndex);
  1666. else
  1667. ClearDirtyFieldStatus((DWORD)nIndex);
  1668. nIndex++;
  1669. }
  1670. }
  1671. void CRecordset::SetFieldNull(void* pv, BOOL bNull)
  1672. {
  1673. ASSERT_VALID(this);
  1674. ASSERT(IsOpen());
  1675. ASSERT(!(m_dwOptions & useMultiRowFetch));
  1676. // If not setting all fields NULL, check simple case (param) first
  1677. if (pv != NULL)
  1678. {
  1679. // Cached index is 1-based
  1680. int nIndex = GetBoundParamIndex(pv) - 1;
  1681. if (nIndex >= 0)
  1682. {
  1683. if (bNull)
  1684. SetNullParamStatus(nIndex);
  1685. else
  1686. ClearNullParamStatus(nIndex);
  1687. return;
  1688. }
  1689. }
  1690. // Not a param, must be a field
  1691. if (m_nFields <= 0)
  1692. {
  1693. ASSERT(FALSE);
  1694. return;
  1695. }
  1696. // Need field exchange mechanism to set PSEUDO NULL values
  1697. // and to reset data lengths (especially for RFX_LongBinary)
  1698. CFieldExchange fx(CFieldExchange::SetFieldNull, this, pv);
  1699. fx.m_nFieldFound = 0;
  1700. fx.m_bField = bNull;
  1701. DoFieldExchange(&fx);
  1702. // If no field found, m_nFieldFound will still be zero
  1703. ASSERT(fx.m_nFieldFound != 0);
  1704. }
  1705. void CRecordset::SetParamNull(int nIndex, BOOL bNull)
  1706. {
  1707. ASSERT_VALID(this);
  1708. ASSERT((DWORD)nIndex < m_nParams);
  1709. // Can be called before Open, but need to alloc status arrays first
  1710. if (!IsOpen())
  1711. AllocStatusArrays();
  1712. if (bNull)
  1713. SetNullParamStatus(nIndex);
  1714. else
  1715. ClearNullParamStatus(nIndex);
  1716. return;
  1717. }
  1718. void CRecordset::SetLockingMode(UINT nLockMode)
  1719. {
  1720. if (nLockMode == pessimistic)
  1721. {
  1722. RETCODE nRetCode;
  1723. UDWORD dwTypes;
  1724. SWORD nResult;
  1725. AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_LOCK_TYPES,
  1726. &dwTypes, sizeof(dwTypes), &nResult));
  1727. if (!Check(nRetCode) || !(dwTypes & SQL_LCK_EXCLUSIVE))
  1728. ThrowDBException(AFX_SQL_ERROR_LOCK_MODE_NOT_SUPPORTED);
  1729. }
  1730. m_nLockMode = nLockMode;
  1731. }
  1732. BOOL CRecordset::Requery()
  1733. {
  1734. RETCODE nRetCode;
  1735. ASSERT_VALID(this);
  1736. ASSERT(IsOpen());
  1737. // Can't requery if using direct execution
  1738. if (m_dwOptions & executeDirect)
  1739. return FALSE;
  1740. TRY
  1741. {
  1742. // Detect changes to filter and sort
  1743. if ((m_strFilter != m_strRequeryFilter) || (m_strSort != m_strRequerySort))
  1744. {
  1745. m_strRequeryFilter = m_strFilter;
  1746. m_strRequerySort = m_strSort;
  1747. Close();
  1748. if (m_strRequerySQL.IsEmpty())
  1749. return Open(m_nOpenType, NULL, m_dwOptions);
  1750. else
  1751. return Open(m_nOpenType, m_strRequerySQL, m_dwOptions);
  1752. }
  1753. else
  1754. {
  1755. // Shutdown current query, preserving buffers for performance
  1756. AFX_SQL_SYNC(::SQLFreeStmt(m_hstmt, SQL_CLOSE));
  1757. m_lOpen = AFX_RECORDSET_STATUS_CLOSED;
  1758. // Rebind date/time parameters
  1759. RebindParams(m_hstmt);
  1760. // now attempt to re-execute the SQL Query
  1761. AFX_ODBC_CALL(::SQLExecute(m_hstmt));
  1762. if (!Check(nRetCode))
  1763. {
  1764. TRACE0("Error: Requery attempt failed.\n");
  1765. ThrowDBException(nRetCode);
  1766. }
  1767. m_lOpen = AFX_RECORDSET_STATUS_OPEN;
  1768. // Reset some cursor properties and fetch first record
  1769. ResetCursor();
  1770. MoveNext();
  1771. // If EOF, then result set empty, so set BOF as well
  1772. m_bBOF = m_bEOF;
  1773. }
  1774. }
  1775. CATCH_ALL(e)
  1776. {
  1777. Close();
  1778. THROW_LAST();
  1779. }
  1780. END_CATCH_ALL
  1781. return TRUE; // all set
  1782. }
  1783. // Shutdown any pending query for CRecordset's hstmt's
  1784. void CRecordset::Cancel()
  1785. {
  1786. ASSERT_VALID(this);
  1787. ASSERT(m_hstmt != SQL_NULL_HSTMT);
  1788. ::SQLCancel(m_hstmt);
  1789. // If Update hstmt has been allocated, shut it down also
  1790. if (m_hstmtUpdate != SQL_NULL_HSTMT)
  1791. ::SQLCancel(m_hstmtUpdate);
  1792. }
  1793. CString CRecordset::GetDefaultConnect()
  1794. {
  1795. ASSERT_VALID(this);
  1796. return _afxODBCTrail;
  1797. }
  1798. CString CRecordset::GetDefaultSQL()
  1799. {
  1800. ASSERT_VALID(this);
  1801. // Override and add table name or entire SQL SELECT statement
  1802. return _T("");
  1803. }
  1804. void CRecordset::DoFieldExchange(CFieldExchange* /* pFX */)
  1805. {
  1806. ASSERT_VALID(this);
  1807. // Do nothing if dynamically retrieving unbound fields,
  1808. // otherwise override CRecordset and add RFX calls
  1809. }
  1810. void CRecordset::DoBulkFieldExchange(CFieldExchange* /* pFX */)
  1811. {
  1812. ASSERT_VALID(this);
  1813. // To use multi-record data fetching, you must use
  1814. // a derived CRecordset class and call Close explicitly.
  1815. ASSERT(FALSE);
  1816. }
  1817. void CRecordset::OnSetOptions(HSTMT hstmt)
  1818. {
  1819. ASSERT_VALID(this);
  1820. ASSERT(hstmt != SQL_NULL_HSTMT);
  1821. // Inherit options settings from CDatabase
  1822. m_pDatabase->OnSetOptions(hstmt);
  1823. // If fowardOnly recordset and not using SQLExtendedFetch, quit now
  1824. if (m_nOpenType == forwardOnly && !(m_dwOptions & useExtendedFetch))
  1825. return;
  1826. // Turn on bookmark support if necessary
  1827. EnableBookmarks();
  1828. // If using forwardOnly and extended fetch, quit now
  1829. if (m_nOpenType == forwardOnly)
  1830. return;
  1831. // Make sure driver supports extended fetch, ODBC 2.0 and requested cursor type
  1832. VerifyDriverBehavior();
  1833. DWORD dwScrollType = VerifyCursorSupport();
  1834. // Set the update method, concurrency and cursor type
  1835. SetUpdateMethod();
  1836. SetConcurrencyAndCursorType(hstmt, dwScrollType);
  1837. }
  1838. // Screen for errors.
  1839. BOOL CRecordset::Check(RETCODE nRetCode) const
  1840. {
  1841. ASSERT_VALID(this);
  1842. switch (nRetCode)
  1843. {
  1844. case SQL_SUCCESS_WITH_INFO:
  1845. #ifdef _DEBUG
  1846. if (afxTraceFlags & traceDatabase)
  1847. {
  1848. CDBException e(nRetCode);
  1849. TRACE0("Warning: ODBC Success With Info, ");
  1850. e.BuildErrorString(m_pDatabase, m_hstmt);
  1851. }
  1852. #endif
  1853. // Fall through
  1854. case SQL_SUCCESS:
  1855. case SQL_NO_DATA_FOUND:
  1856. case SQL_NEED_DATA:
  1857. return TRUE;
  1858. }
  1859. return FALSE;
  1860. }
  1861. void CRecordset::PreBindFields()
  1862. {
  1863. // Do nothing
  1864. }
  1865. //////////////////////////////////////////////////////////////////////////////
  1866. // CRecordset internal functions
  1867. // Cache state information internally in CRecordset
  1868. void CRecordset::SetState(int nOpenType, LPCTSTR lpszSQL, DWORD dwOptions)
  1869. {
  1870. if (nOpenType == AFX_DB_USE_DEFAULT_TYPE)
  1871. m_nOpenType = m_nDefaultType;
  1872. else
  1873. m_nOpenType = nOpenType;
  1874. m_bAppendable = (dwOptions & appendOnly) != 0 ||
  1875. (dwOptions & readOnly) == 0;
  1876. m_bUpdatable = (dwOptions & readOnly) == 0 &&
  1877. (dwOptions & appendOnly) == 0;
  1878. // Can turn off dirty field checking via dwOptions
  1879. if (dwOptions & noDirtyFieldCheck || dwOptions & useMultiRowFetch)
  1880. m_bCheckCacheForDirtyFields = FALSE;
  1881. // Set recordset readOnly if forwardOnly
  1882. if (m_nOpenType == forwardOnly && !(dwOptions & readOnly))
  1883. {
  1884. #ifdef _DEBUG
  1885. if (afxTraceFlags & traceDatabase)
  1886. TRACE0("Warning: Setting forwardOnly recordset readOnly.\n");
  1887. #endif
  1888. dwOptions |= readOnly;
  1889. // If using multiRowFetch also set useExtendFetch
  1890. if (dwOptions & useMultiRowFetch)
  1891. dwOptions |= useExtendedFetch;
  1892. }
  1893. // Archive info for use in Requery
  1894. m_dwOptions = dwOptions;
  1895. m_strRequerySQL = lpszSQL;
  1896. m_strRequeryFilter = m_strFilter;
  1897. m_strRequerySort = m_strSort;
  1898. }
  1899. // Allocate the Hstmt and implicitly create and open Database if necessary
  1900. BOOL CRecordset::AllocHstmt()
  1901. {
  1902. RETCODE nRetCode;
  1903. if (m_hstmt == SQL_NULL_HSTMT)
  1904. {
  1905. CString strDefaultConnect;
  1906. TRY
  1907. {
  1908. if (m_pDatabase == NULL)
  1909. {
  1910. m_pDatabase = new CDatabase();
  1911. m_bRecordsetDb = TRUE;
  1912. }
  1913. strDefaultConnect = GetDefaultConnect();
  1914. // If not already opened, attempt to open
  1915. if (!m_pDatabase->IsOpen())
  1916. {
  1917. BOOL bUseCursorLib = m_bUseODBCCursorLib;
  1918. // If non-readOnly snapshot request must use cursor lib
  1919. if (m_nOpenType == snapshot && !(m_dwOptions & readOnly))
  1920. {
  1921. // This assumes drivers only support readOnly snapshots
  1922. bUseCursorLib = TRUE;
  1923. }
  1924. if (!m_pDatabase->Open(_T(""), FALSE, FALSE,
  1925. strDefaultConnect, bUseCursorLib))
  1926. {
  1927. return FALSE;
  1928. }
  1929. // If snapshot cursor requested and not supported, load cursor lib
  1930. if (m_nOpenType == snapshot && !bUseCursorLib)
  1931. {
  1932. // Get the supported cursor types
  1933. RETCODE nResult;
  1934. UDWORD dwDriverScrollOptions;
  1935. AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_SCROLL_OPTIONS,
  1936. &dwDriverScrollOptions, sizeof(dwDriverScrollOptions), &nResult));
  1937. if (!Check(nRetCode))
  1938. {
  1939. TRACE0("Error: ODBC failure checking for driver capabilities.\n");
  1940. ThrowDBException(nRetCode);
  1941. }
  1942. // Check for STATIC cursor support and load cursor lib
  1943. if (!(dwDriverScrollOptions & SQL_SO_STATIC))
  1944. {
  1945. m_pDatabase->Close();
  1946. if (!m_pDatabase->Open(_T(""), FALSE, FALSE,
  1947. strDefaultConnect, TRUE))
  1948. return FALSE;
  1949. }
  1950. }
  1951. }
  1952. AFX_SQL_SYNC(::SQLAllocStmt(m_pDatabase->m_hdbc, &m_hstmt));
  1953. if (!Check(nRetCode))
  1954. ThrowDBException(SQL_INVALID_HANDLE);
  1955. // Add to list of CRecordsets with alloced hstmts
  1956. AfxLockGlobals(CRIT_ODBC);
  1957. TRY
  1958. {
  1959. m_pDatabase->m_listRecordsets.AddHead(this);
  1960. }
  1961. CATCH_ALL(e)
  1962. {
  1963. AfxUnlockGlobals(CRIT_ODBC);
  1964. THROW_LAST();
  1965. }
  1966. END_CATCH_ALL
  1967. AfxUnlockGlobals(CRIT_ODBC);
  1968. }
  1969. CATCH_ALL(e)
  1970. {
  1971. #ifdef _DEBUG
  1972. if (afxTraceFlags & traceDatabase)
  1973. TRACE0("Error: CDatabase create for CRecordset failed.\n");
  1974. #endif
  1975. NO_CPP_EXCEPTION(strDefaultConnect.Empty());
  1976. if (m_bRecordsetDb)
  1977. {
  1978. delete m_pDatabase;
  1979. m_pDatabase = NULL;
  1980. }
  1981. ASSERT(m_hstmt == SQL_NULL_HSTMT);
  1982. THROW_LAST();
  1983. }
  1984. END_CATCH_ALL
  1985. }
  1986. return TRUE;
  1987. }
  1988. // Initialize the status arrays and create the SQL
  1989. void CRecordset::BuildSQL(LPCTSTR lpszSQL)
  1990. {
  1991. if (lpszSQL == NULL)
  1992. m_strSQL = GetDefaultSQL();
  1993. else
  1994. m_strSQL = lpszSQL;
  1995. // Set any supplied params
  1996. if (m_nParams != 0)
  1997. {
  1998. UINT nParams = BindParams(m_hstmt);
  1999. ASSERT(nParams == m_nParams);
  2000. }
  2001. // Construct the SQL string
  2002. BuildSelectSQL();
  2003. AppendFilterAndSortSQL();
  2004. // Do some extra checking if trying to set recordset updatable or appendable
  2005. if ((m_bUpdatable || m_bAppendable) && !IsRecordsetUpdatable())
  2006. m_bUpdatable = m_bAppendable = FALSE;
  2007. if (m_bUpdatable && m_bUseUpdateSQL && m_pDatabase->m_bAddForUpdate)
  2008. m_strSQL += _afxForUpdate;
  2009. // Replace brackets with SQL_IDENTIFIER_QUOTE_CHAR
  2010. m_pDatabase->ReplaceBrackets(m_strSQL.GetBuffer(0));
  2011. m_strSQL.ReleaseBuffer();
  2012. }
  2013. // Prepare and Execute the SQL or simple call SQLExecDirect, resetting concurrency if necessary
  2014. void CRecordset::PrepareAndExecute()
  2015. {
  2016. USES_CONVERSION;
  2017. RETCODE nRetCode = 0;
  2018. BOOL bConcurrency = FALSE;
  2019. LPCSTR lpszWSQL = T2CA(m_strSQL);
  2020. while (!bConcurrency)
  2021. {
  2022. // Prepare or execute the query
  2023. if (m_dwOptions & executeDirect)
  2024. {
  2025. AFX_ODBC_CALL(::SQLExecDirect(m_hstmt,
  2026. (UCHAR*)lpszWSQL, SQL_NTS));
  2027. }
  2028. else
  2029. {
  2030. AFX_ODBC_CALL(::SQLPrepare(m_hstmt,
  2031. (UCHAR*)lpszWSQL, SQL_NTS));
  2032. }
  2033. if (Check(nRetCode))
  2034. bConcurrency = TRUE;
  2035. else
  2036. {
  2037. // If "Driver Not Capable" error, assume cursor type doesn't
  2038. // support requested concurrency and try alternate concurrency.
  2039. CDBException* e = new CDBException(nRetCode);
  2040. e->BuildErrorString(m_pDatabase, m_hstmt);
  2041. if (m_dwConcurrency != SQL_CONCUR_READ_ONLY &&
  2042. e->m_strStateNativeOrigin.Find(_afxDriverNotCapable) >= 0)
  2043. {
  2044. #ifdef _DEBUG
  2045. if (afxTraceFlags & traceDatabase)
  2046. TRACE0("Warning: Driver does not support requested concurrency.\n");
  2047. #endif
  2048. // Don't need exception to persist while attempting to reset concurrency
  2049. e->Delete();
  2050. // ODBC will automatically attempt to set alternate concurrency if
  2051. // request fails, but it won't try LOCK even if driver supports it.
  2052. if ((m_dwDriverConcurrency & SQL_SCCO_LOCK) &&
  2053. (m_dwConcurrency == SQL_CONCUR_ROWVER ||
  2054. m_dwConcurrency == SQL_CONCUR_VALUES))
  2055. {
  2056. m_dwConcurrency = SQL_CONCUR_LOCK;
  2057. }
  2058. else
  2059. {
  2060. m_dwConcurrency = SQL_CONCUR_READ_ONLY;
  2061. m_bUpdatable = m_bAppendable = FALSE;
  2062. #ifdef _DEBUG
  2063. if (afxTraceFlags & traceDatabase)
  2064. TRACE0("Warning: Setting recordset read only.\n");
  2065. #endif
  2066. }
  2067. // Attempt to reset the concurrency model.
  2068. AFX_SQL_SYNC(::SQLSetStmtOption(m_hstmt, SQL_CONCURRENCY,
  2069. m_dwConcurrency));
  2070. if (!Check(nRetCode))
  2071. {
  2072. TRACE0("Error: ODBC failure setting recordset concurrency.\n");
  2073. ThrowDBException(nRetCode);
  2074. }
  2075. }
  2076. else
  2077. {
  2078. TRACE0("Error: ODBC failure on SQLPrepare or SQLExecDirect\n");
  2079. THROW(e);
  2080. }
  2081. }
  2082. }
  2083. // now attempt to execute the SQL Query if not executed already
  2084. if (!(m_dwOptions & executeDirect))
  2085. {
  2086. AFX_ODBC_CALL(::SQLExecute(m_hstmt));
  2087. if (!Check(nRetCode))
  2088. ThrowDBException(nRetCode);
  2089. }
  2090. m_lOpen = AFX_RECORDSET_STATUS_OPEN;
  2091. // SQLExecute or SQLExecDirect may have changed an option value
  2092. if (nRetCode == SQL_SUCCESS_WITH_INFO)
  2093. {
  2094. // Check if concurrency was changed in order to mark
  2095. // recordset non-updatable if necessary
  2096. DWORD dwConcurrency;
  2097. AFX_SQL_SYNC(::SQLGetStmtOption(m_hstmt, SQL_CONCURRENCY, &dwConcurrency));
  2098. if (!Check(nRetCode))
  2099. ThrowDBException(nRetCode);
  2100. if (dwConcurrency == SQL_CONCUR_READ_ONLY && (m_bUpdatable || m_bAppendable))
  2101. {
  2102. m_bUpdatable = FALSE;
  2103. m_bAppendable = FALSE;
  2104. #ifdef _DEBUG
  2105. if (afxTraceFlags & traceDatabase)
  2106. {
  2107. TRACE0("Warning: Concurrency changed by driver.\n");
  2108. TRACE0("\tMarking CRecordset as not updatable.\n");
  2109. }
  2110. #endif // _DEBUG
  2111. }
  2112. }
  2113. }
  2114. // Ensure that driver supports extended fetch and ODBC 2.0 if necessary
  2115. void CRecordset::VerifyDriverBehavior()
  2116. {
  2117. RETCODE nRetCode;
  2118. UWORD wScrollable;
  2119. // If SQLExtendedFetch not supported, use SQLFetch
  2120. AFX_SQL_SYNC(::SQLGetFunctions(m_pDatabase->m_hdbc,
  2121. SQL_API_SQLEXTENDEDFETCH, &wScrollable));
  2122. if (!Check(nRetCode))
  2123. {
  2124. TRACE0("Error: ODBC failure determining whether recordset is scrollable.\n");
  2125. ThrowDBException(nRetCode);
  2126. }
  2127. m_bScrollable = wScrollable;
  2128. if (!m_bScrollable)
  2129. {
  2130. #ifdef _DEBUG
  2131. if (afxTraceFlags & traceDatabase)
  2132. {
  2133. TRACE0("Warning: SQLExtendedFetch not supported by driver\n");
  2134. TRACE0("and/or cursor library not loaded. Opening forwardOnly.\n");
  2135. TRACE0("for use with SQLFetch.\n");
  2136. }
  2137. #endif
  2138. m_bUpdatable = FALSE;
  2139. return;
  2140. }
  2141. char szResult[30];
  2142. SWORD nResult;
  2143. // require ODBC v2.0
  2144. AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_ODBC_VER,
  2145. &szResult, _countof(szResult), &nResult));
  2146. if (!Check(nRetCode))
  2147. {
  2148. TRACE0("Error: ODBC failure checking for driver capabilities.\n");
  2149. ThrowDBException(nRetCode);
  2150. }
  2151. if (szResult[0] == '0' && szResult[1] < '2')
  2152. ThrowDBException(AFX_SQL_ERROR_ODBC_V2_REQUIRED);
  2153. }
  2154. // Check that driver supports requested cursor type
  2155. DWORD CRecordset::VerifyCursorSupport()
  2156. {
  2157. RETCODE nRetCode;
  2158. SWORD nResult;
  2159. UDWORD dwDriverScrollOptions;
  2160. AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_SCROLL_OPTIONS,
  2161. &dwDriverScrollOptions, sizeof(dwDriverScrollOptions), &nResult));
  2162. if (!Check(nRetCode))
  2163. {
  2164. TRACE0("Error: ODBC failure checking for driver capabilities.\n");
  2165. ThrowDBException(nRetCode);
  2166. }
  2167. SDWORD dwScrollOptions = SQL_CURSOR_FORWARD_ONLY;
  2168. if (m_nOpenType == dynaset)
  2169. {
  2170. // Dynaset support requires ODBC's keyset driven cursor model
  2171. if (!(dwDriverScrollOptions & SQL_SO_KEYSET_DRIVEN))
  2172. ThrowDBException(AFX_SQL_ERROR_DYNASET_NOT_SUPPORTED);
  2173. dwScrollOptions = SQL_CURSOR_KEYSET_DRIVEN;
  2174. }
  2175. else if (m_nOpenType == snapshot)
  2176. {
  2177. // Snapshot support requires ODBC's static cursor model
  2178. if (!(dwDriverScrollOptions & SQL_SO_STATIC))
  2179. ThrowDBException(AFX_SQL_ERROR_SNAPSHOT_NOT_SUPPORTED);
  2180. dwScrollOptions = SQL_CURSOR_STATIC;
  2181. }
  2182. else
  2183. {
  2184. // Dynamic cursor support requires ODBC's dynamic cursor model
  2185. if (!(dwDriverScrollOptions & SQL_SO_DYNAMIC))
  2186. ThrowDBException(AFX_SQL_ERROR_DYNAMIC_CURSOR_NOT_SUPPORTED);
  2187. dwScrollOptions = SQL_CURSOR_DYNAMIC;
  2188. }
  2189. return dwScrollOptions;
  2190. }
  2191. void CRecordset::AllocAndCacheFieldInfo()
  2192. {
  2193. ASSERT(GetODBCFieldCount() < 0);
  2194. ASSERT(m_rgODBCFieldInfos == NULL);
  2195. RETCODE nRetCode;
  2196. SWORD nActualLen;
  2197. // Cache the number of result columns
  2198. AFX_ODBC_CALL(::SQLNumResultCols(m_hstmt, &m_nResultCols));
  2199. if (!Check(nRetCode))
  2200. {
  2201. TRACE0("Error: Can't get field info.\n");
  2202. ThrowDBException(nRetCode);
  2203. }
  2204. // If there are no fields quit now
  2205. if (m_nResultCols == 0)
  2206. return;
  2207. // Allocate buffer and get the ODBC meta data
  2208. m_rgODBCFieldInfos = new CODBCFieldInfo[m_nResultCols];
  2209. LPSTR lpszFieldName;
  2210. #ifdef _UNICODE
  2211. // Need proxy to temporarily store non-UNICODE name
  2212. lpszFieldName = new char[MAX_FNAME_LEN + 1];
  2213. #endif
  2214. // Get the field info each field
  2215. for (WORD n = 1; n <= GetODBCFieldCount(); n++)
  2216. {
  2217. #ifndef _UNICODE
  2218. // Reset the buffer to point to next element
  2219. lpszFieldName =
  2220. m_rgODBCFieldInfos[n - 1].m_strName.GetBuffer(MAX_FNAME_LEN + 1);
  2221. #endif
  2222. AFX_ODBC_CALL(::SQLDescribeCol(m_hstmt, n,
  2223. (UCHAR*)lpszFieldName, MAX_FNAME_LEN, &nActualLen,
  2224. &m_rgODBCFieldInfos[n - 1].m_nSQLType,
  2225. &m_rgODBCFieldInfos[n - 1].m_nPrecision,
  2226. &m_rgODBCFieldInfos[n - 1].m_nScale,
  2227. &m_rgODBCFieldInfos[n - 1].m_nNullability));
  2228. #ifndef _UNICODE
  2229. m_rgODBCFieldInfos[n - 1].m_strName.ReleaseBuffer(nActualLen);
  2230. #else
  2231. // Copy the proxy data to correct element
  2232. m_rgODBCFieldInfos[n - 1].m_strName = lpszFieldName;
  2233. #endif
  2234. if (!Check(nRetCode))
  2235. {
  2236. TRACE1("Error: ODBC failure getting field #%d info.\n", n);
  2237. ThrowDBException(nRetCode);
  2238. }
  2239. }
  2240. #ifdef _UNICODE
  2241. delete[] lpszFieldName;
  2242. #endif
  2243. }
  2244. void CRecordset::AllocRowset()
  2245. {
  2246. if (m_dwOptions & useMultiRowFetch)
  2247. SetRowsetSize(m_dwRowsetSize);