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