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