📄 dbcore.cpp
字号:
ASSERT(lpszSQL == NULL || AfxIsValidString(lpszSQL));
ASSERT(nOpenType == AFX_DB_USE_DEFAULT_TYPE ||
nOpenType == dynaset || nOpenType == snapshot ||
nOpenType == forwardOnly || nOpenType == dynamic);
ASSERT(!(dwOptions & readOnly && dwOptions & appendOnly));
// Can only use optimizeBulkAdd with appendOnly recordsets
ASSERT((dwOptions & optimizeBulkAdd && dwOptions & appendOnly) ||
!(dwOptions & optimizeBulkAdd));
// forwardOnly recordsets have limited functionality
ASSERT(!(nOpenType == forwardOnly && dwOptions & skipDeletedRecords));
// Cache state info and allocate hstmt
SetState(nOpenType, lpszSQL, dwOptions);
if(!AllocHstmt())
return FALSE;
// Check if bookmarks upported (CanBookmark depends on open DB)
ASSERT(dwOptions & useBookmarks ? CanBookmark() : TRUE);
TRY
{
OnSetOptions(m_hstmt);
// Allocate the field/param status arrays, if necessary
BOOL bUnbound = FALSE;
if (m_nFields > 0 || m_nParams > 0)
AllocStatusArrays();
else
bUnbound = TRUE;
// Build SQL and prep/execute or just execute direct
BuildSQL(lpszSQL);
PrepareAndExecute();
// Cache some field info and prepare the rowset
AllocAndCacheFieldInfo();
AllocRowset();
// If late binding, still need to allocate status arrays
if (bUnbound && (m_nFields > 0 || m_nParams > 0))
AllocStatusArrays();
// Give derived classes a call before binding
PreBindFields();
// Fetch the first row of data
MoveNext();
// If EOF, then result set empty, so set BOF as well
m_bBOF = m_bEOF;
}
CATCH_ALL(e)
{
Close();
THROW_LAST();
}
END_CATCH_ALL
return TRUE;
}
void CRecordset::Close()
{
ASSERT_VALID(this);
// Can't close if database has been deleted
ASSERT(m_pDatabase != NULL);
// This will force a requery for cursor name if reopened.
m_strCursorName.Empty();
if (m_rgFieldInfos != NULL &&
m_nFields > 0 && m_bCheckCacheForDirtyFields)
{
FreeDataCache();
}
FreeRowset();
m_nEditMode = noMode;
delete [] m_rgFieldInfos;
m_rgFieldInfos = NULL;
delete [] m_rgODBCFieldInfos;
m_rgODBCFieldInfos = NULL;
delete [] m_pbFieldFlags;
m_pbFieldFlags = NULL;
delete [] m_pbParamFlags;
m_pbParamFlags = NULL;
if (m_pvFieldProxy != NULL)
{
for (UINT nField = 0; nField < m_nProxyFields; nField++)
delete m_pvFieldProxy[nField];
delete [] m_pvFieldProxy;
m_pvFieldProxy = NULL;
m_nProxyFields = 0;
}
if (m_pvParamProxy != NULL)
{
for (UINT nParam = 0; nParam < m_nProxyParams; nParam++)
delete m_pvParamProxy[nParam];
delete [] m_pvParamProxy;
m_pvParamProxy = NULL;
m_nProxyParams = 0;
}
delete [] m_plParamLength;
m_plParamLength = NULL;
RETCODE nRetCode;
if (m_hstmt != SQL_NULL_HSTMT)
{
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmt, SQL_DROP));
m_hstmt = SQL_NULL_HSTMT;
}
if (m_hstmtUpdate != SQL_NULL_HSTMT)
{
AFX_SQL_SYNC(::SQLFreeStmt(m_hstmtUpdate, SQL_DROP));
m_hstmtUpdate = SQL_NULL_HSTMT;
}
// Remove CRecordset from CDatabase's list
AfxLockGlobals(CRIT_ODBC);
TRY
{
POSITION pos = m_pDatabase->m_listRecordsets.Find(this);
if (pos != NULL)
m_pDatabase->m_listRecordsets.RemoveAt(pos);
#ifdef _DEBUG
else if (afxTraceFlags & traceDatabase)
TRACE0("WARNING: CRecordset not found in m_pDatabase->m_listRecordsets.\n");
#endif
}
CATCH_ALL(e)
{
AfxUnlockGlobals(CRIT_ODBC);
THROW_LAST();
}
END_CATCH_ALL
AfxUnlockGlobals(CRIT_ODBC);
m_lOpen = AFX_RECORDSET_STATUS_CLOSED;
m_bBOF = TRUE;
m_bEOF = TRUE;
m_bDeleted = FALSE;
m_bAppendable = FALSE;
m_bUpdatable = FALSE;
m_bScrollable = FALSE;
m_bRebindParams = FALSE;
m_bLongBinaryColumns = FALSE;
m_nLockMode = optimistic;
m_nFieldsBound = 0;
m_nResultCols = -1;
}
BOOL CRecordset::IsOpen() const
// Note: assumes base class CRecordset::Close called
{
if (m_hstmt == NULL)
return FALSE;
if (m_lOpen == AFX_RECORDSET_STATUS_OPEN)
return TRUE;
RETCODE nRetCode;
SWORD nCols;
AFX_ODBC_CALL(::SQLNumResultCols(m_hstmt, &nCols));
if (!Check(nRetCode))
{
// If function sequence error, CRecordset not open
CDBException* e = new CDBException(nRetCode);
e->BuildErrorString(m_pDatabase, m_hstmt, FALSE);
if (e->m_strStateNativeOrigin.Find(_afxOutOfSequence) >= 0)
{
e->Delete();
return FALSE;
}
else
{
#ifdef _DEBUG
TRACE0("Error: SQLNumResultCols failed during IsOpen().\n");
e->TraceErrorMessage(e->m_strError);
e->TraceErrorMessage(e->m_strStateNativeOrigin);
#endif
THROW(e);
}
}
BOOL bOpen = FALSE;
if (nCols != 0)
bOpen = TRUE;
return bOpen;
}
BOOL CRecordset::IsFieldDirty(void* pv)
{
ASSERT_VALID(this);
ASSERT(!(m_dwOptions & useMultiRowFetch));
if (m_nFields <= 0)
{
ASSERT(FALSE);
return FALSE;
}
// If not in update op fields can't be dirty
// must compare saved and current values
if (m_nEditMode == noMode)
return FALSE;
// Must compare values to find dirty fields if necessary
if (m_bCheckCacheForDirtyFields)
{
if (m_nEditMode == edit)
MarkForUpdate();
else
MarkForAddNew();
}
int nIndex = 0, nIndexEnd;
if (pv == NULL)
nIndexEnd = m_nFields - 1;
else
{
// GetBoundFieldIndex returns 1-based index
nIndex = nIndexEnd = GetBoundFieldIndex(pv) - 1;
// must be address of field member
ASSERT(nIndex >= 0);
}
BOOL bDirty = FALSE;
while (nIndex <= nIndexEnd && !bDirty)
bDirty = IsFieldStatusDirty(nIndex++);
return bDirty;
}
BOOL CRecordset::IsFieldNull(void* pv)
{
ASSERT_VALID(this);
ASSERT(!(m_dwOptions & useMultiRowFetch));
int nIndex;
BOOL bRetVal;
if (pv == NULL)
{
bRetVal = FALSE;
for (nIndex = 0; !bRetVal && nIndex <= int(m_nFields-1); nIndex++)
bRetVal = IsFieldStatusNull((DWORD) nIndex);
}
else
{
nIndex = GetBoundFieldIndex(pv) - 1;
if (nIndex < 0)
ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
bRetVal = IsFieldStatusNull((DWORD) nIndex);
}
return bRetVal;
}
BOOL CRecordset::IsFieldNullable(void* pv)
{
ASSERT_VALID(this);
if (pv == NULL)
{
// Must specify valid column name
ASSERT(FALSE);
return FALSE;
}
int nIndex = GetBoundFieldIndex(pv) - 1;
if (nIndex < 0)
ThrowDBException(AFX_SQL_ERROR_FIELD_NOT_FOUND);
return IsFieldNullable((DWORD)nIndex);
}
BOOL CRecordset::CanBookmark() const
{
ASSERT_VALID(this);
ASSERT(m_pDatabase->IsOpen());
if (!(m_dwOptions & useBookmarks) ||
(m_nOpenType == forwardOnly && !(m_dwOptions & useExtendedFetch)))
return FALSE;
return m_pDatabase->GetBookmarkPersistence() & SQL_BP_SCROLL;
}
void CRecordset::Move(long nRows, WORD wFetchType)
{
ASSERT_VALID(this);
ASSERT(m_hstmt != SQL_NULL_HSTMT);
// First call - fields haven't been bound (m_nFieldsBound will change)
if (m_nFieldsBound == 0)
{
InitRecord();
ResetCursor();
}
if (m_nFieldsBound > 0)
{
// Reset field flags - mark all clean, all non-null
memset(m_pbFieldFlags, 0, m_nFields);
// Clear any edit mode that was set
m_nEditMode = noMode;
}
// Check scrollability, EOF/BOF status
CheckRowsetCurrencyStatus(wFetchType, nRows);
RETCODE nRetCode;
// Fetch the data, skipping deleted records if necessary
if ((wFetchType == SQL_FETCH_FIRST ||
wFetchType == SQL_FETCH_LAST ||
wFetchType == SQL_FETCH_NEXT ||
wFetchType == SQL_FETCH_PRIOR ||
wFetchType == SQL_FETCH_RELATIVE) &&
m_dwOptions & skipDeletedRecords)
{
SkipDeletedRecords(wFetchType, nRows, &m_dwRowsFetched, &nRetCode);
}
else
// Fetch the data and check for errors
nRetCode = FetchData(wFetchType, nRows, &m_dwRowsFetched);
// Set currency status and increment the record counters
SetRowsetCurrencyStatus(nRetCode, wFetchType, nRows, m_dwRowsFetched);
// Need to fixup bound fields in some cases
if (m_nFields > 0 && !IsEOF() && !IsBOF() &&
!(m_dwOptions & useMultiRowFetch))
{
Fixups();
}
}
void CRecordset::CheckRowsetError(RETCODE nRetCode)
{
if (nRetCode == SQL_SUCCESS_WITH_INFO)
{
CDBException e(nRetCode);
// Build the error string but don't send nuisance output to TRACE window
e.BuildErrorString(m_pDatabase, m_hstmt, FALSE);
if (e.m_strStateNativeOrigin.Find(_afxDataTruncated) >= 0)
{
// Ignore data truncated warning if binding long binary columns
// (may mask non-long binary truncation warnings or other warnings)
if (!((m_pDatabase->m_dwUpdateOptions & AFX_SQL_SETPOSUPDATES) &&
m_bLongBinaryColumns))
{
NO_CPP_EXCEPTION(e.Empty());
TRACE0("Error: field data truncated during data fetch.\n");
ThrowDBException(AFX_SQL_ERROR_DATA_TRUNCATED);
}
}
else if (e.m_strStateNativeOrigin.Find(_afxRowFetch) >= 0)
{
#ifdef _DEBUG
TRACE0("Error: fetching row from server.\n");
e.TraceErrorMessage(e.m_strError);
e.TraceErrorMessage(e.m_strStateNativeOrigin);
#endif
NO_CPP_EXCEPTION(e.Empty());
ThrowDBException(AFX_SQL_ERROR_ROW_FETCH);
}
else
{
#ifdef _DEBUG
// Not a truncation or row fetch warning so send debug output
if (afxTraceFlags & traceDatabase)
{
TRACE0("Warning: ODBC Success With Info,\n");
e.TraceErrorMessage(e.m_strError);
e.TraceErrorMessage(e.m_strStateNativeOrigin);
}
#endif // _DEBUG
}
}
else if (!Check(nRetCode))
ThrowDBException(nRetCode);
}
void CRecordset::GetBookmark(CDBVariant& varBookmark)
{
ASSERT_VALID(this);
// Validate bookmarks are usable
if (!(m_dwOptions & useBookmarks))
ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_ENABLED);
else if (!CanBookmark())
ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_SUPPORTED);
// Currently ODBC only supports 4 byte bookmarks
// Initialize the variant to a long
if (varBookmark.m_dwType != DBVT_LONG)
{
varBookmark.Clear();
varBookmark.m_dwType = DBVT_LONG;
varBookmark.m_lVal = 0;
}
RETCODE nRetCode;
SDWORD nActualSize;
// Retrieve the bookmark (column 0) data
AFX_ODBC_CALL(::SQLGetData(m_hstmt, 0, SQL_C_BOOKMARK,
&varBookmark.m_lVal, sizeof(varBookmark.m_lVal), &nActualSize));
if (!Check(nRetCode))
{
TRACE0("Error: GetBookmark operation failed.\n");
ThrowDBException(nRetCode);
}
}
void CRecordset::SetBookmark(const CDBVariant& varBookmark)
{
ASSERT_VALID(this);
// Validate bookmarks are usable
if (!(m_dwOptions & useBookmarks))
ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_ENABLED);
else if (!CanBookmark())
ThrowDBException(AFX_SQL_ERROR_BOOKMARKS_NOT_SUPPORTED);
// Currently ODBC only supports 4 byte bookmarks
ASSERT(varBookmark.m_dwType == DBVT_LONG);
Move(varBookmark.m_lVal, SQL_FETCH_BOOKMARK);
}
void CRecordset::SetRowsetSize(DWORD dwNewRowsetSize)
{
ASSERT_VALID(this);
ASSERT(dwNewRowsetSize > 0);
// If not yet open, only set expected length
if (!IsOpen())
{
m_dwRowsetSize = dwNewRowsetSize;
return;
}
if (!(m_dwOptions & useMultiRowFetch))
{
// Only works if bulk row fetching!
ASSERT(FALSE);
return;
}
// Need to reallocate some memory if rowset size grows
if (m_dwAllocatedRowsetSize == 0 ||
(m_dwAllocatedRowsetSize < dwNewRowsetSize))
{
// If rowset already allocated, delete old and reallocate
FreeRowset();
m_rgRowStatus = new WORD[dwNewRowsetSize];
// If not a user allocated buffer grow the data buffers
if (!(m_dwOptions & userAllocMultiRowBuffers))
{
// Allocate the rowset field buffers
m_dwRowsetSize = dwNewRowsetSize;
CFieldExchange fx(CFieldExchange::AllocMultiRowBuffer, this);
DoBulkFieldExchange(&fx);
m_dwAllocatedRowsetSize = dwNewRowsetSize;
// Set bound fields to zero, rebind and reset bound field count
int nOldFieldsBound = m_nFieldsBound;
m_nFieldsBound = 0;
InitRecord();
m_nFieldsBound = nOldFieldsBound;
}
}
else
{
// Just reset the new rowset size
m_dwRowsetSize = dwNewRowsetSize;
}
RETCODE nRetCode;
AFX_SQL_SYNC(::SQLSetStmtOption(m_hstmt, SQL_ROWSET_SIZE,
m_dwRowsetSize));
}
void CRecordset::AddNew()
{
ASSERT_VALID(this);
ASSERT(m_hstmt != SQL_NULL_HSTMT);
// we can't construct an INSERT statement w/o any columns
ASSERT(m_nFields != 0);
if (!m_bAppendable)
{
ThrowDBException(AFX_SQL_ERROR_RECORDSET_READONLY);
}
if (m_dwOptions & useMultiRowFetch)
{
// Can't use update methods on multi-row rowset
ASSERT(FALSE);
return;
}
if (m_bCheckCacheForDirtyFields && m_nFields > 0)
{
if (m_nEditMode == noMode)
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -