mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4912 lines
114 KiB
4912 lines
114 KiB
// This is a part of the Microsoft Foundation Classes C++ library.
|
|
// Copyright (C) 1992-1995 Microsoft Corporation
|
|
// All rights reserved.
|
|
//
|
|
// This source code is only intended as a supplement to the
|
|
// Microsoft Foundation Classes Reference and related
|
|
// electronic documentation provided with the library.
|
|
// See these sources for detailed information regarding the
|
|
// Microsoft Foundation Classes product.
|
|
|
|
#include "stdafx.h"
|
|
|
|
#ifdef AFX_DB_SEG
|
|
#pragma code_seg(AFX_DB_SEG)
|
|
#endif
|
|
|
|
#ifdef _DEBUG
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Global data
|
|
|
|
#ifdef _DEBUG
|
|
BOOL bTraceSql = FALSE;
|
|
#endif
|
|
|
|
#ifdef _68K_
|
|
static int nClassObject = 0;
|
|
#endif
|
|
|
|
static const TCHAR szODBC[] = _T("ODBC;");
|
|
static const TCHAR szComma[] = _T(",");
|
|
static const TCHAR chLiteralSeparator = '\'';
|
|
static const TCHAR szCall[] = _T("{CALL ");
|
|
static const TCHAR szParamCall[] = _T("{?");
|
|
static const TCHAR szSelect[] = _T("SELECT ");
|
|
static const TCHAR szFrom[] = _T(" FROM ");
|
|
static const TCHAR szWhere[] = _T(" WHERE ");
|
|
static const TCHAR szOrderBy[] = _T(" ORDER BY ");
|
|
static const TCHAR szForUpdate[] = _T(" FOR UPDATE ");
|
|
|
|
static const TCHAR szRowFetch[] = _T("State:01S01");
|
|
static const TCHAR szDataTruncated[] = _T("State:01004");
|
|
static const TCHAR szInfoRange[] = _T("State:S1096");
|
|
static const TCHAR szOutOfSequence[] = _T("State:S1010");
|
|
static const TCHAR szDriverNotCapable[] = _T("State:S1C00");
|
|
#ifndef _MPPC_
|
|
static const char szODBCDLL[] = "ODBC32.DLL";
|
|
#else
|
|
static const char szODBCDLL[] = "vsi:ODBC$DriverMgr";
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// for dynamic load of ODBC32.DLL
|
|
|
|
#ifdef _AFXDLL
|
|
|
|
static void PASCAL AfxOdbcLoad(FARPROC* pProcPtrs, LPCSTR lpszEntry)
|
|
{
|
|
HINSTANCE hInst;
|
|
TRY
|
|
{
|
|
// attempt to load but catch error for custom message
|
|
hInst = AfxLoadDll(&_afxDbState->m_hInstODBC, szODBCDLL);
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
// Note: DELETE_EXCEPTION(e) not necessary
|
|
|
|
// ODBC32.DLL not installed correctly
|
|
AfxThrowDBException(AFX_SQL_ERROR_ODBC_LOAD_FAILED, NULL,
|
|
SQL_NULL_HSTMT);
|
|
}
|
|
END_CATCH_ALL
|
|
|
|
ASSERT(hInst != NULL);
|
|
|
|
// cache the procedure pointer
|
|
ASSERT(pProcPtrs != NULL);
|
|
FARPROC proc = GetProcAddress(hInst, lpszEntry);
|
|
if (*pProcPtrs == NULL)
|
|
{
|
|
TRACE2("Error: Couldn't find %s in %s!\n", lpszEntry, szODBCDLL);
|
|
AfxThrowDBException(AFX_SQL_ERROR_INCORRECT_ODBC, NULL, SQL_NULL_HSTMT);
|
|
}
|
|
*pProcPtrs = proc;
|
|
}
|
|
|
|
#define ODBCLOAD(x) AfxOdbcLoad((FARPROC*)&_afxODBC.pfn##x, #x)
|
|
|
|
RETCODE SQL_API AfxThunkSQLAllocConnect(HENV h, HDBC* ph)
|
|
{
|
|
ODBCLOAD(SQLAllocConnect);
|
|
return _afxODBC.pfnSQLAllocConnect(h, ph);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLAllocEnv(HENV* ph)
|
|
{
|
|
ODBCLOAD(SQLAllocEnv);
|
|
return _afxODBC.pfnSQLAllocEnv(ph);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLAllocStmt(HDBC h, HSTMT* ph)
|
|
{
|
|
ODBCLOAD(SQLAllocStmt);
|
|
return _afxODBC.pfnSQLAllocStmt(h, ph);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLBindCol(HSTMT h, UWORD u, SWORD s, PTR p, SDWORD sdw, SDWORD* psdw)
|
|
{
|
|
ODBCLOAD(SQLBindCol);
|
|
return _afxODBC.pfnSQLBindCol(h, u, s, p, sdw, psdw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLCancel(HSTMT h)
|
|
{
|
|
ODBCLOAD(SQLCancel);
|
|
return _afxODBC.pfnSQLCancel(h);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLDescribeCol(HSTMT h, UWORD u, UCHAR* puch, SWORD s, SWORD* ps1, SWORD* ps2, UDWORD* pudw, SWORD* ps3, SWORD* ps4)
|
|
{
|
|
ODBCLOAD(SQLDescribeCol);
|
|
return _afxODBC.pfnSQLDescribeCol(h, u, puch, s, ps1, ps2, pudw, ps3, ps4);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLDisconnect(HDBC h)
|
|
{
|
|
ODBCLOAD(SQLDisconnect);
|
|
return _afxODBC.pfnSQLDisconnect(h);
|
|
}
|
|
|
|
#ifndef _MAC
|
|
RETCODE SQL_API AfxThunkSQLDriverConnect(HDBC hdbc, HWND hwnd, UCHAR* puch1, SWORD s1, UCHAR* puch2, SWORD s2, SWORD* ps1, UWORD u)
|
|
#else
|
|
RETCODE SQL_API AfxThunkSQLDriverConnect(HDBC hdbc, SQLHWND hwnd, UCHAR* puch1, SWORD s1, UCHAR* puch2, SWORD s2, SWORD* ps1, UWORD u)
|
|
#endif
|
|
{
|
|
ODBCLOAD(SQLDriverConnect);
|
|
return _afxODBC.pfnSQLDriverConnect(hdbc, hwnd, puch1, s1, puch2, s2, ps1, u);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLError(HENV henv, HDBC hdbc, HSTMT hstmt, UCHAR* puch1, SDWORD* psdw, UCHAR* puch2, SWORD s, SWORD* ps)
|
|
{
|
|
ODBCLOAD(SQLError);
|
|
return _afxODBC.pfnSQLError(henv, hdbc, hstmt, puch1, psdw, puch2, s, ps);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLExecDirect(HSTMT h, UCHAR* puch, SDWORD sdw)
|
|
{
|
|
ODBCLOAD(SQLExecDirect);
|
|
return _afxODBC.pfnSQLExecDirect(h, puch, sdw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLExecute(HSTMT h)
|
|
{
|
|
ODBCLOAD(SQLExecute);
|
|
return _afxODBC.pfnSQLExecute(h);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLExtendedFetch(HSTMT h, UWORD u, SDWORD sdw, UDWORD* pu1, UWORD* pu2)
|
|
{
|
|
ODBCLOAD(SQLExtendedFetch);
|
|
return _afxODBC.pfnSQLExtendedFetch(h, u, sdw, pu1, pu2);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLFetch(HSTMT h)
|
|
{
|
|
ODBCLOAD(SQLFetch);
|
|
return _afxODBC.pfnSQLFetch(h);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLFreeConnect(HDBC h)
|
|
{
|
|
ODBCLOAD(SQLFreeConnect);
|
|
return _afxODBC.pfnSQLFreeConnect(h);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLFreeEnv(HENV h)
|
|
{
|
|
ODBCLOAD(SQLFreeEnv);
|
|
return _afxODBC.pfnSQLFreeEnv(h);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLFreeStmt(HSTMT h, UWORD u)
|
|
{
|
|
ODBCLOAD(SQLFreeStmt);
|
|
return _afxODBC.pfnSQLFreeStmt(h, u);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLGetCursorName(HSTMT h, UCHAR* puch, SWORD s, SWORD* ps)
|
|
{
|
|
ODBCLOAD(SQLGetCursorName);
|
|
return _afxODBC.pfnSQLGetCursorName(h, puch, s, ps);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLGetData(HSTMT h, UWORD u, SWORD s, PTR p, SDWORD sdw, SDWORD* psdw)
|
|
{
|
|
ODBCLOAD(SQLGetData);
|
|
return _afxODBC.pfnSQLGetData(h, u, s, p, sdw, psdw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLGetFunctions(HDBC h, UWORD u, UWORD* pu)
|
|
{
|
|
ODBCLOAD(SQLGetFunctions);
|
|
return _afxODBC.pfnSQLGetFunctions(h, u, pu);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLGetInfo(HDBC h, UWORD u, PTR p, SWORD s, SWORD* ps)
|
|
{
|
|
ODBCLOAD(SQLGetInfo);
|
|
return _afxODBC.pfnSQLGetInfo(h, u, p, s, ps);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLMoreResults(HSTMT h)
|
|
{
|
|
ODBCLOAD(SQLMoreResults);
|
|
return _afxODBC.pfnSQLMoreResults(h);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLNumResultCols(HSTMT h, SWORD* ps)
|
|
{
|
|
ODBCLOAD(SQLNumResultCols);
|
|
return _afxODBC.pfnSQLNumResultCols(h, ps);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLParamData(HSTMT h, PTR* pp)
|
|
{
|
|
ODBCLOAD(SQLParamData);
|
|
return _afxODBC.pfnSQLParamData(h, pp);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLPrepare(HSTMT h, UCHAR* puch, SDWORD sdw)
|
|
{
|
|
ODBCLOAD(SQLPrepare);
|
|
return _afxODBC.pfnSQLPrepare(h, puch, sdw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLPutData(HSTMT h, PTR p, SDWORD sdw)
|
|
{
|
|
ODBCLOAD(SQLPutData);
|
|
return _afxODBC.pfnSQLPutData(h, p, sdw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLRowCount(HSTMT h, SDWORD* psdw)
|
|
{
|
|
ODBCLOAD(SQLRowCount);
|
|
return _afxODBC.pfnSQLRowCount(h, psdw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLSetConnectOption(HDBC h, UWORD u, UDWORD udw)
|
|
{
|
|
ODBCLOAD(SQLSetConnectOption);
|
|
return _afxODBC.pfnSQLSetConnectOption(h, u, udw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLSetPos(HSTMT h, UWORD u1, UWORD u2, UWORD u3)
|
|
{
|
|
ODBCLOAD(SQLSetPos);
|
|
return _afxODBC.pfnSQLSetPos(h, u1, u2, u3);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLSetStmtOption(HSTMT h, UWORD u, UDWORD udw)
|
|
{
|
|
ODBCLOAD(SQLSetStmtOption);
|
|
return _afxODBC.pfnSQLSetStmtOption(h, u, udw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLTransact(HENV henv, HDBC hdbc, UWORD u)
|
|
{
|
|
ODBCLOAD(SQLTransact);
|
|
return _afxODBC.pfnSQLTransact(henv, hdbc, u);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLBindParameter(HSTMT h, UWORD u, SWORD s1, SWORD s2, SWORD s3, UDWORD udw, SWORD s4, PTR p, SDWORD sdw, SDWORD* psdw)
|
|
{
|
|
ODBCLOAD(SQLBindParameter);
|
|
return _afxODBC.pfnSQLBindParameter(h, u, s1, s2, s3, udw, s4, p, sdw, psdw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLNumParams(HSTMT h, SWORD* psw)
|
|
{
|
|
ODBCLOAD(SQLNumParams);
|
|
return _afxODBC.pfnSQLNumParams(h, psw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLGetStmtOption(HSTMT h, UWORD u, PTR p)
|
|
{
|
|
ODBCLOAD(SQLGetStmtOption);
|
|
return _afxODBC.pfnSQLGetStmtOption(h, u, p);
|
|
}
|
|
|
|
AFX_DATADEF AFX_ODBC_CALL _afxODBC =
|
|
{
|
|
{ AfxThunkSQLAllocConnect, },
|
|
{ AfxThunkSQLAllocEnv, },
|
|
{ AfxThunkSQLAllocStmt, },
|
|
{ AfxThunkSQLBindCol, },
|
|
{ AfxThunkSQLCancel, },
|
|
{ AfxThunkSQLDescribeCol, },
|
|
{ AfxThunkSQLDisconnect, },
|
|
{ AfxThunkSQLDriverConnect, },
|
|
{ AfxThunkSQLError, },
|
|
{ AfxThunkSQLExecDirect, },
|
|
{ AfxThunkSQLExecute, },
|
|
{ AfxThunkSQLExtendedFetch, },
|
|
{ AfxThunkSQLFetch, },
|
|
{ AfxThunkSQLFreeConnect, },
|
|
{ AfxThunkSQLFreeEnv, },
|
|
{ AfxThunkSQLFreeStmt, },
|
|
{ AfxThunkSQLGetCursorName, },
|
|
{ AfxThunkSQLGetData, },
|
|
{ AfxThunkSQLGetFunctions, },
|
|
{ AfxThunkSQLGetInfo, },
|
|
{ AfxThunkSQLMoreResults, },
|
|
{ AfxThunkSQLNumResultCols, },
|
|
{ AfxThunkSQLParamData, },
|
|
{ AfxThunkSQLPrepare, },
|
|
{ AfxThunkSQLPutData, },
|
|
{ AfxThunkSQLRowCount, },
|
|
{ AfxThunkSQLSetConnectOption, },
|
|
{ AfxThunkSQLSetPos, },
|
|
{ AfxThunkSQLSetStmtOption, },
|
|
{ AfxThunkSQLTransact, },
|
|
{ AfxThunkSQLBindParameter, },
|
|
{ AfxThunkSQLNumParams, },
|
|
{ AfxThunkSQLGetStmtOption, },
|
|
};
|
|
|
|
_AFX_DB_STATE::~_AFX_DB_STATE()
|
|
{
|
|
if (m_hInstODBC != NULL)
|
|
::FreeLibrary(m_hInstODBC);
|
|
}
|
|
#endif // _AFXDLL
|
|
|
|
#define new DEBUG_NEW
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CDBException
|
|
|
|
void AFXAPI AfxThrowDBException(RETCODE nRetCode, CDatabase* pdb, HSTMT hstmt)
|
|
{
|
|
CDBException* pException = new CDBException(nRetCode);
|
|
if (nRetCode == SQL_ERROR && pdb != NULL)
|
|
pException->BuildErrorString(pdb, hstmt);
|
|
else if (nRetCode > AFX_SQL_ERROR && nRetCode < AFX_SQL_ERROR_MAX)
|
|
{
|
|
VERIFY(pException->m_strError.LoadString(
|
|
AFX_IDP_SQL_FIRST+(nRetCode-AFX_SQL_ERROR)));
|
|
TRACE1("%s\n", pException->m_strError);
|
|
}
|
|
THROW(pException);
|
|
}
|
|
|
|
CDBException::CDBException(RETCODE nRetCode)
|
|
{
|
|
m_nRetCode = nRetCode;
|
|
}
|
|
|
|
CDBException::~CDBException()
|
|
{
|
|
}
|
|
|
|
void CDBException::BuildErrorString(CDatabase* pdb, HSTMT hstmt, BOOL bTrace)
|
|
{
|
|
ASSERT_VALID(this);
|
|
UNUSED(bTrace); // unused in release builds
|
|
|
|
if (pdb != NULL)
|
|
{
|
|
SWORD nOutlen;
|
|
RETCODE nRetCode;
|
|
UCHAR lpszMsg[SQL_MAX_MESSAGE_LENGTH];
|
|
UCHAR lpszState[SQL_SQLSTATE_SIZE];
|
|
CString strMsg;
|
|
CString strState;
|
|
SDWORD lNative;
|
|
|
|
_AFX_DB_STATE* pDbState = _afxDbState;
|
|
AFX_SQL_SYNC(::SQLError(pDbState->m_henvAllConnections, pdb->m_hdbc,
|
|
hstmt, lpszState, &lNative,
|
|
lpszMsg, SQL_MAX_MESSAGE_LENGTH-1, &nOutlen));
|
|
strState = lpszState;
|
|
|
|
// Skip non-errors
|
|
while ((nRetCode == SQL_SUCCESS || nRetCode == SQL_SUCCESS_WITH_INFO) &&
|
|
lstrcmp(strState, _T("00000")) != 0)
|
|
{
|
|
strMsg = lpszMsg;
|
|
|
|
TCHAR lpszNative[50];
|
|
wsprintf(lpszNative, _T(",Native:%ld,Origin:"), lNative);
|
|
strState += lpszNative;
|
|
|
|
// transfer [origin] from message string to StateNativeOrigin string
|
|
int nCloseBracket;
|
|
int nMsgLength;
|
|
while (!strMsg.IsEmpty() &&
|
|
strMsg[0] == '[' && (nCloseBracket = strMsg.Find(']')) >= 0)
|
|
{
|
|
// Skip ']'
|
|
nCloseBracket++;
|
|
strState += strMsg.Left(nCloseBracket);
|
|
|
|
nMsgLength = strMsg.GetLength();
|
|
// Skip ' ', if present
|
|
if (nCloseBracket < nMsgLength && strMsg[nCloseBracket] == ' ')
|
|
nCloseBracket++;
|
|
strMsg = strMsg.Right(nMsgLength - nCloseBracket);
|
|
}
|
|
strState += _T("\n");
|
|
m_strStateNativeOrigin += _T("State:") + strState;
|
|
m_strError += strMsg + _T("\n");
|
|
|
|
#ifdef _DEBUG
|
|
if (bTrace)
|
|
{
|
|
TraceErrorMessage(strMsg);
|
|
TraceErrorMessage(_T("State:") + strState);
|
|
}
|
|
#endif // _DEBUG
|
|
|
|
AFX_SQL_SYNC(::SQLError(pDbState->m_henvAllConnections,
|
|
pdb->m_hdbc, hstmt, lpszState, &lNative,
|
|
lpszMsg, SQL_MAX_MESSAGE_LENGTH-1, &nOutlen));
|
|
strState = lpszState;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
BOOL CDBException::GetErrorMessage(LPTSTR lpszError, UINT nMaxError,
|
|
PUINT pnHelpContext /* = NULL */)
|
|
{
|
|
ASSERT(lpszError != NULL && AfxIsValidString(lpszError, nMaxError));
|
|
|
|
if (pnHelpContext != NULL)
|
|
*pnHelpContext = 0;
|
|
|
|
lstrcpyn(lpszError, m_strError, nMaxError-1);
|
|
lpszError[nMaxError-1] = '\0';
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
#ifdef _DEBUG
|
|
void CDBException::TraceErrorMessage(LPCTSTR szTrace) const
|
|
{
|
|
CString strTrace = szTrace;
|
|
|
|
if (strTrace.GetLength() <= 80)
|
|
TRACE1("%s\n", strTrace);
|
|
else
|
|
{
|
|
// Display 80 chars/line
|
|
while (strTrace.GetLength() > 80)
|
|
{
|
|
TRACE1("%s\n", strTrace.Left(80));
|
|
strTrace = strTrace.Right(strTrace.GetLength() - 80);
|
|
}
|
|
TRACE1("%s\n", strTrace);
|
|
}
|
|
}
|
|
#endif // _DEBUG
|
|
|
|
void CDBException::Empty()
|
|
{
|
|
m_strError.Empty();
|
|
m_strStateNativeOrigin.Empty();
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CDatabase implementation
|
|
|
|
CDatabase::CDatabase()
|
|
{
|
|
#ifdef _68K_
|
|
if (nClassObject++ == 0)
|
|
{
|
|
if (IsLibraryManagerLoaded())
|
|
UnloadLibraryManager();
|
|
if(InitLibraryManager(0, kCurrentZone, kNormalMemory))
|
|
nClassObject = 0;
|
|
}
|
|
#endif
|
|
|
|
m_hdbc = SQL_NULL_HDBC;
|
|
m_hstmt = SQL_NULL_HSTMT;
|
|
|
|
m_bUpdatable = FALSE;
|
|
m_bTransactions = FALSE;
|
|
#ifdef _DEBUG
|
|
m_bTransactionPending = FALSE;
|
|
#endif
|
|
m_dwLoginTimeout = DEFAULT_LOGIN_TIMEOUT;
|
|
m_dwQueryTimeout = DEFAULT_QUERY_TIMEOUT;
|
|
|
|
m_bStripTrailingSpaces = FALSE;
|
|
m_bIncRecordCountOnAdd = FALSE;
|
|
m_bAddForUpdate = FALSE;
|
|
}
|
|
|
|
CDatabase::~CDatabase()
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
Free();
|
|
|
|
#ifdef _68K_
|
|
if (--nClassObject == 0)
|
|
CleanupLibraryManager();
|
|
#endif
|
|
}
|
|
|
|
BOOL CDatabase::Open(LPCTSTR lpszDSN, BOOL bExclusive,
|
|
BOOL bReadonly, LPCTSTR lpszConnect, BOOL bUseCursorLib)
|
|
{
|
|
ASSERT(lpszDSN == NULL || AfxIsValidString(lpszDSN));
|
|
ASSERT(lpszConnect == NULL || AfxIsValidString(lpszConnect));
|
|
|
|
CString strConnect;
|
|
|
|
if (lpszConnect != NULL)
|
|
strConnect = lpszConnect;
|
|
|
|
// For VB/Access compatibility, require "ODBC;" (or "odbc;")
|
|
// prefix to the connect string
|
|
if (_tcsnicmp(strConnect, szODBC, lstrlen(szODBC)) != 0)
|
|
{
|
|
TRACE0("Error: Missing 'ODBC' prefix on connect string.\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// Strip "ODBC;"
|
|
strConnect = strConnect.Right(strConnect.GetLength()
|
|
- lstrlen(szODBC));
|
|
|
|
if (lpszDSN != NULL && lstrlen(lpszDSN) != 0)
|
|
{
|
|
// Append "DSN=" lpszDSN
|
|
strConnect += _T(";DSN=");
|
|
strConnect += lpszDSN;
|
|
}
|
|
|
|
DWORD dwOptions = 0;
|
|
|
|
if (bExclusive)
|
|
dwOptions |= openExclusive;
|
|
|
|
if (bReadonly)
|
|
dwOptions |= openReadOnly;
|
|
|
|
if (bUseCursorLib)
|
|
dwOptions |= useCursorLib;
|
|
|
|
return OpenEx(strConnect, dwOptions);
|
|
}
|
|
|
|
BOOL CDatabase::OpenEx(LPCTSTR lpszConnectString, DWORD dwOptions)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(lpszConnectString == NULL || AfxIsValidString(lpszConnectString));
|
|
ASSERT(!(dwOptions & noOdbcDialog && dwOptions & forceOdbcDialog));
|
|
|
|
#ifdef _68K_
|
|
if(nClassObject == 0)
|
|
AfxThrowDBException(AFX_SQL_ERROR_ODBC_LOAD_FAILED, NULL,
|
|
SQL_NULL_HSTMT);
|
|
#endif
|
|
|
|
// Exclusive access not supported.
|
|
ASSERT(!(dwOptions & openExclusive));
|
|
|
|
m_bUpdatable = !(dwOptions & openReadOnly);
|
|
|
|
TRY
|
|
{
|
|
m_strConnect = lpszConnectString;
|
|
|
|
// Allocate the HDBC and make connection
|
|
AllocConnect(dwOptions);
|
|
if(!Connect(dwOptions))
|
|
return FALSE;
|
|
|
|
// Verify support for required functionality and cache info
|
|
VerifyConnect();
|
|
GetConnectInfo();
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
Free();
|
|
THROW_LAST();
|
|
}
|
|
END_CATCH_ALL
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CDatabase::ExecuteSQL(LPCTSTR lpszSQL)
|
|
{
|
|
USES_CONVERSION;
|
|
RETCODE nRetCode;
|
|
HSTMT hstmt;
|
|
|
|
ASSERT_VALID(this);
|
|
ASSERT(AfxIsValidString(lpszSQL));
|
|
|
|
AFX_SQL_SYNC(::SQLAllocStmt(m_hdbc, &hstmt));
|
|
if (!Check(nRetCode))
|
|
AfxThrowDBException(nRetCode, this, hstmt);
|
|
|
|
TRY
|
|
{
|
|
OnSetOptions(hstmt);
|
|
|
|
// Give derived CDatabase classes option to use parameters
|
|
BindParameters(hstmt);
|
|
|
|
AFX_ODBC_CALL(::SQLExecDirect(hstmt,
|
|
(UCHAR*)T2A((LPTSTR)lpszSQL), SQL_NTS));
|
|
|
|
if (!Check(nRetCode))
|
|
AfxThrowDBException(nRetCode, this, hstmt);
|
|
else
|
|
{
|
|
do
|
|
{
|
|
SWORD nResultColumns;
|
|
|
|
AFX_ODBC_CALL(::SQLNumResultCols(hstmt, &nResultColumns));
|
|
if (nResultColumns != 0)
|
|
{
|
|
do
|
|
{
|
|
AFX_ODBC_CALL(::SQLFetch(hstmt));
|
|
} while (Check(nRetCode) && nRetCode != SQL_NO_DATA_FOUND);
|
|
}
|
|
AFX_ODBC_CALL(::SQLMoreResults(hstmt));
|
|
} while (Check(nRetCode) && nRetCode != SQL_NO_DATA_FOUND);
|
|
}
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
::SQLCancel(hstmt);
|
|
AFX_SQL_SYNC(::SQLFreeStmt(hstmt, SQL_DROP));
|
|
THROW_LAST();
|
|
}
|
|
END_CATCH_ALL
|
|
|
|
AFX_SQL_SYNC(::SQLFreeStmt(hstmt, SQL_DROP));
|
|
}
|
|
|
|
// Shutdown pending query for CDatabase's private m_hstmt
|
|
void CDatabase::Cancel()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hdbc != SQL_NULL_HDBC);
|
|
|
|
::SQLCancel(m_hstmt);
|
|
}
|
|
|
|
// Disconnect connection
|
|
void CDatabase::Close()
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
// Close any open recordsets
|
|
AfxLockGlobals(CRIT_ODBC);
|
|
TRY
|
|
{
|
|
while (!m_listRecordsets.IsEmpty())
|
|
{
|
|
CRecordset* pSet = (CRecordset*)m_listRecordsets.GetHead();
|
|
pSet->Close(); // will implicitly remove from list
|
|
pSet->m_pDatabase = NULL;
|
|
}
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
AfxUnlockGlobals(CRIT_ODBC);
|
|
THROW_LAST();
|
|
}
|
|
END_CATCH_ALL
|
|
AfxUnlockGlobals(CRIT_ODBC);
|
|
|
|
if (m_hdbc != SQL_NULL_HDBC)
|
|
{
|
|
RETCODE nRetCode;
|
|
AFX_SQL_SYNC(::SQLDisconnect(m_hdbc));
|
|
AFX_SQL_SYNC(::SQLFreeConnect(m_hdbc));
|
|
m_hdbc = SQL_NULL_HDBC;
|
|
|
|
_AFX_DB_STATE* pDbState = _afxDbState;
|
|
|
|
AfxLockGlobals(CRIT_ODBC);
|
|
ASSERT(pDbState->m_nAllocatedConnections != 0);
|
|
pDbState->m_nAllocatedConnections--;
|
|
AfxUnlockGlobals(CRIT_ODBC);
|
|
}
|
|
}
|
|
|
|
// Silently disconnect and free all ODBC resources. Don't throw any exceptions
|
|
void CDatabase::Free()
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
// Trap failures upon close
|
|
TRY
|
|
{
|
|
Close();
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
// Nothing we can do
|
|
TRACE0("Error: exception by CDatabase::Close() ignored in CDatabase::Free().\n");
|
|
DELETE_EXCEPTION(e);
|
|
}
|
|
END_CATCH_ALL
|
|
|
|
// free henv if refcount goes to 0
|
|
_AFX_DB_STATE* pDbState = _afxDbState;
|
|
AfxLockGlobals(CRIT_ODBC);
|
|
if (pDbState->m_henvAllConnections != SQL_NULL_HENV)
|
|
{
|
|
ASSERT(pDbState->m_nAllocatedConnections >= 0);
|
|
if (pDbState->m_nAllocatedConnections == 0)
|
|
{
|
|
// free last connection - release HENV
|
|
RETCODE nRetCodeEnv = ::SQLFreeEnv(pDbState->m_henvAllConnections);
|
|
#ifdef _DEBUG
|
|
if (nRetCodeEnv != SQL_SUCCESS)
|
|
// Nothing we can do
|
|
TRACE0("Error: SQLFreeEnv failure ignored in CDatabase::Free().\n");
|
|
#endif
|
|
pDbState->m_henvAllConnections = SQL_NULL_HENV;
|
|
}
|
|
}
|
|
AfxUnlockGlobals(CRIT_ODBC);
|
|
}
|
|
|
|
void CDatabase::OnSetOptions(HSTMT hstmt)
|
|
{
|
|
RETCODE nRetCode;
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hdbc != SQL_NULL_HDBC);
|
|
|
|
if (m_dwQueryTimeout != -1)
|
|
{
|
|
// Attempt to set query timeout. Ignore failure
|
|
AFX_SQL_SYNC(::SQLSetStmtOption(hstmt, SQL_QUERY_TIMEOUT,
|
|
m_dwQueryTimeout));
|
|
if (!Check(nRetCode))
|
|
// don't attempt it again
|
|
m_dwQueryTimeout = (DWORD)-1;
|
|
}
|
|
}
|
|
|
|
CString CDatabase::GetDatabaseName() const
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hdbc != SQL_NULL_HDBC);
|
|
|
|
char szName[MAX_TNAME_LEN];
|
|
SWORD nResult;
|
|
RETCODE nRetCode;
|
|
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DATABASE_NAME,
|
|
szName, _countof(szName), &nResult));
|
|
if (!Check(nRetCode))
|
|
szName[0] = '\0';
|
|
|
|
return szName;
|
|
}
|
|
|
|
BOOL CDatabase::BeginTrans()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hdbc != SQL_NULL_HDBC);
|
|
|
|
if (!m_bTransactions)
|
|
return FALSE;
|
|
|
|
// Only 1 level of transactions supported
|
|
ASSERT(!m_bTransactionPending);
|
|
|
|
RETCODE nRetCode;
|
|
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_AUTOCOMMIT,
|
|
SQL_AUTOCOMMIT_OFF));
|
|
#ifdef _DEBUG
|
|
m_bTransactionPending = TRUE;
|
|
#endif
|
|
|
|
return Check(nRetCode);
|
|
}
|
|
|
|
BOOL CDatabase::CommitTrans()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hdbc != SQL_NULL_HDBC);
|
|
|
|
if (!m_bTransactions)
|
|
return FALSE;
|
|
|
|
// BeginTrans must be called first
|
|
ASSERT(m_bTransactionPending);
|
|
|
|
_AFX_DB_STATE* pDbState = _afxDbState;
|
|
RETCODE nRetCode;
|
|
AFX_SQL_SYNC(::SQLTransact(pDbState->m_henvAllConnections, m_hdbc, SQL_COMMIT));
|
|
BOOL bSuccess = Check(nRetCode);
|
|
|
|
// Turn back on auto commit
|
|
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_AUTOCOMMIT,
|
|
SQL_AUTOCOMMIT_ON));
|
|
#ifdef _DEBUG
|
|
m_bTransactionPending = FALSE;
|
|
#endif
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
BOOL CDatabase::Rollback()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hdbc != SQL_NULL_HDBC);
|
|
|
|
if (!m_bTransactions)
|
|
return FALSE;
|
|
|
|
// BeginTrans must be called first
|
|
ASSERT(m_bTransactionPending);
|
|
|
|
_AFX_DB_STATE* pDbState = _afxDbState;
|
|
RETCODE nRetCode;
|
|
AFX_SQL_SYNC(::SQLTransact(pDbState->m_henvAllConnections, m_hdbc, SQL_ROLLBACK));
|
|
BOOL bSuccess = Check(nRetCode);
|
|
|
|
// Turn back on auto commit
|
|
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_AUTOCOMMIT,
|
|
SQL_AUTOCOMMIT_ON));
|
|
#ifdef _DEBUG
|
|
m_bTransactionPending = FALSE;
|
|
#endif
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
// Screen for errors.
|
|
BOOL CDatabase::Check(RETCODE nRetCode) const
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
switch (nRetCode)
|
|
{
|
|
case SQL_SUCCESS_WITH_INFO:
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
{
|
|
CDBException e(nRetCode);
|
|
TRACE0("Warning: ODBC Success With Info, ");
|
|
e.BuildErrorString((CDatabase*)this, SQL_NULL_HSTMT);
|
|
}
|
|
#endif // _DEBUG
|
|
|
|
// Fall through
|
|
|
|
case SQL_SUCCESS:
|
|
case SQL_NO_DATA_FOUND:
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CDatabase internal functions
|
|
|
|
//Replace brackets in SQL string with SQL_IDENTIFIER_QUOTE_CHAR
|
|
void CDatabase::ReplaceBrackets(LPTSTR lpchSQL)
|
|
{
|
|
BOOL bInLiteral = FALSE;
|
|
LPTSTR lpchNewSQL = lpchSQL;
|
|
|
|
while (*lpchSQL != '\0')
|
|
{
|
|
if (*lpchSQL == chLiteralSeparator)
|
|
{
|
|
// Handle escaped literal
|
|
if (*_tcsinc(lpchSQL) == chLiteralSeparator)
|
|
{
|
|
*lpchNewSQL = *lpchSQL;
|
|
lpchSQL = _tcsinc(lpchSQL);
|
|
lpchNewSQL = _tcsinc(lpchNewSQL);
|
|
}
|
|
else
|
|
bInLiteral = !bInLiteral;
|
|
|
|
*lpchNewSQL = *lpchSQL;
|
|
}
|
|
else if (!bInLiteral && (*lpchSQL == '['))
|
|
{
|
|
if (*_tcsinc(lpchSQL) == '[')
|
|
{
|
|
// Handle escaped left bracket by inserting one '['
|
|
*lpchNewSQL = *lpchSQL;
|
|
lpchSQL = _tcsinc(lpchSQL);
|
|
}
|
|
else
|
|
*lpchNewSQL = m_chIDQuoteChar;
|
|
}
|
|
else if (!bInLiteral && (*lpchSQL == ']'))
|
|
{
|
|
if (*_tcsinc(lpchSQL) == ']')
|
|
{
|
|
// Handle escaped right bracket by inserting one ']'
|
|
*lpchNewSQL = *lpchSQL;
|
|
lpchSQL = _tcsinc(lpchSQL);
|
|
}
|
|
else
|
|
*lpchNewSQL = m_chIDQuoteChar;
|
|
}
|
|
else
|
|
*lpchNewSQL = *lpchSQL;
|
|
|
|
lpchSQL = _tcsinc(lpchSQL);
|
|
lpchNewSQL = _tcsinc(lpchNewSQL);
|
|
}
|
|
}
|
|
|
|
// Allocate an henv (first time called) and hdbc
|
|
void CDatabase::AllocConnect(DWORD dwOptions)
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
if (m_hdbc != SQL_NULL_HDBC)
|
|
return;
|
|
|
|
_AFX_DB_STATE* pDbState = _afxDbState;
|
|
|
|
RETCODE nRetCode;
|
|
|
|
AfxLockGlobals(CRIT_ODBC);
|
|
if (pDbState->m_henvAllConnections == SQL_NULL_HENV)
|
|
{
|
|
ASSERT(pDbState->m_nAllocatedConnections == 0);
|
|
|
|
// need to allocate an environment for first connection
|
|
AFX_SQL_SYNC(::SQLAllocEnv(&pDbState->m_henvAllConnections));
|
|
if (!Check(nRetCode))
|
|
{
|
|
AfxUnlockGlobals(CRIT_ODBC);
|
|
AfxThrowMemoryException(); // fatal
|
|
}
|
|
}
|
|
|
|
ASSERT(pDbState->m_henvAllConnections != SQL_NULL_HENV);
|
|
AFX_SQL_SYNC(::SQLAllocConnect(pDbState->m_henvAllConnections, &m_hdbc));
|
|
if (!Check(nRetCode))
|
|
{
|
|
AfxUnlockGlobals(CRIT_ODBC);
|
|
ThrowDBException(nRetCode); // fatal
|
|
}
|
|
pDbState->m_nAllocatedConnections++; // allocated at least
|
|
AfxUnlockGlobals(CRIT_ODBC);
|
|
|
|
#ifdef _DEBUG
|
|
if (bTraceSql)
|
|
{
|
|
::SQLSetConnectOption(m_hdbc, SQL_OPT_TRACEFILE,
|
|
(DWORD)"odbccall.txt");
|
|
::SQLSetConnectOption(m_hdbc, SQL_OPT_TRACE, 1);
|
|
}
|
|
#endif // _DEBUG
|
|
|
|
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_LOGIN_TIMEOUT,
|
|
m_dwLoginTimeout));
|
|
#ifdef _DEBUG
|
|
if (nRetCode != SQL_SUCCESS && nRetCode != SQL_SUCCESS_WITH_INFO &&
|
|
(afxTraceFlags & traceDatabase))
|
|
TRACE0("Warning: Failure setting login timeout.\n");
|
|
#endif
|
|
|
|
if (!m_bUpdatable)
|
|
{
|
|
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_ACCESS_MODE,
|
|
SQL_MODE_READ_ONLY));
|
|
#ifdef _DEBUG
|
|
if (nRetCode != SQL_SUCCESS && nRetCode != SQL_SUCCESS_WITH_INFO &&
|
|
(afxTraceFlags & traceDatabase))
|
|
TRACE0("Warning: Failure setting read only access mode.\n");
|
|
#endif
|
|
}
|
|
|
|
// Turn on cursor lib support
|
|
if (dwOptions & useCursorLib)
|
|
{
|
|
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc,
|
|
SQL_ODBC_CURSORS, SQL_CUR_USE_ODBC));
|
|
// With cursor library added records immediately in result set
|
|
m_bIncRecordCountOnAdd = TRUE;
|
|
}
|
|
}
|
|
|
|
BOOL CDatabase::Connect(DWORD dwOptions)
|
|
{
|
|
USES_CONVERSION;
|
|
|
|
HWND hWndTop;
|
|
CWnd* pWnd = CWnd::GetSafeOwner(NULL, &hWndTop);
|
|
if (pWnd == NULL)
|
|
pWnd = CWnd::GetDesktopWindow();
|
|
ASSERT_VALID(pWnd);
|
|
|
|
UCHAR szConnectOutput[MAX_CONNECT_LEN];
|
|
RETCODE nRetCode;
|
|
SWORD nResult;
|
|
UWORD wConnectOption = SQL_DRIVER_COMPLETE;
|
|
if (dwOptions & noOdbcDialog)
|
|
wConnectOption = SQL_DRIVER_NOPROMPT;
|
|
else if (dwOptions & forceOdbcDialog)
|
|
wConnectOption = SQL_DRIVER_PROMPT;
|
|
#ifndef _MAC
|
|
AFX_SQL_SYNC(::SQLDriverConnect(m_hdbc, pWnd->m_hWnd,
|
|
(UCHAR*)T2A((LPTSTR)(LPCTSTR)m_strConnect), SQL_NTS,
|
|
szConnectOutput, _countof(szConnectOutput),
|
|
&nResult, wConnectOption));
|
|
#else
|
|
AFX_SQL_SYNC(::SQLDriverConnect(m_hdbc, GetWrapperWindow(pWnd->m_hWnd),
|
|
(UCHAR*)(const char*)m_strConnect, SQL_NTS,
|
|
szConnectOutput, _countof(szConnectOutput),
|
|
&nResult, wConnectOption));
|
|
#endif
|
|
if (hWndTop != NULL)
|
|
::EnableWindow(hWndTop, TRUE);
|
|
|
|
// If user hit 'Cancel'
|
|
if (nRetCode == SQL_NO_DATA_FOUND)
|
|
{
|
|
Free();
|
|
return FALSE;
|
|
}
|
|
|
|
if (!Check(nRetCode))
|
|
{
|
|
#ifdef _DEBUG
|
|
if (pWnd->m_hWnd == NULL)
|
|
TRACE0("Error: No default window (AfxGetApp()->m_pMainWnd) for SQLDriverConnect.\n");
|
|
#endif
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
|
|
// Connect strings must have "ODBC;"
|
|
m_strConnect = szODBC;
|
|
// Save connect string returned from ODBC
|
|
m_strConnect += (char*)szConnectOutput;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CDatabase::VerifyConnect()
|
|
{
|
|
RETCODE nRetCode;
|
|
SWORD nResult;
|
|
|
|
SWORD nAPIConformance;
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_ODBC_API_CONFORMANCE,
|
|
&nAPIConformance, sizeof(nAPIConformance), &nResult));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode);
|
|
|
|
if (nAPIConformance < SQL_OAC_LEVEL1)
|
|
ThrowDBException(AFX_SQL_ERROR_API_CONFORMANCE);
|
|
|
|
SWORD nSQLConformance;
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_ODBC_SQL_CONFORMANCE,
|
|
&nSQLConformance, sizeof(nSQLConformance), &nResult));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode);
|
|
|
|
if (nSQLConformance < SQL_OSC_MINIMUM)
|
|
ThrowDBException(AFX_SQL_ERROR_SQL_CONFORMANCE);
|
|
}
|
|
|
|
void CDatabase::GetConnectInfo()
|
|
{
|
|
RETCODE nRetCode;
|
|
SWORD nResult;
|
|
|
|
// Reset the database update options
|
|
m_dwUpdateOptions = 0;
|
|
|
|
// Check for SQLSetPos support
|
|
UDWORD dwDriverPosOperations;
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_POS_OPERATIONS,
|
|
&dwDriverPosOperations, sizeof(dwDriverPosOperations), &nResult));
|
|
if (Check(nRetCode) &&
|
|
(dwDriverPosOperations & SQL_POS_UPDATE) &&
|
|
(dwDriverPosOperations & SQL_POS_DELETE) &&
|
|
(dwDriverPosOperations & SQL_POS_ADD))
|
|
m_dwUpdateOptions |= AFX_SQL_SETPOSUPDATES;
|
|
|
|
// Check for positioned update SQL support
|
|
UDWORD dwPositionedStatements;
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_POSITIONED_STATEMENTS,
|
|
&dwPositionedStatements, sizeof(dwPositionedStatements),
|
|
&nResult));
|
|
if (Check(nRetCode) &&
|
|
(dwPositionedStatements & SQL_PS_POSITIONED_DELETE) &&
|
|
(dwPositionedStatements & SQL_PS_POSITIONED_UPDATE))
|
|
m_dwUpdateOptions |= AFX_SQL_POSITIONEDSQL;
|
|
|
|
// Check for transaction support
|
|
SWORD nTxnCapable;
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_TXN_CAPABLE, &nTxnCapable,
|
|
sizeof(nTxnCapable), &nResult));
|
|
if (Check(nRetCode) && nTxnCapable != SQL_TC_NONE)
|
|
m_bTransactions = TRUE;
|
|
|
|
// Cache the effect of transactions on cursors
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_CURSOR_COMMIT_BEHAVIOR,
|
|
&m_nCursorCommitBehavior, sizeof(m_nCursorCommitBehavior),
|
|
&nResult));
|
|
if (!Check(nRetCode))
|
|
m_nCursorCommitBehavior = SQL_ERROR;
|
|
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_CURSOR_ROLLBACK_BEHAVIOR,
|
|
&m_nCursorRollbackBehavior, sizeof(m_nCursorRollbackBehavior),
|
|
&nResult));
|
|
if (!Check(nRetCode))
|
|
m_nCursorRollbackBehavior = SQL_ERROR;
|
|
|
|
// Cache bookmark attributes
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_BOOKMARK_PERSISTENCE,
|
|
&m_dwBookmarkAttributes, sizeof(m_dwBookmarkAttributes),
|
|
&nResult));
|
|
Check(nRetCode);
|
|
|
|
// Check for SQLGetData support req'd by RFX_LongBinary
|
|
UDWORD dwGetDataExtensions;
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_GETDATA_EXTENSIONS,
|
|
&dwGetDataExtensions, sizeof(dwGetDataExtensions),
|
|
&nResult));
|
|
if (!Check(nRetCode))
|
|
dwGetDataExtensions = 0;
|
|
if (dwGetDataExtensions & SQL_GD_BOUND)
|
|
m_dwUpdateOptions |= AFX_SQL_GDBOUND;
|
|
|
|
if (m_bUpdatable)
|
|
{
|
|
// Make sure data source is Updatable
|
|
char szReadOnly[10];
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DATA_SOURCE_READ_ONLY,
|
|
szReadOnly, _countof(szReadOnly), &nResult));
|
|
if (Check(nRetCode) && nResult == 1)
|
|
m_bUpdatable = !(lstrcmpA(szReadOnly, "Y") == 0);
|
|
else
|
|
m_bUpdatable = FALSE;
|
|
#ifdef _DEBUG
|
|
if (!m_bUpdatable && (afxTraceFlags & traceDatabase))
|
|
TRACE0("Warning: data source is readonly.\n");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Make data source is !Updatable
|
|
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc,
|
|
SQL_ACCESS_MODE, SQL_MODE_READ_ONLY));
|
|
}
|
|
|
|
// Cache the quote char to use when constructing SQL
|
|
char szIDQuoteChar[2];
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_IDENTIFIER_QUOTE_CHAR,
|
|
szIDQuoteChar, _countof(szIDQuoteChar), &nResult));
|
|
if (Check(nRetCode) && nResult == 1)
|
|
m_chIDQuoteChar = szIDQuoteChar[0];
|
|
else
|
|
m_chIDQuoteChar = ' ';
|
|
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
{
|
|
char szInfo[64];
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DBMS_NAME,
|
|
szInfo, _countof(szInfo), &nResult));
|
|
if (Check(nRetCode))
|
|
{
|
|
CString strInfo = szInfo;
|
|
TRACE1("DBMS: %s\n", strInfo);
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_hdbc, SQL_DBMS_VER,
|
|
szInfo, _countof(szInfo), &nResult));
|
|
if (Check(nRetCode))
|
|
{
|
|
strInfo = szInfo;
|
|
TRACE1(", Version: %s\n", strInfo);
|
|
}
|
|
}
|
|
}
|
|
#endif // _DEBUG
|
|
}
|
|
|
|
void CDatabase::BindParameters(HSTMT /* hstmt */)
|
|
{
|
|
// Must override and call SQLBindParameter directly
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CDatabase diagnostics
|
|
|
|
#ifdef _DEBUG
|
|
void CDatabase::AssertValid() const
|
|
{
|
|
CObject::AssertValid();
|
|
}
|
|
|
|
void CDatabase::Dump(CDumpContext& dc) const
|
|
{
|
|
CObject::Dump(dc);
|
|
|
|
dc << "m_hdbc = " << m_hdbc;
|
|
dc << "\nm_strConnect = " << m_strConnect;
|
|
dc << "\nm_bUpdatable = " << m_bUpdatable;
|
|
dc << "\nm_bTransactions = " << m_bTransactions;
|
|
dc << "\nm_bTransactionPending = " << m_bTransactionPending;
|
|
dc << "\nm_dwLoginTimeout = " << m_dwLoginTimeout;
|
|
dc << "\nm_dwQueryTimeout = " << m_dwQueryTimeout;
|
|
|
|
if (dc.GetDepth() > 0)
|
|
{
|
|
_AFX_DB_STATE* pDbState = _afxDbState;
|
|
dc << "\nwith env:";
|
|
dc << "\n\tnAllocated = " << pDbState->m_nAllocatedConnections;
|
|
dc << "\n\thenvAllConnections = " << pDbState->m_henvAllConnections;
|
|
}
|
|
|
|
dc << "\n";
|
|
}
|
|
|
|
#endif // _DEBUG
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CRecordset helpers
|
|
|
|
void AFXAPI AfxSetCurrentRecord(long* plCurrentRecord, long nRows, RETCODE nRetCode);
|
|
void AFXAPI AfxSetRecordCount(long* plRecordCount, long lCurrentRecord,
|
|
long nRows, BOOL bEOFSeen, RETCODE nRetCode);
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CRecordset
|
|
|
|
CRecordset::CRecordset(CDatabase* pDatabase)
|
|
{
|
|
ASSERT(pDatabase == NULL || AfxIsValidAddress(pDatabase, sizeof(CDatabase)));
|
|
m_pDatabase = pDatabase;
|
|
|
|
m_nOpenType = snapshot;
|
|
m_lOpen = AFX_RECORDSET_STATUS_UNKNOWN;
|
|
m_nEditMode = noMode;
|
|
m_nDefaultType = snapshot;
|
|
m_dwOptions = none;
|
|
|
|
m_bAppendable = FALSE;
|
|
m_bUpdatable = FALSE;
|
|
m_bScrollable = FALSE;
|
|
m_bRecordsetDb = FALSE;
|
|
m_bRebindParams = FALSE;
|
|
m_bLongBinaryColumns = FALSE;
|
|
m_nLockMode = optimistic;
|
|
m_dwInitialGetDataLen = 0;
|
|
m_rgODBCFieldInfos = NULL;
|
|
m_rgFieldInfos = NULL;
|
|
m_rgRowStatus = NULL;
|
|
m_dwRowsetSize = 25;
|
|
m_dwAllocatedRowsetSize = 0;
|
|
|
|
m_nFields = 0;
|
|
m_nParams = 0;
|
|
m_nFieldsBound = 0;
|
|
m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
|
|
m_lRecordCount = 0;
|
|
m_bUseUpdateSQL = FALSE;
|
|
m_bUseODBCCursorLib = FALSE;
|
|
m_nResultCols = -1;
|
|
m_bCheckCacheForDirtyFields = TRUE;
|
|
|
|
m_pbFieldFlags = NULL;
|
|
m_pbParamFlags = NULL;
|
|
m_plParamLength = NULL;
|
|
m_pvFieldProxy = NULL;
|
|
m_pvParamProxy = NULL;
|
|
m_nProxyFields = 0;
|
|
m_nProxyParams = 0;
|
|
|
|
m_hstmtUpdate = SQL_NULL_HSTMT;
|
|
m_hstmt = SQL_NULL_HSTMT;
|
|
if (m_pDatabase != NULL && m_pDatabase->IsOpen())
|
|
{
|
|
ASSERT_VALID(m_pDatabase);
|
|
TRY
|
|
{
|
|
RETCODE nRetCode;
|
|
AFX_SQL_SYNC(::SQLAllocStmt(m_pDatabase->m_hdbc, &m_hstmt));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(SQL_INVALID_HANDLE);
|
|
|
|
// Add to list of CRecordsets with alloced hstmts
|
|
AfxLockGlobals(CRIT_ODBC);
|
|
TRY
|
|
{
|
|
m_pDatabase->m_listRecordsets.AddHead(this);
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
AfxUnlockGlobals(CRIT_ODBC);
|
|
THROW_LAST();
|
|
}
|
|
END_CATCH_ALL
|
|
AfxUnlockGlobals(CRIT_ODBC);
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
ASSERT(m_hstmt == SQL_NULL_HSTMT);
|
|
DELETE_EXCEPTION(e);
|
|
}
|
|
END_CATCH_ALL
|
|
}
|
|
}
|
|
|
|
CRecordset::~CRecordset()
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
TRY
|
|
{
|
|
if (m_hstmt != NULL)
|
|
{
|
|
#ifdef _DEBUG
|
|
if (m_dwOptions & useMultiRowFetch && afxTraceFlags & traceDatabase)
|
|
{
|
|
TRACE0("\nWARNING: Close called implicitly from destructor.");
|
|
TRACE0("\nUse of multi row fetch requires explicit call");
|
|
TRACE0("\nto Close or memory leaks will result.\n");
|
|
}
|
|
#endif
|
|
Close();
|
|
}
|
|
if (m_bRecordsetDb)
|
|
delete m_pDatabase;
|
|
m_pDatabase = NULL;
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
// Nothing we can do
|
|
TRACE0("Error: Exception ignored in ~CRecordset().\n");
|
|
DELETE_EXCEPTION(e);
|
|
}
|
|
END_CATCH_ALL
|
|
}
|
|
|
|
BOOL CRecordset::Open(UINT nOpenType, LPCTSTR lpszSQL, DWORD dwOptions)
|
|
{
|
|
ASSERT(!IsOpen());
|
|
ASSERT_VALID(this);
|
|
ASSERT(lpszSQL == NULL || AfxIsValidString(lpszSQL));
|
|
ASSERT(nOpenType == AFX_DB_USE_DEFAULT_TYPE ||
|
|
nOpenType == dynaset || nOpenType == snapshot ||
|
|
nOpenType == forwardOnly || nOpenType == dynamic);
|
|
ASSERT(!(dwOptions & readOnly && dwOptions & appendOnly));
|
|
|
|
// Can only use optimizeBulkAdd with appendOnly recordsets
|
|
ASSERT((dwOptions & optimizeBulkAdd && dwOptions & appendOnly) ||
|
|
!(dwOptions & optimizeBulkAdd));
|
|
|
|
// forwardOnly recordsets have limited functionality
|
|
ASSERT(!(nOpenType == forwardOnly && dwOptions & skipDeletedRecords));
|
|
|
|
// Cache state info and allocate hstmt
|
|
SetState(nOpenType, lpszSQL, dwOptions);
|
|
if(!AllocHstmt())
|
|
return FALSE;
|
|
|
|
// Check if bookmarks upported (CanBookmark depends on open DB)
|
|
ASSERT(dwOptions & useBookmarks ? CanBookmark() : TRUE);
|
|
|
|
TRY
|
|
{
|
|
OnSetOptions(m_hstmt);
|
|
|
|
// Allocate the field/param status arrays, if necessary
|
|
BOOL bUnbound = FALSE;
|
|
if (m_nFields > 0 || m_nParams > 0)
|
|
AllocStatusArrays();
|
|
else
|
|
bUnbound = TRUE;
|
|
|
|
// Build SQL and prep/execute or just execute direct
|
|
BuildSQL(lpszSQL);
|
|
PrepareAndExecute();
|
|
|
|
// Cache some field info and prepare the rowset
|
|
AllocAndCacheFieldInfo();
|
|
AllocRowset();
|
|
|
|
// If late binding, still need to allocate status arrays
|
|
if (bUnbound && (m_nFields > 0 || m_nParams > 0))
|
|
AllocStatusArrays();
|
|
|
|
// Give derived classes a call before binding
|
|
PreBindFields();
|
|
|
|
// Fetch the first row of data
|
|
MoveNext();
|
|
|
|
// If EOF, then result set empty, so set BOF as well
|
|
m_bBOF = m_bEOF;
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
Close();
|
|
THROW_LAST();
|
|
}
|
|
END_CATCH_ALL
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CRecordset::Close()
|
|
{
|
|
ASSERT_VALID(this);
|
|
// Can't close if database has been deleted
|
|
ASSERT(m_pDatabase != NULL);
|
|
|
|
// This will force a requery for cursor name if reopened.
|
|
m_strCursorName.Empty();
|
|
|
|
if (m_rgFieldInfos != NULL &&
|
|
m_nFields > 0 && m_bCheckCacheForDirtyFields)
|
|
{
|
|
FreeDataCache();
|
|
}
|
|
|
|
FreeRowset();
|
|
|
|
m_nEditMode = noMode;
|
|
|
|
delete [] m_rgFieldInfos;
|
|
m_rgFieldInfos = NULL;
|
|
|
|
delete [] m_rgODBCFieldInfos;
|
|
m_rgODBCFieldInfos = NULL;
|
|
|
|
delete [] m_pbFieldFlags;
|
|
m_pbFieldFlags = NULL;
|
|
|
|
delete [] m_pbParamFlags;
|
|
m_pbParamFlags = NULL;
|
|
|
|
if (m_pvFieldProxy != NULL)
|
|
{
|
|
for (UINT nField = 0; nField < m_nProxyFields; nField++)
|
|
delete m_pvFieldProxy[nField];
|
|
|
|
delete [] m_pvFieldProxy;
|
|
m_pvFieldProxy = NULL;
|
|
m_nProxyFields = 0;
|
|
}
|
|
|
|
if (m_pvParamProxy != NULL)
|
|
{
|
|
for (UINT nParam = 0; nParam < m_nProxyParams; nParam++)
|
|
delete m_pvParamProxy[nParam];
|
|
|
|
delete [] m_pvParamProxy;
|
|
m_pvParamProxy = NULL;
|
|
m_nProxyParams = 0;
|
|
}
|
|
|
|
delete [] m_plParamLength;
|
|
m_plParamLength = NULL;
|
|
|
|
RETCODE nRetCode;
|
|
if (m_hstmt != SQL_NULL_HSTMT)
|
|
{
|
|
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmt, SQL_DROP));
|
|
m_hstmt = SQL_NULL_HSTMT;
|
|
}
|
|
|
|
if (m_hstmtUpdate != SQL_NULL_HSTMT)
|
|
{
|
|
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmtUpdate, SQL_DROP));
|
|
m_hstmtUpdate = SQL_NULL_HSTMT;
|
|
}
|
|
|
|
// Remove CRecordset from CDatabase's list
|
|
AfxLockGlobals(CRIT_ODBC);
|
|
TRY
|
|
{
|
|
POSITION pos = m_pDatabase->m_listRecordsets.Find(this);
|
|
if (pos != NULL)
|
|
m_pDatabase->m_listRecordsets.RemoveAt(pos);
|
|
#ifdef _DEBUG
|
|
else if (afxTraceFlags & traceDatabase)
|
|
TRACE0("WARNING: CRecordset not found in m_pDatabase->m_listRecordsets.\n");
|
|
#endif
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
AfxUnlockGlobals(CRIT_ODBC);
|
|
THROW_LAST();
|
|
}
|
|
END_CATCH_ALL
|
|
AfxUnlockGlobals(CRIT_ODBC);
|
|
|
|
m_lOpen = AFX_RECORDSET_STATUS_CLOSED;
|
|
m_bBOF = TRUE;
|
|
m_bEOF = TRUE;
|
|
m_bDeleted = FALSE;
|
|
m_bAppendable = FALSE;
|
|
m_bUpdatable = FALSE;
|
|
m_bScrollable = FALSE;
|
|
m_bRebindParams = FALSE;
|
|
m_bLongBinaryColumns = FALSE;
|
|
m_nLockMode = optimistic;
|
|
|
|
m_nFieldsBound = 0;
|
|
m_nResultCols = -1;
|
|
}
|
|
|
|
BOOL CRecordset::IsOpen() const
|
|
// Note: assumes base class CRecordset::Close called
|
|
{
|
|
if (m_hstmt == NULL)
|
|
return FALSE;
|
|
|
|
if (m_lOpen == AFX_RECORDSET_STATUS_OPEN)
|
|
return TRUE;
|
|
|
|
RETCODE nRetCode;
|
|
SWORD nCols;
|
|
|
|
AFX_ODBC_CALL(::SQLNumResultCols(m_hstmt, &nCols));
|
|
|
|
if (!Check(nRetCode))
|
|
{
|
|
// If function sequence error, CRecordset not open
|
|
CDBException* e = new CDBException(nRetCode);
|
|
e->BuildErrorString(m_pDatabase, m_hstmt, FALSE);
|
|
if (e->m_strStateNativeOrigin.Find(szOutOfSequence) >= 0)
|
|
{
|
|
e->Delete();
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
#ifdef _DEBUG
|
|
TRACE0("Error: SQLNumResultCols failed during IsOpen().\n");
|
|
e->TraceErrorMessage(e->m_strError);
|
|
e->TraceErrorMessage(e->m_strStateNativeOrigin);
|
|
#endif
|
|
THROW(e);
|
|
}
|
|
}
|
|
|
|
BOOL bOpen = FALSE;
|
|
|
|
if (nCols != 0)
|
|
bOpen = TRUE;
|
|
|
|
return bOpen;
|
|
}
|
|
|
|
BOOL CRecordset::IsFieldDirty(void* pv)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(!(m_dwOptions & useMultiRowFetch));
|
|
|
|
if (m_nFields <= 0)
|
|
{
|
|
ASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
// If not in update op fields can't be dirty
|
|
// must compare saved and current values
|
|
if (m_nEditMode == noMode)
|
|
return FALSE;
|
|
|
|
// Must compare values to find dirty fields if necessary
|
|
if (m_bCheckCacheForDirtyFields)
|
|
{
|
|
if (m_nEditMode == edit)
|
|
MarkForUpdate();
|
|
else
|
|
MarkForAddNew();
|
|
}
|
|
|
|
int nIndex = 0, nIndexEnd;
|
|
|
|
if (pv == NULL)
|
|
nIndexEnd = m_nFields - 1;
|
|
else
|
|
{
|
|
// GetBoundFieldIndex returns 1-based index
|
|
nIndex = nIndexEnd = GetBoundFieldIndex(pv) - 1;
|
|
|
|
// must be address of field member
|
|
ASSERT(nIndex >= 0);
|
|
}
|
|
|
|
BOOL bDirty = FALSE;
|
|
|
|
while (nIndex <= nIndexEnd && !bDirty)
|
|
bDirty = IsFieldStatusDirty(nIndex++);
|
|
|
|
return bDirty;
|
|
}
|
|
|
|
BOOL CRecordset::IsFieldNull(void* pv)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(!(m_dwOptions & useMultiRowFetch));
|
|
|
|
int nIndex = GetBoundFieldIndex(pv) - 1;
|
|
if (nIndex < 0)
|
|
ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
|
|
|
|
return IsFieldStatusNull((DWORD)nIndex);
|
|
}
|
|
|
|
BOOL CRecordset::IsFieldNullable(void* pv)
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
if (pv == NULL)
|
|
{
|
|
// Must specify valid column name
|
|
ASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
int nIndex = GetBoundFieldIndex(pv) - 1;
|
|
if (nIndex < 0)
|
|
ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
|
|
|
|
return IsFieldNullable((DWORD)nIndex);
|
|
}
|
|
|
|
BOOL CRecordset::CanBookmark() const
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_pDatabase->IsOpen());
|
|
|
|
if (!(m_dwOptions & useBookmarks) ||
|
|
(m_nOpenType == forwardOnly && !(m_dwOptions & useExtendedFetch)))
|
|
return FALSE;
|
|
|
|
return m_pDatabase->GetBookmarkPersistence() & SQL_BP_SCROLL;
|
|
}
|
|
|
|
void CRecordset::Move(long nRows, WORD wFetchType)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
// First call - fields haven't been bound (m_nFieldsBound will change)
|
|
if (m_nFieldsBound == 0)
|
|
{
|
|
InitRecord();
|
|
ResetCursor();
|
|
}
|
|
|
|
if (m_nFieldsBound > 0)
|
|
{
|
|
// Reset field flags - mark all clean, all non-null
|
|
memset(m_pbFieldFlags, 0, m_nFields);
|
|
|
|
// Clear any edit mode that was set
|
|
m_nEditMode = noMode;
|
|
}
|
|
|
|
// Check scrollability, EOF/BOF status
|
|
CheckRowsetCurrencyStatus(wFetchType, nRows);
|
|
|
|
RETCODE nRetCode;
|
|
|
|
// Fetch the data, skipping deleted records if necessary
|
|
if ((wFetchType == SQL_FETCH_FIRST ||
|
|
wFetchType == SQL_FETCH_LAST ||
|
|
wFetchType == SQL_FETCH_NEXT ||
|
|
wFetchType == SQL_FETCH_PRIOR ||
|
|
wFetchType == SQL_FETCH_RELATIVE) &&
|
|
m_dwOptions & skipDeletedRecords)
|
|
{
|
|
SkipDeletedRecords(wFetchType, nRows, &m_dwRowsFetched, &nRetCode);
|
|
}
|
|
else
|
|
// Fetch the data and check for errors
|
|
nRetCode = FetchData(wFetchType, nRows, &m_dwRowsFetched);
|
|
|
|
// Set currency status and increment the record counters
|
|
SetRowsetCurrencyStatus(nRetCode, wFetchType, nRows, m_dwRowsFetched);
|
|
|
|
// Need to fixup bound fields in some cases
|
|
if (m_nFields > 0 && !IsEOF() && !IsBOF() &&
|
|
!(m_dwOptions & useMultiRowFetch))
|
|
{
|
|
Fixups();
|
|
}
|
|
}
|
|
|
|
void CRecordset::CheckRowsetError(RETCODE nRetCode)
|
|
{
|
|
if (nRetCode == SQL_SUCCESS_WITH_INFO)
|
|
{
|
|
CDBException e(nRetCode);
|
|
// Build the error string but don't send nuisance output to TRACE window
|
|
e.BuildErrorString(m_pDatabase, m_hstmt, FALSE);
|
|
|
|
if (e.m_strStateNativeOrigin.Find(szDataTruncated) >= 0)
|
|
{
|
|
// Ignore data truncated warning if binding long binary columns
|
|
// (may mask non-long binary truncation warnings or other warnings)
|
|
if (!((m_pDatabase->m_dwUpdateOptions & AFX_SQL_SETPOSUPDATES) &&
|
|
m_bLongBinaryColumns))
|
|
{
|
|
NO_CPP_EXCEPTION(e.Empty());
|
|
TRACE0("Error: field data truncated during data fetch.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_DATA_TRUNCATED);
|
|
}
|
|
}
|
|
else if (e.m_strStateNativeOrigin.Find(szRowFetch) >= 0)
|
|
{
|
|
#ifdef _DEBUG
|
|
TRACE0("Error: fetching row from server.\n");
|
|
e.TraceErrorMessage(e.m_strError);
|
|
e.TraceErrorMessage(e.m_strStateNativeOrigin);
|
|
#endif
|
|
NO_CPP_EXCEPTION(e.Empty());
|
|
ThrowDBException(AFX_SQL_ERROR_ROW_FETCH);
|
|
}
|
|
else
|
|
{
|
|
#ifdef _DEBUG
|
|
// Not a truncation or row fetch warning so send debug output
|
|
if (afxTraceFlags & traceDatabase)
|
|
{
|
|
TRACE0("Warning: ODBC Success With Info,\n");
|
|
e.TraceErrorMessage(e.m_strError);
|
|
e.TraceErrorMessage(e.m_strStateNativeOrigin);
|
|
}
|
|
#endif // _DEBUG
|
|
}
|
|
}
|
|
else if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
|
|
void CRecordset::GetBookmark(CDBVariant& varBookmark)
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
// Validate bookmarks are usable
|
|
if (!(m_dwOptions & useBookmarks))
|
|
ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_ENABLED);
|
|
else if (!CanBookmark())
|
|
ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_SUPPORTED);
|
|
|
|
// Currently ODBC only supports 4 byte bookmarks
|
|
// Initialize the variant to a long
|
|
if (varBookmark.m_dwType != DBVT_LONG)
|
|
{
|
|
varBookmark.Clear();
|
|
varBookmark.m_dwType = DBVT_LONG;
|
|
varBookmark.m_lVal = 0;
|
|
}
|
|
|
|
RETCODE nRetCode;
|
|
SDWORD nActualSize;
|
|
|
|
// Retrieve the bookmark (column 0) data
|
|
AFX_ODBC_CALL(::SQLGetData(m_hstmt, 0, SQL_C_BOOKMARK,
|
|
&varBookmark.m_lVal, sizeof(varBookmark.m_lVal), &nActualSize));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: GetBookmark operation failed.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
}
|
|
|
|
void CRecordset::SetBookmark(const CDBVariant& varBookmark)
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
// Validate bookmarks are usable
|
|
if (!(m_dwOptions & useBookmarks))
|
|
ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_ENABLED);
|
|
else if (!CanBookmark())
|
|
ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_SUPPORTED);
|
|
|
|
// Currently ODBC only supports 4 byte bookmarks
|
|
ASSERT(varBookmark.m_dwType == DBVT_LONG);
|
|
|
|
Move(varBookmark.m_lVal, SQL_FETCH_BOOKMARK);
|
|
}
|
|
|
|
void CRecordset::SetRowsetSize(DWORD dwNewRowsetSize)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(dwNewRowsetSize > 0);
|
|
|
|
// If not yet open, only set expected length
|
|
if (!IsOpen())
|
|
{
|
|
m_dwRowsetSize = dwNewRowsetSize;
|
|
return;
|
|
}
|
|
|
|
if (!(m_dwOptions & useMultiRowFetch))
|
|
{
|
|
// Only works if bulk row fetching!
|
|
ASSERT(FALSE);
|
|
return;
|
|
}
|
|
|
|
// Need to reallocate some memory if rowset size grows
|
|
if (m_dwAllocatedRowsetSize == 0 ||
|
|
(m_dwAllocatedRowsetSize < dwNewRowsetSize))
|
|
{
|
|
// If rowset already allocated, delete old and reallocate
|
|
FreeRowset();
|
|
m_rgRowStatus = new WORD[dwNewRowsetSize];
|
|
|
|
// If not a user allocated buffer grow the data buffers
|
|
if (!(m_dwOptions & userAllocMultiRowBuffers))
|
|
{
|
|
// Allocate the rowset field buffers
|
|
m_dwRowsetSize = dwNewRowsetSize;
|
|
CFieldExchange fx(CFieldExchange::AllocMultiRowBuffer, this);
|
|
DoBulkFieldExchange(&fx);
|
|
|
|
m_dwAllocatedRowsetSize = dwNewRowsetSize;
|
|
|
|
// Set bound fields to zero, rebind and reset bound field count
|
|
int nOldFieldsBound = m_nFieldsBound;
|
|
m_nFieldsBound = 0;
|
|
InitRecord();
|
|
m_nFieldsBound = nOldFieldsBound;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Just reset the new rowset size
|
|
m_dwRowsetSize = dwNewRowsetSize;
|
|
}
|
|
|
|
RETCODE nRetCode;
|
|
AFX_SQL_SYNC(::SQLSetStmtOption(m_hstmt, SQL_ROWSET_SIZE,
|
|
m_dwRowsetSize));
|
|
}
|
|
|
|
void CRecordset::AddNew()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
// we can't construct an INSERT statement w/o any columns
|
|
ASSERT(m_nFields != 0);
|
|
|
|
if (!m_bAppendable)
|
|
{
|
|
ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
|
|
}
|
|
|
|
if (m_dwOptions & useMultiRowFetch)
|
|
{
|
|
// Can't use update methods on multi-row rowset
|
|
ASSERT(FALSE);
|
|
return;
|
|
}
|
|
|
|
if (m_bCheckCacheForDirtyFields && m_nFields > 0)
|
|
{
|
|
if (m_nEditMode == noMode)
|
|
{
|
|
// First addnew call, cache record values
|
|
StoreFields();
|
|
}
|
|
else
|
|
{
|
|
// subsequent Edit/AddNew call. Restore values, save them again
|
|
LoadFields();
|
|
StoreFields();
|
|
}
|
|
}
|
|
|
|
SetFieldNull(NULL);
|
|
SetFieldDirty(NULL, FALSE);
|
|
|
|
m_nEditMode = addnew;
|
|
}
|
|
|
|
void CRecordset::Edit()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
// we can't construct an UPDATE statement w/o any columns
|
|
ASSERT(m_nFields != 0);
|
|
|
|
if (!m_bUpdatable)
|
|
ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
|
|
|
|
if (m_dwOptions & useMultiRowFetch)
|
|
{
|
|
// Can't use update methods on multi-row rowset
|
|
ASSERT(FALSE);
|
|
return;
|
|
}
|
|
|
|
if (m_bEOF || m_bBOF || m_bDeleted)
|
|
{
|
|
TRACE0("Error: Edit attempt failed - not on a record.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_NO_CURRENT_RECORD);
|
|
}
|
|
|
|
if ((m_nOpenType == dynaset || m_nOpenType == dynamic) &&
|
|
m_nLockMode == pessimistic)
|
|
{
|
|
RETCODE nRetCode;
|
|
AFX_ODBC_CALL(::SQLSetPos(m_hstmt, 1, SQL_POSITION,
|
|
SQL_LCK_EXCLUSIVE));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: attempt to lock record failed during Edit function.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
}
|
|
|
|
if (m_bCheckCacheForDirtyFields && m_nFields > 0)
|
|
{
|
|
if (m_nEditMode == noMode)
|
|
// First edit call, cache record values
|
|
StoreFields();
|
|
else
|
|
{
|
|
// subsequent Edit/AddNew call. Restore values, save them again
|
|
LoadFields();
|
|
StoreFields();
|
|
}
|
|
}
|
|
|
|
m_nEditMode = edit;
|
|
}
|
|
|
|
BOOL CRecordset::Update()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
if (m_dwOptions & useMultiRowFetch)
|
|
{
|
|
// Can't use update methods on multi-row rowset
|
|
ASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
if (m_nEditMode != addnew && m_nEditMode != edit)
|
|
{
|
|
TRACE0("Error: must enter Edit or AddNew mode before updating.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_ILLEGAL_MODE);
|
|
}
|
|
return UpdateInsertDelete();
|
|
}
|
|
|
|
void CRecordset::Delete()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
if (m_dwOptions & useMultiRowFetch)
|
|
{
|
|
// Can't use update methods on multi-row rowset
|
|
ASSERT(FALSE);
|
|
return;
|
|
}
|
|
|
|
if (m_nEditMode != noMode)
|
|
{
|
|
TRACE0("Error: attempted to delete while still in Edit or AddNew mode.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_ILLEGAL_MODE);
|
|
}
|
|
UpdateInsertDelete(); // This call can't fail in delete mode (noMode)
|
|
}
|
|
|
|
void CRecordset::CancelUpdate()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(IsOpen());
|
|
|
|
if (m_nEditMode == noMode)
|
|
// Do nothing if not in edit mode
|
|
return;
|
|
else
|
|
// Reset the edit mode
|
|
m_nEditMode = noMode;
|
|
|
|
// Restore cache if necessary
|
|
if (m_bCheckCacheForDirtyFields && m_nFields > 0)
|
|
LoadFields();
|
|
}
|
|
|
|
BOOL CRecordset::FlushResultSet() const
|
|
{
|
|
RETCODE nRetCode;
|
|
AFX_ODBC_CALL(::SQLMoreResults(m_hstmt));
|
|
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: attempt FlushResultSet failed.\n");
|
|
AfxThrowDBException(nRetCode, m_pDatabase, m_hstmt);
|
|
}
|
|
|
|
return nRetCode != SQL_NO_DATA_FOUND;
|
|
}
|
|
|
|
void CRecordset::GetODBCFieldInfo(LPCTSTR lpszName,
|
|
CODBCFieldInfo& fieldinfo)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(IsOpen());
|
|
ASSERT(lpszName != NULL);
|
|
|
|
// No data or no column info fetched yet
|
|
if (GetODBCFieldCount() <= 0)
|
|
{
|
|
ASSERT(FALSE);
|
|
return;
|
|
}
|
|
|
|
// Get the index of the field corresponding to name
|
|
short nField = GetFieldIndexByName(lpszName);
|
|
|
|
GetODBCFieldInfo(nField, fieldinfo);
|
|
}
|
|
|
|
void CRecordset::GetODBCFieldInfo(short nIndex,
|
|
CODBCFieldInfo& fieldinfo)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(IsOpen());
|
|
|
|
// No data or no column info fetched yet
|
|
if (GetODBCFieldCount() <= 0)
|
|
{
|
|
ASSERT(FALSE);
|
|
return;
|
|
}
|
|
|
|
// Just copy the data into the field info
|
|
CODBCFieldInfo* pInfo = &m_rgODBCFieldInfos[nIndex];
|
|
fieldinfo.m_strName = pInfo->m_strName;
|
|
fieldinfo.m_nSQLType = pInfo->m_nSQLType;
|
|
fieldinfo.m_nPrecision = pInfo->m_nPrecision;
|
|
fieldinfo.m_nScale = pInfo->m_nScale;
|
|
fieldinfo.m_nNullability = pInfo->m_nNullability;
|
|
}
|
|
|
|
void CRecordset::GetFieldValue(LPCTSTR lpszName,
|
|
CDBVariant& varValue, short nFieldType)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(IsOpen());
|
|
ASSERT(lpszName != NULL);
|
|
|
|
// No data or no column info fetched yet
|
|
if (GetODBCFieldCount() <= 0)
|
|
{
|
|
ASSERT(FALSE);
|
|
varValue.Clear();
|
|
return;
|
|
}
|
|
|
|
// Get the index of the field corresponding to name
|
|
short nField = GetFieldIndexByName(lpszName);
|
|
|
|
GetFieldValue(nField, varValue, nFieldType);
|
|
}
|
|
|
|
void CRecordset::GetFieldValue(short nIndex,
|
|
CDBVariant& varValue, short nFieldType)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(IsOpen());
|
|
|
|
// No data or no column info fetched yet
|
|
if (GetODBCFieldCount() <= 0)
|
|
{
|
|
ASSERT(FALSE);
|
|
varValue.Clear();
|
|
return;
|
|
}
|
|
|
|
// Convert index to 1-based and check range
|
|
nIndex++;
|
|
if (nIndex < 1 || nIndex > GetODBCFieldCount())
|
|
{
|
|
ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
|
|
}
|
|
|
|
void* pvData = NULL;
|
|
int nLen = 0;
|
|
|
|
// Determine the default field type and get the data buffer
|
|
if (nFieldType == DEFAULT_FIELD_TYPE)
|
|
{
|
|
nFieldType =
|
|
GetDefaultFieldType(m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
|
|
}
|
|
pvData = GetDataBuffer(varValue, nFieldType, &nLen,
|
|
m_rgODBCFieldInfos[nIndex - 1].m_nSQLType,
|
|
m_rgODBCFieldInfos[nIndex - 1].m_nPrecision);
|
|
|
|
// Now can actually get the data
|
|
long nActualSize = GetData(m_pDatabase, m_hstmt, nIndex,
|
|
nFieldType, pvData, nLen,
|
|
m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
|
|
|
|
// Handle NULL data separately
|
|
if (nActualSize == SQL_NULL_DATA)
|
|
{
|
|
// Clear value and set the value NULL
|
|
varValue.Clear();
|
|
}
|
|
else
|
|
{
|
|
// May need to cleanup and call SQLGetData again if LONG_VAR data
|
|
if (nFieldType == SQL_C_CHAR)
|
|
{
|
|
GetLongCharDataAndCleanup(m_pDatabase, m_hstmt, nIndex,
|
|
nActualSize, &pvData, nLen, *varValue.m_pstring,
|
|
m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
|
|
|
|
#ifdef _UNICODE
|
|
// Now must convert string to UNICODE
|
|
LPCSTR lpszOld = (LPCSTR)varValue.m_pstring->GetBuffer(0);
|
|
CString* pStringNew = new CString(lpszOld);
|
|
delete varValue.m_pstring;
|
|
varValue.m_pstring = pStringNew;
|
|
#endif // _UNICODE
|
|
}
|
|
else if (nFieldType == SQL_C_BINARY)
|
|
{
|
|
GetLongBinaryDataAndCleanup(m_pDatabase, m_hstmt, nIndex,
|
|
nActualSize, &pvData, nLen, varValue,
|
|
m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CRecordset::GetFieldValue(LPCTSTR lpszName, CString& strValue)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(IsOpen());
|
|
ASSERT(lpszName != NULL);
|
|
|
|
// No data or no column info fetched yet
|
|
if (GetODBCFieldCount() <= 0)
|
|
{
|
|
ASSERT(FALSE);
|
|
return;
|
|
}
|
|
|
|
// Get the index of the field corresponding to name
|
|
short nField = GetFieldIndexByName(lpszName);
|
|
|
|
GetFieldValue(nField, strValue);
|
|
}
|
|
|
|
void CRecordset::GetFieldValue(short nIndex, CString& strValue)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(IsOpen());
|
|
|
|
// No data or no column info fetched yet
|
|
if (GetODBCFieldCount() <= 0)
|
|
{
|
|
ASSERT(FALSE);
|
|
return;
|
|
}
|
|
|
|
// Convert index to 1-based and check range
|
|
nIndex++;
|
|
if (nIndex < 1 || nIndex > GetODBCFieldCount())
|
|
{
|
|
ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
|
|
}
|
|
|
|
int nLen = GetTextLen(m_rgODBCFieldInfos[nIndex - 1].m_nSQLType,
|
|
m_rgODBCFieldInfos[nIndex - 1].m_nPrecision);
|
|
|
|
#ifndef _UNICODE
|
|
CString& strData = strValue;
|
|
#else
|
|
CString strProxy;
|
|
CString& strData = strProxy;
|
|
#endif
|
|
void* pvData = strData.GetBufferSetLength(nLen);
|
|
|
|
// Now can actually get the data
|
|
long nActualSize = GetData(m_pDatabase, m_hstmt, nIndex,
|
|
SQL_C_CHAR, pvData, nLen,
|
|
m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
|
|
|
|
// Handle NULL data separately
|
|
if (nActualSize == SQL_NULL_DATA)
|
|
{
|
|
// Clear value
|
|
strValue.Empty();
|
|
}
|
|
else
|
|
{
|
|
// May need to cleanup and call SQLGetData again if necessary
|
|
GetLongCharDataAndCleanup(m_pDatabase, m_hstmt, nIndex,
|
|
nActualSize, &pvData, nLen, strData,
|
|
m_rgODBCFieldInfos[nIndex - 1].m_nSQLType);
|
|
|
|
#ifdef _UNICODE
|
|
// Now must convert string to UNICODE
|
|
strValue = (LPCSTR)strData.GetBuffer(0);
|
|
#endif // _UNIOCDE
|
|
}
|
|
}
|
|
|
|
void CRecordset::SetFieldDirty(void* pv, BOOL bDirty)
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
int nIndex, nIndexEnd;
|
|
|
|
// If not setting all NULL, check simple case
|
|
if (pv != NULL)
|
|
{
|
|
// GetBoundFieldIndex returns 1-based index
|
|
nIndex = GetBoundFieldIndex(pv) - 1;
|
|
|
|
if (nIndex < 0)
|
|
{
|
|
// pv must be address of field member
|
|
ASSERT(FALSE);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
nIndexEnd = nIndex;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nIndex = 0;
|
|
nIndexEnd = m_nFields - 1;
|
|
}
|
|
|
|
while (nIndex <= nIndexEnd)
|
|
{
|
|
if (bDirty)
|
|
SetDirtyFieldStatus((DWORD)nIndex);
|
|
else
|
|
ClearDirtyFieldStatus((DWORD)nIndex);
|
|
|
|
nIndex++;
|
|
}
|
|
}
|
|
|
|
void CRecordset::SetFieldNull(void* pv, BOOL bNull)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(!(m_dwOptions & useMultiRowFetch));
|
|
|
|
// If not setting all fields NULL, check simple case (param) first
|
|
if (pv != NULL)
|
|
{
|
|
int nIndex = GetBoundParamIndex(pv);
|
|
if (nIndex >= 0)
|
|
{
|
|
if (bNull)
|
|
SetNullParamStatus(nIndex);
|
|
else
|
|
ClearNullParamStatus(nIndex);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Not a param, must be a field
|
|
if (m_nFields <= 0)
|
|
{
|
|
ASSERT(FALSE);
|
|
return;
|
|
}
|
|
|
|
// Need field exchange mechanism to set PSEUDO NULL values
|
|
// and to reset data lengths (especially for RFX_LongBinary)
|
|
CFieldExchange fx(CFieldExchange::SetFieldNull, this, pv);
|
|
fx.m_nFieldFound = 0;
|
|
fx.m_bField = bNull;
|
|
DoFieldExchange(&fx);
|
|
|
|
// If no field found, m_nFieldFound will still be zero
|
|
ASSERT(fx.m_nFieldFound != 0);
|
|
}
|
|
|
|
void CRecordset::SetLockingMode(UINT nLockMode)
|
|
{
|
|
if (nLockMode == pessimistic)
|
|
{
|
|
RETCODE nRetCode;
|
|
UDWORD dwTypes;
|
|
SWORD nResult;
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_LOCK_TYPES,
|
|
&dwTypes, sizeof(dwTypes), &nResult));
|
|
if (!Check(nRetCode) || !(dwTypes & SQL_LCK_EXCLUSIVE))
|
|
ThrowDBException(AFX_SQL_ERROR_LOCK_MODE_NOT_SUPPORTED);
|
|
}
|
|
m_nLockMode = nLockMode;
|
|
}
|
|
|
|
BOOL CRecordset::Requery()
|
|
{
|
|
RETCODE nRetCode;
|
|
|
|
ASSERT_VALID(this);
|
|
ASSERT(IsOpen());
|
|
|
|
// Can't requery if using direct execution
|
|
if (m_dwOptions & executeDirect)
|
|
return FALSE;
|
|
|
|
TRY
|
|
{
|
|
// Detect changes to filter and sort
|
|
if ((m_strFilter != m_strRequeryFilter) || (m_strSort != m_strRequerySort))
|
|
{
|
|
m_strRequeryFilter = m_strFilter;
|
|
m_strRequerySort = m_strSort;
|
|
Close();
|
|
if (m_strRequerySQL.IsEmpty())
|
|
return Open(m_nOpenType, NULL, m_dwOptions);
|
|
else
|
|
return Open(m_nOpenType, m_strRequerySQL, m_dwOptions);
|
|
}
|
|
else
|
|
{
|
|
// Shutdown current query, preserving buffers for performance
|
|
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmt, SQL_CLOSE));
|
|
m_lOpen = AFX_RECORDSET_STATUS_CLOSED;
|
|
|
|
// Rebind date/time parameters
|
|
RebindParams(m_hstmt);
|
|
|
|
// now attempt to re-execute the SQL Query
|
|
AFX_ODBC_CALL(::SQLExecute(m_hstmt));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: Requery attempt failed.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
|
|
m_lOpen = AFX_RECORDSET_STATUS_OPEN;
|
|
|
|
// Reset some cursor properties and fetch first record
|
|
ResetCursor();
|
|
MoveNext();
|
|
|
|
// If EOF, then result set empty, so set BOF as well
|
|
m_bBOF = m_bEOF;
|
|
}
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
Close();
|
|
THROW_LAST();
|
|
}
|
|
END_CATCH_ALL
|
|
|
|
return TRUE; // all set
|
|
}
|
|
|
|
// Shutdown any pending query for CRecordset's hstmt's
|
|
void CRecordset::Cancel()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
::SQLCancel(m_hstmt);
|
|
|
|
// If Update hstmt has been allocated, shut it down also
|
|
if (m_hstmtUpdate != SQL_NULL_HSTMT)
|
|
::SQLCancel(m_hstmtUpdate);
|
|
}
|
|
|
|
CString CRecordset::GetDefaultConnect()
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
return szODBC;
|
|
}
|
|
|
|
CString CRecordset::GetDefaultSQL()
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
// Override and add table name or entire SQL SELECT statement
|
|
return _T("");
|
|
}
|
|
|
|
void CRecordset::DoFieldExchange(CFieldExchange* /* pFX */)
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
// Do nothing if dynamically retrieving unbound fields,
|
|
// otherwise override CRecordset and add RFX calls
|
|
}
|
|
|
|
void CRecordset::DoBulkFieldExchange(CFieldExchange* /* pFX */)
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
// To use multi-record data fetching, you must use
|
|
// a derived CRecordset class and call Close explicitly.
|
|
ASSERT(FALSE);
|
|
}
|
|
|
|
void CRecordset::OnSetOptions(HSTMT hstmt)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(hstmt != SQL_NULL_HSTMT);
|
|
|
|
// Inherit options settings from CDatabase
|
|
m_pDatabase->OnSetOptions(hstmt);
|
|
|
|
// If fowardOnly recordset and not using SQLExtendedFetch, quit now
|
|
if (m_nOpenType == forwardOnly && !(m_dwOptions & useExtendedFetch))
|
|
return;
|
|
|
|
// Turn on bookmark support if necessary
|
|
EnableBookmarks();
|
|
|
|
// If using forwardOnly and extended fetch, quit now
|
|
if (m_nOpenType == forwardOnly)
|
|
return;
|
|
|
|
// Make sure driver supports extended fetch, ODBC 2.0 and requested cursor type
|
|
VerifyDriverBehavior();
|
|
DWORD dwScrollType = VerifyCursorSupport();
|
|
|
|
// Set the update method, concurrency and cursor type
|
|
SetUpdateMethod();
|
|
SetConcurrencyAndCursorType(hstmt, dwScrollType);
|
|
}
|
|
|
|
// Screen for errors.
|
|
BOOL CRecordset::Check(RETCODE nRetCode) const
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
switch (nRetCode)
|
|
{
|
|
case SQL_SUCCESS_WITH_INFO:
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
{
|
|
CDBException e(nRetCode);
|
|
TRACE0("Warning: ODBC Success With Info, ");
|
|
e.BuildErrorString(m_pDatabase, m_hstmt);
|
|
}
|
|
#endif
|
|
|
|
// Fall through
|
|
|
|
case SQL_SUCCESS:
|
|
case SQL_NO_DATA_FOUND:
|
|
case SQL_NEED_DATA:
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void CRecordset::PreBindFields()
|
|
{
|
|
// Do nothing
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CRecordset internal functions
|
|
|
|
// Cache state information internally in CRecordset
|
|
void CRecordset::SetState(int nOpenType, LPCTSTR lpszSQL, DWORD dwOptions)
|
|
{
|
|
if (nOpenType == AFX_DB_USE_DEFAULT_TYPE)
|
|
m_nOpenType = m_nDefaultType;
|
|
else
|
|
m_nOpenType = nOpenType;
|
|
|
|
m_bAppendable = (dwOptions & appendOnly) != 0 ||
|
|
(dwOptions & readOnly) == 0;
|
|
m_bUpdatable = (dwOptions & readOnly) == 0 &&
|
|
(dwOptions & appendOnly) == 0;
|
|
|
|
// Can turn off dirty field checking via dwOptions
|
|
if (dwOptions & noDirtyFieldCheck || dwOptions & useMultiRowFetch)
|
|
m_bCheckCacheForDirtyFields = FALSE;
|
|
|
|
// Set recordset readOnly if forwardOnly
|
|
if (m_nOpenType == forwardOnly && !(dwOptions & readOnly))
|
|
{
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
TRACE0("Warning: Setting forwardOnly recordset readOnly.\n");
|
|
#endif
|
|
dwOptions |= readOnly;
|
|
|
|
// If using multiRowFetch also set useExtendFetch
|
|
if (dwOptions & useMultiRowFetch)
|
|
dwOptions |= useExtendedFetch;
|
|
}
|
|
|
|
// Archive info for use in Requery
|
|
m_dwOptions = dwOptions;
|
|
m_strRequerySQL = lpszSQL;
|
|
m_strRequeryFilter = m_strFilter;
|
|
m_strRequerySort = m_strSort;
|
|
}
|
|
|
|
// Allocate the Hstmt and implicitly create and open Database if necessary
|
|
BOOL CRecordset::AllocHstmt()
|
|
{
|
|
RETCODE nRetCode;
|
|
if (m_hstmt == SQL_NULL_HSTMT)
|
|
{
|
|
CString strDefaultConnect;
|
|
TRY
|
|
{
|
|
if (m_pDatabase == NULL)
|
|
{
|
|
m_pDatabase = new CDatabase();
|
|
m_bRecordsetDb = TRUE;
|
|
}
|
|
|
|
strDefaultConnect = GetDefaultConnect();
|
|
|
|
// If not already opened, attempt to open
|
|
if (!m_pDatabase->IsOpen())
|
|
{
|
|
BOOL bUseCursorLib = m_bUseODBCCursorLib;
|
|
|
|
// If non-readOnly snapshot request must use cursor lib
|
|
if (m_nOpenType == snapshot && !(m_dwOptions & readOnly))
|
|
{
|
|
// This assumes drivers only support readOnly snapshots
|
|
bUseCursorLib = TRUE;
|
|
}
|
|
|
|
if (!m_pDatabase->Open(&afxChNil, FALSE, FALSE,
|
|
strDefaultConnect, bUseCursorLib))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// If snapshot cursor requested and not supported, load cursor lib
|
|
if (m_nOpenType == snapshot && !bUseCursorLib)
|
|
{
|
|
// Get the supported cursor types
|
|
RETCODE nResult;
|
|
UDWORD dwDriverScrollOptions;
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_SCROLL_OPTIONS,
|
|
&dwDriverScrollOptions, sizeof(dwDriverScrollOptions), &nResult));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: ODBC failure checking for driver capabilities.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
|
|
// Check for STATIC cursor support and load cursor lib
|
|
if (!(dwDriverScrollOptions & SQL_SO_STATIC))
|
|
{
|
|
m_pDatabase->Close();
|
|
if (!m_pDatabase->Open(&afxChNil, FALSE, FALSE,
|
|
strDefaultConnect, TRUE))
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
AFX_SQL_SYNC(::SQLAllocStmt(m_pDatabase->m_hdbc, &m_hstmt));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(SQL_INVALID_HANDLE);
|
|
|
|
// Add to list of CRecordsets with alloced hstmts
|
|
AfxLockGlobals(CRIT_ODBC);
|
|
TRY
|
|
{
|
|
m_pDatabase->m_listRecordsets.AddHead(this);
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
AfxUnlockGlobals(CRIT_ODBC);
|
|
THROW_LAST();
|
|
}
|
|
END_CATCH_ALL
|
|
AfxUnlockGlobals(CRIT_ODBC);
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
TRACE0("Error: CDatabase create for CRecordset failed.\n");
|
|
#endif
|
|
NO_CPP_EXCEPTION(strDefaultConnect.Empty());
|
|
if (m_bRecordsetDb)
|
|
{
|
|
delete m_pDatabase;
|
|
m_pDatabase = NULL;
|
|
}
|
|
ASSERT(m_hstmt == SQL_NULL_HSTMT);
|
|
THROW_LAST();
|
|
}
|
|
END_CATCH_ALL
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Initialize the status arrays and create the SQL
|
|
void CRecordset::BuildSQL(LPCTSTR lpszSQL)
|
|
{
|
|
if (lpszSQL == NULL)
|
|
m_strSQL = GetDefaultSQL();
|
|
else
|
|
m_strSQL = lpszSQL;
|
|
|
|
// Set any supplied params
|
|
if (m_nParams != 0)
|
|
{
|
|
UINT nParams = BindParams(m_hstmt);
|
|
ASSERT(nParams == m_nParams);
|
|
}
|
|
|
|
// Construct the SQL string
|
|
BuildSelectSQL();
|
|
AppendFilterAndSortSQL();
|
|
|
|
// Do some extra checking if trying to set recordset updatable or appendable
|
|
if ((m_bUpdatable || m_bAppendable) && !IsRecordsetUpdatable())
|
|
m_bUpdatable = m_bAppendable = FALSE;
|
|
|
|
if (m_bUpdatable && m_bUseUpdateSQL && m_pDatabase->m_bAddForUpdate)
|
|
m_strSQL += szForUpdate;
|
|
|
|
// Replace brackets with SQL_IDENTIFIER_QUOTE_CHAR
|
|
m_pDatabase->ReplaceBrackets(m_strSQL.GetBuffer(0));
|
|
m_strSQL.ReleaseBuffer();
|
|
}
|
|
|
|
// Prepare and Execute the SQL or simple call SQLExecDirect, resetting concurrency if necessary
|
|
void CRecordset::PrepareAndExecute()
|
|
{
|
|
USES_CONVERSION;
|
|
RETCODE nRetCode;
|
|
BOOL bConcurrency = FALSE;
|
|
LPCSTR lpszWSQL = T2CA(m_strSQL);
|
|
|
|
while (!bConcurrency)
|
|
{
|
|
// Prepare or execute the query
|
|
if (m_dwOptions & executeDirect)
|
|
{
|
|
AFX_ODBC_CALL(::SQLExecDirect(m_hstmt,
|
|
(UCHAR*)lpszWSQL, SQL_NTS));
|
|
}
|
|
else
|
|
{
|
|
AFX_ODBC_CALL(::SQLPrepare(m_hstmt,
|
|
(UCHAR*)lpszWSQL, SQL_NTS));
|
|
}
|
|
if (Check(nRetCode))
|
|
bConcurrency = TRUE;
|
|
else
|
|
{
|
|
// If "Driver Not Capable" error, assume cursor type doesn't
|
|
// support requested concurrency and try alternate concurrency.
|
|
CDBException* e = new CDBException(nRetCode);
|
|
e->BuildErrorString(m_pDatabase, m_hstmt);
|
|
if (m_dwConcurrency != SQL_CONCUR_READ_ONLY &&
|
|
e->m_strStateNativeOrigin.Find(szDriverNotCapable) >= 0)
|
|
{
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
TRACE0("Warning: Driver does not support requested concurrency.\n");
|
|
#endif
|
|
|
|
// Don't need exception to persist while attempting to reset concurrency
|
|
e->Delete();
|
|
|
|
// ODBC will automatically attempt to set alternate concurrency if
|
|
// request fails, but it won't try LOCK even if driver supports it.
|
|
if ((m_dwDriverConcurrency & SQL_SCCO_LOCK) &&
|
|
(m_dwConcurrency == SQL_CONCUR_ROWVER ||
|
|
m_dwConcurrency == SQL_CONCUR_VALUES))
|
|
{
|
|
m_dwConcurrency = SQL_CONCUR_LOCK;
|
|
}
|
|
else
|
|
{
|
|
m_dwConcurrency = SQL_CONCUR_READ_ONLY;
|
|
m_bUpdatable = m_bAppendable = FALSE;
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
TRACE0("Warning: Setting recordset read only.\n");
|
|
#endif
|
|
}
|
|
|
|
// Attempt to reset the concurrency model.
|
|
AFX_SQL_SYNC(::SQLSetStmtOption(m_hstmt, SQL_CONCURRENCY,
|
|
m_dwConcurrency));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: ODBC failure setting recordset concurrency.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TRACE0("Error: ODBC failure on SQLPrepare or SQLExecDirect\n");
|
|
THROW(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// now attempt to execute the SQL Query if not executed already
|
|
if (!(m_dwOptions & executeDirect))
|
|
{
|
|
AFX_ODBC_CALL(::SQLExecute(m_hstmt));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
m_lOpen = AFX_RECORDSET_STATUS_OPEN;
|
|
|
|
// SQLExecute or SQLExecDirect may have changed an option value
|
|
if (nRetCode == SQL_SUCCESS_WITH_INFO)
|
|
{
|
|
// Check if concurrency was changed in order to mark
|
|
// recordset non-updatable if necessary
|
|
DWORD dwConcurrency;
|
|
AFX_SQL_SYNC(::SQLGetStmtOption(m_hstmt, SQL_CONCURRENCY, &dwConcurrency));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode);
|
|
|
|
if (dwConcurrency == SQL_CONCUR_READ_ONLY && (m_bUpdatable || m_bAppendable))
|
|
{
|
|
m_bUpdatable = FALSE;
|
|
m_bAppendable = FALSE;
|
|
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
{
|
|
TRACE0("Warning: Concurrency changed by driver.\n");
|
|
TRACE0("\tMarking CRecordset as not updatable.\n");
|
|
}
|
|
#endif // _DEBUG
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure that driver supports extended fetch and ODBC 2.0 if necessary
|
|
void CRecordset::VerifyDriverBehavior()
|
|
{
|
|
RETCODE nRetCode;
|
|
UWORD wScrollable;
|
|
// If SQLExtendedFetch not supported, use SQLFetch
|
|
AFX_SQL_SYNC(::SQLGetFunctions(m_pDatabase->m_hdbc,
|
|
SQL_API_SQLEXTENDEDFETCH, &wScrollable));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: ODBC failure determining whether recordset is scrollable.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
m_bScrollable = wScrollable;
|
|
if (!m_bScrollable)
|
|
{
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
{
|
|
TRACE0("Warning: SQLExtendedFetch not supported by driver\n");
|
|
TRACE0("and/or cursor library not loaded. Opening forwardOnly.\n");
|
|
TRACE0("for use with SQLFetch.\n");
|
|
}
|
|
#endif
|
|
m_bUpdatable = FALSE;
|
|
return;
|
|
}
|
|
|
|
char szResult[30];
|
|
SWORD nResult;
|
|
// require ODBC v2.0
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_ODBC_VER,
|
|
&szResult, _countof(szResult), &nResult));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: ODBC failure checking for driver capabilities.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
if (szResult[0] == '0' && szResult[1] < '2')
|
|
ThrowDBException(AFX_SQL_ERROR_ODBC_V2_REQUIRED);
|
|
}
|
|
|
|
// Check that driver supports requested cursor type
|
|
DWORD CRecordset::VerifyCursorSupport()
|
|
{
|
|
RETCODE nRetCode;
|
|
SWORD nResult;
|
|
UDWORD dwDriverScrollOptions;
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_SCROLL_OPTIONS,
|
|
&dwDriverScrollOptions, sizeof(dwDriverScrollOptions), &nResult));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: ODBC failure checking for driver capabilities.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
|
|
SDWORD dwScrollOptions = SQL_CURSOR_FORWARD_ONLY;
|
|
if (m_nOpenType == dynaset)
|
|
{
|
|
// Dynaset support requires ODBC's keyset driven cursor model
|
|
if (!(dwDriverScrollOptions & SQL_SO_KEYSET_DRIVEN))
|
|
ThrowDBException(AFX_SQL_ERROR_DYNASET_NOT_SUPPORTED);
|
|
dwScrollOptions = SQL_CURSOR_KEYSET_DRIVEN;
|
|
}
|
|
else if (m_nOpenType == snapshot)
|
|
{
|
|
// Snapshot support requires ODBC's static cursor model
|
|
if (!(dwDriverScrollOptions & SQL_SO_STATIC))
|
|
ThrowDBException(AFX_SQL_ERROR_SNAPSHOT_NOT_SUPPORTED);
|
|
dwScrollOptions = SQL_CURSOR_STATIC;
|
|
}
|
|
else
|
|
{
|
|
// Dynamic cursor support requires ODBC's dynamic cursor model
|
|
if (!(dwDriverScrollOptions & SQL_SO_DYNAMIC))
|
|
ThrowDBException(AFX_SQL_ERROR_DYNAMIC_CURSOR_NOT_SUPPORTED);
|
|
dwScrollOptions = SQL_CURSOR_DYNAMIC;
|
|
}
|
|
|
|
return dwScrollOptions;
|
|
}
|
|
|
|
void CRecordset::AllocAndCacheFieldInfo()
|
|
{
|
|
ASSERT(GetODBCFieldCount() < 0);
|
|
ASSERT(m_rgODBCFieldInfos == NULL);
|
|
|
|
RETCODE nRetCode;
|
|
SWORD nActualLen;
|
|
|
|
// Cache the number of result columns
|
|
AFX_ODBC_CALL(::SQLNumResultCols(m_hstmt, &m_nResultCols));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: Can't get field info.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
|
|
// If there are no fields quit now
|
|
if (m_nResultCols == 0)
|
|
return;
|
|
|
|
// Allocate buffer and get the ODBC meta data
|
|
m_rgODBCFieldInfos = new CODBCFieldInfo[m_nResultCols];
|
|
LPSTR lpszFieldName;
|
|
|
|
#ifdef _UNICODE
|
|
// Need proxy to temporarily store non-UNICODE name
|
|
lpszFieldName = new char[MAX_FNAME_LEN + 1];
|
|
#endif
|
|
|
|
// Get the field info each field
|
|
for (WORD n = 1; n <= GetODBCFieldCount(); n++)
|
|
{
|
|
#ifndef _UNICODE
|
|
// Reset the buffer to point to next element
|
|
lpszFieldName =
|
|
m_rgODBCFieldInfos[n - 1].m_strName.GetBuffer(MAX_FNAME_LEN + 1);
|
|
#endif
|
|
|
|
AFX_ODBC_CALL(::SQLDescribeCol(m_hstmt, n,
|
|
(UCHAR*)lpszFieldName, MAX_FNAME_LEN, &nActualLen,
|
|
&m_rgODBCFieldInfos[n - 1].m_nSQLType,
|
|
&m_rgODBCFieldInfos[n - 1].m_nPrecision,
|
|
&m_rgODBCFieldInfos[n - 1].m_nScale,
|
|
&m_rgODBCFieldInfos[n - 1].m_nNullability));
|
|
|
|
#ifndef _UNICODE
|
|
m_rgODBCFieldInfos[n - 1].m_strName.ReleaseBuffer(nActualLen);
|
|
#else
|
|
// Copy the proxy data to correct element
|
|
m_rgODBCFieldInfos[n - 1].m_strName = lpszFieldName;
|
|
#endif
|
|
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE1("Error: ODBC failure getting field #%d info.\n", n);
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
}
|
|
|
|
#ifdef _UNICODE
|
|
delete[] lpszFieldName;
|
|
#endif
|
|
}
|
|
|
|
void CRecordset::AllocRowset()
|
|
{
|
|
if (m_dwOptions & useMultiRowFetch)
|
|
SetRowsetSize(m_dwRowsetSize);
|
|
else
|
|
{
|
|
// Not using bulk row fetch, set rowset size to 1
|
|
m_rgRowStatus = new WORD[1];
|
|
m_dwRowsetSize = 1;
|
|
}
|
|
}
|
|
|
|
void CRecordset::FreeRowset()
|
|
{
|
|
// Delete the rowset status
|
|
delete [] m_rgRowStatus;
|
|
m_rgRowStatus = NULL;
|
|
|
|
if (m_dwOptions & useMultiRowFetch &&
|
|
!(m_dwOptions & userAllocMultiRowBuffers))
|
|
{
|
|
// Calling virtual function, DoBulkFieldExchange, here is bad
|
|
// because Close then FreeRowset may get called from destructor.
|
|
// There is no simple choice however if RFX_Bulk functions do
|
|
// a memory allocation. The net result is that users MUST call
|
|
// Close explicitly (rather than relying on destructor) if
|
|
// using multi row fetches, otherwise they will get a memory leak.
|
|
// If rowset already allocated, delete old rowset buffers
|
|
if (m_dwAllocatedRowsetSize != 0)
|
|
{
|
|
CFieldExchange fx(CFieldExchange::DeleteMultiRowBuffer, this);
|
|
DoBulkFieldExchange(&fx);
|
|
}
|
|
}
|
|
|
|
m_dwAllocatedRowsetSize = 0;
|
|
}
|
|
|
|
void CRecordset::EnableBookmarks()
|
|
{
|
|
// Turn on bookmark support if necessary
|
|
if (m_dwOptions & useBookmarks)
|
|
{
|
|
RETCODE nRetCode;
|
|
|
|
// Set stmt option if bookmarks supported by driver
|
|
if (m_pDatabase->GetBookmarkPersistence() & SQL_BP_SCROLL)
|
|
{
|
|
AFX_SQL_SYNC(::SQLSetStmtOption(m_hstmt, SQL_USE_BOOKMARKS,
|
|
SQL_UB_ON));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: Can't enable bookmark support.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine whether to use SQLSetPos or positioned update SQL
|
|
void CRecordset::SetUpdateMethod()
|
|
{
|
|
// Determine update method
|
|
if (m_pDatabase->m_dwUpdateOptions & AFX_SQL_SETPOSUPDATES)
|
|
m_bUseUpdateSQL = FALSE;
|
|
else if (m_pDatabase->m_dwUpdateOptions & AFX_SQL_POSITIONEDSQL)
|
|
m_bUseUpdateSQL = TRUE;
|
|
else
|
|
m_bUpdatable = FALSE;
|
|
}
|
|
|
|
// Determine which type of concurrency to set, set it and cursor type
|
|
void CRecordset::SetConcurrencyAndCursorType(HSTMT hstmt, DWORD dwScrollOptions)
|
|
{
|
|
RETCODE nRetCode;
|
|
SWORD nResult;
|
|
|
|
m_dwConcurrency = SQL_CONCUR_READ_ONLY;
|
|
if ((m_bUpdatable || m_bAppendable) && m_pDatabase->m_bUpdatable)
|
|
{
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_SCROLL_CONCURRENCY,
|
|
&m_dwDriverConcurrency, sizeof(m_dwDriverConcurrency), &nResult));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: ODBC failure checking recordset updatability.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
|
|
if (m_nLockMode == pessimistic)
|
|
{
|
|
if (m_dwDriverConcurrency & SQL_SCCO_LOCK)
|
|
m_dwConcurrency = SQL_CONCUR_LOCK;
|
|
#ifdef _DEBUG
|
|
else
|
|
if (afxTraceFlags & traceDatabase)
|
|
TRACE0("Warning: locking not supported, setting recordset read only.\n");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Use cheapest, most concurrent model
|
|
if (m_dwDriverConcurrency & SQL_SCCO_OPT_ROWVER)
|
|
m_dwConcurrency = SQL_CONCUR_ROWVER;
|
|
else if (m_dwDriverConcurrency & SQL_SCCO_OPT_VALUES)
|
|
m_dwConcurrency = SQL_CONCUR_VALUES;
|
|
else if (m_dwDriverConcurrency & SQL_SCCO_LOCK)
|
|
m_dwConcurrency = SQL_CONCUR_LOCK;
|
|
}
|
|
}
|
|
|
|
// Set cursor type (Let rowset size default to 1).
|
|
AFX_SQL_SYNC(::SQLSetStmtOption(hstmt, SQL_CURSOR_TYPE, dwScrollOptions));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: ODBC failure setting recordset cursor type.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
|
|
// Set the concurrency model (NOTE: may have to reset concurrency later).
|
|
AFX_SQL_SYNC(::SQLSetStmtOption(hstmt, SQL_CONCURRENCY, m_dwConcurrency));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: ODBC failure setting recordset concurrency.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
}
|
|
|
|
// Is there a join, stored proc call, GROUP BY, UNION or missing FROM?
|
|
BOOL CRecordset::IsSQLUpdatable(LPCTSTR lpszSQL)
|
|
{
|
|
// Parse for query procedure call keyword or return param
|
|
if (!(_tcsnicmp(lpszSQL, szCall, lstrlen(szCall)-1) == 0 ||
|
|
_tcsnicmp(lpszSQL, szParamCall, lstrlen(szParamCall)-1) == 0))
|
|
// Assume this is a select query
|
|
return IsSelectQueryUpdatable(lpszSQL);
|
|
else
|
|
// Don't know the table name to update in procedure call
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CRecordset::IsSelectQueryUpdatable(LPCTSTR lpszSQL)
|
|
{
|
|
LPCTSTR lpchTokenFrom;
|
|
LPCTSTR lpchToken;
|
|
LPCTSTR lpchTokenNext;
|
|
LPTSTR lpszSQLStart;
|
|
CString strSQL = lpszSQL;
|
|
|
|
lpchTokenFrom = FindSQLToken(strSQL, szFrom);
|
|
if (lpchTokenFrom == NULL)
|
|
{
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
TRACE0("Warning: Missing ' FROM ', recordset not updatable \n");
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
|
|
lpchToken = FindSQLToken(strSQL, _T(" GROUP BY "));
|
|
if (lpchToken != NULL)
|
|
{
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
TRACE0("Warning: SQL contains ' GROUP BY ', recordset not updatable \n");
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
|
|
lpchToken = FindSQLToken(strSQL, _T(" UNION "));
|
|
if (lpchToken != NULL)
|
|
{
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
TRACE0("Warning: SQL contains ' UNION ', recordset not updatable \n");
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
|
|
// Find next token after FROM (can't have HAVING clause without GROUP BY)
|
|
lpchToken = FindSQLToken(strSQL, szWhere);
|
|
lpchTokenNext = FindSQLToken(strSQL, szOrderBy);
|
|
|
|
lpszSQLStart = strSQL.GetBuffer(0);
|
|
|
|
if (lpchTokenNext == NULL)
|
|
lpchTokenNext = lpchToken;
|
|
else if (lpchToken != NULL && lpchToken < lpchTokenNext)
|
|
lpchTokenNext = lpchToken;
|
|
|
|
if (lpchTokenNext != NULL)
|
|
{
|
|
int nFromLength = lpchTokenNext - lpchTokenFrom;
|
|
memcpy(lpszSQLStart, lpchTokenFrom, nFromLength*sizeof(TCHAR));
|
|
lpszSQLStart[nFromLength] = '\0';
|
|
}
|
|
else
|
|
lstrcpy(lpszSQLStart, lpchTokenFrom);
|
|
|
|
strSQL.ReleaseBuffer();
|
|
|
|
if (IsJoin(strSQL))
|
|
{
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
TRACE0("Warning: SQL contains join, recordset not updatable \n");
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
|
|
// Cache table name (skip over " FROM ")
|
|
m_strTableName = strSQL.Right(strSQL.GetLength()-6);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// Check FROM clause for join syntax
|
|
BOOL PASCAL CRecordset::IsJoin(LPCTSTR lpszJoinClause)
|
|
{
|
|
// Look for comma in join clause
|
|
if (FindSQLToken(lpszJoinClause, szComma) != NULL)
|
|
return TRUE;
|
|
|
|
// Look for outer join clause
|
|
if (FindSQLToken(lpszJoinClause, _T(" JOIN ")) != NULL)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
// Searches string for given token not in single quotes or brackets
|
|
LPCTSTR PASCAL CRecordset::FindSQLToken(LPCTSTR lpszSQL, LPCTSTR lpszSQLToken)
|
|
{
|
|
BOOL bInLiteral;
|
|
BOOL bInBrackets;
|
|
int nLeftBrackets;
|
|
int nRightBrackets;
|
|
LPCTSTR lpch;
|
|
LPCTSTR lpchSQLStart;
|
|
LPCTSTR lpszFoundToken;
|
|
int nTokenOffset = 0;
|
|
CString strSQL = lpszSQL;
|
|
|
|
strSQL.MakeUpper();
|
|
lpszFoundToken = strSQL.GetBuffer(0);
|
|
lpchSQLStart = lpszFoundToken;
|
|
|
|
do
|
|
{
|
|
lpszFoundToken = _tcsstr(lpszFoundToken + nTokenOffset, lpszSQLToken);
|
|
if (lpszFoundToken == NULL)
|
|
{
|
|
strSQL.ReleaseBuffer();
|
|
return NULL;
|
|
}
|
|
|
|
bInLiteral = bInBrackets = FALSE;
|
|
nLeftBrackets = nRightBrackets = 0;
|
|
|
|
// Check if embedded in literal or brackets
|
|
for (lpch = lpchSQLStart; lpch < lpszFoundToken; lpch = _tcsinc(lpch))
|
|
{
|
|
if (*lpch == chLiteralSeparator)
|
|
{
|
|
// Skip if escape literal
|
|
if (*_tcsinc(lpch) == chLiteralSeparator)
|
|
lpch = _tcsinc(lpch);
|
|
else
|
|
bInLiteral = !bInLiteral;
|
|
}
|
|
else if (!bInLiteral && (*lpch == '['))
|
|
{
|
|
// Skip if escape left bracket
|
|
if (*_tcsinc(lpch) == '[')
|
|
lpch = _tcsinc(lpch);
|
|
else
|
|
{
|
|
nLeftBrackets++;
|
|
if ((nLeftBrackets - nRightBrackets) > 0)
|
|
bInBrackets = TRUE;
|
|
else
|
|
bInBrackets = FALSE;
|
|
}
|
|
}
|
|
else if (!bInLiteral && (*lpch == ']'))
|
|
{
|
|
// Skip if escape right bracket
|
|
if (*_tcsinc(lpch) == ']')
|
|
lpch = _tcsinc(lpch);
|
|
else
|
|
{
|
|
nRightBrackets++;
|
|
if ((nLeftBrackets - nRightBrackets) > 0)
|
|
bInBrackets = TRUE;
|
|
else
|
|
bInBrackets = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If first iteration, reset the offset to jump over found token
|
|
if (nTokenOffset == 0)
|
|
nTokenOffset = lstrlen(lpszSQLToken);
|
|
|
|
} while (bInLiteral || bInBrackets);
|
|
|
|
lpszFoundToken = lpszSQL + (lpszFoundToken - lpchSQLStart);
|
|
strSQL.ReleaseBuffer();
|
|
return lpszFoundToken;
|
|
}
|
|
|
|
// Bind fields (if not already bound), then retrieve 1st record
|
|
void CRecordset::InitRecord()
|
|
{
|
|
// fields to bind
|
|
if (m_nFields != 0)
|
|
{
|
|
m_nFieldsBound = BindFieldsToColumns();
|
|
// m_nFields doesn't reflect number of
|
|
// RFX_ output column calls in Do[Bulk]FieldExchange
|
|
ASSERT((int)m_nFields == m_nFieldsBound);
|
|
|
|
// Allocate the data cache if necessary
|
|
if (m_nFields > 0 && m_bCheckCacheForDirtyFields)
|
|
AllocDataCache();
|
|
}
|
|
else
|
|
// No fields to bind, don't attempt to bind again
|
|
m_nFieldsBound = -1;
|
|
}
|
|
|
|
void CRecordset::ResetCursor()
|
|
{
|
|
m_bEOFSeen = m_bBOF = m_bEOF = FALSE;
|
|
m_bDeleted = FALSE;
|
|
m_lCurrentRecord = AFX_CURRENT_RECORD_BOF;
|
|
m_lRecordCount = 0;
|
|
}
|
|
|
|
void CRecordset::CheckRowsetCurrencyStatus(UWORD wFetchType, long nRows)
|
|
{
|
|
if (!m_bScrollable && wFetchType != SQL_FETCH_NEXT)
|
|
{
|
|
TRACE0("Error: forward-only recordsets only support MoveNext.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_RECORDSET_FORWARD_ONLY);
|
|
}
|
|
|
|
if (IsEOF() && IsBOF())
|
|
{
|
|
// Can't position cursor if recordset empty
|
|
TRACE0("Error: attempted to position cursor on empty recordset.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_NO_DATA_FOUND);
|
|
}
|
|
|
|
if (m_nOpenType != dynamic)
|
|
{
|
|
if (IsEOF() && (wFetchType == SQL_FETCH_NEXT ||
|
|
(wFetchType == SQL_FETCH_RELATIVE && nRows > 0)))
|
|
{
|
|
// if already at EOF, throw an exception
|
|
TRACE0("Error: attempted to move past EOF.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_NO_DATA_FOUND);
|
|
}
|
|
else if (IsBOF() && (wFetchType == SQL_FETCH_PRIOR ||
|
|
(wFetchType == SQL_FETCH_RELATIVE && nRows < 0)))
|
|
{
|
|
// if already at BOF, throw an exception
|
|
TRACE0("Error: attempted to move before BOF.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_NO_DATA_FOUND);
|
|
}
|
|
}
|
|
}
|
|
|
|
RETCODE CRecordset::FetchData(UWORD wFetchType, SDWORD nRow,
|
|
DWORD* pdwRowsFetched)
|
|
{
|
|
RETCODE nRetCode;
|
|
|
|
if (m_nOpenType == forwardOnly && !(m_dwOptions & useExtendedFetch))
|
|
{
|
|
ASSERT(wFetchType == SQL_FETCH_NEXT);
|
|
|
|
AFX_ODBC_CALL(::SQLFetch(m_hstmt));
|
|
*pdwRowsFetched = 1;
|
|
|
|
m_bDeleted = FALSE;
|
|
}
|
|
else
|
|
{
|
|
AFX_ODBC_CALL(::SQLExtendedFetch(m_hstmt, wFetchType,
|
|
nRow, pdwRowsFetched, m_rgRowStatus));
|
|
|
|
// Set deleted status
|
|
m_bDeleted = GetRowStatus(1) == SQL_ROW_DELETED;
|
|
}
|
|
|
|
CheckRowsetError(nRetCode);
|
|
|
|
return nRetCode;
|
|
}
|
|
|
|
void CRecordset::SkipDeletedRecords(UWORD wFetchType, long nRows,
|
|
DWORD* pdwRowsFetched, RETCODE* pnRetCode)
|
|
{
|
|
ASSERT(!(m_dwOptions & useMultiRowFetch));
|
|
ASSERT(wFetchType == SQL_FETCH_RELATIVE ||
|
|
wFetchType == SQL_FETCH_FIRST ||
|
|
wFetchType == SQL_FETCH_NEXT ||
|
|
wFetchType == SQL_FETCH_LAST ||
|
|
wFetchType == SQL_FETCH_PRIOR);
|
|
ASSERT(nRows != 0);
|
|
|
|
UWORD wDeletedFetchType = wFetchType;
|
|
DWORD dwDeletedRows = abs(nRows);
|
|
BOOL m_bDone;
|
|
|
|
switch (wFetchType)
|
|
{
|
|
case SQL_FETCH_FIRST:
|
|
wDeletedFetchType = SQL_FETCH_NEXT;
|
|
break;
|
|
|
|
case SQL_FETCH_LAST:
|
|
wDeletedFetchType = SQL_FETCH_PRIOR;
|
|
break;
|
|
|
|
case SQL_FETCH_RELATIVE:
|
|
if (nRows > 0)
|
|
wDeletedFetchType = SQL_FETCH_NEXT;
|
|
else
|
|
wDeletedFetchType = SQL_FETCH_PRIOR;
|
|
break;
|
|
}
|
|
|
|
// First fetch is as expected unless relative fetch
|
|
if (wFetchType != SQL_FETCH_RELATIVE)
|
|
{
|
|
*pnRetCode = FetchData(wFetchType, 1, pdwRowsFetched);
|
|
m_bDone = !m_bDeleted;
|
|
}
|
|
else
|
|
{
|
|
// Since deleted records must be skipped Move(n)
|
|
// must be turned into n MoveNext/MovePrev calls
|
|
*pnRetCode = FetchData(wDeletedFetchType, 1, pdwRowsFetched);
|
|
if (!m_bDeleted)
|
|
{
|
|
dwDeletedRows--;
|
|
m_bDone = dwDeletedRows == 0 ? TRUE : FALSE;
|
|
}
|
|
else
|
|
m_bDone = FALSE;
|
|
}
|
|
|
|
// Continue fetching until all req'd deleted records skipped
|
|
while (*pnRetCode != SQL_NO_DATA_FOUND && !m_bDone)
|
|
{
|
|
*pnRetCode = FetchData(wDeletedFetchType, 1, pdwRowsFetched);
|
|
|
|
if (wFetchType == SQL_FETCH_RELATIVE)
|
|
{
|
|
if (!m_bDeleted)
|
|
{
|
|
dwDeletedRows--;
|
|
m_bDone = dwDeletedRows == 0 ? TRUE : FALSE;
|
|
}
|
|
else
|
|
m_bDone = FALSE;
|
|
}
|
|
else
|
|
m_bDone = !m_bDeleted;
|
|
}
|
|
}
|
|
|
|
void CRecordset::SetRowsetCurrencyStatus(RETCODE nRetCode,
|
|
UWORD wFetchType, long nRows, DWORD dwRowsFetched)
|
|
{
|
|
UNUSED_ALWAYS(dwRowsFetched);
|
|
int nDirection;
|
|
|
|
// Set the fetch direction
|
|
switch (wFetchType)
|
|
{
|
|
case SQL_FETCH_FIRST:
|
|
nDirection = 1;
|
|
if (nRetCode == SQL_NO_DATA_FOUND)
|
|
{
|
|
m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
|
|
m_lRecordCount = 0;
|
|
}
|
|
else
|
|
m_lCurrentRecord = 0;
|
|
break;
|
|
|
|
case SQL_FETCH_NEXT:
|
|
nDirection = 1;
|
|
AfxSetCurrentRecord(&m_lCurrentRecord, nRows, nRetCode);
|
|
AfxSetRecordCount(&m_lRecordCount, m_lCurrentRecord, nRows,
|
|
m_bEOFSeen, nRetCode);
|
|
|
|
// This is the only way to know you've hit the end (m_bEOFSeen)
|
|
if (!m_bEOFSeen && nRetCode == SQL_NO_DATA_FOUND && m_lRecordCount == m_lCurrentRecord + 1)
|
|
m_bEOFSeen = TRUE;
|
|
break;
|
|
|
|
case SQL_FETCH_LAST:
|
|
nDirection = -1;
|
|
if (nRetCode == SQL_NO_DATA_FOUND)
|
|
{
|
|
m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
|
|
m_lRecordCount = 0;
|
|
}
|
|
else if (m_bEOFSeen)
|
|
m_lCurrentRecord = m_lRecordCount - 1;
|
|
else
|
|
m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
|
|
break;
|
|
|
|
case SQL_FETCH_PRIOR:
|
|
nDirection = -1;
|
|
AfxSetCurrentRecord(&m_lCurrentRecord, nRows, nRetCode);
|
|
break;
|
|
|
|
case SQL_FETCH_RELATIVE:
|
|
nDirection = nRows;
|
|
AfxSetCurrentRecord(&m_lCurrentRecord, nRows, nRetCode);
|
|
AfxSetRecordCount(&m_lRecordCount, m_lCurrentRecord, nRows,
|
|
m_bEOFSeen, nRetCode);
|
|
break;
|
|
|
|
case SQL_FETCH_ABSOLUTE:
|
|
nDirection = nRows;
|
|
if (nRetCode != SQL_NO_DATA_FOUND)
|
|
{
|
|
if (nRows > 0)
|
|
m_lCurrentRecord = nRows - 1;
|
|
else if (m_bEOFSeen)
|
|
m_lCurrentRecord = m_lRecordCount + nRows;
|
|
else
|
|
m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
|
|
}
|
|
else
|
|
m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
|
|
|
|
AfxSetRecordCount(&m_lRecordCount, m_lCurrentRecord, nRows,
|
|
m_bEOFSeen, nRetCode);
|
|
break;
|
|
|
|
case SQL_FETCH_BOOKMARK:
|
|
nDirection = 0;
|
|
m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
|
|
break;
|
|
}
|
|
|
|
// Set the BOF/EOF flags
|
|
if (nRetCode == SQL_NO_DATA_FOUND)
|
|
{
|
|
if (wFetchType == SQL_FETCH_FIRST || wFetchType == SQL_FETCH_LAST ||
|
|
wFetchType == SQL_FETCH_BOOKMARK)
|
|
{
|
|
// If MoveFirst/MoveLast fails, result set is empty
|
|
// If SetBookmark fails, currency undefined
|
|
m_bEOF = m_bBOF = TRUE;
|
|
}
|
|
else
|
|
{
|
|
m_bEOF = nDirection >= 0 ? TRUE : FALSE;
|
|
m_bBOF = !m_bEOF;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_bEOF = m_bBOF = FALSE;
|
|
}
|
|
}
|
|
|
|
void CRecordset::RefreshRowset(WORD wRow, WORD wLockType)
|
|
{
|
|
ASSERT(IsOpen());
|
|
ASSERT(m_dwOptions & useMultiRowFetch);
|
|
|
|
RETCODE nRetCode;
|
|
|
|
AFX_ODBC_CALL(::SQLSetPos(m_hstmt, wRow, SQL_REFRESH, wLockType));
|
|
|
|
// Need to fixup bound fields in some cases
|
|
if (m_nFields > 0 && !IsEOF() && !IsBOF() &&
|
|
!(m_dwOptions & useMultiRowFetch))
|
|
{
|
|
Fixups();
|
|
}
|
|
}
|
|
|
|
void CRecordset::SetRowsetCursorPosition(WORD wRow, WORD wLockType)
|
|
{
|
|
ASSERT(IsOpen());
|
|
ASSERT(m_dwOptions & useMultiRowFetch);
|
|
|
|
RETCODE nRetCode;
|
|
|
|
AFX_ODBC_CALL(::SQLSetPos(m_hstmt, wRow, SQL_POSITION, wLockType));
|
|
}
|
|
|
|
// "SELECT <user column name list> FROM <table name>"
|
|
void CRecordset::BuildSelectSQL()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
// Ignore queries with procedure call keyword or output param
|
|
if (!(_tcsnicmp(m_strSQL, szCall, lstrlen(szCall)-1) == 0 ||
|
|
_tcsnicmp(m_strSQL, szParamCall, lstrlen(szParamCall)-1) == 0))
|
|
{
|
|
// Ignore queries already built
|
|
if (_tcsnicmp(m_strSQL, szSelect, lstrlen(szSelect)-1) != 0)
|
|
{
|
|
// Assume m_strSQL specifies table name
|
|
ASSERT(m_nFields != 0);
|
|
|
|
CString strTableName;
|
|
strTableName = m_strSQL;
|
|
m_strSQL.Empty();
|
|
m_strSQL = szSelect;
|
|
|
|
// Set all fields dirty. AppendNames only outputs dirty field names
|
|
SetFieldDirty(NULL);
|
|
if (AppendNames(&m_strSQL, _T(",")) == 0)
|
|
{
|
|
TRACE0("Error: no field names - at least 1 required.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_EMPTY_COLUMN_LIST);
|
|
}
|
|
|
|
// Overwrite final ',' separator with ' '
|
|
ASSERT(m_strSQL[m_strSQL.GetLength()-1] == ',');
|
|
m_strSQL.SetAt(m_strSQL.GetLength()-1, ' ');
|
|
|
|
m_strSQL += szFrom;
|
|
m_strSQL += strTableName;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the filter and sort strings to query SQL
|
|
void CRecordset::AppendFilterAndSortSQL()
|
|
{
|
|
if (!m_strFilter.IsEmpty())
|
|
{
|
|
m_strSQL += szWhere;
|
|
m_strSQL += m_strFilter;
|
|
}
|
|
|
|
if (!m_strSort.IsEmpty())
|
|
{
|
|
m_strSQL += szOrderBy;
|
|
m_strSQL += m_strSort;
|
|
}
|
|
}
|
|
|
|
// Check for required SQLGetData support and do limited SQL parsing
|
|
BOOL CRecordset::IsRecordsetUpdatable()
|
|
{
|
|
// Do limited SQL parsing to determine if SQL updatable
|
|
if (!IsSQLUpdatable(m_strSQL))
|
|
return FALSE;
|
|
|
|
// Updatable recordsets with long binary columns must support
|
|
// SQL_GD_BOUND to use SQLSetPos, otherwise must use SQL updates
|
|
BOOL bUpdatable = TRUE;
|
|
if (m_bLongBinaryColumns && !m_bUseUpdateSQL)
|
|
{
|
|
// Set non-updatable if you can't use SQLGetData on bound columns
|
|
if (!(m_pDatabase->m_dwUpdateOptions & AFX_SQL_GDBOUND))
|
|
{
|
|
// Okay can't use SetPos, try and use positioned update SQL
|
|
if (m_pDatabase->m_dwUpdateOptions & AFX_SQL_POSITIONEDSQL)
|
|
{
|
|
m_bUseUpdateSQL = TRUE;
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
{
|
|
TRACE0("Warning: Can't use SQLSetPos due to lack of SQLGetData support.\n");
|
|
TRACE0("\tWill use positioned update SQL.\n");
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
TRACE0("Warning: Setting recordset read only due to lack of SQLGetData support.\n");
|
|
#endif
|
|
bUpdatable = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bUpdatable;
|
|
}
|
|
|
|
// Execute the update (or delete) using SQLSetPos
|
|
void CRecordset::ExecuteSetPosUpdate()
|
|
{
|
|
UWORD wExpectedRowStatus;
|
|
UWORD wPosOption;
|
|
if (m_nEditMode == noMode)
|
|
{
|
|
wPosOption = SQL_DELETE;
|
|
wExpectedRowStatus = SQL_ROW_DELETED;
|
|
}
|
|
else
|
|
{
|
|
if (m_nEditMode == edit)
|
|
{
|
|
wPosOption = SQL_UPDATE;
|
|
wExpectedRowStatus = SQL_ROW_UPDATED;
|
|
}
|
|
else
|
|
{
|
|
wPosOption = SQL_ADD;
|
|
wExpectedRowStatus = SQL_ROW_ADDED;
|
|
}
|
|
}
|
|
|
|
BindFieldsForUpdate();
|
|
|
|
RETCODE nRetCode;
|
|
AFX_ODBC_CALL(::SQLSetPos(m_hstmt, 1, wPosOption, SQL_LOCK_NO_CHANGE));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: failure updating record.\n");
|
|
AfxThrowDBException(nRetCode, m_pDatabase, m_hstmt);
|
|
}
|
|
// Only have data-at-execution columns for CLongBinary columns
|
|
if (nRetCode == SQL_NEED_DATA)
|
|
SendLongBinaryData(m_hstmt);
|
|
// This should only fail if SQLSetPos returned SQL_SUCCESS_WITH_INFO explaining why
|
|
if (nRetCode == SQL_SUCCESS_WITH_INFO && GetRowStatus(1) != wExpectedRowStatus)
|
|
ThrowDBException(AFX_SQL_ERROR_UPDATE_DELETE_FAILED);
|
|
|
|
UnbindFieldsForUpdate();
|
|
}
|
|
|
|
// Prepare for sending update SQL by initializing m_hstmtUpdate
|
|
void CRecordset::PrepareUpdateHstmt()
|
|
{
|
|
RETCODE nRetCode;
|
|
if (m_hstmtUpdate == SQL_NULL_HSTMT)
|
|
{
|
|
AFX_SQL_SYNC(::SQLAllocStmt(m_pDatabase->m_hdbc, &m_hstmtUpdate));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: failure to allocate update statement.\n");
|
|
AfxThrowDBException(nRetCode, m_pDatabase, m_hstmtUpdate);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmtUpdate, SQL_CLOSE));
|
|
if (!Check(nRetCode))
|
|
goto LErrRetCode;
|
|
|
|
// Re-use (prepared) hstmt & param binding if optimizeBulkAdd option
|
|
if(!(m_dwOptions & optimizeBulkAdd))
|
|
{
|
|
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmtUpdate, SQL_RESET_PARAMS));
|
|
if (!Check(nRetCode))
|
|
{
|
|
LErrRetCode:
|
|
// Bad hstmt, free it and allocate another one
|
|
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmtUpdate, SQL_DROP));
|
|
m_hstmtUpdate = SQL_NULL_HSTMT;
|
|
|
|
AFX_SQL_SYNC(::SQLAllocStmt(m_pDatabase->m_hdbc, &m_hstmtUpdate));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: failure to allocate update statement.\n");
|
|
AfxThrowDBException(nRetCode, m_pDatabase, m_hstmtUpdate);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build the UPDATE, INSERT or DELETE SQL
|
|
void CRecordset::BuildUpdateSQL()
|
|
{
|
|
switch (m_nEditMode)
|
|
{
|
|
case noMode:
|
|
// DELETE FROM <tablename> WHERE CURRENT OF
|
|
{
|
|
m_strUpdateSQL = _T("DELETE FROM ");
|
|
m_strUpdateSQL += m_strTableName;
|
|
}
|
|
break;
|
|
|
|
case addnew:
|
|
// INSERT INTO <tablename> (<colname1>[,<colname2>]) VALUES (?[,?])
|
|
{
|
|
m_strUpdateSQL = _T("INSERT INTO ");
|
|
m_strUpdateSQL += m_strTableName;
|
|
|
|
m_strUpdateSQL += _T(" (");
|
|
|
|
// Append column names
|
|
AppendNames(&m_strUpdateSQL, szComma);
|
|
|
|
// overwrite last ',' with ')'
|
|
ASSERT(m_strUpdateSQL[m_strUpdateSQL.GetLength()-1] == ',');
|
|
m_strUpdateSQL.SetAt(m_strUpdateSQL.GetLength()-1, ')');
|
|
|
|
// Append values
|
|
m_strUpdateSQL += _T(" VALUES (");
|
|
AppendValues(m_hstmtUpdate, &m_strUpdateSQL, szComma);
|
|
|
|
// overwrite last ',' with ')'
|
|
ASSERT(m_strUpdateSQL[m_strUpdateSQL.GetLength()-1] == ',');
|
|
m_strUpdateSQL.SetAt(m_strUpdateSQL.GetLength()-1, ')');
|
|
}
|
|
break;
|
|
|
|
case edit:
|
|
// UPDATE <tablename> SET <colname1>=?[,<colname2>=?] WHERE CURRENT OF
|
|
{
|
|
m_strUpdateSQL = _T("UPDATE ");
|
|
m_strUpdateSQL += m_strTableName;
|
|
|
|
m_strUpdateSQL += _T(" SET ");
|
|
AppendNamesValues(m_hstmtUpdate, &m_strUpdateSQL, szComma);
|
|
|
|
// overwrite last ',' with ' '
|
|
ASSERT(m_strUpdateSQL[m_strUpdateSQL.GetLength()-1] == ',');
|
|
m_strUpdateSQL.SetAt(m_strUpdateSQL.GetLength()-1, ' ');
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Update and Delete need "WHERE CURRENT OF <cursorname>"
|
|
if (m_nEditMode == edit || m_nEditMode == noMode)
|
|
{
|
|
m_strUpdateSQL += _T(" WHERE CURRENT OF ");
|
|
|
|
// Cache cursor name assigned by ODBC
|
|
if (m_strCursorName.IsEmpty())
|
|
{
|
|
// Get predefined cursor name from datasource
|
|
RETCODE nRetCode;
|
|
UCHAR szCursorName[MAX_CURSOR_NAME+1];
|
|
SWORD nLength = _countof(szCursorName)-1;
|
|
AFX_SQL_SYNC(::SQLGetCursorName(m_hstmt,
|
|
szCursorName, _countof(szCursorName), &nLength));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode);
|
|
m_strCursorName = (char*)szCursorName;
|
|
}
|
|
m_strUpdateSQL += m_strCursorName;
|
|
}
|
|
|
|
m_pDatabase->ReplaceBrackets(m_strUpdateSQL.GetBuffer(0));
|
|
m_strUpdateSQL.ReleaseBuffer();
|
|
|
|
// Must prepare the hstmt on first optimized bulk add
|
|
if(m_dwOptions & firstBulkAdd)
|
|
{
|
|
RETCODE nRetCode;
|
|
|
|
USES_CONVERSION;
|
|
AFX_ODBC_CALL(::SQLPrepare(m_hstmtUpdate,
|
|
(UCHAR*)T2A((LPTSTR)(LPCTSTR)m_strUpdateSQL), SQL_NTS));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode, m_hstmtUpdate);
|
|
}
|
|
}
|
|
|
|
void CRecordset::ExecuteUpdateSQL()
|
|
{
|
|
RETCODE nRetCode;
|
|
|
|
if(!(m_dwOptions & optimizeBulkAdd))
|
|
{
|
|
USES_CONVERSION;
|
|
AFX_ODBC_CALL(::SQLExecDirect(m_hstmtUpdate,
|
|
(UCHAR*)T2A((LPTSTR)(LPCTSTR)m_strUpdateSQL), SQL_NTS));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode, m_hstmtUpdate);
|
|
}
|
|
else
|
|
{
|
|
AFX_ODBC_CALL(::SQLExecute(m_hstmtUpdate));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode, m_hstmtUpdate);
|
|
}
|
|
|
|
// Only have data-at-execution parameters for CLongBinary columns
|
|
if (nRetCode == SQL_NEED_DATA)
|
|
SendLongBinaryData(m_hstmtUpdate);
|
|
|
|
SDWORD lRowsAffected = 0;
|
|
|
|
AFX_SQL_SYNC(::SQLRowCount(m_hstmtUpdate, &lRowsAffected));
|
|
if (!Check(nRetCode) || lRowsAffected == -1)
|
|
{
|
|
// Assume 1 row affected if # rows affected can't be determined
|
|
lRowsAffected = 1;
|
|
}
|
|
else
|
|
{
|
|
if (lRowsAffected != 1)
|
|
{
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
TRACE1("Warning: %u rows affected by update operation (expected 1).\n",
|
|
lRowsAffected);
|
|
#endif
|
|
ThrowDBException((RETCODE)(lRowsAffected == 0 ?
|
|
AFX_SQL_ERROR_NO_ROWS_AFFECTED :
|
|
AFX_SQL_ERROR_MULTIPLE_ROWS_AFFECTED));
|
|
}
|
|
}
|
|
m_strUpdateSQL.Empty();
|
|
}
|
|
|
|
|
|
void CRecordset::SendLongBinaryData(HSTMT hstmt)
|
|
{
|
|
RETCODE nRetCode;
|
|
void* pv;
|
|
AFX_ODBC_CALL(::SQLParamData(hstmt, &pv));
|
|
if (!Check(nRetCode))
|
|
{
|
|
// cache away error
|
|
CDBException* pException = new CDBException(nRetCode);
|
|
pException->BuildErrorString(m_pDatabase, hstmt);
|
|
|
|
// then cancel Execute operation
|
|
Cancel();
|
|
THROW(pException);
|
|
}
|
|
|
|
while (nRetCode == SQL_NEED_DATA)
|
|
{
|
|
CLongBinary* pLongBinary = (CLongBinary*)pv;
|
|
ASSERT_VALID(pLongBinary);
|
|
|
|
const BYTE* lpData = (const BYTE*)::GlobalLock(pLongBinary->m_hData);
|
|
ASSERT(lpData != NULL);
|
|
|
|
AFX_ODBC_CALL(::SQLPutData(hstmt, (PTR)lpData,
|
|
pLongBinary->m_dwDataLength));
|
|
|
|
::GlobalUnlock(pLongBinary->m_hData);
|
|
|
|
if (!Check(nRetCode))
|
|
{
|
|
// cache away error
|
|
CDBException* pException = new CDBException(nRetCode);
|
|
pException->BuildErrorString(m_pDatabase, hstmt);
|
|
|
|
// then cancel Execute operation
|
|
Cancel();
|
|
THROW(pException);
|
|
}
|
|
|
|
// Check for another DATA_AT_EXEC
|
|
AFX_ODBC_CALL(::SQLParamData(hstmt, &pv));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: failure handling long binary value during update.\n");
|
|
ThrowDBException(nRetCode, hstmt);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CRecordset RFX implementations
|
|
|
|
void CRecordset::AllocStatusArrays()
|
|
{
|
|
ASSERT(m_rgFieldInfos == NULL);
|
|
ASSERT(m_pbFieldFlags == NULL);
|
|
ASSERT(m_pbParamFlags == NULL);
|
|
ASSERT(m_plParamLength == NULL);
|
|
|
|
TRY
|
|
{
|
|
if (m_nFields != 0)
|
|
{
|
|
// Allocate buffers to hold field info
|
|
m_rgFieldInfos = new CFieldInfo[m_nFields];
|
|
memset(m_rgFieldInfos, 0, sizeof(CFieldInfo) * m_nFields);
|
|
|
|
m_pbFieldFlags = new BYTE[m_nFields];
|
|
memset(m_pbFieldFlags, 0, m_nFields);
|
|
}
|
|
|
|
if (m_nParams != 0)
|
|
{
|
|
m_pbParamFlags = new BYTE[m_nParams];
|
|
memset(m_pbParamFlags, 0, m_nParams);
|
|
|
|
m_plParamLength = new LONG[m_nParams];
|
|
memset(m_plParamLength, 0, m_nParams*sizeof(LONG));
|
|
}
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
Close();
|
|
THROW_LAST();
|
|
}
|
|
END_CATCH_ALL
|
|
}
|
|
|
|
int CRecordset::GetBoundFieldIndex(void* pv)
|
|
{
|
|
void* pvIndex;
|
|
|
|
if (!m_mapFieldIndex.Lookup(pv, pvIndex))
|
|
return -1;
|
|
else
|
|
// Cached value is short not ptr
|
|
return (int)pvIndex;
|
|
}
|
|
|
|
int CRecordset::GetBoundParamIndex(void* pv)
|
|
{
|
|
void* pvIndex;
|
|
|
|
if (!m_mapParamIndex.Lookup(pv, pvIndex))
|
|
return -1;
|
|
else
|
|
// Cached value in data not ptr
|
|
return (int)pvIndex;
|
|
}
|
|
|
|
short CRecordset::GetFieldIndexByName(LPCTSTR lpszFieldName)
|
|
{
|
|
for (short nIndex = 0; nIndex < GetODBCFieldCount(); nIndex++)
|
|
{
|
|
if (m_rgODBCFieldInfos[nIndex].m_strName == lpszFieldName)
|
|
break;
|
|
}
|
|
|
|
// Check if field name found
|
|
if (nIndex == GetODBCFieldCount())
|
|
ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
|
|
|
|
return nIndex;
|
|
}
|
|
|
|
long* CRecordset::GetFieldLengthBuffer(DWORD nField, int nFieldType)
|
|
{
|
|
if (nFieldType == CFieldExchange::outputColumn)
|
|
{
|
|
ASSERT(nField < m_nFields);
|
|
return &m_rgFieldInfos[nField].m_nLength;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(nField < m_nParams);
|
|
return &m_plParamLength[nField];
|
|
}
|
|
}
|
|
|
|
BYTE CRecordset::GetFieldStatus(DWORD nField)
|
|
{
|
|
ASSERT(nField < m_nFields);
|
|
|
|
return m_pbFieldFlags[nField];
|
|
}
|
|
|
|
void CRecordset::SetFieldStatus(DWORD nField, BYTE bFlags)
|
|
{
|
|
ASSERT(nField < m_nFields);
|
|
|
|
m_pbFieldFlags[nField] |= bFlags;
|
|
}
|
|
|
|
void CRecordset::ClearFieldStatus()
|
|
{
|
|
memset(m_pbFieldFlags, 0, m_nFields);
|
|
}
|
|
|
|
BOOL CRecordset::IsFieldStatusDirty(DWORD nField) const
|
|
{
|
|
ASSERT(nField < m_nFields);
|
|
|
|
return m_pbFieldFlags[nField] & AFX_SQL_FIELD_FLAG_DIRTY;
|
|
}
|
|
|
|
void CRecordset::SetDirtyFieldStatus(DWORD nField)
|
|
{
|
|
ASSERT(nField < m_nFields);
|
|
|
|
m_pbFieldFlags[nField] |= AFX_SQL_FIELD_FLAG_DIRTY;
|
|
}
|
|
|
|
void CRecordset::ClearDirtyFieldStatus(DWORD nField)
|
|
{
|
|
ASSERT(nField < m_nFields);
|
|
|
|
m_pbFieldFlags[nField] &= ~AFX_SQL_FIELD_FLAG_DIRTY;
|
|
}
|
|
|
|
BOOL CRecordset::IsFieldStatusNull(DWORD nField) const
|
|
{
|
|
ASSERT(nField < m_nFields);
|
|
|
|
return m_pbFieldFlags[nField] & AFX_SQL_FIELD_FLAG_NULL;
|
|
}
|
|
|
|
void CRecordset::SetNullFieldStatus(DWORD nField)
|
|
{
|
|
ASSERT(nField < m_nFields);
|
|
|
|
m_pbFieldFlags[nField] |= AFX_SQL_FIELD_FLAG_NULL;
|
|
}
|
|
|
|
void CRecordset::ClearNullFieldStatus(DWORD nField)
|
|
{
|
|
ASSERT(nField < m_nFields);
|
|
|
|
m_pbFieldFlags[nField] &= ~AFX_SQL_FIELD_FLAG_NULL;
|
|
}
|
|
|
|
BOOL CRecordset::IsParamStatusNull(DWORD nParam) const
|
|
{
|
|
ASSERT(nParam < m_nParams);
|
|
|
|
return m_pbParamFlags[nParam] & AFX_SQL_FIELD_FLAG_NULL;
|
|
}
|
|
|
|
void CRecordset::SetNullParamStatus(DWORD nParam)
|
|
{
|
|
ASSERT(nParam < m_nParams);
|
|
|
|
m_pbParamFlags[nParam] |= AFX_SQL_FIELD_FLAG_NULL;
|
|
}
|
|
|
|
void CRecordset::ClearNullParamStatus(DWORD nParam)
|
|
{
|
|
ASSERT(nParam < m_nParams);
|
|
|
|
m_pbParamFlags[nParam] &= ~AFX_SQL_FIELD_FLAG_NULL;
|
|
}
|
|
|
|
BOOL CRecordset::IsFieldNullable(DWORD nField) const
|
|
{
|
|
ASSERT(nField <= INT_MAX);
|
|
ASSERT((long)nField < GetODBCFieldCount());
|
|
|
|
// return TRUE if nulls allowed or if not known
|
|
return m_rgODBCFieldInfos[nField].m_nNullability != SQL_NO_NULLS;
|
|
}
|
|
|
|
UINT CRecordset::BindParams(HSTMT hstmt)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
CFieldExchange fx(CFieldExchange::BindParam, this);
|
|
fx.m_hstmt = hstmt;
|
|
|
|
DoFieldExchange(&fx);
|
|
|
|
return fx.m_nParams;
|
|
}
|
|
|
|
void CRecordset::RebindParams(HSTMT hstmt)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
if (m_bRebindParams)
|
|
{
|
|
CFieldExchange fx(CFieldExchange::RebindParam, this);
|
|
fx.m_hstmt = hstmt;
|
|
|
|
DoFieldExchange(&fx);
|
|
}
|
|
}
|
|
|
|
UINT CRecordset::BindFieldsToColumns()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
ASSERT(m_nFieldsBound == 0);
|
|
ASSERT(m_nFields != 0 && m_nFields <= 255);
|
|
|
|
CFieldExchange fx(CFieldExchange::BindFieldToColumn, this);
|
|
fx.m_hstmt = m_hstmt;
|
|
|
|
// Binding depends on fetch type
|
|
if (m_dwOptions & useMultiRowFetch)
|
|
DoBulkFieldExchange(&fx);
|
|
else
|
|
DoFieldExchange(&fx);
|
|
|
|
return fx.m_nFields;
|
|
}
|
|
|
|
void CRecordset::BindFieldsForUpdate()
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
if (m_nEditMode == edit || m_nEditMode == addnew)
|
|
{
|
|
CFieldExchange fx(CFieldExchange::BindFieldForUpdate, this);
|
|
fx.m_hstmt = m_hstmt;
|
|
DoFieldExchange(&fx);
|
|
}
|
|
}
|
|
|
|
void CRecordset::UnbindFieldsForUpdate()
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
if (m_nEditMode == edit || m_nEditMode == addnew)
|
|
{
|
|
CFieldExchange fx(CFieldExchange::UnbindFieldForUpdate, this);
|
|
fx.m_hstmt = m_hstmt;
|
|
DoFieldExchange(&fx);
|
|
}
|
|
}
|
|
|
|
// After Move operation, reflect status and lengths of columns in RFX fields
|
|
void CRecordset::Fixups()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
ASSERT(m_nFieldsBound != 0);
|
|
|
|
CFieldExchange fx(CFieldExchange::Fixup, this);
|
|
fx.m_hstmt = m_hstmt;
|
|
DoFieldExchange(&fx);
|
|
}
|
|
|
|
UINT CRecordset::AppendNames(CString* pstr, LPCTSTR lpszSeparator)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(AfxIsValidAddress(pstr, sizeof(CString)));
|
|
ASSERT(AfxIsValidString(lpszSeparator));
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
CFieldExchange fx(CFieldExchange::Name, this);
|
|
fx.m_pstr = pstr;
|
|
fx.m_lpszSeparator = lpszSeparator;
|
|
|
|
if (m_dwOptions & useMultiRowFetch)
|
|
DoBulkFieldExchange(&fx);
|
|
else
|
|
DoFieldExchange(&fx);
|
|
|
|
return fx.m_nFields;
|
|
}
|
|
|
|
// For each "changed" column, append <column name>=<column value>,
|
|
UINT CRecordset::AppendNamesValues(HSTMT hstmt, CString* pstr,
|
|
LPCTSTR lpszSeparator)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(AfxIsValidAddress(pstr, sizeof(CString)));
|
|
ASSERT(AfxIsValidString(lpszSeparator));
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
ASSERT(hstmt != SQL_NULL_HSTMT);
|
|
|
|
CFieldExchange fx(CFieldExchange::NameValue, this);
|
|
fx.m_pstr = pstr;
|
|
fx.m_lpszSeparator = lpszSeparator;
|
|
fx.m_hstmt = hstmt;
|
|
|
|
DoFieldExchange(&fx);
|
|
|
|
return fx.m_nFields;
|
|
}
|
|
|
|
// For each "changed" column, append <column value>,
|
|
UINT CRecordset::AppendValues(HSTMT hstmt, CString* pstr,
|
|
LPCTSTR lpszSeparator)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(AfxIsValidAddress(pstr, sizeof(CString)));
|
|
ASSERT(AfxIsValidString(lpszSeparator));
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
ASSERT(hstmt != SQL_NULL_HSTMT);
|
|
|
|
CFieldExchange fx(CFieldExchange::Value, this);
|
|
fx.m_pstr = pstr;
|
|
fx.m_lpszSeparator = lpszSeparator;
|
|
fx.m_hstmt = hstmt;
|
|
|
|
DoFieldExchange(&fx);
|
|
|
|
return fx.m_nFields;
|
|
}
|
|
|
|
|
|
// Cache fields of copy buffer in a CMemFile with CArchive
|
|
void CRecordset::StoreFields()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_nFieldsBound != 0);
|
|
|
|
CFieldExchange fx(CFieldExchange::StoreField, this);
|
|
DoFieldExchange(&fx);
|
|
}
|
|
|
|
// Restore fields of copy buffer from archived memory file
|
|
void CRecordset::LoadFields()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_nFieldsBound != 0);
|
|
|
|
// Must clear out the old status
|
|
ClearFieldStatus();
|
|
|
|
CFieldExchange fx(CFieldExchange::LoadField, this);
|
|
DoFieldExchange(&fx);
|
|
}
|
|
|
|
void CRecordset::MarkForUpdate()
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
CFieldExchange fx(CFieldExchange::MarkForUpdate, this);
|
|
DoFieldExchange(&fx);
|
|
}
|
|
|
|
void CRecordset::MarkForAddNew()
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
CFieldExchange fx(CFieldExchange::MarkForAddNew, this);
|
|
DoFieldExchange(&fx);
|
|
}
|
|
|
|
void CRecordset::AllocDataCache()
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
CFieldExchange fx(CFieldExchange::AllocCache, this);
|
|
DoFieldExchange(&fx);
|
|
}
|
|
|
|
void CRecordset::FreeDataCache()
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
CFieldInfo* pInfo;
|
|
|
|
for (DWORD nField = 0; nField < m_nFields; nField++)
|
|
{
|
|
pInfo = &m_rgFieldInfos[nField];
|
|
|
|
switch(pInfo->m_nDataType)
|
|
{
|
|
default:
|
|
ASSERT(FALSE);
|
|
// fall through
|
|
|
|
// Data not cached
|
|
case AFX_RFX_NO_TYPE:
|
|
break;
|
|
|
|
// Types cached by value (sizeof(TYPE) <= sizeof(void*))
|
|
case AFX_RFX_BOOL:
|
|
case AFX_RFX_BYTE:
|
|
case AFX_RFX_INT:
|
|
case AFX_RFX_LONG:
|
|
case AFX_RFX_SINGLE:
|
|
pInfo->m_pvDataCache = NULL;
|
|
break;
|
|
|
|
case AFX_RFX_TEXT:
|
|
delete (CString*)pInfo->m_pvDataCache;
|
|
pInfo->m_pvDataCache = NULL;
|
|
break;
|
|
|
|
case AFX_RFX_DOUBLE:
|
|
delete (double*)pInfo->m_pvDataCache;
|
|
pInfo->m_pvDataCache = NULL;
|
|
break;
|
|
|
|
case AFX_RFX_TIMESTAMP:
|
|
delete (TIMESTAMP_STRUCT*)pInfo->m_pvDataCache;
|
|
pInfo->m_pvDataCache = NULL;
|
|
break;
|
|
|
|
case AFX_RFX_DATE:
|
|
delete (CTime*)pInfo->m_pvDataCache;
|
|
pInfo->m_pvDataCache = NULL;
|
|
break;
|
|
|
|
case AFX_RFX_BINARY:
|
|
delete (CByteArray*)pInfo->m_pvDataCache;
|
|
pInfo->m_pvDataCache = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
void CRecordset::DumpFields(CDumpContext& dc) const
|
|
{
|
|
CFieldExchange fx(CFieldExchange::DumpField, (CRecordset *)this);
|
|
fx.m_pdcDump = &dc;
|
|
((CRecordset *)this)->DoFieldExchange(&fx);
|
|
}
|
|
#endif // _DEBUG
|
|
|
|
|
|
// Perform Update (m_nModeEdit == edit), Insert (addnew),
|
|
// or Delete (noMode)
|
|
BOOL CRecordset::UpdateInsertDelete()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
// Delete mode
|
|
if (m_nEditMode == addnew)
|
|
{
|
|
if (!m_bAppendable)
|
|
{
|
|
TRACE0("Error: attempted to add a record to a read only recordset.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!m_bUpdatable)
|
|
{
|
|
TRACE0("Error: attempted to update a read only recordset.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
|
|
}
|
|
|
|
// Requires currency
|
|
if (m_bEOF || m_bBOF || m_bDeleted)
|
|
{
|
|
TRACE0("Error: attempting to update recordset - but no record is current.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_NO_CURRENT_RECORD);
|
|
}
|
|
}
|
|
|
|
// Update or AddNew is NOP w/o at least 1 changed field
|
|
if (m_nEditMode != noMode && !IsFieldDirty(NULL))
|
|
return FALSE;
|
|
|
|
if (!m_bUseUpdateSQL)
|
|
{
|
|
// Most efficient update method
|
|
ExecuteSetPosUpdate();
|
|
}
|
|
else
|
|
{
|
|
|
|
BOOL bNullHstmt = (m_hstmtUpdate == NULL);
|
|
|
|
// Make sure m_hstmtUpdate allocated
|
|
PrepareUpdateHstmt();
|
|
|
|
// Build update SQL unless optimizing bulk adds and hstmt not NULL
|
|
if(!(m_dwOptions & optimizeBulkAdd) || bNullHstmt)
|
|
{
|
|
// Mark as first bulk add if optimizing
|
|
if(m_dwOptions & optimizeBulkAdd)
|
|
{
|
|
m_dwOptions &= ~optimizeBulkAdd;
|
|
m_dwOptions |= firstBulkAdd;
|
|
}
|
|
BuildUpdateSQL();
|
|
|
|
// Reset flag marking first optimization
|
|
if(m_dwOptions & firstBulkAdd)
|
|
{
|
|
m_dwOptions &= ~firstBulkAdd;
|
|
m_dwOptions |= optimizeBulkAdd;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Just reset the data lengths and datetime proxies
|
|
AppendValues(m_hstmtUpdate, &m_strUpdateSQL, szComma);
|
|
}
|
|
|
|
ExecuteUpdateSQL();
|
|
}
|
|
|
|
TRY
|
|
{
|
|
// Delete
|
|
switch (m_nEditMode)
|
|
{
|
|
case noMode:
|
|
// Decrement record count
|
|
if (m_lCurrentRecord >= 0)
|
|
{
|
|
if (m_lRecordCount > 0)
|
|
m_lRecordCount--;
|
|
m_lCurrentRecord--;
|
|
}
|
|
|
|
// indicate on a deleted record
|
|
m_bDeleted = TRUE;
|
|
// Set all fields to NULL
|
|
SetFieldNull(NULL);
|
|
break;
|
|
|
|
case addnew:
|
|
if (m_pDatabase->m_bIncRecordCountOnAdd && m_lCurrentRecord >= 0)
|
|
{
|
|
if (m_lRecordCount != -1)
|
|
m_lRecordCount++;
|
|
m_lCurrentRecord++;
|
|
}
|
|
|
|
// Reset the data cache if necessary
|
|
if (m_bCheckCacheForDirtyFields && m_nFields > 0)
|
|
LoadFields();
|
|
break;
|
|
|
|
case edit:
|
|
break;
|
|
}
|
|
|
|
// Reset the edit mode
|
|
m_nEditMode = noMode;
|
|
}
|
|
END_TRY
|
|
// fall through - must return TRUE since record updated
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Fetch and alloc algorithms for CLongBinary data when length unknown
|
|
long CRecordset::GetLBFetchSize(long lOldSize)
|
|
{
|
|
// Make it twice as big
|
|
return lOldSize << 1;
|
|
}
|
|
|
|
long CRecordset::GetLBReallocSize(long lOldSize)
|
|
{
|
|
// Make it twice as big (no effect if less than fetch size)
|
|
return lOldSize << 1;
|
|
}
|
|
|
|
short PASCAL CRecordset::GetDefaultFieldType(short nSQLType)
|
|
{
|
|
short nFieldType;
|
|
|
|
switch (nSQLType)
|
|
{
|
|
case SQL_BIT:
|
|
nFieldType = SQL_C_BIT;
|
|
break;
|
|
|
|
case SQL_TINYINT:
|
|
nFieldType = SQL_C_UTINYINT;
|
|
break;
|
|
|
|
case SQL_SMALLINT:
|
|
nFieldType = SQL_C_SSHORT;
|
|
break;
|
|
|
|
case SQL_INTEGER:
|
|
nFieldType = SQL_C_SLONG;
|
|
break;
|
|
|
|
case SQL_REAL:
|
|
nFieldType = SQL_C_FLOAT;
|
|
break;
|
|
|
|
case SQL_FLOAT:
|
|
case SQL_DOUBLE:
|
|
nFieldType = SQL_C_DOUBLE;
|
|
break;
|
|
|
|
case SQL_DATE:
|
|
case SQL_TIME:
|
|
case SQL_TIMESTAMP:
|
|
nFieldType = SQL_C_TIMESTAMP;
|
|
break;
|
|
|
|
case SQL_NUMERIC:
|
|
case SQL_DECIMAL:
|
|
case SQL_BIGINT:
|
|
case SQL_CHAR:
|
|
case SQL_VARCHAR:
|
|
case SQL_LONGVARCHAR:
|
|
nFieldType = SQL_C_CHAR;
|
|
break;
|
|
|
|
case SQL_BINARY:
|
|
case SQL_VARBINARY:
|
|
case SQL_LONGVARBINARY:
|
|
nFieldType = SQL_C_BINARY;
|
|
break;
|
|
|
|
default:
|
|
ASSERT(FALSE);
|
|
}
|
|
|
|
return nFieldType;
|
|
}
|
|
|
|
void* PASCAL CRecordset::GetDataBuffer(CDBVariant& varValue,
|
|
short nFieldType, int* pnLen, short nSQLType, UDWORD nPrecision)
|
|
{
|
|
void* pvData = NULL;
|
|
|
|
// Clear variant if dynamically allocated type and types don't match
|
|
if ((varValue.m_dwType == DBVT_DATE && nFieldType != SQL_C_TIMESTAMP) ||
|
|
(varValue.m_dwType == DBVT_STRING && nFieldType != SQL_C_CHAR) ||
|
|
(varValue.m_dwType == DBVT_BINARY && nFieldType != SQL_C_BINARY))
|
|
{
|
|
varValue.Clear();
|
|
}
|
|
|
|
switch (nFieldType)
|
|
{
|
|
case SQL_C_BIT:
|
|
pvData = &varValue.m_boolVal;
|
|
varValue.m_dwType = DBVT_BOOL;
|
|
*pnLen = sizeof(varValue.m_boolVal);
|
|
break;
|
|
|
|
case SQL_C_UTINYINT:
|
|
pvData = &varValue.m_chVal;
|
|
varValue.m_dwType = DBVT_UCHAR;
|
|
*pnLen = sizeof(varValue.m_chVal);
|
|
break;
|
|
|
|
case SQL_C_SSHORT:
|
|
pvData = &varValue.m_iVal;
|
|
varValue.m_dwType = DBVT_SHORT;
|
|
*pnLen = sizeof(varValue.m_iVal);
|
|
break;
|
|
|
|
case SQL_C_SLONG:
|
|
pvData = &varValue.m_lVal;
|
|
varValue.m_dwType = DBVT_LONG;
|
|
*pnLen = sizeof(varValue.m_lVal);
|
|
break;
|
|
|
|
case SQL_C_FLOAT:
|
|
pvData = &varValue.m_fltVal;
|
|
varValue.m_dwType = DBVT_SINGLE;
|
|
*pnLen = sizeof(varValue.m_fltVal);
|
|
break;
|
|
|
|
case SQL_C_DOUBLE:
|
|
pvData = &varValue.m_dblVal;
|
|
varValue.m_dwType = DBVT_DOUBLE;
|
|
*pnLen = sizeof(varValue.m_dblVal);
|
|
break;
|
|
|
|
case SQL_C_TIMESTAMP:
|
|
pvData = varValue.m_pdate = new TIMESTAMP_STRUCT;
|
|
varValue.m_dwType = DBVT_DATE;
|
|
*pnLen = sizeof(*varValue.m_pdate);
|
|
break;
|
|
|
|
case SQL_C_CHAR:
|
|
varValue.m_pstring = new CString;
|
|
varValue.m_dwType = DBVT_STRING;
|
|
|
|
*pnLen = GetTextLen(nSQLType, nPrecision);
|
|
|
|
pvData = varValue.m_pstring->GetBufferSetLength(*pnLen);
|
|
break;
|
|
|
|
|
|
case SQL_C_BINARY:
|
|
varValue.m_pbinary = new CLongBinary;
|
|
varValue.m_dwType = DBVT_BINARY;
|
|
|
|
if (nSQLType == SQL_LONGVARBINARY)
|
|
{
|
|
// pvData can't be NULL, so nLen must be at least 1
|
|
*pnLen = 1;
|
|
}
|
|
else
|
|
{
|
|
// better know the length!
|
|
ASSERT(nPrecision != 0);
|
|
*pnLen = nPrecision;
|
|
}
|
|
|
|
varValue.m_pbinary->m_hData = ::GlobalAlloc(GMEM_MOVEABLE, *pnLen);
|
|
varValue.m_pbinary->m_dwDataLength = *pnLen;
|
|
|
|
pvData = ::GlobalLock(varValue.m_pbinary->m_hData);
|
|
break;
|
|
|
|
default:
|
|
ASSERT(FALSE);
|
|
}
|
|
|
|
return pvData;
|
|
}
|
|
|
|
int PASCAL CRecordset::GetTextLen(short nSQLType, UDWORD nPrecision)
|
|
{
|
|
int nLen;
|
|
|
|
if (nSQLType == SQL_LONGVARCHAR || nSQLType == SQL_LONGVARBINARY)
|
|
{
|
|
// Use a dummy length of 1 (will just get NULL terminator)
|
|
nLen = 1;
|
|
}
|
|
else
|
|
{
|
|
// better know the length
|
|
ASSERT(nPrecision >= 0);
|
|
|
|
nLen = nPrecision + 1;
|
|
|
|
// If converting Numeric or Decimal to text need
|
|
// room for decimal point and sign in string
|
|
if (nSQLType == SQL_NUMERIC || nSQLType == SQL_DECIMAL)
|
|
nLen += 2;
|
|
}
|
|
|
|
return nLen;
|
|
}
|
|
|
|
long PASCAL CRecordset::GetData(CDatabase* pdb, HSTMT hstmt,
|
|
short nFieldIndex, short nFieldType, LPVOID pvData, int nLen,
|
|
short nSQLType)
|
|
{
|
|
UNUSED(nSQLType);
|
|
|
|
long nActualSize;
|
|
RETCODE nRetCode;
|
|
|
|
// Retrieve the column in question
|
|
AFX_ODBC_CALL(::SQLGetData(hstmt, nFieldIndex,
|
|
nFieldType, pvData, nLen, &nActualSize));
|
|
|
|
// Ignore data truncated warnings for long data
|
|
if (nRetCode == SQL_SUCCESS_WITH_INFO)
|
|
{
|
|
#ifdef _DEBUG
|
|
CDBException e(nRetCode);
|
|
|
|
if (afxTraceFlags & traceDatabase)
|
|
{
|
|
CDBException e(nRetCode);
|
|
// Build the error string but don't send nuisance output to TRACE window
|
|
e.BuildErrorString(pdb, hstmt, FALSE);
|
|
|
|
// If not a data truncated warning on long var column,
|
|
// then send debug output
|
|
if ((nSQLType != SQL_LONGVARCHAR &&
|
|
nSQLType != SQL_LONGVARBINARY) ||
|
|
(e.m_strStateNativeOrigin.Find(szDataTruncated) < 0))
|
|
{
|
|
TRACE1("Warning: ODBC Success With Info on field %d.\n",
|
|
nFieldIndex - 1);
|
|
e.TraceErrorMessage(e.m_strError);
|
|
e.TraceErrorMessage(e.m_strStateNativeOrigin);
|
|
}
|
|
}
|
|
#endif // _DEBUG
|
|
}
|
|
else if (nRetCode == SQL_NO_DATA_FOUND)
|
|
{
|
|
TRACE0("Error: GetFieldValue operation failed on field %d.\n");
|
|
TRACE1("\tData already fetched for this field.\n",
|
|
nFieldIndex - 1);
|
|
AfxThrowDBException(nRetCode, pdb, hstmt);
|
|
}
|
|
else if (nRetCode != SQL_SUCCESS)
|
|
{
|
|
TRACE1("Error: GetFieldValue operation failed on field %d.\n",
|
|
nFieldIndex - 1);
|
|
AfxThrowDBException(nRetCode, pdb, hstmt);
|
|
}
|
|
|
|
return nActualSize;
|
|
}
|
|
|
|
void PASCAL CRecordset::GetLongCharDataAndCleanup(CDatabase* pdb,
|
|
HSTMT hstmt, short nFieldIndex, long nActualSize, LPVOID* ppvData,
|
|
int nLen, CString& strValue, short nSQLType)
|
|
{
|
|
RETCODE nRetCode;
|
|
|
|
// Release the buffer now that data has been fetched
|
|
strValue.ReleaseBuffer(nActualSize < nLen ? nActualSize : nLen);
|
|
|
|
// If long data, may need to call SQLGetData again
|
|
if (nLen < nActualSize &&
|
|
(nSQLType == SQL_LONGVARCHAR || nSQLType == SQL_LONGVARBINARY))
|
|
{
|
|
// Reallocate the size (this will copy the data)
|
|
*ppvData = strValue.GetBufferSetLength(nActualSize + 1);
|
|
|
|
// Get pointer, skipping over original data, but not the NULL
|
|
int nOldLen = nLen - 1;
|
|
*ppvData = (BYTE*)*ppvData + nOldLen;
|
|
nLen = nActualSize + 1 - nOldLen;
|
|
|
|
// Retrieve the column in question
|
|
AFX_ODBC_CALL(::SQLGetData(hstmt, nFieldIndex,
|
|
SQL_C_CHAR, *ppvData, nLen, &nActualSize));
|
|
if (nRetCode == SQL_SUCCESS_WITH_INFO)
|
|
{
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
{
|
|
TRACE1("Warning: ODBC Success With Info on field %d.\n",
|
|
nFieldIndex - 1);
|
|
CDBException e(nRetCode);
|
|
e.BuildErrorString(pdb, hstmt);
|
|
}
|
|
#endif // _DEBUG
|
|
}
|
|
else if (nRetCode != SQL_SUCCESS)
|
|
{
|
|
TRACE1("Error: GetFieldValue operation failed on field %d.\n",
|
|
nFieldIndex - 1);
|
|
AfxThrowDBException(nRetCode, pdb, hstmt);
|
|
}
|
|
|
|
// Release the buffer now that data has been fetched
|
|
strValue.ReleaseBuffer(nActualSize + nOldLen);
|
|
}
|
|
}
|
|
|
|
void PASCAL CRecordset::GetLongBinaryDataAndCleanup(CDatabase* pdb, HSTMT hstmt,
|
|
short nFieldIndex, long nActualSize, LPVOID* ppvData, int nLen,
|
|
CDBVariant& varValue, short nSQLType)
|
|
{
|
|
RETCODE nRetCode;
|
|
|
|
::GlobalUnlock(varValue.m_pbinary->m_hData);
|
|
|
|
// If long data, may need to call SQLGetData again
|
|
if (nLen < nActualSize && nSQLType == SQL_LONGVARBINARY)
|
|
{
|
|
// Reallocate a bigger buffer
|
|
HGLOBAL hOldData = varValue.m_pbinary->m_hData;
|
|
varValue.m_pbinary->m_hData = ::GlobalReAlloc(hOldData,
|
|
nActualSize, GMEM_MOVEABLE);
|
|
|
|
// Validate the memory was allocated and can be locked
|
|
if (varValue.m_pbinary->m_hData == NULL)
|
|
{
|
|
// Restore the old handle (not NULL if Realloc failed)
|
|
varValue.m_pbinary->m_hData = hOldData;
|
|
AfxThrowMemoryException();
|
|
}
|
|
varValue.m_pbinary->m_dwDataLength = nActualSize;
|
|
|
|
// Get pointer, skipping over original data
|
|
*ppvData = (BYTE*)::GlobalLock(varValue.m_pbinary->m_hData) + nLen;
|
|
int nOldLen = nLen;
|
|
nLen = nActualSize - nOldLen;
|
|
|
|
// Retrieve the column in question
|
|
AFX_ODBC_CALL(::SQLGetData(hstmt, nFieldIndex,
|
|
SQL_C_BINARY, *ppvData, nLen, &nActualSize));
|
|
if (nRetCode == SQL_SUCCESS_WITH_INFO)
|
|
{
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
{
|
|
TRACE1("Warning: ODBC Success With Info on field %d.\n",
|
|
nFieldIndex - 1);
|
|
CDBException e(nRetCode);
|
|
e.BuildErrorString(pdb, hstmt);
|
|
}
|
|
#endif // _DEBUG
|
|
}
|
|
else if (nRetCode != SQL_SUCCESS)
|
|
{
|
|
TRACE1("Error: GetFieldValue operation failed on field %d.\n",
|
|
nFieldIndex - 1);
|
|
AfxThrowDBException(nRetCode, pdb, hstmt);
|
|
}
|
|
|
|
ASSERT((int)varValue.m_pbinary->m_dwDataLength ==
|
|
nActualSize + nOldLen);
|
|
|
|
// Release the buffer now that data has been fetched
|
|
::GlobalUnlock(varValue.m_pbinary->m_hData);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CRecordset diagnostics
|
|
|
|
#ifdef _DEBUG
|
|
void CRecordset::AssertValid() const
|
|
{
|
|
CObject::AssertValid();
|
|
if (m_pDatabase != NULL)
|
|
m_pDatabase->AssertValid();
|
|
}
|
|
|
|
void CRecordset::Dump(CDumpContext& dc) const
|
|
{
|
|
CObject::Dump(dc);
|
|
|
|
dc << "m_nOpenType = " << m_nOpenType;
|
|
dc << "\nm_strSQL = " << m_strSQL;
|
|
dc << "\nm_hstmt = " << m_hstmt;
|
|
dc << "\nm_bRecordsetDb = " << m_bRecordsetDb;
|
|
|
|
dc << "\nm_lOpen = " << m_lOpen;
|
|
dc << "\nm_bScrollable = " << m_bScrollable;
|
|
dc << "\nm_bUpdatable = " << m_bUpdatable;
|
|
dc << "\nm_bAppendable = " << m_bAppendable;
|
|
|
|
dc << "\nm_nFields = " << m_nFields;
|
|
dc << "\nm_nFieldsBound = " << m_nFieldsBound;
|
|
dc << "\nm_nParams = " << m_nParams;
|
|
|
|
dc << "\nm_bEOF = " << m_bEOF;
|
|
dc << "\nm_bBOF = " << m_bBOF;
|
|
dc << "\nm_bDeleted = " << m_bEOF;
|
|
|
|
dc << "\nm_bLockMode = " << m_nLockMode;
|
|
dc << "\nm_nEditMode = " << m_nEditMode;
|
|
dc << "\nm_strCursorName = " << m_strCursorName;
|
|
dc << "\nm_hstmtUpdate = " << m_hstmtUpdate;
|
|
|
|
dc << "\nDump values for each field in current record.";
|
|
DumpFields(dc);
|
|
|
|
if (dc.GetDepth() > 0)
|
|
{
|
|
if (m_pDatabase == NULL)
|
|
dc << "with no database\n";
|
|
else
|
|
dc << "with database: " << m_pDatabase;
|
|
}
|
|
}
|
|
#endif // _DEBUG
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Helpers
|
|
|
|
void AFXAPI AfxSetCurrentRecord(long* plCurrentRecord, long nRows, RETCODE nRetCode)
|
|
{
|
|
if (*plCurrentRecord != AFX_CURRENT_RECORD_UNDEFINED &&
|
|
nRetCode != SQL_NO_DATA_FOUND)
|
|
*plCurrentRecord += nRows;
|
|
}
|
|
|
|
void AFXAPI AfxSetRecordCount(long* plRecordCount, long lCurrentRecord,
|
|
long nRows, BOOL bEOFSeen, RETCODE nRetCode)
|
|
{
|
|
// If not at the end and haven't yet been to the end, incr count
|
|
if (nRetCode != SQL_NO_DATA_FOUND && !bEOFSeen &&
|
|
lCurrentRecord != AFX_CURRENT_RECORD_UNDEFINED)
|
|
{
|
|
// Normally lCurrentRecord 0-based, but it's already been set
|
|
*plRecordCount =
|
|
__max(*plRecordCount, lCurrentRecord + nRows);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Inline function declarations expanded out-of-line
|
|
|
|
#ifndef _AFX_ENABLE_INLINES
|
|
|
|
static char _szAfxDbInl[] = "afxdb.inl";
|
|
#undef THIS_FILE
|
|
#define THIS_FILE _szAfxDbInl
|
|
#define _AFXDBCORE_INLINE
|
|
#include "afxdb.inl"
|
|
|
|
#endif
|
|
|
|
#ifdef AFX_INIT_SEG
|
|
#pragma code_seg(AFX_INIT_SEG)
|
|
#endif
|
|
|
|
IMPLEMENT_DYNAMIC(CDBException, CException)
|
|
IMPLEMENT_DYNAMIC(CDatabase, CObject)
|
|
IMPLEMENT_DYNAMIC(CRecordset, CObject)
|
|
|
|
#pragma warning(disable: 4074)
|
|
#pragma init_seg(lib)
|
|
|
|
PROCESS_LOCAL(_AFX_DB_STATE, _afxDbState)
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|