📄 dbcore.cpp
字号:
// 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 _afxODBCTrail;
}
CString CRecordset::GetDefaultSQL()
{
ASSERT_VALID(this);
// Override and add table name or entire SQL SELECT statement
return _T("");
}
void CRecordset::DoFieldExchange(CFieldExchange* /* pFX */)
{
ASSERT_VALID(this);
// Do nothing if dynamically retrieving unbound fields,
// otherwise override CRecordset and add RFX calls
}
void CRecordset::DoBulkFieldExchange(CFieldExchange* /* pFX */)
{
ASSERT_VALID(this);
// To use multi-record data fetching, you must use
// a derived CRecordset class and call Close explicitly.
ASSERT(FALSE);
}
void CRecordset::OnSetOptions(HSTMT hstmt)
{
ASSERT_VALID(this);
ASSERT(hstmt != SQL_NULL_HSTMT);
// Inherit options settings from CDatabase
m_pDatabase->OnSetOptions(hstmt);
// If fowardOnly recordset and not using SQLExtendedFetch, quit now
if (m_nOpenType == forwardOnly && !(m_dwOptions & useExtendedFetch))
return;
// Turn on bookmark support if necessary
EnableBookmarks();
// If using forwardOnly and extended fetch, quit now
if (m_nOpenType == forwardOnly)
return;
// Make sure driver supports extended fetch, ODBC 2.0 and requested cursor type
VerifyDriverBehavior();
DWORD dwScrollType = VerifyCursorSupport();
// Set the update method, concurrency and cursor type
SetUpdateMethod();
SetConcurrencyAndCursorType(hstmt, dwScrollType);
}
// Screen for errors.
BOOL CRecordset::Check(RETCODE nRetCode) const
{
ASSERT_VALID(this);
switch (nRetCode)
{
case SQL_SUCCESS_WITH_INFO:
#ifdef _DEBUG
if (afxTraceFlags & traceDatabase)
{
CDBException e(nRetCode);
TRACE0("Warning: ODBC Success With Info, ");
e.BuildErrorString(m_pDatabase, m_hstmt);
}
#endif
// Fall through
case SQL_SUCCESS:
case SQL_NO_DATA_FOUND:
case SQL_NEED_DATA:
return TRUE;
}
return FALSE;
}
void CRecordset::PreBindFields()
{
// Do nothing
}
//////////////////////////////////////////////////////////////////////////////
// CRecordset internal functions
// Cache state information internally in CRecordset
void CRecordset::SetState(int nOpenType, LPCTSTR lpszSQL, DWORD dwOptions)
{
if (nOpenType == AFX_DB_USE_DEFAULT_TYPE)
m_nOpenType = m_nDefaultType;
else
m_nOpenType = nOpenType;
m_bAppendable = (dwOptions & appendOnly) != 0 ||
(dwOptions & readOnly) == 0;
m_bUpdatable = (dwOptions & readOnly) == 0 &&
(dwOptions & appendOnly) == 0;
// Can turn off dirty field checking via dwOptions
if (dwOptions & noDirtyFieldCheck || dwOptions & useMultiRowFetch)
m_bCheckCacheForDirtyFields = FALSE;
// Set recordset readOnly if forwardOnly
if (m_nOpenType == forwardOnly && !(dwOptions & readOnly))
{
#ifdef _DEBUG
if (afxTraceFlags & traceDatabase)
TRACE0("Warning: Setting forwardOnly recordset readOnly.\n");
#endif
dwOptions |= readOnly;
// If using multiRowFetch also set useExtendFetch
if (dwOptions & useMultiRowFetch)
dwOptions |= useExtendedFetch;
}
// Archive info for use in Requery
m_dwOptions = dwOptions;
m_strRequerySQL = lpszSQL;
m_strRequeryFilter = m_strFilter;
m_strRequerySort = m_strSort;
}
// Allocate the Hstmt and implicitly create and open Database if necessary
BOOL CRecordset::AllocHstmt()
{
RETCODE nRetCode;
if (m_hstmt == SQL_NULL_HSTMT)
{
CString strDefaultConnect;
TRY
{
if (m_pDatabase == NULL)
{
m_pDatabase = new CDatabase();
m_bRecordsetDb = TRUE;
}
strDefaultConnect = GetDefaultConnect();
// If not already opened, attempt to open
if (!m_pDatabase->IsOpen())
{
BOOL bUseCursorLib = m_bUseODBCCursorLib;
// If non-readOnly snapshot request must use cursor lib
if (m_nOpenType == snapshot && !(m_dwOptions & readOnly))
{
// This assumes drivers only support readOnly snapshots
bUseCursorLib = TRUE;
}
if (!m_pDatabase->Open(_T(""), 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(_T(""), FALSE, FALSE,
strDefaultConnect, TRUE))
return FALSE;
}
}
}
AFX_SQL_SYNC(::SQLAllocStmt(m_pDatabase->m_hdbc, &m_hstmt));
if (!Check(nRetCode))
ThrowDBException(SQL_INVALID_HANDLE);
// Add to list of CRecordsets with alloced hstmts
AfxLockGlobals(CRIT_ODBC);
TRY
{
m_pDatabase->m_listRecordsets.AddHead(this);
}
CATCH_ALL(e)
{
AfxUnlockGlobals(CRIT_ODBC);
THROW_LAST();
}
END_CATCH_ALL
AfxUnlockGlobals(CRIT_ODBC);
}
CATCH_ALL(e)
{
#ifdef _DEBUG
if (afxTraceFlags & traceDatabase)
TRACE0("Error: CDatabase create for CRecordset failed.\n");
#endif
NO_CPP_EXCEPTION(strDefaultConnect.Empty());
if (m_bRecordsetDb)
{
delete m_pDatabase;
m_pDatabase = NULL;
}
ASSERT(m_hstmt == SQL_NULL_HSTMT);
THROW_LAST();
}
END_CATCH_ALL
}
return TRUE;
}
// Initialize the status arrays and create the SQL
void CRecordset::BuildSQL(LPCTSTR lpszSQL)
{
if (lpszSQL == NULL)
m_strSQL = GetDefaultSQL();
else
m_strSQL = lpszSQL;
// Set any supplied params
if (m_nParams != 0)
{
UINT nParams = BindParams(m_hstmt);
ASSERT(nParams == m_nParams);
}
// Construct the SQL string
BuildSelectSQL();
AppendFilterAndSortSQL();
// Do some extra checking if trying to set recordset updatable or appendable
if ((m_bUpdatable || m_bAppendable) && !IsRecordsetUpdatable())
m_bUpdatable = m_bAppendable = FALSE;
if (m_bUpdatable && m_bUseUpdateSQL && m_pDatabase->m_bAddForUpdate)
m_strSQL += _afxForUpdate;
// Replace brackets with SQL_IDENTIFIER_QUOTE_CHAR
m_pDatabase->ReplaceBrackets(m_strSQL.GetBuffer(0));
m_strSQL.ReleaseBuffer();
}
// Prepare and Execute the SQL or simple call SQLExecDirect, resetting concurrency if necessary
void CRecordset::PrepareAndExecute()
{
USES_CONVERSION;
RETCODE nRetCode = 0;
BOOL bConcurrency = FALSE;
LPCSTR lpszWSQL = T2CA(m_strSQL);
while (!bConcurrency)
{
// Prepare or execute the query
if (m_dwOptions & executeDirect)
{
AFX_ODBC_CALL(::SQLExecDirect(m_hstmt,
(UCHAR*)lpszWSQL, SQL_NTS));
}
else
{
AFX_ODBC_CALL(::SQLPrepare(m_hstmt,
(UCHAR*)lpszWSQL, SQL_NTS));
}
if (Check(nRetCode))
bConcurrency = TRUE;
else
{
// If "Driver Not Capable" error, assume cursor type doesn't
// support requested concurrency and try alternate concurrency.
CDBException* e = new CDBException(nRetCode);
e->BuildErrorString(m_pDatabase, m_hstmt);
if (m_dwConcurrency != SQL_CONCUR_READ_ONLY &&
e->m_strStateNativeOrigin.Find(_afxDriverNotCapable) >= 0)
{
#ifdef _DEBUG
if (afxTraceFlags & traceDatabase)
TRACE0("Warning: Driver does not support requested concurrency.\n");
#endif
// Don't need exception to persist while attempting to reset concurrency
e->Delete();
// ODBC will automatically attempt to set alternate concurrency if
// request fails, but it won't try LOCK even if driver supports it.
if ((m_dwDriverConcurrency & SQL_SCCO_LOCK) &&
(m_dwConcurrency == SQL_CONCUR_ROWVER ||
m_dwConcurrency == SQL_CONCUR_VALUES))
{
m_dwConcurrency = SQL_CONCUR_LOCK;
}
else
{
m_dwConcurrency = SQL_CONCUR_READ_ONLY;
m_bUpdatable = m_bAppendable = FALSE;
#ifdef _DEBUG
if (afxTraceFlags & traceDatabase)
TRACE0("Warning: Setting recordset read only.\n");
#endif
}
// Attempt to reset the concurrency model.
AFX_SQL_SYNC(::SQLSetStmtOption(m_hstmt, SQL_CONCURRENCY,
m_dwConcurrency));
if (!Check(nRetCode))
{
TRACE0("Error: ODBC failure setting recordset concurrency.\n");
ThrowDBException(nRetCode);
}
}
else
{
TRACE0("Error: ODBC failure on SQLPrepare or SQLExecDirect\n");
THROW(e);
}
}
}
// now attempt to execute the SQL Query if not executed already
if (!(m_dwOptions & executeDirect))
{
AFX_ODBC_CALL(::SQLExecute(m_hstmt));
if (!Check(nRetCode))
ThrowDBException(nRetCode);
}
m_lOpen = AFX_RECORDSET_STATUS_OPEN;
// SQLExecute or SQLExecDirect may have changed an option value
if (nRetCode == SQL_SUCCESS_WITH_INFO)
{
// Check if concurrency was changed in order to mark
// recordset non-updatable if necessary
DWORD dwConcurrency;
AFX_SQL_SYNC(::SQLGetStmtOption(m_hstmt, SQL_CONCURRENCY, &dwConcurrency));
if (!Check(nRetCode))
ThrowDBException(nRetCode);
if (dwConcurrency == SQL_CONCUR_READ_ONLY && (m_bUpdatable || m_bAppendable))
{
m_bUpdatable = FALSE;
m_bAppendable = FALSE;
#ifdef _DEBUG
if (afxTraceFlags & traceDatabase)
{
TRACE0("Warning: Concurrency changed by driver.\n");
TRACE0("\tMarking CRecordset as not updatable.\n");
}
#endif // _DEBUG
}
}
}
// Ensure that driver supports extended fetch and ODBC 2.0 if necessary
void CRecordset::VerifyDriverBehavior()
{
RETCODE nRetCode;
UWORD wScrollable;
// If SQLExtendedFetch not supported, use SQLFetch
AFX_SQL_SYNC(::SQLGetFunctions(m_pDatabase->m_hdbc,
SQL_API_SQLEXTENDEDFETCH, &wScrollable));
if (!Check(nRetCode))
{
TRACE0("Error: ODBC failure determining whether recordset is scrollable.\n");
ThrowDBException(nRetCode);
}
m_bScrollable = wScrollable;
if (!m_bScrollable)
{
#ifdef _DEBUG
if (afxTraceFlags & traceDatabase)
{
TRACE0("Warning: SQLExtendedFetch not supported by driver\n");
TRACE0("and/or cursor library not loaded. Opening forwardOnly.\n");
TRACE0("for use with SQLFetch.\n");
}
#endif
m_bUpdatable = FALSE;
return;
}
char szResult[30];
SWORD nResult;
// require ODBC v2.0
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_ODBC_VER,
&szResult, _countof(szResult), &nResult));
if (!Check(nRetCode))
{
TRACE0("Error: ODBC failure checking for driver capabilities.\n");
ThrowDBException(nRetCode);
}
if (szResult[0] == '0' && szResult[1] < '2')
ThrowDBException(AFX_SQL_ERROR_ODBC_V2_REQUIRED);
}
// Check that driver supports requested cursor type
DWORD CRecordset::VerifyCursorSupport()
{
RETCODE nRetCode;
SWORD nResult;
UDWORD dwDriverScrollOptions;
AFX_SQL_SYNC(::SQLGetInfo(m_pDatabase->m_hdbc, SQL_SCROLL_OPTIONS,
&dwDriverScrollOptions, sizeof(dwDriverScrollOptions), &nResult));
if (!Check(nRetCode))
{
TRACE0("Error: ODBC failure checking for driver capabilities.\n");
ThrowDBException(nRetCode);
}
SDWORD dwScrollOptions = SQL_CURSOR_FORWARD_ONLY;
if (m_nOpenType == dynaset)
{
// Dynaset support requires ODBC's keyset driven cursor model
if (!(dwDriverScrollOptions & SQL_SO_KEYSET_DRIVEN))
ThrowDBException(AFX_SQL_ERROR_DYNASET_NOT_SUPPORTED);
dwScrollOptions = SQL_CURSOR_KEYSET_DRIVEN;
}
else if (m_nOpenType == snapshot)
{
// Snapshot support requires ODBC's static cursor model
if (!(dwDriverScrollOptions & SQL_SO_STATIC))
ThrowDBException(AFX_SQL_ERROR_SNAPSHOT_NOT_SUPPORTED);
dwScrollOptions = SQL_CURSOR_STATIC;
}
else
{
// Dynamic cursor support requires ODBC's dynamic cursor model
if (!(dwDriverScrollOptions & SQL_SO_DYNAMIC))
ThrowDBException(AFX_SQL_ERROR_DYNAMIC_CURSOR_NOT_SUPPORTED);
dwScrollOptions = SQL_CURSOR_DYNAMIC;
}
return dwScrollOptions;
}
void CRecordset::AllocAndCacheFieldInfo()
{
ASSERT(GetODBCFieldCount() < 0);
ASSERT(m_rgODBCFieldInfos == NULL);
RETCODE nRetCode;
SWORD nActualLen;
// Cache the number of result columns
AFX_ODBC_CALL(::SQLNumResultCols(m_hstmt, &m_nResultCols));
if (!Check(nRetCode))
{
TRACE0("Error: Can't get field info.\n");
ThrowDBException(nRetCode);
}
// If there are no fields quit now
if (m_nResultCols == 0)
return;
// Allocate buffer and get the ODBC meta data
m_rgODBCFieldInfos = new CODBCFieldInfo[m_nResultCols];
LPSTR lpszFieldName;
#ifdef _UNICODE
// Need proxy to temporarily store non-UNICODE name
lpszFieldName = new char[MAX_FNAME_LEN + 1];
#endif
// Get the field info each field
for (WORD n = 1; n <= GetODBCFieldCount(); n++)
{
#ifndef _UNICODE
// Reset the buffer to point to next element
lpszFieldName =
m_rgODBCFieldInfos[n - 1].m_strName.GetBuffer(MAX_FNAME_LEN + 1);
#endif
AFX_ODBC_CALL(::SQLDescribeCol(m_hstmt, n,
(UCHAR*)lpszFieldName, MAX_FNAME_LEN, &nActualLen,
&m_rgODBCFieldInfos[n - 1].m_nSQLType,
&m_rgODBCFieldInfos[n - 1].m_nPrecision,
&m_rgODBCFieldInfos[n - 1].m_nScale,
&m_rgODBCFieldInfos[n - 1].m_nNullability));
#ifndef _UNICODE
m_rgODBCFieldInfos[n - 1].m_strName.ReleaseBuffer(nActualLen);
#else
// Copy the proxy data to correct element
m_rgODBCFieldInfos[n - 1].m_strName = lpszFieldName;
#endif
if (!Check(nRetCode))
{
TRACE1("Error: ODBC failure getting field #%d info.\n", n);
ThrowDBException(nRetCode);
}
}
#ifdef _UNICODE
delete[] lpszFieldName;
#endif
}
void CRecordset::AllocRowset()
{
if (m_dwOptions & useMultiRowFetch)
SetRowsetSize(m_dwRowsetSize);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -