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