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