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.
3796 lines
88 KiB
3796 lines
88 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 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 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
|
|
|
|
#ifndef _MAC
|
|
static BOOL bWin31 = -1; // unknown right now
|
|
#endif
|
|
|
|
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 if cache memory is provided
|
|
if (pProcPtrs[1] == NULL)
|
|
{
|
|
pProcPtrs[1] = GetProcAddress(hInst, lpszEntry);
|
|
if (pProcPtrs[1] == NULL)
|
|
AfxThrowDBException(AFX_SQL_ERROR_INCORRECT_ODBC, NULL, SQL_NULL_HSTMT);
|
|
|
|
#ifndef _MAC
|
|
if (bWin31 == -1)
|
|
{
|
|
DWORD dwVersion = GetVersion();
|
|
bWin31 = (dwVersion & 0x80000000) && (BYTE)dwVersion < 4;
|
|
}
|
|
// only substitute the thunk on real Win32 -- not Win32s
|
|
if (!bWin31)
|
|
#endif
|
|
pProcPtrs[0] = pProcPtrs[1];
|
|
}
|
|
}
|
|
|
|
#define ODBCLOAD(x) AfxOdbcLoad((FARPROC*)_afxODBC.pfn##x, #x)
|
|
|
|
RETCODE SQL_API AfxThunkSQLAllocConnect(HENV h, HDBC* ph)
|
|
{
|
|
ODBCLOAD(SQLAllocConnect);
|
|
return _afxODBC.pfnSQLAllocConnect[1](h, ph);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLAllocEnv(HENV* ph)
|
|
{
|
|
ODBCLOAD(SQLAllocEnv);
|
|
return _afxODBC.pfnSQLAllocEnv[1](ph);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLAllocStmt(HDBC h, HSTMT* ph)
|
|
{
|
|
ODBCLOAD(SQLAllocStmt);
|
|
return _afxODBC.pfnSQLAllocStmt[1](h, ph);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLBindCol(HSTMT h, UWORD u, SWORD s, PTR p, SDWORD sdw, SDWORD* psdw)
|
|
{
|
|
ODBCLOAD(SQLBindCol);
|
|
return _afxODBC.pfnSQLBindCol[1](h, u, s, p, sdw, psdw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLCancel(HSTMT h)
|
|
{
|
|
ODBCLOAD(SQLCancel);
|
|
return _afxODBC.pfnSQLCancel[1](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[1](h, u, puch, s, ps1, ps2, pudw, ps3, ps4);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLDisconnect(HDBC h)
|
|
{
|
|
ODBCLOAD(SQLDisconnect);
|
|
return _afxODBC.pfnSQLDisconnect[1](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[1](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[1](henv, hdbc, hstmt, puch1, psdw, puch2, s, ps);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLExecDirect(HSTMT h, UCHAR* puch, SDWORD sdw)
|
|
{
|
|
ODBCLOAD(SQLExecDirect);
|
|
return _afxODBC.pfnSQLExecDirect[1](h, puch, sdw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLExecute(HSTMT h)
|
|
{
|
|
ODBCLOAD(SQLExecute);
|
|
return _afxODBC.pfnSQLExecute[1](h);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLExtendedFetch(HSTMT h, UWORD u, SDWORD sdw, UDWORD* pu1, UWORD* pu2)
|
|
{
|
|
ODBCLOAD(SQLExtendedFetch);
|
|
return _afxODBC.pfnSQLExtendedFetch[1](h, u, sdw, pu1, pu2);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLFetch(HSTMT h)
|
|
{
|
|
ODBCLOAD(SQLFetch);
|
|
return _afxODBC.pfnSQLFetch[1](h);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLFreeConnect(HDBC h)
|
|
{
|
|
ODBCLOAD(SQLFreeConnect);
|
|
return _afxODBC.pfnSQLFreeConnect[1](h);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLFreeEnv(HENV h)
|
|
{
|
|
ODBCLOAD(SQLFreeEnv);
|
|
return _afxODBC.pfnSQLFreeEnv[1](h);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLFreeStmt(HSTMT h, UWORD u)
|
|
{
|
|
ODBCLOAD(SQLFreeStmt);
|
|
return _afxODBC.pfnSQLFreeStmt[1](h, u);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLGetCursorName(HSTMT h, UCHAR* puch, SWORD s, SWORD* ps)
|
|
{
|
|
ODBCLOAD(SQLGetCursorName);
|
|
return _afxODBC.pfnSQLGetCursorName[1](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[1](h, u, s, p, sdw, psdw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLGetFunctions(HDBC h, UWORD u, UWORD* pu)
|
|
{
|
|
ODBCLOAD(SQLGetFunctions);
|
|
return _afxODBC.pfnSQLGetFunctions[1](h, u, pu);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLGetInfo(HDBC h, UWORD u, PTR p, SWORD s, SWORD* ps)
|
|
{
|
|
ODBCLOAD(SQLGetInfo);
|
|
return _afxODBC.pfnSQLGetInfo[1](h, u, p, s, ps);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLMoreResults(HSTMT h)
|
|
{
|
|
ODBCLOAD(SQLMoreResults);
|
|
return _afxODBC.pfnSQLMoreResults[1](h);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLNumResultCols(HSTMT h, SWORD* ps)
|
|
{
|
|
ODBCLOAD(SQLNumResultCols);
|
|
return _afxODBC.pfnSQLNumResultCols[1](h, ps);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLParamData(HSTMT h, PTR* pp)
|
|
{
|
|
ODBCLOAD(SQLParamData);
|
|
return _afxODBC.pfnSQLParamData[1](h, pp);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLPrepare(HSTMT h, UCHAR* puch, SDWORD sdw)
|
|
{
|
|
ODBCLOAD(SQLPrepare);
|
|
return _afxODBC.pfnSQLPrepare[1](h, puch, sdw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLPutData(HSTMT h, PTR p, SDWORD sdw)
|
|
{
|
|
ODBCLOAD(SQLPutData);
|
|
return _afxODBC.pfnSQLPutData[1](h, p, sdw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLRowCount(HSTMT h, SDWORD* psdw)
|
|
{
|
|
ODBCLOAD(SQLRowCount);
|
|
return _afxODBC.pfnSQLRowCount[1](h, psdw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLSetConnectOption(HDBC h, UWORD u, UDWORD udw)
|
|
{
|
|
ODBCLOAD(SQLSetConnectOption);
|
|
return _afxODBC.pfnSQLSetConnectOption[1](h, u, udw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLSetPos(HSTMT h, UWORD u1, UWORD u2, UWORD u3)
|
|
{
|
|
ODBCLOAD(SQLSetPos);
|
|
return _afxODBC.pfnSQLSetPos[1](h, u1, u2, u3);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLSetStmtOption(HSTMT h, UWORD u, UDWORD udw)
|
|
{
|
|
ODBCLOAD(SQLSetStmtOption);
|
|
return _afxODBC.pfnSQLSetStmtOption[1](h, u, udw);
|
|
}
|
|
|
|
RETCODE SQL_API AfxThunkSQLTransact(HENV henv, HDBC hdbc, UWORD u)
|
|
{
|
|
ODBCLOAD(SQLTransact);
|
|
return _afxODBC.pfnSQLTransact[1](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[1](h, u, s1, s2, s3, udw, s4, p, sdw, psdw);
|
|
}
|
|
|
|
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, },
|
|
};
|
|
|
|
_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 retail 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_dwWait = 0;
|
|
m_dwMinWaitForDataSource = DEFAULT_MIN_WAIT_FOR_DATASOURCE;
|
|
m_dwMaxWaitForDataSource = DEFAULT_MAX_WAIT_FOR_DATASOURCE;
|
|
|
|
m_bStripTrailingSpaces = FALSE;
|
|
m_bIncRecordCountOnAdd = FALSE;
|
|
|
|
// be a good windows application, and yield to others
|
|
m_bAsync = TRUE;
|
|
}
|
|
|
|
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)
|
|
{
|
|
UCHAR szConnectOutput[MAX_CONNECT_LEN];
|
|
|
|
#ifdef _68K_
|
|
if(nClassObject == 0)
|
|
AfxThrowDBException(AFX_SQL_ERROR_ODBC_LOAD_FAILED, NULL,
|
|
SQL_NULL_HSTMT);
|
|
#endif
|
|
|
|
ASSERT_VALID(this);
|
|
ASSERT(lpszDSN == NULL || AfxIsValidString(lpszDSN));
|
|
ASSERT(lpszConnect == NULL || AfxIsValidString(lpszConnect));
|
|
|
|
// Exclusive access not supported.
|
|
ASSERT(!bExclusive);
|
|
UNUSED(bExclusive); // unused in release builds
|
|
|
|
m_bUpdatable = !bReadonly;
|
|
|
|
TRY
|
|
{
|
|
if (lpszConnect != NULL)
|
|
m_strConnect = lpszConnect;
|
|
|
|
// For VB/Access compatibility, require "ODBC;" (or "odbc;")
|
|
// prefix to the connect string
|
|
if (_tcsnicmp(m_strConnect, szODBC, lstrlen(szODBC)) != 0)
|
|
{
|
|
TRACE0("Error: Missing 'ODBC' prefix on connect string.\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// Strip "ODBC;"
|
|
m_strConnect = m_strConnect.Right(m_strConnect.GetLength()
|
|
- lstrlen(szODBC));
|
|
|
|
if (lpszDSN != NULL && lstrlen(lpszDSN) != 0)
|
|
{
|
|
// Append "DSN=" lpszDSN
|
|
m_strConnect += ";DSN=";
|
|
m_strConnect += lpszDSN;
|
|
}
|
|
|
|
AllocConnect();
|
|
|
|
RETCODE nRetCode;
|
|
// Turn on cursor lib support
|
|
if (bUseCursorLib)
|
|
{
|
|
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;
|
|
}
|
|
|
|
HWND hWndTop;
|
|
CWnd* pWnd = CWnd::GetSafeOwner(NULL, &hWndTop);
|
|
if (pWnd == NULL)
|
|
pWnd = CWnd::GetDesktopWindow();
|
|
ASSERT_VALID(pWnd);
|
|
|
|
SWORD nResult;
|
|
#ifndef _MAC
|
|
USES_CONVERSION;
|
|
AFX_SQL_SYNC(::SQLDriverConnect(m_hdbc, pWnd->m_hWnd,
|
|
(UCHAR*)T2A((LPCTSTR)m_strConnect), SQL_NTS,
|
|
szConnectOutput, _countof(szConnectOutput),
|
|
&nResult, SQL_DRIVER_COMPLETE));
|
|
#else
|
|
AFX_SQL_SYNC(::SQLDriverConnect(m_hdbc, GetWrapperWindow(pWnd->m_hWnd),
|
|
(UCHAR*)(const char*)m_strConnect, SQL_NTS,
|
|
szConnectOutput, _countof(szConnectOutput),
|
|
&nResult, SQL_DRIVER_COMPLETE));
|
|
#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;
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
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;
|
|
else
|
|
m_dwUpdateOptions = 0;
|
|
|
|
// Set required transaction support for CRecordset cursors
|
|
if ((m_nCursorCommitBehavior == SQL_CB_PRESERVE) &&
|
|
(m_nCursorRollbackBehavior == SQL_CB_PRESERVE))
|
|
m_bTransactions = TRUE;
|
|
|
|
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));
|
|
}
|
|
|
|
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
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
Free();
|
|
THROW_LAST();
|
|
}
|
|
END_CATCH_ALL
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CDatabase::ExecuteSQL(LPCTSTR lpszSQL)
|
|
{
|
|
RETCODE nRetCode;
|
|
|
|
ASSERT_VALID(this);
|
|
ASSERT(AfxIsValidString(lpszSQL));
|
|
// Can't close till all pending Async operations have completed
|
|
ASSERT(!InWaitForDataSource());
|
|
|
|
ASSERT(m_hstmt == SQL_NULL_HSTMT);
|
|
AFX_SQL_SYNC(::SQLAllocStmt(m_hdbc, &m_hstmt));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode);
|
|
|
|
TRY
|
|
{
|
|
OnSetOptions(m_hstmt);
|
|
|
|
USES_CONVERSION;
|
|
AFX_SQL_ASYNC(this, ::SQLExecDirect(m_hstmt,
|
|
(UCHAR*)T2A(lpszSQL), SQL_NTS));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode);
|
|
else
|
|
{
|
|
do
|
|
{
|
|
SWORD nResultColumns;
|
|
|
|
AFX_SQL_ASYNC(this, ::SQLNumResultCols(m_hstmt, &nResultColumns));
|
|
if (nResultColumns != 0)
|
|
{
|
|
do
|
|
{
|
|
AFX_SQL_ASYNC(this, ::SQLFetch(m_hstmt));
|
|
} while (Check(nRetCode) && nRetCode != SQL_NO_DATA_FOUND);
|
|
}
|
|
AFX_SQL_ASYNC(this, ::SQLMoreResults(m_hstmt));
|
|
} while (Check(nRetCode) && nRetCode != SQL_NO_DATA_FOUND);
|
|
}
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
::SQLCancel(m_hstmt);
|
|
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmt, SQL_DROP));
|
|
m_hstmt = SQL_NULL_HSTMT;
|
|
THROW_LAST();
|
|
}
|
|
END_CATCH_ALL
|
|
|
|
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmt, SQL_DROP));
|
|
m_hstmt = SQL_NULL_HSTMT;
|
|
}
|
|
|
|
// 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);
|
|
// Can't close till all pending Async operations have completed
|
|
ASSERT(!InWaitForDataSource());
|
|
|
|
// Close any open recordsets
|
|
while (!m_listRecordsets.IsEmpty())
|
|
{
|
|
CRecordset* pSet = (CRecordset*)m_listRecordsets.GetHead();
|
|
pSet->Close(); // will implicitly remove from list
|
|
pSet->m_pDatabase = NULL;
|
|
}
|
|
|
|
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;
|
|
ASSERT(pDbState->m_nAllocatedConnections != 0);
|
|
pDbState->m_nAllocatedConnections--;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Attempt to set AFX_SQL_ASYNC. Ignore failure
|
|
if (m_bAsync)
|
|
{
|
|
AFX_SQL_SYNC(::SQLSetStmtOption(hstmt, SQL_ASYNC_ENABLE, m_bAsync));
|
|
if (!Check(nRetCode))
|
|
m_bAsync = FALSE;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
BOOL PASCAL CDatabase::InWaitForDataSource()
|
|
{
|
|
return AfxGetThreadState()->m_bWaitForDataSource != 0;
|
|
}
|
|
|
|
void CDatabase::OnWaitForDataSource(BOOL bStillExecuting)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hdbc != SQL_NULL_HDBC);
|
|
|
|
_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
|
|
CWinApp* pApp = AfxGetApp();
|
|
|
|
if (!bStillExecuting)
|
|
{
|
|
// If never actually waited. . .
|
|
if (m_dwWait == 0)
|
|
return;
|
|
|
|
if (m_dwWait == m_dwMaxWaitForDataSource)
|
|
pApp->DoWaitCursor(-1); // EndWaitCursor
|
|
m_dwWait = 0;
|
|
pThreadState->m_bWaitForDataSource--;
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
TRACE0("DONE WAITING for datasource.\n");
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if (m_dwWait == 0)
|
|
{
|
|
pThreadState->m_bWaitForDataSource++;
|
|
// 1st call, wait for min amount of time
|
|
m_dwWait = m_dwMinWaitForDataSource;
|
|
#ifdef _DEBUG
|
|
if (afxTraceFlags & traceDatabase)
|
|
TRACE0("WAITING for datasource.\n");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
if (m_dwWait == m_dwMinWaitForDataSource)
|
|
{
|
|
// 2nd call, wait max time, put up wait cursor
|
|
m_dwWait = m_dwMaxWaitForDataSource;
|
|
pApp->DoWaitCursor(1); // BeginWaitCursor
|
|
}
|
|
}
|
|
|
|
CWinThread* pThread = AfxGetThread();
|
|
DWORD clockFirst = GetTickCount();
|
|
while (GetTickCount() - clockFirst < m_dwWait)
|
|
{
|
|
MSG msg;
|
|
if (::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE))
|
|
{
|
|
TRY
|
|
{
|
|
pThread->PumpMessage();
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
TRACE0("Error: exception in OnWaitForDataSource - continuing.\n");
|
|
DELETE_EXCEPTION(e);
|
|
}
|
|
END_CATCH_ALL
|
|
}
|
|
else
|
|
pThread->OnIdle(-1);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// 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()
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
if (m_hdbc != SQL_NULL_HDBC)
|
|
return;
|
|
|
|
_AFX_DB_STATE* pDbState = _afxDbState;
|
|
|
|
RETCODE nRetCode;
|
|
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))
|
|
AfxThrowMemoryException(); // fatal
|
|
}
|
|
|
|
ASSERT(pDbState->m_henvAllConnections != SQL_NULL_HENV);
|
|
AFX_SQL_SYNC(::SQLAllocConnect(pDbState->m_henvAllConnections, &m_hdbc));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode); // fatal
|
|
|
|
pDbState->m_nAllocatedConnections++; // allocated at least
|
|
ASSERT(m_hdbc != SQL_NULL_HDBC);
|
|
|
|
#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
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// 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;
|
|
dc << "\nm_bAsync = " << m_bAsync;
|
|
|
|
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
|
|
|
|
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_bBOF = TRUE;
|
|
m_bEOF = TRUE;
|
|
m_bEOFSeen = FALSE;
|
|
m_bDeleted = FALSE;
|
|
m_bAppendable = FALSE;
|
|
m_bExtendedFetch = FALSE;
|
|
m_bUpdatable = FALSE;
|
|
m_bScrollable = FALSE;
|
|
m_bRecordsetDb = FALSE;
|
|
m_bRebindParams = FALSE;
|
|
m_bLongBinaryColumns = FALSE;
|
|
m_nLockMode = optimistic;
|
|
|
|
m_dwWait = 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_pmemfile = NULL;
|
|
m_par = NULL;
|
|
m_pbFieldFlags = NULL;
|
|
m_pbParamFlags = NULL;
|
|
m_plFieldLength = 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
|
|
m_pDatabase->m_listRecordsets.AddHead(this);
|
|
}
|
|
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)
|
|
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 == 0 || (dwOptions & appendOnly) || (dwOptions & readOnly) ||
|
|
(dwOptions & (appendOnly | optimizeBulkAdd)));
|
|
|
|
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't update forward only cursor
|
|
ASSERT(!((m_nOpenType == forwardOnly) && m_bUpdatable));
|
|
|
|
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 && !(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
|
|
m_pDatabase->m_listRecordsets.AddHead(this);
|
|
}
|
|
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
|
|
}
|
|
|
|
// Musn't open new recordset till any pending async ops have completed
|
|
ASSERT(!m_pDatabase->InWaitForDataSource());
|
|
|
|
TRY
|
|
{
|
|
// Allocate flag and length arrays if not already
|
|
if ((m_nFields != 0 && m_pbFieldFlags == NULL) ||
|
|
(m_nParams != 0 && m_pbParamFlags == NULL))
|
|
AllocFlags();
|
|
|
|
OnSetOptions(m_hstmt);
|
|
|
|
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_dwDriverPositionedStatements & SQL_PS_SELECT_FOR_UPDATE))
|
|
m_strSQL += " FOR UPDATE OF ";
|
|
|
|
// Replace brackets with SQL_IDENTIFIER_QUOTE_CHAR
|
|
m_pDatabase->ReplaceBrackets(m_strSQL.GetBuffer(0));
|
|
m_strSQL.ReleaseBuffer();
|
|
|
|
// Archive info for use in Requery
|
|
m_dwOptions = dwOptions;
|
|
m_strRequerySQL = lpszSQL;
|
|
m_strRequeryFilter = m_strFilter;
|
|
m_strRequerySort = m_strSort;
|
|
|
|
BOOL bConcurrency = FALSE;
|
|
|
|
while (!bConcurrency)
|
|
{
|
|
USES_CONVERSION;
|
|
AFX_SQL_ASYNC(this, ::SQLPrepare(m_hstmt,
|
|
(UCHAR*)T2A((LPCTSTR)m_strSQL), 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->\n");
|
|
THROW(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// now attempt to execute the SQL Query
|
|
AFX_SQL_ASYNC(this, ::SQLExecute(m_hstmt));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode);
|
|
m_lOpen = AFX_RECORDSET_STATUS_OPEN;
|
|
|
|
// Give derived classes a call before binding
|
|
PreBindFields();
|
|
|
|
MoveFirst();
|
|
}
|
|
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);
|
|
// Can't close till all pending Async operations have completed
|
|
ASSERT(!m_pDatabase->InWaitForDataSource());
|
|
|
|
// This will force a requery for cursor name if reopened.
|
|
m_strCursorName.Empty();
|
|
|
|
m_nEditMode = noMode;
|
|
|
|
delete m_par;
|
|
m_par = NULL;
|
|
|
|
delete m_pmemfile;
|
|
m_pmemfile = 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_plFieldLength;
|
|
m_plFieldLength = NULL;
|
|
|
|
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
|
|
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
|
|
|
|
m_lOpen = AFX_RECORDSET_STATUS_CLOSED;
|
|
m_bBOF = TRUE;
|
|
m_bEOF = TRUE;
|
|
m_bDeleted = FALSE;
|
|
m_bAppendable = FALSE;
|
|
m_bExtendedFetch = FALSE;
|
|
m_bUpdatable = FALSE;
|
|
m_bScrollable = FALSE;
|
|
m_bRebindParams = FALSE;
|
|
m_bLongBinaryColumns = FALSE;
|
|
m_nLockMode = optimistic;
|
|
|
|
m_dwWait = 0;
|
|
m_nFieldsBound = 0;
|
|
}
|
|
|
|
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_SQL_SYNC(::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);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (nCols != 0)
|
|
((CRecordset*)this)->m_lOpen = AFX_RECORDSET_STATUS_OPEN;
|
|
else
|
|
((CRecordset*)this)->m_lOpen = AFX_RECORDSET_STATUS_CLOSED;
|
|
}
|
|
|
|
return m_lOpen == AFX_RECORDSET_STATUS_OPEN;
|
|
}
|
|
|
|
BOOL CRecordset::IsFieldDirty(void* pv)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
// If not in update op fields can't be dirty
|
|
// must compare saved and current values
|
|
if (m_nEditMode == noMode)
|
|
return FALSE;
|
|
|
|
ASSERT(m_pmemfile != NULL);
|
|
|
|
// Must compare values to find dirty fields
|
|
if (m_nEditMode == edit)
|
|
MarkForUpdate();
|
|
else
|
|
MarkForAddNew();
|
|
|
|
CFieldExchange fx(CFieldExchange::IsFieldDirty, this);
|
|
fx.m_pvField = pv;
|
|
|
|
DoFieldExchange(&fx);
|
|
// pv not found: pv must be 'value' argument to an RFX call
|
|
ASSERT(fx.m_bFieldFound);
|
|
|
|
return fx.m_bField;
|
|
}
|
|
|
|
BOOL CRecordset::IsFieldNull(void* pv)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
CFieldExchange fx(CFieldExchange::IsFieldNull, this);
|
|
fx.m_pvField = pv;
|
|
|
|
DoFieldExchange(&fx);
|
|
// pv not found: pv must be 'value' argument to an RFX call
|
|
ASSERT(fx.m_bFieldFound);
|
|
|
|
return fx.m_bField;
|
|
}
|
|
|
|
BOOL CRecordset::IsFieldNullable(void* pv)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
CFieldExchange fx(CFieldExchange::IsFieldNullable, this);
|
|
fx.m_pvField = pv;
|
|
|
|
DoFieldExchange(&fx);
|
|
// pv not found: pv must be 'value' argument to an RFX call
|
|
ASSERT(fx.m_bFieldFound);
|
|
|
|
return fx.m_bField;
|
|
}
|
|
|
|
void CRecordset::Move(long lRows)
|
|
{
|
|
RETCODE nRetCode;
|
|
ASSERT_VALID(m_pDatabase);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
// First call - fields haven't been bound
|
|
if (m_nFieldsBound == 0)
|
|
{
|
|
InitRecord();
|
|
|
|
// First move completed
|
|
return;
|
|
}
|
|
|
|
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
|
|
ReleaseCopyBuffer();
|
|
|
|
if (lRows == 0)
|
|
// Do nothing
|
|
return;
|
|
|
|
BOOL bForward = lRows > 0;
|
|
if (!m_bScrollable && !bForward)
|
|
{
|
|
TRACE0("Error: can not move backward with forward-only recordset.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_RECORDSET_FORWARD_ONLY);
|
|
}
|
|
|
|
WORD wFetchType;
|
|
if (lRows == AFX_MOVE_FIRST)
|
|
{
|
|
wFetchType = SQL_FETCH_FIRST;
|
|
lRows = 1;
|
|
}
|
|
else
|
|
{
|
|
if (lRows == AFX_MOVE_LAST)
|
|
{
|
|
wFetchType = SQL_FETCH_LAST;
|
|
lRows = 1;
|
|
}
|
|
else
|
|
{
|
|
if (bForward)
|
|
{
|
|
// if already at EOF, throw an exception
|
|
if (m_bEOF)
|
|
{
|
|
TRACE0("Error: attempted to move past EOF.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_NO_DATA_FOUND);
|
|
}
|
|
|
|
wFetchType = SQL_FETCH_NEXT;
|
|
}
|
|
else
|
|
{
|
|
// if already at BOF, throw an exception
|
|
if (m_bBOF)
|
|
{
|
|
TRACE0("Error: attempted to move before BOF.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_NO_DATA_FOUND);
|
|
}
|
|
|
|
lRows = -lRows;
|
|
wFetchType = SQL_FETCH_PREV;
|
|
}
|
|
}
|
|
}
|
|
|
|
UDWORD dwRowsMoved = 1;
|
|
UWORD wFetchNext = wFetchType;
|
|
m_wRowStatus = SQL_ROW_SUCCESS;
|
|
nRetCode = SQL_SUCCESS;
|
|
// Skip deleted rows
|
|
while (Check(nRetCode) && nRetCode != SQL_NO_DATA_FOUND && lRows != 0)
|
|
{
|
|
m_wRowStatus = SQL_ROW_SUCCESS;
|
|
if (!m_bScrollable)
|
|
AFX_SQL_ASYNC(this, ::SQLFetch(m_hstmt));
|
|
else
|
|
AFX_SQL_ASYNC(this, ::SQLExtendedFetch(m_hstmt, wFetchNext, 0L,
|
|
&dwRowsMoved, &m_wRowStatus));
|
|
|
|
if (wFetchType == SQL_FETCH_FIRST || wFetchType == SQL_FETCH_LAST)
|
|
// If doing MoveFirst/Last and first/last record is deleted, must do MoveNext/Prev
|
|
wFetchNext = (UWORD)(bForward ? SQL_FETCH_PREV : SQL_FETCH_NEXT);
|
|
|
|
m_bDeleted = (m_wRowStatus == SQL_ROW_DELETED);
|
|
if (!m_bDeleted)
|
|
{
|
|
lRows--;
|
|
if (nRetCode != SQL_NO_DATA_FOUND)
|
|
{
|
|
if (wFetchType == SQL_FETCH_FIRST)
|
|
m_lCurrentRecord = 0;
|
|
else if (wFetchType == SQL_FETCH_LAST)
|
|
{
|
|
if (m_bEOFSeen)
|
|
m_lCurrentRecord = m_lRecordCount-1;
|
|
else
|
|
m_lRecordCount = m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
|
|
}
|
|
else if (m_lCurrentRecord != AFX_CURRENT_RECORD_UNDEFINED)
|
|
{
|
|
if (bForward)
|
|
m_lCurrentRecord++;
|
|
else
|
|
// If past end, current record already decremented
|
|
if (!m_bEOF)
|
|
m_lCurrentRecord--;
|
|
}
|
|
|
|
// Must not be at EOF/BOF anymore
|
|
m_bEOF = m_bBOF = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 Open or Requery.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_DATA_TRUNCATED);
|
|
}
|
|
}
|
|
else if (e.m_strStateNativeOrigin.Find(szRowFetch) >= 0)
|
|
{
|
|
NO_CPP_EXCEPTION(e.Empty());
|
|
TRACE0("Error: fetching row from server during Open or Requery.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_ROW_FETCH);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: Move operation failed.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
}
|
|
|
|
if (nRetCode != SQL_NO_DATA_FOUND)
|
|
{
|
|
ASSERT(m_wRowStatus != SQL_ROW_NOROW && dwRowsMoved == 1);
|
|
if (m_lCurrentRecord+1 > m_lRecordCount)
|
|
m_lRecordCount = m_lCurrentRecord+1;
|
|
Fixups();
|
|
m_bBOF = FALSE;
|
|
m_bEOF = FALSE;
|
|
return;
|
|
}
|
|
|
|
// Only deleted records are left in set
|
|
if (m_bDeleted)
|
|
{
|
|
m_bEOF = m_bBOF = m_bEOFSeen = TRUE;
|
|
return;
|
|
}
|
|
|
|
// SQL_NO_DATA_FOUND
|
|
if (bForward)
|
|
{
|
|
// hit end of set
|
|
m_bEOF = TRUE;
|
|
|
|
// If current record is known
|
|
if (m_lCurrentRecord != AFX_CURRENT_RECORD_UNDEFINED)
|
|
{
|
|
m_bEOFSeen = TRUE;
|
|
m_lRecordCount = m_lCurrentRecord+1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_bBOF = TRUE;
|
|
m_lCurrentRecord = AFX_CURRENT_RECORD_BOF;
|
|
}
|
|
}
|
|
|
|
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_nFieldsBound == 0)
|
|
{
|
|
m_nFieldsBound = BindFieldsToColumns();
|
|
ASSERT(m_nFields == m_nFieldsBound);
|
|
}
|
|
|
|
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_bEOF || m_bBOF || m_bDeleted)
|
|
{
|
|
TRACE0("Error: Edit attempt failed - not on a record.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_NO_CURRENT_RECORD);
|
|
}
|
|
|
|
if (m_nFieldsBound == 0)
|
|
{
|
|
m_nFieldsBound = BindFieldsToColumns();
|
|
ASSERT(m_nFieldsBound == m_nFields);
|
|
}
|
|
|
|
if ((m_nOpenType == dynaset || m_nOpenType == dynamic) &&
|
|
m_nLockMode == pessimistic)
|
|
{
|
|
RETCODE nRetCode;
|
|
AFX_SQL_ASYNC(this, ::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_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_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_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::SetFieldDirty(void* pv, BOOL bDirty)
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
CFieldExchange fx(CFieldExchange::SetFieldDirty, this);
|
|
fx.m_pvField = pv;
|
|
fx.m_bField = bDirty;
|
|
DoFieldExchange(&fx);
|
|
// pv not found: pv must be 'value' argument to an RFX call
|
|
ASSERT(fx.m_bFieldFound);
|
|
}
|
|
|
|
void CRecordset::SetFieldNull(void* pv, BOOL bNull)
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
CFieldExchange fx(CFieldExchange::SetFieldNull, this);
|
|
fx.m_pvField = pv;
|
|
fx.m_bField = bNull;
|
|
DoFieldExchange(&fx);
|
|
// pv not found: pv must be 'value' argument to an RFX call
|
|
ASSERT(fx.m_bFieldFound);
|
|
}
|
|
|
|
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 till all pending Async operations have completed
|
|
ASSERT(!m_pDatabase->InWaitForDataSource());
|
|
|
|
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_SQL_ASYNC(this, ::SQLExecute(m_hstmt));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: Requery attempt failed.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
|
|
m_lOpen = AFX_RECORDSET_STATUS_OPEN;
|
|
m_nFieldsBound = 0;
|
|
InitRecord();
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
void CRecordset::OnSetOptions(HSTMT hstmt)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(hstmt != SQL_NULL_HSTMT);
|
|
|
|
// Inherit options settings from CDatabase
|
|
m_pDatabase->OnSetOptions(hstmt);
|
|
|
|
// ODBC "cursor" is forwardOnly by default
|
|
if (m_nOpenType == forwardOnly)
|
|
{
|
|
ASSERT(!m_bUpdatable);
|
|
return;
|
|
}
|
|
|
|
RETCODE nRetCode;
|
|
UWORD wScrollable;
|
|
// If SQLExtendedFetch not supported open forwardOnly (uses 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");
|
|
}
|
|
#endif
|
|
m_bUpdatable = FALSE;
|
|
return;
|
|
}
|
|
|
|
char szResult[30];
|
|
SWORD nResult;
|
|
// Snapshot, dynaset and dynamic cursors 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);
|
|
|
|
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;
|
|
}
|
|
|
|
// Reset the database update options
|
|
m_pDatabase->m_dwUpdateOptions &=
|
|
~(AFX_SQL_SETPOSUPDATES | AFX_SQL_POSITIONEDSQL);
|
|
|
|
// Check for SQLSetPos support
|
|
UDWORD dwDriverPosOperations;
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_POS_OPERATIONS,
|
|
&dwDriverPosOperations, sizeof(dwDriverPosOperations), &nResult));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: ODBC failure checking for driver capabilities.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
|
|
if ((dwDriverPosOperations & SQL_POS_UPDATE) &&
|
|
(dwDriverPosOperations & SQL_POS_DELETE) &&
|
|
(dwDriverPosOperations & SQL_POS_ADD))
|
|
m_pDatabase->m_dwUpdateOptions |= AFX_SQL_SETPOSUPDATES;
|
|
|
|
// Check for positioned update SQL support
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_POSITIONED_STATEMENTS,
|
|
&m_dwDriverPositionedStatements, sizeof(m_dwDriverPositionedStatements),
|
|
&nResult));
|
|
if (nRetCode != SQL_SUCCESS)
|
|
{
|
|
CDBException e(nRetCode);
|
|
e.BuildErrorString(m_pDatabase, m_hstmt);
|
|
if (e.m_strStateNativeOrigin.Find(szInfoRange) < 0)
|
|
{
|
|
NO_CPP_EXCEPTION(e.Empty());
|
|
TRACE0("Error: determining if POSITIONED UPDATES supported.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
}
|
|
else if ((m_dwDriverPositionedStatements & SQL_PS_POSITIONED_DELETE) &&
|
|
(m_dwDriverPositionedStatements & SQL_PS_POSITIONED_UPDATE))
|
|
m_pDatabase->m_dwUpdateOptions |= AFX_SQL_POSITIONEDSQL;
|
|
|
|
if (!ValidateSelectForUpdateSupport())
|
|
m_dwDriverPositionedStatements &= ~SQL_PS_SELECT_FOR_UPDATE;
|
|
|
|
// 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;
|
|
|
|
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 on SQLPrepare).
|
|
AFX_SQL_SYNC(::SQLSetStmtOption(hstmt, SQL_CONCURRENCY, m_dwConcurrency));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: ODBC failure setting recordset concurrency.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
}
|
|
|
|
void CRecordset::OnWaitForDataSource(BOOL bStillExecuting)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
m_pDatabase->OnWaitForDataSource(bStillExecuting);
|
|
}
|
|
|
|
|
|
// 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
|
|
|
|
// Is there a join, stored proc call, GROUP BY, UNION or missing FROM?
|
|
BOOL CRecordset::IsSQLUpdatable(LPCTSTR lpszSQL)
|
|
{
|
|
// Parse for query procedure call keyword
|
|
if (_tcsnicmp(lpszSQL, szCall, lstrlen(szCall)-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()
|
|
{
|
|
RETCODE nRetCode;
|
|
|
|
// fields to bind
|
|
if (m_nFields != 0)
|
|
{
|
|
m_nFieldsBound = BindFieldsToColumns();
|
|
// m_nFields doesn't reflect number of
|
|
// RFX_ output column calls in DoFieldExchange
|
|
ASSERT(m_nFields == m_nFieldsBound);
|
|
|
|
// Reset field flags - mark all clean, all non-null
|
|
memset(m_pbFieldFlags, 0, m_nFields);
|
|
}
|
|
else
|
|
// No fields to bind, don't attempt to bind again
|
|
m_nFieldsBound = (UINT)-1;
|
|
|
|
ReleaseCopyBuffer();
|
|
m_bEOFSeen = m_bBOF = m_bEOF = TRUE;
|
|
m_bDeleted = FALSE;
|
|
|
|
// prime the pump, retrieve first record
|
|
if (!m_bScrollable)
|
|
AFX_SQL_ASYNC(this, ::SQLFetch(m_hstmt));
|
|
else
|
|
{
|
|
DWORD dwRowsMoved;
|
|
|
|
AFX_SQL_ASYNC(this, ::SQLExtendedFetch(m_hstmt, SQL_FETCH_NEXT, 0L,
|
|
&dwRowsMoved, &m_wRowStatus));
|
|
}
|
|
|
|
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 Open or Requery.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_DATA_TRUNCATED);
|
|
}
|
|
}
|
|
else if (e.m_strStateNativeOrigin.Find(szRowFetch) >= 0)
|
|
{
|
|
NO_CPP_EXCEPTION(e.Empty());
|
|
TRACE0("Error: fetching row from server during Open or Requery.\n");
|
|
ThrowDBException(AFX_SQL_ERROR_ROW_FETCH);
|
|
}
|
|
}
|
|
else if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode);
|
|
|
|
// if data found, bound fields now filled with first record
|
|
if (nRetCode != SQL_NO_DATA_FOUND)
|
|
{
|
|
Fixups();
|
|
m_bEOFSeen = m_bBOF = m_bEOF = FALSE;
|
|
m_lCurrentRecord = 0;
|
|
m_lRecordCount = 1;
|
|
}
|
|
else
|
|
{
|
|
// If recordset empty, set all values to NULL
|
|
SetFieldNull(NULL);
|
|
|
|
// If recordset empty, it doesn't make sense to check
|
|
// record count and current record, but we'll set them anyway
|
|
m_lCurrentRecord = AFX_CURRENT_RECORD_UNDEFINED;
|
|
m_lRecordCount = 0;
|
|
}
|
|
}
|
|
|
|
// "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
|
|
if (_tcsnicmp(m_strSQL, szCall, lstrlen(szCall)-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;
|
|
}
|
|
|
|
// Sql Server driver incorrectly reports SELECT_FOR_UPDATE support
|
|
BOOL CRecordset::ValidateSelectForUpdateSupport()
|
|
{
|
|
RETCODE nRetCode;
|
|
SWORD nResult;
|
|
BOOL bReturn = TRUE;
|
|
char lpszDriverInfo[32];
|
|
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_DRIVER_NAME,
|
|
lpszDriverInfo, _countof(lpszDriverInfo), &nResult));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: ODBC failure checking for driver capabilities.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
|
|
if (lstrcmpA(lpszDriverInfo, "SQLSRV32.DLL") == 0)
|
|
{
|
|
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_DRIVER_VER,
|
|
lpszDriverInfo, _countof(lpszDriverInfo), &nResult));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: ODBC failure checking for driver capabilities.\n");
|
|
ThrowDBException(nRetCode);
|
|
}
|
|
|
|
// Version of form XX.XX.XXXX, only need XX.XX
|
|
lpszDriverInfo[5] = '\0';
|
|
|
|
// The problem exists in version 2.5 and before
|
|
if (lstrcmpA(lpszDriverInfo, "02.50") <= 0)
|
|
bReturn = FALSE;
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
// 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_SQL_SYNC(::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 && m_wRowStatus != 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 = "DELETE FROM ";
|
|
m_strUpdateSQL += m_strTableName;
|
|
}
|
|
break;
|
|
|
|
case addnew:
|
|
// INSERT INTO <tablename> (<colname1>[,<colname2>]) VALUES (?[,?])
|
|
{
|
|
m_strUpdateSQL = "INSERT INTO ";
|
|
m_strUpdateSQL += m_strTableName;
|
|
|
|
m_strUpdateSQL += " (";
|
|
|
|
// 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 += " 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 = "UPDATE ";
|
|
m_strUpdateSQL += m_strTableName;
|
|
|
|
m_strUpdateSQL += " 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 += " 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_SQL_ASYNC(this, ::SQLPrepare(m_hstmtUpdate,
|
|
(UCHAR*)T2A((LPCTSTR)m_strUpdateSQL), SQL_NTS));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode, m_hstmtUpdate);
|
|
}
|
|
}
|
|
|
|
void CRecordset::ExecuteUpdateSQL()
|
|
{
|
|
RETCODE nRetCode;
|
|
|
|
if(!(m_dwOptions & optimizeBulkAdd))
|
|
{
|
|
USES_CONVERSION;
|
|
AFX_SQL_ASYNC(this, ::SQLExecDirect(m_hstmtUpdate,
|
|
(UCHAR*)T2A((LPCTSTR)m_strUpdateSQL), SQL_NTS));
|
|
if (!Check(nRetCode))
|
|
ThrowDBException(nRetCode, m_hstmtUpdate);
|
|
}
|
|
else
|
|
{
|
|
AFX_SQL_ASYNC(this, ::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_SQL_ASYNC(this, ::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_SQL_ASYNC(this, ::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_SQL_ASYNC(this, ::SQLParamData(hstmt, &pv));
|
|
if (!Check(nRetCode))
|
|
{
|
|
TRACE0("Error: failure handling long binary value during update.\n");
|
|
ThrowDBException(nRetCode, hstmt);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CRecordset RFX implementations
|
|
|
|
void CRecordset::AllocFlags()
|
|
{
|
|
TRY
|
|
{
|
|
if (m_nFields != 0)
|
|
{
|
|
m_pbFieldFlags = new BYTE[m_nFields];
|
|
memset(m_pbFieldFlags, 0, m_nFields);
|
|
|
|
m_plFieldLength = new LONG[m_nFields];
|
|
memset(m_plFieldLength, 0, m_nFields*sizeof(LONG));
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
BYTE CRecordset::GetFieldFlags(UINT nField, UINT nFieldType)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(nFieldType == CFieldExchange::outputColumn || nFieldType == CFieldExchange::param);
|
|
|
|
if (nFieldType == CFieldExchange::outputColumn)
|
|
{
|
|
ASSERT(nField != 0 && nField <= m_nFields);
|
|
if (m_pbFieldFlags == NULL)
|
|
AllocFlags();
|
|
|
|
return m_pbFieldFlags[nField-1];
|
|
}
|
|
else
|
|
{
|
|
ASSERT(nField != 0 && nField <= m_nParams);
|
|
if (m_pbParamFlags == NULL)
|
|
AllocFlags();
|
|
|
|
return m_pbParamFlags[nField-1];
|
|
}
|
|
}
|
|
|
|
void CRecordset::SetFieldFlags(UINT nField, BYTE bFlags, UINT nFieldType)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(nFieldType == CFieldExchange::outputColumn || nFieldType == CFieldExchange::param);
|
|
|
|
if (nFieldType == CFieldExchange::outputColumn)
|
|
{
|
|
ASSERT(nField != 0 && nField <= m_nFields);
|
|
if (m_pbFieldFlags == NULL)
|
|
AllocFlags();
|
|
|
|
m_pbFieldFlags[nField-1] |= bFlags;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(nField != 0 && nField <= m_nParams);
|
|
if (m_pbParamFlags == NULL)
|
|
AllocFlags();
|
|
|
|
m_pbParamFlags[nField-1] |= bFlags;
|
|
}
|
|
}
|
|
|
|
void CRecordset::ClearFieldFlags(UINT nField, BYTE bFlags, UINT nFieldType)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(nFieldType == CFieldExchange::outputColumn || nFieldType == CFieldExchange::param);
|
|
|
|
if (nFieldType == CFieldExchange::outputColumn)
|
|
{
|
|
ASSERT(nField != 0 && nField <= m_nFields);
|
|
if (m_pbFieldFlags == NULL)
|
|
AllocFlags();
|
|
|
|
m_pbFieldFlags[nField-1] &= ~bFlags;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(nField != 0 && nField <= m_nParams);
|
|
if (m_pbParamFlags == NULL)
|
|
AllocFlags();
|
|
|
|
m_pbParamFlags[nField-1] &= ~bFlags;
|
|
}
|
|
}
|
|
|
|
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;
|
|
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;
|
|
|
|
DoFieldExchange(&fx);
|
|
|
|
return fx.m_nFields;
|
|
}
|
|
|
|
LONG* CRecordset::GetFieldLength(CFieldExchange* pFX)
|
|
{
|
|
ASSERT_VALID(this);
|
|
|
|
if (pFX->m_nFieldType == CFieldExchange::outputColumn)
|
|
{
|
|
ASSERT(pFX->m_nFields != 0 && pFX->m_nFields <= m_nFields);
|
|
if (m_pbFieldFlags == NULL)
|
|
AllocFlags();
|
|
|
|
return &m_plFieldLength[pFX->m_nFields-1];
|
|
}
|
|
else
|
|
{
|
|
|
|
ASSERT(pFX->m_nParams != 0 && pFX->m_nParams <= m_nParams);
|
|
if (m_pbParamFlags == NULL)
|
|
AllocFlags();
|
|
|
|
return &m_plParamLength[pFX->m_nParams-1];
|
|
}
|
|
}
|
|
|
|
// 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_hstmt != SQL_NULL_HSTMT);
|
|
|
|
ASSERT(m_nFieldsBound != 0);
|
|
CFieldExchange fx(CFieldExchange::StoreField, this);
|
|
// could be left around if no call to Update after AddNew/Edit
|
|
delete m_par;
|
|
m_par = NULL;
|
|
delete m_pmemfile;
|
|
m_pmemfile = NULL;
|
|
|
|
m_pmemfile = new CMemFile();
|
|
TRY
|
|
{
|
|
fx.m_par = m_par = new CArchive(m_pmemfile, CArchive::store);
|
|
DoFieldExchange(&fx);
|
|
m_par->Close();
|
|
}
|
|
CATCH_ALL(e)
|
|
{
|
|
delete m_par;
|
|
m_par = NULL;
|
|
delete m_pmemfile;
|
|
m_pmemfile = NULL;
|
|
THROW_LAST();
|
|
}
|
|
END_CATCH_ALL
|
|
}
|
|
|
|
// Restore fields of copy buffer from archived memory file
|
|
void CRecordset::LoadFields()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
ASSERT(m_nFieldsBound != 0);
|
|
ASSERT(m_pmemfile != NULL && m_par != NULL);
|
|
|
|
// free 'store' archive
|
|
delete m_par;
|
|
m_par = NULL;
|
|
|
|
// reset back to beginning
|
|
m_pmemfile->SeekToBegin();
|
|
|
|
// allocate 'load' archive
|
|
// (do here instead of StoreFields to re-use alloc'd memory)
|
|
m_par = new CArchive(m_pmemfile, CArchive::load);
|
|
|
|
// Clear flags
|
|
memset(m_pbFieldFlags, 0, m_nFields);
|
|
|
|
CFieldExchange fx(CFieldExchange::LoadField, this);
|
|
fx.m_par = m_par;
|
|
DoFieldExchange(&fx);
|
|
|
|
// free archive and cache memory
|
|
delete m_par;
|
|
m_par = NULL;
|
|
delete m_pmemfile;
|
|
m_pmemfile = NULL;
|
|
}
|
|
|
|
void CRecordset::MarkForUpdate()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
// Must have already stored field values in memfile
|
|
ASSERT(m_pmemfile != NULL);
|
|
|
|
ASSERT(m_par != NULL);
|
|
delete m_par;
|
|
m_par = NULL;
|
|
|
|
// reset back to beginning
|
|
m_pmemfile->SeekToBegin();
|
|
m_par = new CArchive(m_pmemfile, CArchive::load);
|
|
|
|
CFieldExchange fx(CFieldExchange::MarkForUpdate, this);
|
|
fx.m_par = m_par;
|
|
DoFieldExchange(&fx);
|
|
}
|
|
|
|
void CRecordset::MarkForAddNew()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
CFieldExchange fx(CFieldExchange::MarkForAddNew, this);
|
|
DoFieldExchange(&fx);
|
|
}
|
|
|
|
BOOL CRecordset::GetFieldInfo(void* pv, CFieldInfo* pfi)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
// Use frame variable if user doesn't supply an fi
|
|
CFieldInfo fi;
|
|
if (pfi == NULL)
|
|
pfi = &fi;
|
|
|
|
ASSERT(AfxIsValidAddress(pfi, sizeof(CFieldInfo)));
|
|
CFieldExchange fx(CFieldExchange::GetFieldInfoValue, this);
|
|
pfi->pv = pv;
|
|
fx.m_pfi = pfi;
|
|
|
|
DoFieldExchange(&fx);
|
|
|
|
return fx.m_bFieldFound;
|
|
}
|
|
|
|
BOOL CRecordset::GetFieldInfo(UINT nField, CFieldInfo* pfi)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
// Use frame variable if user doesn't supply an fi
|
|
CFieldInfo fi;
|
|
if (pfi == NULL)
|
|
pfi = &fi;
|
|
|
|
ASSERT(AfxIsValidAddress(pfi, sizeof(CFieldInfo)));
|
|
if (nField >= m_nFields)
|
|
return UnboundFieldInfo(nField, pfi);
|
|
|
|
CFieldExchange fx(CFieldExchange::GetFieldInfoOrdinal, this);
|
|
pfi->nField = nField;
|
|
fx.m_pfi = pfi;
|
|
|
|
DoFieldExchange(&fx);
|
|
|
|
return fx.m_bFieldFound;
|
|
}
|
|
|
|
#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);
|
|
// Can't close till all pending Async operations have completed
|
|
ASSERT(!m_pDatabase->InWaitForDataSource());
|
|
|
|
// 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++;
|
|
}
|
|
// Fall through
|
|
|
|
case edit:
|
|
// Update, AddNew
|
|
ReleaseCopyBuffer();
|
|
break;
|
|
}
|
|
}
|
|
END_TRY
|
|
// fall through - must return TRUE since record updated
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CRecordset::ReleaseCopyBuffer()
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(m_hstmt != SQL_NULL_HSTMT);
|
|
|
|
switch (m_nEditMode)
|
|
{
|
|
// Update
|
|
case edit:
|
|
// keep updated values
|
|
// free archive and cache memory
|
|
delete m_par;
|
|
m_par = NULL;
|
|
delete m_pmemfile;
|
|
m_pmemfile = NULL;
|
|
break;
|
|
|
|
// Insert
|
|
case addnew:
|
|
// Restore copy buffer to pre-AddNew values
|
|
// regardless of success of Insert operation
|
|
LoadFields();
|
|
break;
|
|
|
|
// Delete
|
|
case noMode:
|
|
// no copy buffer to release on a delete call
|
|
break;
|
|
}
|
|
m_nEditMode = noMode;
|
|
}
|
|
|
|
// Field is beyond bound columns, get info field directly from data source
|
|
BOOL CRecordset::UnboundFieldInfo(UINT nField, CFieldInfo* pfi)
|
|
{
|
|
ASSERT_VALID(this);
|
|
ASSERT(AfxIsValidAddress(pfi, sizeof(CFieldInfo)));
|
|
ASSERT(m_hstmt != NULL);
|
|
|
|
pfi->nField = nField;
|
|
nField++;
|
|
|
|
// Make sure nField falls within number of columns in the result set
|
|
SWORD nResultColumns;
|
|
RETCODE nRetCode;
|
|
AFX_SQL_ASYNC(this, ::SQLNumResultCols(m_hstmt, &nResultColumns));
|
|
if ((long)nField > (long)nResultColumns)
|
|
return FALSE;
|
|
|
|
UCHAR szName[65];
|
|
SWORD nNameLength = _countof(szName);
|
|
SWORD nSqlType;
|
|
SWORD nScale;
|
|
SWORD nNullable;
|
|
AFX_SQL_ASYNC(this, ::SQLDescribeCol(m_hstmt, (UWORD)nField,
|
|
szName, _countof(szName), &nNameLength, &nSqlType,
|
|
&pfi->dwSize, &nScale, &nNullable));
|
|
if (!Check(nRetCode))
|
|
return FALSE;
|
|
|
|
pfi->strName = (char*)szName;
|
|
pfi->pv = NULL;
|
|
|
|
switch (nSqlType)
|
|
{
|
|
case SQL_BIT:
|
|
pfi->nDataType = AFX_RFX_BOOL;
|
|
break;
|
|
|
|
case SQL_TINYINT:
|
|
pfi->nDataType = AFX_RFX_BYTE;
|
|
break;
|
|
|
|
case SQL_SMALLINT:
|
|
pfi->nDataType = AFX_RFX_INT;
|
|
break;
|
|
|
|
case SQL_INTEGER:
|
|
pfi->nDataType = AFX_RFX_LONG;
|
|
break;
|
|
|
|
case SQL_REAL:
|
|
pfi->nDataType = AFX_RFX_SINGLE;
|
|
break;
|
|
|
|
case SQL_FLOAT:
|
|
case SQL_DOUBLE:
|
|
pfi->nDataType = AFX_RFX_DOUBLE;
|
|
break;
|
|
|
|
case SQL_DATE:
|
|
case SQL_TIME:
|
|
case SQL_TIMESTAMP:
|
|
pfi->nDataType = AFX_RFX_DATE;
|
|
break;
|
|
|
|
case SQL_BINARY:
|
|
case SQL_VARBINARY:
|
|
pfi->nDataType = AFX_RFX_BINARY;
|
|
break;
|
|
|
|
case SQL_DECIMAL: // ODBC default xfer type
|
|
case SQL_NUMERIC: // ODBC default xfer type
|
|
case SQL_CHAR:
|
|
case SQL_VARCHAR:
|
|
pfi->nDataType = AFX_RFX_TEXT;
|
|
break;
|
|
|
|
case SQL_LONGVARCHAR:
|
|
case SQL_LONGVARBINARY:
|
|
pfi->nDataType = AFX_RFX_LONGBINARY;
|
|
break;
|
|
|
|
default:
|
|
ASSERT(FALSE);
|
|
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// 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
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// 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)
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|