Annotation of sql/odbc/MFCpatches/dbcore.cpp, revision 1.2

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

E-mail: