// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1998 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

#define new DEBUG_NEW

/////////////////////////////////////////////////////////////////////////////
// CDBByteArray db specific class for holding byte array data
class CDBByteArray : public CByteArray
{
	DECLARE_DYNAMIC(CDBByteArray)

// Operations
	void SetLength(int nNewSize);
};

inline void CDBByteArray::SetLength(int nNewSize)
{
	// Can't grow buffer since ODBC has been SQLBindCol'd on it.
	ASSERT(nNewSize <= m_nMaxSize);
	m_nSize = nNewSize;
}

//////////////////////////////////////////////////////////////////////////////
// CFieldExchange

CFieldExchange::CFieldExchange(UINT nOperation, CRecordset* prs, void* pvField)
{
#ifdef _DEBUG
	ASSERT(nOperation >= BindParam && nOperation <= DumpField);
#endif
	ASSERT_VALID(prs);
	ASSERT(prs->m_hstmt != SQL_NULL_HSTMT);

	m_nFieldType = (UINT) noFieldType;
	m_nOperation = nOperation;
	m_prs = prs;
	m_pvField = pvField;

	m_nFields = 0;
	m_nParams = 0;
	m_nParamFields = 0;
	m_bField = FALSE;
	m_pstr = NULL;
	m_hstmt = SQL_NULL_HSTMT;

	m_lDefaultLBFetchSize = 0x00010000;
	m_lDefaultLBReallocSize = 0x00010000;
}

BOOL CFieldExchange::IsFieldType(UINT* pnField)
{
	if (m_nFieldType == outputColumn)
	{
		*pnField = ++m_nFields;
		// Recordset's m_nFields must match number of Fields!
		ASSERT(m_nFields <= m_prs->m_nFields);
	}
	else
	{
		// Make sure SetFieldType was called
		ASSERT(m_nFieldType == inputParam ||
			m_nFieldType == outputParam ||
			m_nFieldType == inoutParam);

		*pnField = ++m_nParams;
		// Recordset's m_nParams must match number of Params!
		ASSERT(m_nParams <= m_prs->m_nParams);
	}

	if (m_nOperation == BindParam || m_nOperation == RebindParam)
	{
		// only valid on a param field type
		return m_nFieldType != outputColumn;
	}
	else
	{
		// valid only on an outputColumn field type
		return m_nFieldType == outputColumn;
	}
}

// Default implementation for RFX functions
void CFieldExchange::Default(LPCTSTR szName,
	void* pv, LONG* plLength, int nCType, UINT cbValue, UINT cbPrecision)
{
	RETCODE nRetCode;
	UINT nField = (m_nFieldType == outputColumn)? m_nFields: m_nParams;
	switch (m_nOperation)
	{
	case BindParam:
		if (m_prs->IsParamStatusNull(nField - 1))
			*plLength = SQL_NULL_DATA;
		else
			*plLength = cbValue;
		// For params, CType is same as SQL type
		AFX_SQL_SYNC(::SQLBindParameter(m_hstmt, (UWORD)nField,
			(SWORD)m_nFieldType, (SWORD)nCType, (SWORD)nCType, cbPrecision, 0,
			pv, 0, plLength));
		if (nRetCode != SQL_SUCCESS)
			m_prs->ThrowDBException(nRetCode, m_hstmt);

		// Add the member address to the param map
		m_prs->m_mapParamIndex.SetAt(pv, (void*)nField);
		return;

	case RebindParam:
		// Only need to reset param length
		*plLength = m_prs->IsParamStatusNull(nField - 1) ? SQL_NULL_DATA : cbValue;
		return;

	case BindFieldForUpdate:
		if (!m_prs->IsFieldStatusDirty(nField - 1))
		{
			// If not dirty, set length to SQL_IGNORE for SQLSetPos updates
			*plLength = SQL_IGNORE;
		}
		else if (!m_prs->IsFieldStatusNull(nField - 1))
		{
			// Reset the length as it may have changed for var length fields
			*plLength = cbValue;
		}
		return;


	case UnbindFieldForUpdate:
		// Reset bound length to actual length to clear SQL_IGNOREs
		if (!m_prs->IsFieldStatusDirty(nField - 1))
			*plLength = cbValue;
		return;

	case BindFieldToColumn:
		AFX_SQL_SYNC(::SQLBindCol(m_prs->m_hstmt, (UWORD)nField, (SWORD)nCType,
			pv, cbValue, plLength));
		if (!m_prs->Check(nRetCode))
			m_prs->ThrowDBException(nRetCode);

		// Add the member address to the field map
		m_prs->m_mapFieldIndex.SetAt(pv, (void*)nField);
		return;

	case Name:
		if (m_prs->IsFieldStatusDirty(nField - 1))
		{
			// We require a name
			ASSERT(lstrlen(szName) != 0);

			*m_pstr += szName;
			*m_pstr += m_lpszSeparator;
		}
		return;

	case NameValue:
		if (m_prs->IsFieldStatusDirty(nField - 1))
		{
			*m_pstr += szName;
			*m_pstr += '=';
		}

		// Fall through
	case Value:
		if (m_prs->IsFieldStatusDirty(nField - 1))
		{
			// If user marked column NULL, reflect this in length
			if (m_prs->IsFieldStatusNull(nField - 1))
				*plLength = SQL_NULL_DATA;
			else
				*plLength = cbValue;

			// If optimizing for bulk add, only need lengths set correctly
			if(!(m_prs->m_dwOptions & CRecordset::optimizeBulkAdd))
			{
				*m_pstr += '?';
				*m_pstr += m_lpszSeparator;
				m_nParamFields++;

				// Assumes all bound fields BEFORE unbound fields
				CODBCFieldInfo* pODBCInfo =
					&m_prs->m_rgODBCFieldInfos[nField - 1];

				AFX_SQL_SYNC(::SQLBindParameter(m_hstmt,
					(UWORD)m_nParamFields, SQL_PARAM_INPUT,
					(SWORD)nCType, pODBCInfo->m_nSQLType,
					pODBCInfo->m_nPrecision, pODBCInfo->m_nScale,
					pv, 0, plLength));
				if (nRetCode != SQL_SUCCESS)
					m_prs->ThrowDBException(nRetCode, m_hstmt);
			}
		}
		return;

	case MarkForUpdate:
		{
			// Get the field data
			CFieldInfo* pInfo = &m_prs->m_rgFieldInfos[nField - 1];

			// If user changed field value from previous value, mark field dirty
			if ((pInfo->m_bStatus & AFX_SQL_FIELD_FLAG_NULL))
			{
				if (!m_prs->IsFieldStatusNull(nField - 1))
					m_prs->SetDirtyFieldStatus(nField - 1);
			}
			else
			{
				// Saved field is not NULL. current field null, so field dirty
				BOOL bDirty = m_prs->IsFieldStatusNull(nField - 1);

				// If values differ, then field dirty
				void* pvDataCache;

				if (pInfo->m_nDataType == AFX_RFX_BOOL ||
					pInfo->m_nDataType == AFX_RFX_BYTE ||
					pInfo->m_nDataType == AFX_RFX_INT ||
					pInfo->m_nDataType == AFX_RFX_LONG ||
					pInfo->m_nDataType == AFX_RFX_SINGLE)
				{
					// If caching data by value, pass a ref
					pvDataCache = &pInfo->m_pvDataCache;
				}
				else
					pvDataCache = pInfo->m_pvDataCache;

				if (bDirty || !AfxCompareValueByRef(pv, pvDataCache, pInfo->m_nDataType))
					m_prs->SetDirtyFieldStatus(nField - 1);
			}

#ifdef _DEBUG
			// Field address must not change - ODBC's SQLBindCol depends upon this
			void* pvBind;

			switch (pInfo->m_nDataType)
			{
			default:
				pvBind = pv;
				break;

			case AFX_RFX_LPTSTR:
#ifdef _UNICODE
				pvBind = m_prs->m_pvFieldProxy[nField-1];
#else // !_UNICODE
				pvBind = pv;
#endif
				break;

			case AFX_RFX_TEXT:
#ifdef _UNICODE
				pvBind = m_prs->m_pvFieldProxy[nField-1];
#else // !_UNICODE
				{
					pvBind = ((CString*)pv)->GetBuffer(0);
					((CString*)pv)->ReleaseBuffer();
				}
#endif
				break;

			case AFX_RFX_OLEDATE:
			case AFX_RFX_DATE:
				pvBind = m_prs->m_pvFieldProxy[nField-1];
				break;

			case AFX_RFX_BINARY:
				pvBind = ((CByteArray*)pv)->GetData();
				break;
			}

			if (pInfo->m_pvBindAddress != pvBind)
			{
				TRACE1("Error: field address (column %u) has changed!\n",
					nField);
				ASSERT(FALSE);
			}
#endif // _DEBUG

			if ((m_pvField == NULL  || m_pvField == pv) &&
				m_prs->IsFieldStatusDirty(nField - 1))
			{
				m_bField = TRUE;
			}
		}
		return;

	case StoreField:
		AfxStoreField(*m_prs, nField, pv);
		return;

	case LoadField:
		AfxLoadField(*m_prs, nField, pv, plLength);
		return;

	default:
		ASSERT(FALSE);
	}
}

void AFXAPI RFX_Text(CFieldExchange* pFX, LPCTSTR szName,
	LPTSTR value, int nMaxLength, int nColumnType, short nScale)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));
	ASSERT(AfxIsValidAddress(value, nMaxLength));

	RETCODE nRetCode;
	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	LONG* plLength = pFX->m_prs->GetFieldLengthBuffer(
		nField - 1, pFX->m_nFieldType);
	switch (pFX->m_nOperation)
	{
	default:
		pFX->Default(szName, value, plLength,
			SQL_C_CHAR, lstrlen(value), nMaxLength);
		return;

	case CFieldExchange::BindParam:
		{
			void* pvParam = value; // will be overwritten if UNICODE

#ifdef _UNICODE
			// Must use proxy to translate unicode data into non-unicode param
			pFX->m_prs->m_bRebindParams = TRUE;

			// Allocate proxy array if necessary
			if (pFX->m_prs->m_pvParamProxy == NULL)
			{
				pFX->m_prs->m_pvParamProxy = new void*[pFX->m_prs->m_nParams];
				memset(pFX->m_prs->m_pvParamProxy, 0,
					pFX->m_prs->m_nParams*sizeof(void*));
				pFX->m_prs->m_nProxyParams = pFX->m_prs->m_nParams;
			}

			// Allocate non-unicode string to nMaxLength if necessary for SQLBindParameter
			if (pFX->m_prs->m_pvParamProxy[nField-1] == NULL)
			{
				pvParam = new CHAR[nMaxLength+1];
				pFX->m_prs->m_pvParamProxy[nField-1] = pvParam;
			}
			else
				pvParam = pFX->m_prs->m_pvParamProxy[nField-1];

			// Now fill in the data value
			USES_CONVERSION;
			lstrcpyA((char*)pvParam, T2A((LPCTSTR)value));
#endif // _UNICODE

			*plLength = pFX->m_prs->IsParamStatusNull(nField - 1) ?
				SQL_NULL_DATA : SQL_NTS;

			AFX_SQL_SYNC(::SQLBindParameter(pFX->m_hstmt, (UWORD)nField,
				 (SWORD)pFX->m_nFieldType, SQL_C_CHAR, (SWORD)nColumnType,
				 nMaxLength, nScale, pvParam, nMaxLength, plLength));

			if (nRetCode != SQL_SUCCESS)
				pFX->m_prs->ThrowDBException(nRetCode, pFX->m_hstmt);

			// Add the member address to the param map
			pFX->m_prs->m_mapParamIndex.SetAt(value, (void*)nField);
		}
		return;

#ifdef _UNICODE
	case CFieldExchange::RebindParam:
		*plLength = pFX->m_prs->IsParamStatusNull(nField - 1) ?
			SQL_NULL_DATA : SQL_NTS;
		if (pFX->m_prs->m_nProxyParams != 0)
		{
			// Fill buffer (expected by SQLBindParameter) with new param data
			USES_CONVERSION;
			LPSTR lpszParam = (LPSTR)pFX->m_prs->m_pvParamProxy[nField-1];
			lstrcpyA(lpszParam, T2A((LPCTSTR)value));
		}
		return;
#endif // _UNICODE

	case CFieldExchange::BindFieldToColumn:
		{
			// Assumes all bound fields BEFORE unbound fields
			CODBCFieldInfo* pODBCInfo =
				&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];
			UINT cbColumn = pODBCInfo->m_nPrecision;

			switch (pODBCInfo->m_nSQLType)
			{
			default:
#ifdef _DEBUG
				// Warn of possible field schema mismatch
				if (afxTraceFlags & traceDatabase)
					TRACE1("Warning: string converted from SQL type %ld.\n",
						pODBCInfo->m_nSQLType);
#endif // _DEBUG

				// Add room for extra information like sign, decimal point, etc.
				cbColumn += 10;
				break;

			case SQL_LONGVARCHAR:
			case SQL_CHAR:
			case SQL_VARCHAR:
				break;

			case SQL_FLOAT:
			case SQL_REAL:
			case SQL_DOUBLE:
				// Add room for sign, decimal point and " E +XXX"
				cbColumn += 10;
				break;

			case SQL_DECIMAL:
			case SQL_NUMERIC:
				// Add room for sign and decimal point
				cbColumn += 2;
				break;

			case SQL_TIMESTAMP:
			case SQL_DATE:
			case SQL_TIME:
				// May need extra space, i.e. "{TS mm/dd/yyyy hh:mm:ss}"
				cbColumn += 10;
				break;

			case SQL_TINYINT:
			case SQL_SMALLINT:
			case SQL_INTEGER:
			case SQL_BIGINT:
				// Add room for sign
				cbColumn += 1;
				break;
			}

			// Constrain to user specified max length, subject to 256 byte min
			if (cbColumn > (UINT)nMaxLength || cbColumn < 256)
				cbColumn = nMaxLength;

			// Set up binding addres

			void* pvData = value;   // overwritten if _UNICODE
			value[cbColumn] = '\0';

#ifdef _UNICODE
			// Allocate proxy array if necessary
			if (pFX->m_prs->m_pvFieldProxy == NULL)
			{
				pFX->m_prs->m_pvFieldProxy = new void*[pFX->m_prs->m_nFields];
				memset(pFX->m_prs->m_pvFieldProxy, 0,
					pFX->m_prs->m_nFields*sizeof(void*));
				pFX->m_prs->m_nProxyFields = pFX->m_prs->m_nFields;
			}

			// Allocate non-unicode string for SQLBindCol (not necessary on Requery)
			if (pFX->m_prs->m_pvFieldProxy[nField-1] == NULL)
				pFX->m_prs->m_pvFieldProxy[nField-1] = new CHAR[cbColumn+2];

			pvData = pFX->m_prs->m_pvFieldProxy[nField-1];
#endif // _UNICODE

			AFX_SQL_SYNC(::SQLBindCol(pFX->m_prs->m_hstmt, (UWORD)nField,
				SQL_C_CHAR, pvData, cbColumn+1, plLength));
			if (!pFX->m_prs->Check(nRetCode))
				pFX->m_prs->ThrowDBException(nRetCode);

			// Add the member address to the field map
			pFX->m_prs->m_mapFieldIndex.SetAt(value, (void*)nField);
		}
		return;

#ifdef _UNICODE
	case CFieldExchange::BindFieldForUpdate:
		if (pFX->m_prs->m_nProxyFields != 0)
		{
			// Fill buffer (expected by SQLSetPos) with new field data
			USES_CONVERSION;
			LPSTR lpszData = (LPSTR)pFX->m_prs->m_pvFieldProxy[nField-1];
			lstrcpyA(lpszData, T2A((LPCTSTR)value));

			pFX->Default(szName, (void *)lpszData, plLength, SQL_C_CHAR,
				lstrlenA(lpszData), nMaxLength);
		}
		return;
#endif // _UNICODE

	case CFieldExchange::Fixup:
			if (*plLength == SQL_NULL_DATA)
		{
			pFX->m_prs->SetNullFieldStatus(nField - 1);
			value[0] = '\0';
		}
		else
		{
#ifdef _UNICODE
			// Copy value out of the proxy
			value = (LPTSTR)pFX->m_prs->m_pvFieldProxy[nField-1];
#endif

			LPTSTR lpsz = value;
			if (pFX->m_prs->m_pDatabase->m_bStripTrailingSpaces)
			{
				// find first trailing space
				LPTSTR lpszFirstTrailing = NULL;
				while (*lpsz != '\0')
				{
					if (*lpsz != ' ')
						lpszFirstTrailing = NULL;
					else
					{
						if (lpszFirstTrailing == NULL)
							lpszFirstTrailing = lpsz;
					}
					lpsz = _tcsinc(lpsz);
				}
				// truncate
				if (lpszFirstTrailing != NULL)
					*lpszFirstTrailing = '\0';

			}
			*plLength = lstrlen(value);
		}
		return;

	case CFieldExchange::SetFieldNull:
		if ((pFX->m_pvField == NULL &&
			pFX->m_nFieldType == CFieldExchange::outputColumn) ||
			pFX->m_pvField == value)
		{
			if (pFX->m_bField)
			{
				// Mark fields null
				pFX->m_prs->SetNullFieldStatus(nField - 1);
				// Set string 0 length
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				pFX->m_prs->ClearNullFieldStatus(nField - 1);
				*plLength = SQL_NTS;
			}
#ifdef _DEBUG
			pFX->m_nFieldFound = nField;
#endif
		}
		return;


#ifdef _UNICODE
	case CFieldExchange::NameValue:
		if (pFX->m_prs->IsFieldStatusDirty(nField - 1))
		{
			*pFX->m_pstr += szName;
			*pFX->m_pstr += '=';
		}
		// Fall through

	case CFieldExchange::Value:
		if (pFX->m_prs->IsFieldStatusDirty(nField - 1))
		{
			// Get the field data
			CODBCFieldInfo* pODBCInfo =
					&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];

			LPSTR lpszData = (LPSTR)pFX->m_prs->m_pvFieldProxy[nField-1];
			if (pFX->m_prs->IsFieldStatusNull(nField - 1))
			{
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				USES_CONVERSION;
				lstrcpyA(lpszData, T2A((LPCTSTR)value));
				*plLength = lstrlen(value);
			}

			// If optimizing for bulk add, only need lengths & proxy set correctly
			if(!(pFX->m_prs->m_dwOptions & CRecordset::optimizeBulkAdd))
			{
				*pFX->m_pstr += '?';
				*pFX->m_pstr += pFX->m_lpszSeparator;
				pFX->m_nParamFields++;
				AFX_SQL_SYNC(::SQLBindParameter(pFX->m_hstmt,
					(UWORD)pFX->m_nParamFields, SQL_PARAM_INPUT,
					SQL_C_CHAR, pODBCInfo->m_nSQLType, nMaxLength,
					pODBCInfo->m_nScale, lpszData, 0, plLength));
			}
		}
		return;
#endif // _UNICODE

	case CFieldExchange::MarkForAddNew:
		// can force writing of psuedo-null value (as a non-null) by setting field dirty
		if (*value != '\0')
		{
			pFX->m_prs->SetDirtyFieldStatus(nField - 1);
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		}
		return;

	case CFieldExchange::MarkForUpdate:
		if (*value == '\0')
			pFX->m_prs->SetNullFieldStatus(nField - 1);
		else
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		pFX->Default(szName, value, plLength,
			SQL_C_CHAR, lstrlen(value), nMaxLength);
		return;

	case CFieldExchange::LoadField:
		{
			// Get the field data
			CFieldInfo* pInfo = &pFX->m_prs->m_rgFieldInfos[nField - 1];

			// Restore the status
			pFX->m_prs->SetFieldStatus(nField - 1, pInfo->m_bStatus);

			// If not NULL, restore the value and length
			if (!pFX->m_prs->IsFieldStatusNull(nField - 1))
			{
				value = LPTSTR(pInfo->m_pvDataCache);
				*plLength = lstrlen(value);

#ifdef _UNICODE
				// Must restore proxy for correct WHERE CURRENT OF operation
				USES_CONVERSION;
				LPSTR lpszData = (LPSTR)pFX->m_prs->m_pvFieldProxy[nField-1];
				lstrcpyA(lpszData, T2A((LPCTSTR)value));
#endif // _UNICODE
			}
			else
			{
				*plLength = SQL_NULL_DATA;
			}

#ifdef _DEBUG
			// Buffer address must not change - ODBC's SQLBindCol depends upon this
			void* pvBind;

#ifdef _UNICODE
			pvBind = pFX->m_prs->m_pvFieldProxy[nField-1];
#else // !_UNICODE
			pvBind = value;
#endif

			if (pvBind != pInfo->m_pvBindAddress)
			{
				TRACE1("Error: buffer (column %u) address has changed!\n",
					nField);
				ASSERT(FALSE);
			}
#endif // _DEBUG
		}
		return;

	case CFieldExchange::StoreField:
		AfxStoreField(*pFX->m_prs, nField, value);
		return;

	case CFieldExchange::AllocCache:
		{
			CFieldInfo* pInfo = &pFX->m_prs->m_rgFieldInfos[nField - 1];
			pInfo->m_pvDataCache = new TCHAR[nMaxLength];
			pInfo->m_nDataType = AFX_RFX_LPTSTR;
		}
		return;

#ifdef _DEBUG
	case CFieldExchange::DumpField:
		*pFX->m_pdcDump << "\n" << szName << " = " << value;
		return;
#endif // _DEBUG

	}
}

// Note: CString.m_pchData must not be changed.  This address is registered
// with ODBC and must remain valid until the recordset is released.
void AFXAPI RFX_Text(CFieldExchange* pFX, LPCTSTR szName,
	CString& value, int nMaxLength, int nColumnType, short nScale)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));
	ASSERT(AfxIsValidAddress(&value, sizeof(CString)));

	RETCODE nRetCode;
	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	LONG* plLength = pFX->m_prs->GetFieldLengthBuffer(
		nField - 1, pFX->m_nFieldType);
	switch (pFX->m_nOperation)
	{
	default:
		pFX->Default(szName, value.GetBuffer(0), plLength,
			SQL_C_CHAR, value.GetLength(), nMaxLength);
		value.ReleaseBuffer();
		return;

	case CFieldExchange::BindParam:
		{
			// Preallocate to nMaxLength and setup binding address
			value.GetBufferSetLength(nMaxLength);
			void* pvParam = value.LockBuffer(); // will be overwritten if UNICODE

#ifdef _UNICODE
			// Must use proxy to translate unicode data into non-unicode param
			pFX->m_prs->m_bRebindParams = TRUE;

			// Allocate proxy array if necessary
			if (pFX->m_prs->m_pvParamProxy == NULL)
			{
				pFX->m_prs->m_pvParamProxy = new void*[pFX->m_prs->m_nParams];
				memset(pFX->m_prs->m_pvParamProxy, 0,
					pFX->m_prs->m_nParams*sizeof(void*));
				pFX->m_prs->m_nProxyParams = pFX->m_prs->m_nParams;
			}

			// Allocate non-unicode string to nMaxLength if necessary for SQLBindParameter
			if (pFX->m_prs->m_pvParamProxy[nField-1] == NULL)
			{
				pvParam = new CHAR[nMaxLength+1];
				pFX->m_prs->m_pvParamProxy[nField-1] = pvParam;
			}
			else
				pvParam = pFX->m_prs->m_pvParamProxy[nField-1];

			// Now fill in the data value
			USES_CONVERSION;
			lstrcpyA((char*)pvParam, T2A((LPCTSTR)value));
#endif // _UNICODE

			*plLength = pFX->m_prs->IsParamStatusNull(nField - 1) ?
				SQL_NULL_DATA : SQL_NTS;

			AFX_SQL_SYNC(::SQLBindParameter(pFX->m_hstmt, (UWORD)nField,
				 (SWORD)pFX->m_nFieldType, SQL_C_CHAR, (SWORD)nColumnType,
				 nMaxLength, nScale, pvParam, nMaxLength, plLength));

			value.ReleaseBuffer();

			if (nRetCode != SQL_SUCCESS)
				pFX->m_prs->ThrowDBException(nRetCode, pFX->m_hstmt);

			// Add the member address to the param map
			pFX->m_prs->m_mapParamIndex.SetAt(&value, (void*)nField);
		}
		return;

#ifdef _UNICODE
	case CFieldExchange::RebindParam:
		*plLength = pFX->m_prs->IsParamStatusNull(nField - 1) ?
			SQL_NULL_DATA : SQL_NTS;
		if (pFX->m_prs->m_nProxyParams != 0)
		{
			// Fill buffer (expected by SQLBindParameter) with new param data
			USES_CONVERSION;
			LPSTR lpszParam = (LPSTR)pFX->m_prs->m_pvParamProxy[nField-1];
			lstrcpyA(lpszParam, T2A((LPCTSTR)value));
		}
		return;
#endif // _UNICODE

	case CFieldExchange::BindFieldToColumn:
		{
			// Assumes all bound fields BEFORE unbound fields
			CODBCFieldInfo* pODBCInfo =
				&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];
			UINT cbColumn = pODBCInfo->m_nPrecision;

			switch (pODBCInfo->m_nSQLType)
			{
			default:
#ifdef _DEBUG
				// Warn of possible field schema mismatch
				if (afxTraceFlags & traceDatabase)
					TRACE1("Warning: CString converted from SQL type %ld.\n",
						pODBCInfo->m_nSQLType);
#endif // _DEBUG

				// Add room for extra information like sign, decimal point, etc.
				cbColumn += 10;
				break;

			case SQL_LONGVARCHAR:
			case SQL_CHAR:
			case SQL_VARCHAR:
				break;

			case SQL_FLOAT:
			case SQL_REAL:
			case SQL_DOUBLE:
				// Add room for sign, decimal point and " E +XXX"
				cbColumn += 10;
				break;

			case SQL_DECIMAL:
			case SQL_NUMERIC:
				// Add room for sign and decimal point
				cbColumn += 2;
				break;

			case SQL_TIMESTAMP:
			case SQL_DATE:
			case SQL_TIME:
				// May need extra space, i.e. "{TS mm/dd/yyyy hh:mm:ss}"
				cbColumn += 10;
				break;

			case SQL_TINYINT:
			case SQL_SMALLINT:
			case SQL_INTEGER:
			case SQL_BIGINT:
				// Add room for sign
				cbColumn += 1;
				break;
			}

			// Constrain to user specified max length, subject to 256 byte min
			if (cbColumn > (UINT)nMaxLength || cbColumn < 256)
				cbColumn = nMaxLength;

			// Set up binding addres
			void* pvData;
			value.GetBufferSetLength(cbColumn+1);
			pvData = value.LockBuffer();    // will be overwritten if UNICODE

#ifdef _UNICODE
			// Allocate proxy array if necessary
			if (pFX->m_prs->m_pvFieldProxy == NULL)
			{
				pFX->m_prs->m_pvFieldProxy = new void*[pFX->m_prs->m_nFields];
				memset(pFX->m_prs->m_pvFieldProxy, 0,
					pFX->m_prs->m_nFields*sizeof(void*));
				pFX->m_prs->m_nProxyFields = pFX->m_prs->m_nFields;
			}

			// Allocate non-unicode string for SQLBindCol (not necessary on Requery)
			if (pFX->m_prs->m_pvFieldProxy[nField-1] == NULL)
				pFX->m_prs->m_pvFieldProxy[nField-1] = new CHAR[cbColumn+2];

			pvData = pFX->m_prs->m_pvFieldProxy[nField-1];
#endif // _UNICODE

			AFX_SQL_SYNC(::SQLBindCol(pFX->m_prs->m_hstmt, (UWORD)nField,
				SQL_C_CHAR, pvData, cbColumn+1, plLength));
			value.ReleaseBuffer();
			if (!pFX->m_prs->Check(nRetCode))
				pFX->m_prs->ThrowDBException(nRetCode);

			// Add the member address to the field map
			pFX->m_prs->m_mapFieldIndex.SetAt(&value, (void*)nField);
		}
		return;

#ifdef _UNICODE
	case CFieldExchange::BindFieldForUpdate:
		if (pFX->m_prs->m_nProxyFields != 0)
		{
			// Fill buffer (expected by SQLSetPos) with new field data
			USES_CONVERSION;
			LPSTR lpszData = (LPSTR)pFX->m_prs->m_pvFieldProxy[nField-1];
			lstrcpyA(lpszData, T2A((LPCTSTR)value));

			pFX->Default(szName, (void *)lpszData, plLength, SQL_C_CHAR,
				value.GetLength(), nMaxLength);
		}
		return;
#endif // _UNICODE

	case CFieldExchange::Fixup:
		if (*plLength == SQL_NULL_DATA)
		{
			pFX->m_prs->SetNullFieldStatus(nField - 1);
			value.GetBufferSetLength(0);
			value.ReleaseBuffer();
		}
		else
		{
#ifdef _UNICODE
			// Copy value out of the proxy
			value = (LPSTR)pFX->m_prs->m_pvFieldProxy[nField-1];
#endif

			LPTSTR lpsz = value.GetBuffer(0);
			if (pFX->m_prs->m_pDatabase->m_bStripTrailingSpaces)
			{
				// find first trailing space
				LPTSTR lpszFirstTrailing = NULL;
				while (*lpsz != '\0')
				{
					if (*lpsz != ' ')
						lpszFirstTrailing = NULL;
					else
					{
						if (lpszFirstTrailing == NULL)
							lpszFirstTrailing = lpsz;
					}
					lpsz = _tcsinc(lpsz);
				}
				// truncate
				if (lpszFirstTrailing != NULL)
					*lpszFirstTrailing = '\0';

			}
			value.ReleaseBuffer();
			*plLength = value.GetLength();
		}
		return;

	case CFieldExchange::SetFieldNull:
		if ((pFX->m_pvField == NULL &&
			pFX->m_nFieldType == CFieldExchange::outputColumn) ||
			pFX->m_pvField == &value)
		{
			if (pFX->m_bField)
			{
				// Mark fields null
				pFX->m_prs->SetNullFieldStatus(nField - 1);
				// Set string 0 length
				value.GetBufferSetLength(0);
				value.ReleaseBuffer();
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				pFX->m_prs->ClearNullFieldStatus(nField - 1);
				*plLength = SQL_NTS;
			}
#ifdef _DEBUG
			pFX->m_nFieldFound = nField;
#endif
		}
		return;


#ifdef _UNICODE
	case CFieldExchange::NameValue:
		if (pFX->m_prs->IsFieldStatusDirty(nField - 1))
		{
			*pFX->m_pstr += szName;
			*pFX->m_pstr += '=';
		}
		// Fall through

	case CFieldExchange::Value:
		if (pFX->m_prs->IsFieldStatusDirty(nField - 1))
		{
			// Get the field data
			CODBCFieldInfo* pODBCInfo =
					&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];

			LPSTR lpszData = (LPSTR)pFX->m_prs->m_pvFieldProxy[nField-1];
			if (pFX->m_prs->IsFieldStatusNull(nField - 1))
			{
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				USES_CONVERSION;
				lstrcpyA(lpszData, T2A((LPCTSTR)value));
				*plLength = value.GetLength();
			}

			// If optimizing for bulk add, only need lengths & proxy set correctly
			if(!(pFX->m_prs->m_dwOptions & CRecordset::optimizeBulkAdd))
			{
				*pFX->m_pstr += '?';
				*pFX->m_pstr += pFX->m_lpszSeparator;
				pFX->m_nParamFields++;
				AFX_SQL_SYNC(::SQLBindParameter(pFX->m_hstmt,
					(UWORD)pFX->m_nParamFields, SQL_PARAM_INPUT,
					SQL_C_CHAR, pODBCInfo->m_nSQLType, nMaxLength,
					pODBCInfo->m_nScale, lpszData, 0, plLength));
			}
		}
		return;
#endif // _UNICODE

	case CFieldExchange::MarkForAddNew:
		// can force writing of psuedo-null value (as a non-null) by setting field dirty
		if (!value.IsEmpty())
		{
			pFX->m_prs->SetDirtyFieldStatus(nField - 1);
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		}
		return;

	case CFieldExchange::MarkForUpdate:
		if (value.IsEmpty())
			pFX->m_prs->SetNullFieldStatus(nField - 1);
		else
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		pFX->Default(szName, &value, plLength,
			SQL_C_CHAR, value.GetLength(), nMaxLength);
		return;

	case CFieldExchange::LoadField:
		{
			// Get the field data
			CFieldInfo* pInfo = &pFX->m_prs->m_rgFieldInfos[nField - 1];
			CString* pStrCachedValue = (CString*)pInfo->m_pvDataCache;

			// Restore the status
			pFX->m_prs->SetFieldStatus(nField - 1, pInfo->m_bStatus);

			// If not NULL, restore the value and length
			if (!pFX->m_prs->IsFieldStatusNull(nField - 1))
			{
				value = *pStrCachedValue;
				*plLength = value.GetLength();

#ifdef _UNICODE
				// Must restore proxy for correct WHERE CURRENT OF operation
				USES_CONVERSION;
				LPSTR lpszData = (LPSTR)pFX->m_prs->m_pvFieldProxy[nField-1];
				lstrcpyA(lpszData, T2A((LPCTSTR)value));
#endif // _UNICODE
			}
			else
			{
				*plLength = SQL_NULL_DATA;
			}

#ifdef _DEBUG
			// Buffer address must not change - ODBC's SQLBindCol depends upon this
			void* pvBind;

	#ifdef _UNICODE
			pvBind = pFX->m_prs->m_pvFieldProxy[nField-1];
	#else // !_UNICODE
			pvBind = value.GetBuffer(0);
			value.ReleaseBuffer();
	#endif

			if (pvBind != pInfo->m_pvBindAddress)
			{
				TRACE1("Error: CString buffer (column %u) address has changed!\n",
					nField);
				ASSERT(FALSE);
			}
#endif // _DEBUG
		}
		return;

	case CFieldExchange::StoreField:
		AfxStoreField(*pFX->m_prs, nField, &value);
		return;

	case CFieldExchange::AllocCache:
		{
			CFieldInfo* pInfo = &pFX->m_prs->m_rgFieldInfos[nField - 1];
			pInfo->m_pvDataCache = new CString;
			pInfo->m_nDataType = AFX_RFX_TEXT;
		}
		return;

#ifdef _DEBUG
	case CFieldExchange::DumpField:
		*pFX->m_pdcDump << "\n" << szName << " = " << value;
		return;
#endif // _DEBUG

	}
}

void AFXAPI RFX_Int(CFieldExchange* pFX, LPCTSTR szName, int& value)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	LONG* plLength = pFX->m_prs->GetFieldLengthBuffer(
		nField - 1, pFX->m_nFieldType);
	switch (pFX->m_nOperation)
	{
	case CFieldExchange::BindFieldToColumn:
		{
#ifdef _DEBUG
			// Assumes all bound fields BEFORE unbound fields
			CODBCFieldInfo* pODBCInfo =
				&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];

			if (pODBCInfo->m_nSQLType != SQL_C_SHORT)
			{
				// Warn of possible field schema mismatch
				if (afxTraceFlags & traceDatabase)
					TRACE1("Warning: int converted from SQL type %ld.\n",
						pODBCInfo->m_nSQLType);
			}
#endif // _DEBUG
		}
		// fall through

	default:
LDefault:
		pFX->Default(szName, &value, plLength, SQL_C_LONG,
			sizeof(value), 5);
		return;

	case CFieldExchange::Fixup:
		if (*plLength == SQL_NULL_DATA)
		{
			pFX->m_prs->SetNullFieldStatus(nField - 1);
			value = AFX_RFX_INT_PSEUDO_NULL;
		}
		return;

	case CFieldExchange::SetFieldNull:
		if ((pFX->m_pvField == NULL &&
			pFX->m_nFieldType == CFieldExchange::outputColumn) ||
			pFX->m_pvField == &value)
		{
			if (pFX->m_bField)
			{
				// Mark fields null
				pFX->m_prs->SetNullFieldStatus(nField - 1);
				value = AFX_RFX_INT_PSEUDO_NULL;
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				pFX->m_prs->ClearNullFieldStatus(nField - 1);
				*plLength = sizeof(value);
			}
#ifdef _DEBUG
			pFX->m_nFieldFound = nField;
#endif
		}
		return;

	case CFieldExchange::MarkForAddNew:
		// can force writing of psuedo-null value (as a non-null) by setting field dirty
		if (value != AFX_RFX_INT_PSEUDO_NULL)
		{
			pFX->m_prs->SetDirtyFieldStatus(nField - 1);
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		}
		return;

	case CFieldExchange::MarkForUpdate:
		if (value != AFX_RFX_INT_PSEUDO_NULL)
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		goto LDefault;

	case CFieldExchange::AllocCache:
		{
			CFieldInfo* pInfo = &pFX->m_prs->m_rgFieldInfos[nField - 1];

			// Data cached by value, no allocation necessary
			pInfo->m_nDataType = AFX_RFX_INT;
		}
		return;

#ifdef _DEBUG
	case CFieldExchange::DumpField:
		*pFX->m_pdcDump << "\n" << szName << " = " << value;
		return;
#endif // _DEBUG

	}
}

void AFXAPI RFX_Long(CFieldExchange* pFX, LPCTSTR szName, long& value)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	LONG* plLength = pFX->m_prs->GetFieldLengthBuffer(
		nField - 1, pFX->m_nFieldType);
	switch (pFX->m_nOperation)
	{
	case CFieldExchange::BindFieldToColumn:
		{
#ifdef _DEBUG
			// Assumes all bound fields BEFORE unbound fields
			CODBCFieldInfo* pODBCInfo =
				&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];

			if (pODBCInfo->m_nSQLType != SQL_C_LONG)
			{
				// Warn of possible field schema mismatch
				if (afxTraceFlags & traceDatabase)
					TRACE1("Warning: long converted from SQL type %ld.\n",
						pODBCInfo->m_nSQLType);
			}
#endif // _DEBUG
		}
		// fall through

	default:
LDefault:
		pFX->Default(szName, &value, plLength, SQL_C_LONG,
			sizeof(value), 10);
		return;

	case CFieldExchange::Fixup:
		if (*plLength == SQL_NULL_DATA)
		{
			pFX->m_prs->SetNullFieldStatus(nField - 1);
			value = AFX_RFX_LONG_PSEUDO_NULL;
		}
		return;

	case CFieldExchange::SetFieldNull:
		if ((pFX->m_pvField == NULL &&
			pFX->m_nFieldType == CFieldExchange::outputColumn) ||
			pFX->m_pvField == &value)
		{
			if (pFX->m_bField)
			{
				// Mark fields null
				pFX->m_prs->SetNullFieldStatus(nField - 1);
				value = AFX_RFX_LONG_PSEUDO_NULL;
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				pFX->m_prs->ClearNullFieldStatus(nField - 1);
				*plLength = sizeof(value);
			}
#ifdef _DEBUG
			pFX->m_nFieldFound = nField;
#endif
		}
		return;

	case CFieldExchange::MarkForAddNew:
		// can force writing of psuedo-null value (as a non-null) by setting field dirty
		if (value != AFX_RFX_LONG_PSEUDO_NULL)
		{
			pFX->m_prs->SetDirtyFieldStatus(nField - 1);
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		}
		return;

	case CFieldExchange::MarkForUpdate:
		if (value != AFX_RFX_LONG_PSEUDO_NULL)
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		goto LDefault;

	case CFieldExchange::AllocCache:
		{
			CFieldInfo* pInfo = &pFX->m_prs->m_rgFieldInfos[nField - 1];

			// Data cached by value, no allocation necessary
			pInfo->m_nDataType = AFX_RFX_LONG;
		}
		return;

#ifdef _DEBUG
	case CFieldExchange::DumpField:
		*pFX->m_pdcDump << "\n" << szName << " = " << value;
		return;
#endif // _DEBUG

	}
}

void AFXAPI RFX_Byte(CFieldExchange* pFX, LPCTSTR szName, BYTE& value)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	LONG* plLength = pFX->m_prs->GetFieldLengthBuffer(
		nField - 1, pFX->m_nFieldType);
	switch (pFX->m_nOperation)
	{
	case CFieldExchange::BindFieldToColumn:
		{
#ifdef _DEBUG
			// Assumes all bound fields BEFORE unbound fields
			CODBCFieldInfo* pODBCInfo =
				&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];

			if (pODBCInfo->m_nSQLType != SQL_TINYINT)
			{
				// Warn of possible field schema mismatch
				if (afxTraceFlags & traceDatabase)
					TRACE1("Warning: BYTE converted from SQL type %ld.\n",
						pODBCInfo->m_nSQLType);
			}
#endif // _DEBUG
		}
		// fall through

	default:
LDefault:
		pFX->Default(szName, &value, plLength, SQL_TINYINT,
			sizeof(value), 3);
		break;

	case CFieldExchange::Fixup:
		if (*plLength == SQL_NULL_DATA)
		{
			pFX->m_prs->SetNullFieldStatus(nField - 1);
			value = AFX_RFX_BYTE_PSEUDO_NULL;
		}
		return;

	case CFieldExchange::SetFieldNull:
		if ((pFX->m_pvField == NULL &&
			pFX->m_nFieldType == CFieldExchange::outputColumn) ||
			pFX->m_pvField == &value)
		{
			if (pFX->m_bField)
			{
				// Mark fields null
				pFX->m_prs->SetNullFieldStatus(nField - 1);
				value = AFX_RFX_BYTE_PSEUDO_NULL;
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				pFX->m_prs->ClearNullFieldStatus(nField - 1);
				*plLength = sizeof(value);
			}
#ifdef _DEBUG
			pFX->m_nFieldFound = nField;
#endif
		}
		return;

	case CFieldExchange::MarkForAddNew:
		// can force writing of psuedo-null value (as a non-null) by setting field dirty
		if (value != AFX_RFX_BYTE_PSEUDO_NULL)
		{
			pFX->m_prs->SetDirtyFieldStatus(nField - 1);
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		}
		return;

	case CFieldExchange::MarkForUpdate:
		if (value != AFX_RFX_BYTE_PSEUDO_NULL)
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		goto LDefault;

	case CFieldExchange::AllocCache:
		{
			CFieldInfo* pInfo = &pFX->m_prs->m_rgFieldInfos[nField - 1];

			// Data cached by value, no allocation necessary
			pInfo->m_nDataType = AFX_RFX_BYTE;
		}
		return;

#ifdef _DEBUG
	case CFieldExchange::DumpField:
		*pFX->m_pdcDump << "\n" << szName << " = " << value;
		return;
#endif // _DEBUG

	}
}

void AFXAPI RFX_Bool(CFieldExchange* pFX, LPCTSTR szName, BOOL& value)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	LONG* plLength = pFX->m_prs->GetFieldLengthBuffer(
		nField - 1, pFX->m_nFieldType);
	switch (pFX->m_nOperation)
	{
	case CFieldExchange::BindFieldToColumn:
		{
#ifdef _DEBUG
			// Assumes all bound fields BEFORE unbound fields
			CODBCFieldInfo* pODBCInfo =
				&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];

			if (pODBCInfo->m_nSQLType != SQL_BIT)
			{
				// Warn of possible field schema mismatch
				if (afxTraceFlags & traceDatabase)
					TRACE1("Warning: BOOL converted from SQL type %ld.\n",
						pODBCInfo->m_nSQLType);
			}
#endif // _DEBUG
		}
		// Fall through

	default:
LDefault:
		pFX->Default(szName, &value, plLength, SQL_BIT,
			sizeof(value), 1);
		return;

	case CFieldExchange::Fixup:
		if (*plLength == SQL_NULL_DATA)
		{
			pFX->m_prs->SetNullFieldStatus(nField - 1);
			value = AFX_RFX_BOOL_PSEUDO_NULL;
		}
		else
			// Cast BYTE into BOOL (int)
			value = *(BYTE *)&value;
		return;

	case CFieldExchange::SetFieldNull:
		if ((pFX->m_pvField == NULL &&
			pFX->m_nFieldType == CFieldExchange::outputColumn) ||
			pFX->m_pvField == &value)
		{
			if (pFX->m_bField)
			{
				// Mark fields null
				pFX->m_prs->SetNullFieldStatus(nField - 1);
				value = AFX_RFX_BOOL_PSEUDO_NULL;
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				pFX->m_prs->ClearNullFieldStatus(nField - 1);
				*plLength = sizeof(value);
			}
#ifdef _DEBUG
			pFX->m_nFieldFound = nField;
#endif
		}
		return;

	case CFieldExchange::MarkForAddNew:
		// can force writing of psuedo-null value (as a non-null) by setting field dirty
		if (value != AFX_RFX_BOOL_PSEUDO_NULL)
		{
			pFX->m_prs->SetDirtyFieldStatus(nField - 1);
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		}
		return;

	case CFieldExchange::MarkForUpdate:
		if (value != AFX_RFX_BOOL_PSEUDO_NULL)
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		goto LDefault;

	case CFieldExchange::AllocCache:
		{
			CFieldInfo* pInfo = &pFX->m_prs->m_rgFieldInfos[nField - 1];

			// Data cached by value, no allocation necessary
			pInfo->m_nDataType = AFX_RFX_BOOL;
		}
		return;

#ifdef _DEBUG
	case CFieldExchange::DumpField:
		*pFX->m_pdcDump << "\n" << szName << " = " << value;
		return;
#endif // _DEBUG

	}
}

// Note: CByteArray.m_pData must not be changed.  This address is registered
// with ODBC and must remain valid until the recordset is released.
void AFXAPI RFX_Binary(CFieldExchange* pFX, LPCTSTR szName,
	CByteArray& value, int nMaxLength)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	RETCODE nRetCode;
	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	LONG* plLength = pFX->m_prs->GetFieldLengthBuffer(
		nField - 1, pFX->m_nFieldType);

	BOOL bByRef = FALSE;
	switch (pFX->m_nOperation)
	{
	default:
LDefault:
		{
			void* pvData = NULL;
			if (value.GetSize() > 0)
			{
				if (bByRef)
					pvData = &value;
				else
					pvData = &value[0];
			}

			pFX->Default(szName, pvData, plLength, SQL_C_BINARY,
				(int)value.GetSize(), (UINT)value.GetSize());
		}
		return;

	case CFieldExchange::BindFieldToColumn:
		{
			// Assumes all bound fields BEFORE unbound fields
			CODBCFieldInfo* pODBCInfo =
				&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];
			UDWORD cbColumn = pODBCInfo->m_nPrecision;

#ifdef _DEBUG
			if (pODBCInfo->m_nSQLType != SQL_BINARY &&
				pODBCInfo->m_nSQLType != SQL_VARBINARY &&
				pODBCInfo->m_nSQLType != SQL_LONGVARBINARY)
			{
				// Warn of possible field schema mismatch
				if (afxTraceFlags & traceDatabase)
					TRACE1("Warning: CByteArray converted from SQL type %ld.\n",
						pODBCInfo->m_nSQLType);
			}
#endif // _DEBUG

			// Constrain to user specified max length
			if (cbColumn > (UINT)nMaxLength)
				cbColumn = nMaxLength;
			value.SetSize(cbColumn);
			AFX_SQL_SYNC(::SQLBindCol(pFX->m_prs->m_hstmt, (UWORD)nField,
				SQL_C_BINARY, &value[0], (LONG)cbColumn, plLength));
			if (!pFX->m_prs->Check(nRetCode))
				pFX->m_prs->ThrowDBException(nRetCode);

			// Add the member address to the field map
			pFX->m_prs->m_mapFieldIndex.SetAt(&value, (void*)nField);
		}
		return;

	case CFieldExchange::Fixup:
		if (*plLength == SQL_NULL_DATA)
		{
			pFX->m_prs->SetNullFieldStatus(nField - 1);
			value.SetSize(1);
			value[0] = AFX_RFX_BYTE_PSEUDO_NULL;
		}
		else
		{
			ASSERT(*plLength <= (LONG)nMaxLength);
			((CDBByteArray&)value).SetLength((UINT)*plLength);
		}
		return;

	case CFieldExchange::SetFieldNull:
		if ((pFX->m_pvField == NULL &&
			pFX->m_nFieldType == CFieldExchange::outputColumn) ||
			pFX->m_pvField == &value)
		{
			if (pFX->m_bField)
			{
				// Mark fields null
				pFX->m_prs->SetNullFieldStatus(nField - 1);
				value.SetSize(1);
				value[0] = AFX_RFX_BYTE_PSEUDO_NULL;
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				pFX->m_prs->ClearNullFieldStatus(nField - 1);
				*plLength = value.GetSize();
			}
#ifdef _DEBUG
			pFX->m_nFieldFound = nField;
#endif
		}
		return;

	case CFieldExchange::MarkForAddNew:
		// can force writing of psuedo-null value (as a non-null) by setting field dirty
		if (value.GetSize() != 1 || value[0] != AFX_RFX_BYTE_PSEUDO_NULL)
		{
			pFX->m_prs->SetDirtyFieldStatus(nField - 1);
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		}
		return;

	case CFieldExchange::MarkForUpdate:
		if (value.GetSize() != 1 || value[0] != AFX_RFX_BYTE_PSEUDO_NULL)
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		bByRef = TRUE;
		goto LDefault;

	case CFieldExchange::StoreField:
		AfxStoreField(*pFX->m_prs, nField, &value);
		return;

	case CFieldExchange::LoadField:
		AfxLoadField(*pFX->m_prs, nField, &value, plLength);
		return;

	case CFieldExchange::AllocCache:
		{
			CFieldInfo* pInfo = &pFX->m_prs->m_rgFieldInfos[nField - 1];
			pInfo->m_pvDataCache = new CByteArray;
			pInfo->m_nDataType = AFX_RFX_BINARY;
		}
		return;

#ifdef _DEBUG
	case CFieldExchange::DumpField:
		*pFX->m_pdcDump << "\n" << szName << ":";
		value.Dump(*pFX->m_pdcDump);
		return;
#endif // _DEBUG

	}
}

void AFXAPI RFX_Date(CFieldExchange* pFX, LPCTSTR szName, CTime& value)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	RETCODE nRetCode;
	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	LONG* plLength = pFX->m_prs->GetFieldLengthBuffer(
		nField - 1, pFX->m_nFieldType);
	switch (pFX->m_nOperation)
	{
	default:
LDefault:
		pFX->Default(szName, &value, plLength, SQL_C_TIMESTAMP,
			sizeof(value), TIMESTAMP_PRECISION);
		return;

	case CFieldExchange::BindParam:
		{
			TIMESTAMP_STRUCT* pts;
			pFX->m_prs->m_bRebindParams = TRUE;

			if (pFX->m_prs->IsParamStatusNull(nField - 1))
			{
				pts = NULL;
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				// Allocate proxy array if necessary
				if (pFX->m_prs->m_pvParamProxy == NULL)
				{
					pFX->m_prs->m_pvParamProxy = new void*[pFX->m_prs->m_nParams];
					memset(pFX->m_prs->m_pvParamProxy, 0,
						pFX->m_prs->m_nParams*sizeof(void*));
					pFX->m_prs->m_nProxyParams = pFX->m_prs->m_nParams;
				}

				// Allocate TIMESTAMP_STRUCT if necessary for SQLBindParameter
				if (pFX->m_prs->m_pvParamProxy[nField-1] == NULL)
				{
					pts = new TIMESTAMP_STRUCT;
					pFX->m_prs->m_pvParamProxy[nField-1] = pts;
				}
				else
					pts = (TIMESTAMP_STRUCT *)pFX->m_prs->m_pvParamProxy[nField-1];

				pts->year = (SWORD)value.GetYear();
				pts->month = (UWORD)value.GetMonth();
				pts->day = (UWORD)value.GetDay();
				pts->hour = (UWORD)value.GetHour();
				pts->minute = (UWORD)value.GetMinute();
				pts->second = (UWORD)value.GetSecond();
				pts->fraction = 0;
				*plLength = sizeof(TIMESTAMP_STRUCT);
			}

			AFX_SQL_SYNC(::SQLBindParameter(pFX->m_hstmt, (UWORD)nField,
				(SWORD)pFX->m_nFieldType, SQL_C_TIMESTAMP, SQL_C_TIMESTAMP,
				TIMESTAMP_PRECISION, 0, pts, 0, plLength));
			if (nRetCode != SQL_SUCCESS)
				pFX->m_prs->ThrowDBException(nRetCode, pFX->m_hstmt);

			// Add the member address to the param map
			pFX->m_prs->m_mapParamIndex.SetAt(&value, (void*)nField);
		}
		return;

	case CFieldExchange::RebindParam:
		{
			*plLength = pFX->m_prs->IsParamStatusNull(nField - 1) ?
				SQL_NULL_DATA : sizeof(TIMESTAMP_STRUCT);
			if (pFX->m_prs->m_nProxyParams != 0)
			{
				// Fill buffer (expected by SQLBindParameter) with new param data
				TIMESTAMP_STRUCT* pts;
				pts = (TIMESTAMP_STRUCT *)pFX->m_prs->m_pvParamProxy[nField-1];
				pts->year = (SWORD)value.GetYear();
				pts->month = (UWORD)value.GetMonth();
				pts->day = (UWORD)value.GetDay();
				pts->hour = (UWORD)value.GetHour();
				pts->minute = (UWORD)value.GetMinute();
				pts->second = (UWORD)value.GetSecond();
				pts->fraction = 0;
			}
		}
		return;

	case CFieldExchange::BindFieldToColumn:
		{
#ifdef _DEBUG
			// Assumes all bound fields BEFORE unbound fields
			CODBCFieldInfo* pODBCInfo =
				&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];

			if (pODBCInfo->m_nSQLType != SQL_DATE &&
				pODBCInfo->m_nSQLType != SQL_TIME &&
				pODBCInfo->m_nSQLType != SQL_TIMESTAMP)
			{
				// Warn of possible field schema mismatch
				if (afxTraceFlags & traceDatabase)
					TRACE1("Warning: CTime converted from SQL type %ld.\n",
						pODBCInfo->m_nSQLType);
			}
#endif // _DEBUG

			// Allocate proxy array if necessary
			if (pFX->m_prs->m_pvFieldProxy == NULL)
			{
				pFX->m_prs->m_pvFieldProxy = new void*[pFX->m_prs->m_nFields];
				memset(pFX->m_prs->m_pvFieldProxy, 0,
					pFX->m_prs->m_nFields*sizeof(void*));
				pFX->m_prs->m_nProxyFields = pFX->m_prs->m_nFields;
			}

			// Allocate TIMESTAMP_STRUCT for SQLBindCol (not necessary on Requery)
			if (pFX->m_prs->m_pvFieldProxy[nField-1] == NULL)
				pFX->m_prs->m_pvFieldProxy[nField-1] = new TIMESTAMP_STRUCT;

			AFX_SQL_SYNC(::SQLBindCol(pFX->m_prs->m_hstmt, (UWORD)nField,
				SQL_C_TIMESTAMP, pFX->m_prs->m_pvFieldProxy[nField-1],
				sizeof(TIMESTAMP_STRUCT), plLength));
			if (!pFX->m_prs->Check(nRetCode))
				pFX->m_prs->ThrowDBException(nRetCode);

			// Add the member address to the field map
			pFX->m_prs->m_mapFieldIndex.SetAt(&value, (void*)nField);
		}
		return;

	case CFieldExchange::BindFieldForUpdate:
		if (pFX->m_prs->m_nProxyFields != 0)
		{
			// Fill buffer (expected by SQLSetPos) with new field data
			TIMESTAMP_STRUCT* pts;
			pts = (TIMESTAMP_STRUCT *)pFX->m_prs->m_pvFieldProxy[nField-1];
			pts->year = (SWORD)value.GetYear();
			pts->month = (UWORD)value.GetMonth();
			pts->day = (UWORD)value.GetDay();
			pts->hour = (UWORD)value.GetHour();
			pts->minute = (UWORD)value.GetMinute();
			pts->second = (UWORD)value.GetSecond();
			pts->fraction = 0;

			pFX->Default(szName, (void *)pts, plLength, SQL_C_TIMESTAMP,
				sizeof(TIMESTAMP_STRUCT), TIMESTAMP_PRECISION);
		}
		return;

	case CFieldExchange::Fixup:
		if (*plLength == SQL_NULL_DATA)
		{
			pFX->m_prs->SetNullFieldStatus(nField - 1);
			value = AFX_RFX_DATE_PSEUDO_NULL;
		}
		else
		{
			TIMESTAMP_STRUCT* pts =
				(TIMESTAMP_STRUCT*)pFX->m_prs->m_pvFieldProxy[nField-1];
			if (pts->year < 1970 || pts->year > 2038)
			{
				// Time value out of range, return NULL
#ifdef _DEBUG
				if (afxTraceFlags & traceDatabase)
					TRACE0("Warning: date value out of range, returning NULL value.\n");
#endif
				pFX->m_prs->SetNullFieldStatus(nField - 1);
				value = AFX_RFX_DATE_PSEUDO_NULL;
			}
			else
			{
#ifdef _DEBUG
				if ((afxTraceFlags & traceDatabase) && pts->fraction != 0)
					TRACE0("Warning: ignoring milliseconds.\n");
#endif
				value = CTime(pts->year, pts->month, pts->day,
					pts->hour, pts->minute, pts->second);
			}
		}
		return;

	case CFieldExchange::NameValue:
		if (pFX->m_prs->IsFieldStatusDirty(nField - 1))
		{
			*pFX->m_pstr += szName;
			*pFX->m_pstr += '=';
		}
		// Fall through

	case CFieldExchange::Value:
		if (pFX->m_prs->IsFieldStatusDirty(nField - 1))
		{
			TIMESTAMP_STRUCT* pts =
				(TIMESTAMP_STRUCT*)pFX->m_prs->m_pvFieldProxy[nField-1];
			if (pFX->m_prs->IsFieldStatusNull(nField - 1))
			{
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				pts->year = (SWORD)value.GetYear();
				pts->month = (UWORD)value.GetMonth();
				pts->day = (UWORD)value.GetDay();
				pts->hour = (UWORD)value.GetHour();
				pts->minute = (UWORD)value.GetMinute();
				pts->second = (UWORD)value.GetSecond();
				pts->fraction = 0;
				*plLength = sizeof(TIMESTAMP_STRUCT);
			}

			// If optimizing for bulk add, only need lengths & proxy set correctly
			if(!(pFX->m_prs->m_dwOptions & CRecordset::optimizeBulkAdd))
			{
				*pFX->m_pstr += '?';
				*pFX->m_pstr += pFX->m_lpszSeparator;
				pFX->m_nParamFields++;

				// Assumes all bound fields BEFORE unbound fields
				CODBCFieldInfo* pODBCInfo =
					&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];

				AFX_SQL_SYNC(::SQLBindParameter(pFX->m_hstmt,
					(UWORD)pFX->m_nParamFields, SQL_PARAM_INPUT,
					SQL_C_TIMESTAMP, pODBCInfo->m_nSQLType,
					TIMESTAMP_PRECISION, 0, pts, 0, plLength));
			}
		}
		return;

	case CFieldExchange::SetFieldNull:
		if ((pFX->m_pvField == NULL &&
			pFX->m_nFieldType == CFieldExchange::outputColumn) ||
			pFX->m_pvField == &value)
		{
			if (pFX->m_bField)
			{
				// Mark fields null
				pFX->m_prs->SetNullFieldStatus(nField - 1);
				value = AFX_RFX_DATE_PSEUDO_NULL;
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				pFX->m_prs->ClearNullFieldStatus(nField - 1);
				*plLength = sizeof(TIMESTAMP_STRUCT);
			}
#ifdef _DEBUG
			pFX->m_nFieldFound = nField;
#endif
		}
		return;

	case CFieldExchange::MarkForAddNew:
		{
			// can force writing of psuedo-null value (as a non-null) by setting field dirty
			CTime timeNull = AFX_RFX_DATE_PSEUDO_NULL;
			if (value != timeNull)
			{
				pFX->m_prs->SetDirtyFieldStatus(nField - 1);
				pFX->m_prs->ClearNullFieldStatus(nField - 1);
			}
		}
		return;

	case CFieldExchange::MarkForUpdate:
		{
			CTime timeNull = AFX_RFX_DATE_PSEUDO_NULL;
			if (value != timeNull)
				pFX->m_prs->ClearNullFieldStatus(nField - 1);
		}
		goto LDefault;

	case CFieldExchange::LoadField:
		{
			// Get the field data
			CFieldInfo* pInfo = &pFX->m_prs->m_rgFieldInfos[nField - 1];

			// Restore the status
			pFX->m_prs->SetFieldStatus(nField - 1, pInfo->m_bStatus);

			// If not NULL, restore the value, length and proxy
			if (!pFX->m_prs->IsFieldStatusNull(nField - 1))
			{
				AfxCopyValueByRef(pInfo->m_pvDataCache, &value,
					plLength, pInfo->m_nDataType);

				// Restore proxy for correct WHERE CURRENT OF operations
				TIMESTAMP_STRUCT* pts =
					(TIMESTAMP_STRUCT*)pFX->m_prs->m_pvFieldProxy[nField-1];

				pts->year = (SWORD)value.GetYear();
				pts->month = (UWORD)value.GetMonth();
				pts->day = (UWORD)value.GetDay();
				pts->hour = (UWORD)value.GetHour();
				pts->minute = (UWORD)value.GetMinute();
				pts->second = (UWORD)value.GetSecond();
				pts->fraction = 0;
			}
			else
				*plLength = SQL_NULL_DATA;

#ifdef _DEBUG
			// Buffer address must not change - ODBC's SQLBindCol depends upon this
			if (pInfo->m_pvBindAddress != pFX->m_prs->m_pvFieldProxy[nField-1])
			{
				TRACE1("Error: CString buffer (column %u) address has changed!\n",
					nField);
				ASSERT(FALSE);
			}
#endif // _DEBUG
		}
		return;

	case CFieldExchange::AllocCache:
		{
			CFieldInfo* pInfo = &pFX->m_prs->m_rgFieldInfos[nField - 1];
			pInfo->m_pvDataCache = new CTime;
			pInfo->m_nDataType = AFX_RFX_DATE;
		}
		return;

#ifdef _DEBUG
	case CFieldExchange::DumpField:
		*pFX->m_pdcDump << "\n" << szName << " = " << value;
		return;
#endif // _DEBUG

	}
}

void AFXAPI RFX_Date(CFieldExchange* pFX, LPCTSTR szName,
	TIMESTAMP_STRUCT& value)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	LONG* plLength = pFX->m_prs->GetFieldLengthBuffer(
		nField - 1, pFX->m_nFieldType);
	switch (pFX->m_nOperation)
	{
	case CFieldExchange::BindFieldToColumn:
		{
#ifdef _DEBUG
			// Assumes all bound fields BEFORE unbound fields
			CODBCFieldInfo* pODBCInfo =
				&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];

			if (pODBCInfo->m_nSQLType != SQL_DATE &&
				pODBCInfo->m_nSQLType != SQL_TIME &&
				pODBCInfo->m_nSQLType != SQL_TIMESTAMP)
			{
				// Warn of possible field schema mismatch
				if (afxTraceFlags & traceDatabase)
					TRACE1("Warning: TIMESTAMP_STRUCT converted from SQL type %ld.\n",
						pODBCInfo->m_nSQLType);
			}
#endif // _DEBUG
			// fall through
		}

	default:
LDefault:
		pFX->Default(szName, &value, plLength, SQL_C_TIMESTAMP,
			sizeof(value), TIMESTAMP_PRECISION);
		return;

	case CFieldExchange::Fixup:
		if (*plLength == SQL_NULL_DATA)
		{
			pFX->m_prs->SetNullFieldStatus(nField - 1);
			value.year = AFX_RFX_TIMESTAMP_PSEUDO_NULL;
			value.month = AFX_RFX_TIMESTAMP_PSEUDO_NULL;
			value.day = AFX_RFX_TIMESTAMP_PSEUDO_NULL;
			value.hour = AFX_RFX_TIMESTAMP_PSEUDO_NULL;
			value.minute = AFX_RFX_TIMESTAMP_PSEUDO_NULL;
			value.second = AFX_RFX_TIMESTAMP_PSEUDO_NULL;
			value.fraction = AFX_RFX_TIMESTAMP_PSEUDO_NULL;
		}
		return;

	case CFieldExchange::SetFieldNull:
		if ((pFX->m_pvField == NULL &&
			pFX->m_nFieldType == CFieldExchange::outputColumn) ||
			pFX->m_pvField == &value)
		{
			if (pFX->m_bField)
			{
				// Mark fields null
				pFX->m_prs->SetNullFieldStatus(nField - 1);
				value.year = AFX_RFX_TIMESTAMP_PSEUDO_NULL;
				value.month = AFX_RFX_TIMESTAMP_PSEUDO_NULL;
				value.day = AFX_RFX_TIMESTAMP_PSEUDO_NULL;
				value.hour = AFX_RFX_TIMESTAMP_PSEUDO_NULL;
				value.minute = AFX_RFX_TIMESTAMP_PSEUDO_NULL;
				value.second = AFX_RFX_TIMESTAMP_PSEUDO_NULL;
				value.fraction = AFX_RFX_TIMESTAMP_PSEUDO_NULL;
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				pFX->m_prs->ClearNullFieldStatus(nField - 1);
				*plLength = sizeof(TIMESTAMP_STRUCT);
			}
#ifdef _DEBUG
			pFX->m_nFieldFound = nField;
#endif
		}
		return;

	case CFieldExchange::MarkForAddNew:
		// can force writing of psuedo-null value (as a non-null) by setting field dirty
		if (!(value.year == AFX_RFX_TIMESTAMP_PSEUDO_NULL &&
			value.month == AFX_RFX_TIMESTAMP_PSEUDO_NULL &&
			value.day == AFX_RFX_TIMESTAMP_PSEUDO_NULL &&
			value.hour == AFX_RFX_TIMESTAMP_PSEUDO_NULL &&
			value.minute == AFX_RFX_TIMESTAMP_PSEUDO_NULL &&
			value.second == AFX_RFX_TIMESTAMP_PSEUDO_NULL &&
			value.fraction == AFX_RFX_TIMESTAMP_PSEUDO_NULL ))
		{
			pFX->m_prs->SetDirtyFieldStatus(nField - 1);
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		}
		return;

	case CFieldExchange::MarkForUpdate:
		if (!(value.year == AFX_RFX_TIMESTAMP_PSEUDO_NULL &&
			value.month == AFX_RFX_TIMESTAMP_PSEUDO_NULL &&
			value.day == AFX_RFX_TIMESTAMP_PSEUDO_NULL &&
			value.hour == AFX_RFX_TIMESTAMP_PSEUDO_NULL &&
			value.minute == AFX_RFX_TIMESTAMP_PSEUDO_NULL &&
			value.second == AFX_RFX_TIMESTAMP_PSEUDO_NULL &&
			value.fraction == AFX_RFX_TIMESTAMP_PSEUDO_NULL ))
		{
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		}
		goto LDefault;

	case CFieldExchange::AllocCache:
		{
			CFieldInfo* pInfo = &pFX->m_prs->m_rgFieldInfos[nField - 1];
			pInfo->m_pvDataCache = new TIMESTAMP_STRUCT;
			pInfo->m_nDataType = AFX_RFX_TIMESTAMP;
		}
		return;

#ifdef _DEBUG
	case CFieldExchange::DumpField:
		*pFX->m_pdcDump << "\n" << szName << ".year = " << (int)value.year;
		*pFX->m_pdcDump << "\n" << szName << ".month = " << value.month;
		*pFX->m_pdcDump << "\n" << szName << ".day = " << value.day;
		*pFX->m_pdcDump << "\n" << szName << ".hour = " << value.hour;
		*pFX->m_pdcDump << "\n" << szName << ".minute = " << value.minute;
		*pFX->m_pdcDump << "\n" << szName << ".second = " << value.second;
		*pFX->m_pdcDump << "\n" << szName << ".fraction = " << value.fraction;
		return;
#endif // _DEBUG

	}
}

void AFXAPI RFX_Date(CFieldExchange* pFX, LPCTSTR szName,
	COleDateTime& value)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	RETCODE nRetCode;
	LONG* plLength = pFX->m_prs->GetFieldLengthBuffer(
		nField - 1, pFX->m_nFieldType);
	switch (pFX->m_nOperation)
	{
	case CFieldExchange::BindParam:
		{
			TIMESTAMP_STRUCT* pts;
			pFX->m_prs->m_bRebindParams = TRUE;

			if (pFX->m_prs->IsParamStatusNull(nField - 1))
			{
				pts = NULL;
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				// Allocate proxy array if necessary
				if (pFX->m_prs->m_pvParamProxy == NULL)
				{
					pFX->m_prs->m_pvParamProxy = new void*[pFX->m_prs->m_nParams];
					memset(pFX->m_prs->m_pvParamProxy, 0,
						pFX->m_prs->m_nParams*sizeof(void*));
					pFX->m_prs->m_nProxyParams = pFX->m_prs->m_nParams;
				}

				// Allocate TIMESTAMP_STRUCT if necessary for SQLBindParameter
				if (pFX->m_prs->m_pvParamProxy[nField-1] == NULL)
				{
					pts = new TIMESTAMP_STRUCT;
					pFX->m_prs->m_pvParamProxy[nField-1] = pts;
				}
				else
					pts = (TIMESTAMP_STRUCT *)pFX->m_prs->m_pvParamProxy[nField-1];

				pts->year = (SWORD)value.GetYear();
				pts->month = (UWORD)value.GetMonth();
				pts->day = (UWORD)value.GetDay();
				pts->hour = (UWORD)value.GetHour();
				pts->minute = (UWORD)value.GetMinute();
				pts->second = (UWORD)value.GetSecond();
				pts->fraction = 0;
				*plLength = sizeof(TIMESTAMP_STRUCT);
			}

			AFX_SQL_SYNC(::SQLBindParameter(pFX->m_hstmt, (UWORD)nField,
				(SWORD)pFX->m_nFieldType, SQL_C_TIMESTAMP, SQL_C_TIMESTAMP,
				TIMESTAMP_PRECISION, 0, pts, 0, plLength));
			if (nRetCode != SQL_SUCCESS)
				pFX->m_prs->ThrowDBException(nRetCode, pFX->m_hstmt);

			// Add the member address to the param map
			pFX->m_prs->m_mapParamIndex.SetAt(&value, (void*)nField);
		}
		return;

	case CFieldExchange::NameValue:
		if (pFX->m_prs->IsFieldStatusDirty(nField - 1))
		{
			*pFX->m_pstr += szName;
			*pFX->m_pstr += '=';
		}
		// Fall through

	case CFieldExchange::Value:
		if (pFX->m_prs->IsFieldStatusDirty(nField - 1))
		{
			TIMESTAMP_STRUCT* pts =
				(TIMESTAMP_STRUCT*)pFX->m_prs->m_pvFieldProxy[nField-1];
			if (pFX->m_prs->IsFieldStatusNull(nField - 1))
			{
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				pts->year = (SWORD)value.GetYear();
				pts->month = (UWORD)value.GetMonth();
				pts->day = (UWORD)value.GetDay();
				pts->hour = (UWORD)value.GetHour();
				pts->minute = (UWORD)value.GetMinute();
				pts->second = (UWORD)value.GetSecond();
				pts->fraction = 0;
				*plLength = sizeof(TIMESTAMP_STRUCT);
			}

			// If optimizing for bulk add, only need lengths & proxy set correctly
			if(!(pFX->m_prs->m_dwOptions & CRecordset::optimizeBulkAdd))
			{
				*pFX->m_pstr += '?';
				*pFX->m_pstr += pFX->m_lpszSeparator;
				pFX->m_nParamFields++;

				// Assumes all bound fields BEFORE unbound fields
				CODBCFieldInfo* pODBCInfo =
					&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];

				AFX_SQL_SYNC(::SQLBindParameter(pFX->m_hstmt,
					(UWORD)pFX->m_nParamFields, SQL_PARAM_INPUT,
					SQL_C_TIMESTAMP, pODBCInfo->m_nSQLType,
					TIMESTAMP_PRECISION, 0, pts, 0, plLength));
			}
		}
		return;

	case CFieldExchange::RebindParam:
		{
			*plLength = pFX->m_prs->IsParamStatusNull(nField - 1) ?
				SQL_NULL_DATA : sizeof(TIMESTAMP_STRUCT);
			if (pFX->m_prs->m_nProxyParams != 0)
			{
				// Fill buffer (expected by SQLBindParameter) with new param data
				TIMESTAMP_STRUCT* pts;
				pts = (TIMESTAMP_STRUCT *)pFX->m_prs->m_pvParamProxy[nField-1];
				pts->year = (SWORD)value.GetYear();
				pts->month = (UWORD)value.GetMonth();
				pts->day = (UWORD)value.GetDay();
				pts->hour = (UWORD)value.GetHour();
				pts->minute = (UWORD)value.GetMinute();
				pts->second = (UWORD)value.GetSecond();
				pts->fraction = 0;
			}
		}
		return;

	case CFieldExchange::BindFieldForUpdate:
		if (pFX->m_prs->m_nProxyFields != 0)
		{
			// Fill buffer (expected by SQLSetPos) with new field data
			TIMESTAMP_STRUCT* pts;
			pts = (TIMESTAMP_STRUCT *)pFX->m_prs->m_pvFieldProxy[nField-1];
			pts->year = (SWORD)value.GetYear();
			pts->month = (UWORD)value.GetMonth();
			pts->day = (UWORD)value.GetDay();
			pts->hour = (UWORD)value.GetHour();
			pts->minute = (UWORD)value.GetMinute();
			pts->second = (UWORD)value.GetSecond();
			pts->fraction = 0;

			pFX->Default(szName, (void *)pts, plLength, SQL_C_TIMESTAMP,
				sizeof(TIMESTAMP_STRUCT), TIMESTAMP_PRECISION);
		}
		return;

	case CFieldExchange::LoadField:
		{
			// Get the field data
			CFieldInfo* pInfo = &pFX->m_prs->m_rgFieldInfos[nField - 1];

			// Restore the status
			pFX->m_prs->SetFieldStatus(nField - 1, pInfo->m_bStatus);

			// If not NULL, restore the value, length and proxy
			if (!pFX->m_prs->IsFieldStatusNull(nField - 1))
			{
				AfxCopyValueByRef(pInfo->m_pvDataCache, &value,
					plLength, pInfo->m_nDataType);

				// Restore proxy for correct WHERE CURRENT OF operations
				TIMESTAMP_STRUCT* pts =
					(TIMESTAMP_STRUCT*)pFX->m_prs->m_pvFieldProxy[nField-1];

				pts->year = (SWORD)value.GetYear();
				pts->month = (UWORD)value.GetMonth();
				pts->day = (UWORD)value.GetDay();
				pts->hour = (UWORD)value.GetHour();
				pts->minute = (UWORD)value.GetMinute();
				pts->second = (UWORD)value.GetSecond();
				pts->fraction = 0;
			}
			else
				*plLength = SQL_NULL_DATA;

#ifdef _DEBUG
			// Buffer address must not change - ODBC's SQLBindCol depends upon this
			if (pInfo->m_pvBindAddress != pFX->m_prs->m_pvFieldProxy[nField-1])
			{
				TRACE1("Error: CString buffer (column %u) address has changed!\n",
					nField);
				ASSERT(FALSE);
			}
#endif // _DEBUG
		}
		return;

	case CFieldExchange::BindFieldToColumn:
		{
#ifdef _DEBUG
			// Assumes all bound fields BEFORE unbound fields
			CODBCFieldInfo* pODBCInfo =
				&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];

			if (pODBCInfo->m_nSQLType != SQL_DATE &&
				pODBCInfo->m_nSQLType != SQL_TIME &&
				pODBCInfo->m_nSQLType != SQL_TIMESTAMP)
			{
				// Warn of possible field schema mismatch
				if (afxTraceFlags & traceDatabase)
					TRACE1("Warning: COleDateTime converted from SQL type %ld.\n",
						pODBCInfo->m_nSQLType);
			}
#endif // _DEBUG

			// Allocate proxy array if necessary
			if (pFX->m_prs->m_pvFieldProxy == NULL)
			{
				pFX->m_prs->m_pvFieldProxy = new void*[pFX->m_prs->m_nFields];
				memset(pFX->m_prs->m_pvFieldProxy, 0,
					pFX->m_prs->m_nFields*sizeof(void*));
				pFX->m_prs->m_nProxyFields = pFX->m_prs->m_nFields;
			}

			// Allocate TIMESTAMP_STRUCT for SQLBindCol (not necessary on Requery)
			if (pFX->m_prs->m_pvFieldProxy[nField-1] == NULL)
				pFX->m_prs->m_pvFieldProxy[nField-1] = new TIMESTAMP_STRUCT;

			AFX_SQL_SYNC(::SQLBindCol(pFX->m_prs->m_hstmt, (UWORD)nField,
				SQL_C_TIMESTAMP, pFX->m_prs->m_pvFieldProxy[nField-1],
				sizeof(TIMESTAMP_STRUCT), plLength));
			if (!pFX->m_prs->Check(nRetCode))
				pFX->m_prs->ThrowDBException(nRetCode);

			// Add the member address to the field map
			pFX->m_prs->m_mapFieldIndex.SetAt(&value, (void*)nField);
		}
		return;

	default:
LDefault:
		pFX->Default(szName, &value, plLength, SQL_C_TIMESTAMP,
			sizeof(value), TIMESTAMP_PRECISION);
		return;

	case CFieldExchange::AllocCache:
		{
			CFieldInfo* pInfo = &pFX->m_prs->m_rgFieldInfos[nField - 1];
			pInfo->m_pvDataCache = new COleDateTime;
			pInfo->m_nDataType = AFX_RFX_OLEDATE;
		}
		return;

	case CFieldExchange::Fixup:
		if (*plLength == SQL_NULL_DATA)
		{
			pFX->m_prs->SetNullFieldStatus(nField - 1);
			value.SetStatus(COleDateTime::null);
		}
		else
		{
			TIMESTAMP_STRUCT* pts =
				(TIMESTAMP_STRUCT*)pFX->m_prs->m_pvFieldProxy[nField-1];
#ifdef _DEBUG
			if ((afxTraceFlags & traceDatabase) && pts->fraction != 0)
				TRACE0("Warning: ignoring milliseconds.\n");
#endif
			value = COleDateTime(pts->year, pts->month, pts->day,
				pts->hour, pts->minute, pts->second);
		}
		return;

	case CFieldExchange::SetFieldNull:
		if ((pFX->m_pvField == NULL &&
			pFX->m_nFieldType == CFieldExchange::outputColumn) ||
			pFX->m_pvField == &value)
		{
			if (pFX->m_bField)
			{
				// Mark fields null
				pFX->m_prs->SetNullFieldStatus(nField - 1);
				value.SetStatus(COleDateTime::null);
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				pFX->m_prs->ClearNullFieldStatus(nField - 1);
				*plLength = sizeof(TIMESTAMP_STRUCT);
			}
#ifdef _DEBUG
			pFX->m_nFieldFound = nField;
#endif
		}
		return;

	case CFieldExchange::MarkForAddNew:
		// can force writing of psuedo-null value (as a non-null) by setting field dirty
		if (value.GetStatus() != COleDateTime::null)
		{
			pFX->m_prs->SetDirtyFieldStatus(nField - 1);
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		}
		return;

	case CFieldExchange::MarkForUpdate:
		if (value.GetStatus() != COleDateTime::null)
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		goto LDefault;

#ifdef _DEBUG
	case CFieldExchange::DumpField:
		CString str;
		str = value.Format();
		*pFX->m_pdcDump << "\n" << str;
		return;
#endif // _DEBUG

	}
}

void AFXAPI RFX_LongBinary(CFieldExchange* pFX, LPCTSTR szName,
	CLongBinary& value)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	RETCODE nRetCode;
	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	LONG* plLength = pFX->m_prs->GetFieldLengthBuffer(
		nField - 1, pFX->m_nFieldType);
	switch (pFX->m_nOperation)
	{
	case CFieldExchange::Name:
		pFX->m_prs->m_bLongBinaryColumns = TRUE;
		pFX->Default(szName, &value, plLength, SQL_C_DEFAULT, 0, 0);
		return;

	case CFieldExchange::BindFieldToColumn:
		// Don't bind if using update SQL, simply do SQLGetData on Fixup
		if (!pFX->m_prs->m_bUseUpdateSQL && pFX->m_prs->CanUpdate())
		{
			// Bind for updates with cb=0 now. Driver may not support post Execute or ExtendedFetch binding
			AFX_SQL_SYNC(::SQLBindCol(pFX->m_prs->m_hstmt, (UWORD)nField, SQL_C_DEFAULT,
				&value, 0, plLength));
			if (!pFX->m_prs->Check(nRetCode))
				pFX->m_prs->ThrowDBException(nRetCode);
		}

		// Add the member address to the field map
		pFX->m_prs->m_mapFieldIndex.SetAt(&value, (void*)nField);
		return;

#ifdef _DEBUG
	case CFieldExchange::BindParam:
		// CLongBinary parameters are not supported
		ASSERT(FALSE);

	case CFieldExchange::MarkForAddNew:
	case CFieldExchange::MarkForUpdate:
		// We do not archive LongBinary values
	case CFieldExchange::StoreField:
	case CFieldExchange::LoadField:
		// We do not archive LongBinary values
#endif // _DEBUG
	default:
		return;

	case CFieldExchange::Fixup:
		// Get the size of the long binary field
		*plLength = pFX->GetLongBinarySize(nField);

		// Get the data if necessary
		if (*plLength != SQL_NULL_DATA)
			pFX->GetLongBinaryData(nField, value, plLength);

		// Set the status and length
		if (*plLength == SQL_NULL_DATA)
		{
			// Field NULL, set length and status
			value.m_dwDataLength = 0;
			pFX->m_prs->SetNullFieldStatus(nField - 1);
		}
		else
		{
			// Field not NULL, clear the status (length already set)
			pFX->m_prs->ClearNullFieldStatus(nField - 1);
		}

		return;

	case CFieldExchange::NameValue:
		if (pFX->m_prs->IsFieldStatusDirty(nField - 1))
		{
			*pFX->m_pstr += szName;
			*pFX->m_pstr += '=';
		}

		// Fall through
	case CFieldExchange::Value:
		if (pFX->m_prs->IsFieldStatusDirty(nField - 1))
		{
			// If user marked column NULL, reflect this in length
			if (pFX->m_prs->IsFieldStatusNull(nField - 1))
				*plLength = SQL_NULL_DATA;
			else
			{
				// Indicate data will be sent after SQLExecute
				// Length is signed value, it's limited by LONG_MAX
				if (value.m_dwDataLength >
					(ULONG)(LONG_MAX - labs(SQL_LEN_DATA_AT_EXEC_OFFSET)))
				{
					ASSERT(FALSE);
					*plLength = LONG_MAX - labs(SQL_LEN_DATA_AT_EXEC_OFFSET);
				}
				else
					*plLength = value.m_dwDataLength;

				*plLength = SQL_LEN_DATA_AT_EXEC(*plLength);
			}

			// If optimizing for bulk add, only need lengths set correctly
			if(!(pFX->m_prs->m_dwOptions & CRecordset::optimizeBulkAdd))
			{
				*pFX->m_pstr += '?';
				*pFX->m_pstr += pFX->m_lpszSeparator;
				pFX->m_nParamFields++;

				// Assumes all bound fields BEFORE unbound fields
				CODBCFieldInfo* pODBCInfo =
					&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];

				AFX_SQL_SYNC(::SQLBindParameter(pFX->m_hstmt,
					(UWORD)pFX->m_nParamFields, SQL_PARAM_INPUT,
					SQL_C_DEFAULT, pODBCInfo->m_nSQLType,
					value.m_dwDataLength, 0, &value, 0, plLength));
				if (nRetCode != SQL_SUCCESS)
					pFX->m_prs->ThrowDBException(nRetCode, pFX->m_hstmt);
			}
		}
		return;

	case CFieldExchange::BindFieldForUpdate:
		if (pFX->m_prs->IsFieldStatusDirty(nField - 1))
		{
			// If user marked column NULL, reflect this in length
			if (pFX->m_prs->IsFieldStatusNull(nField - 1))
				*plLength = SQL_NULL_DATA;
			else
			{
				// Length is signed value, it's limited by LONG_MAX
				if (value.m_dwDataLength >
					(ULONG)(LONG_MAX - labs(SQL_LEN_DATA_AT_EXEC_OFFSET)))
				{
					ASSERT(FALSE);
					*plLength = LONG_MAX - labs(SQL_LEN_DATA_AT_EXEC_OFFSET);
				}
				else
					*plLength = value.m_dwDataLength;

				*plLength = SQL_LEN_DATA_AT_EXEC(*plLength);
			}
		}
		else
			*plLength = SQL_IGNORE;

		return;

	case CFieldExchange::UnbindFieldForUpdate:
		*plLength = value.m_dwDataLength;
		return;

	case CFieldExchange::SetFieldNull:
		if ((pFX->m_pvField == NULL &&
			pFX->m_nFieldType == CFieldExchange::outputColumn) ||
			pFX->m_pvField == &value)
		{
			if (pFX->m_bField)
			{
				// Mark fields null
				pFX->m_prs->SetNullFieldStatus(nField - 1);
				value.m_dwDataLength = 0;
				*plLength = SQL_NULL_DATA;
			}
			else
			{
				pFX->m_prs->ClearNullFieldStatus(nField - 1);

				// Length is signed value, it's limited by LONG_MAX
				if (value.m_dwDataLength >
					(ULONG)(LONG_MAX - labs(SQL_LEN_DATA_AT_EXEC_OFFSET)))
				{
					ASSERT(FALSE);
					*plLength = LONG_MAX - labs(SQL_LEN_DATA_AT_EXEC_OFFSET);
				}
				else
					*plLength = value.m_dwDataLength;

				*plLength = SQL_LEN_DATA_AT_EXEC(*plLength);
			}
#ifdef _DEBUG
			pFX->m_nFieldFound = nField;
#endif
		}
		return;

	case CFieldExchange::AllocCache:
		// Caching not supported for long binary
		return;

#ifdef _DEBUG
	case CFieldExchange::DumpField:
		*pFX->m_pdcDump << "\n" << szName << " = ";
		value.Dump(*pFX->m_pdcDump);
		return;
#endif // _DEBUG

	}
}

long CFieldExchange::GetLongBinarySize(int nField)
{
	RETCODE nRetCode;
	int nDummy;
	long lSize;

	// Give empty buffer to find size of entire LongBinary
	AFX_ODBC_CALL(::SQLGetData(m_prs->m_hstmt,
		(UWORD)nField, SQL_C_DEFAULT, &nDummy, 0, &lSize));

	switch (nRetCode)
	{
		case SQL_SUCCESS:
			break;

		case SQL_SUCCESS_WITH_INFO:
#ifdef _DEBUG
			if (afxTraceFlags & traceDatabase)
			{
				CDBException* e = new CDBException(nRetCode);
				e->BuildErrorString(m_prs->m_pDatabase,
					m_prs->m_hstmt, FALSE);

				// Ignore data truncated messages
				if (e->m_strStateNativeOrigin.Find(_T("State:01004")) < 0)
				{
					TRACE0("Warning: ODBC Success With Info, ");
					e->TraceErrorMessage(e->m_strError);
					e->TraceErrorMessage(e->m_strStateNativeOrigin);
				}
				e->Delete();
			}
#endif // _DEBUG
			break;

		default:
			m_prs->ThrowDBException(nRetCode);
	}

	return lSize;
}

void CFieldExchange::GetLongBinaryData(int nField, CLongBinary& lb,
	long* plSize)
{
	RETCODE nRetCode;
	long lActualDataSize = 0;
	long lChunkDataSize;
	long lReallocSize;
	const BYTE* lpLongBinary;

	lb.m_dwDataLength = 0;

	// Determine initial chunk sizes
	if (*plSize == SQL_NO_TOTAL)
	{
		lChunkDataSize = m_lDefaultLBFetchSize;
		lReallocSize = m_lDefaultLBReallocSize;
	}
	else
	{
		lChunkDataSize = *plSize;
		lReallocSize = *plSize;
	}

	do
	{
		// Check if CLongBianary is big enough
		lpLongBinary = ReallocLongBinary(lb,
			(long)lb.m_dwDataLength + lChunkDataSize,
			(long)lb.m_dwDataLength + lReallocSize);

		// Adjust the pointer so that data added at end
		lpLongBinary += lb.m_dwDataLength;

		AFX_ODBC_CALL(::SQLGetData(m_prs->m_hstmt, (UWORD)nField,
			SQL_C_BINARY, (UCHAR*)lpLongBinary, lChunkDataSize, &lActualDataSize));
		::GlobalUnlock(lb.m_hData);

		switch (nRetCode)
		{
			case SQL_NO_DATA_FOUND:
				m_prs->SetNullFieldStatus(nField - 1);
				*plSize = SQL_NULL_DATA;
				break;

			case SQL_SUCCESS:
				// All data fetched
				lb.m_dwDataLength += lActualDataSize;
				*plSize = (long)lb.m_dwDataLength;
				return;

			case SQL_SUCCESS_WITH_INFO:
				{
					CDBException* e = new CDBException(nRetCode);
					e->BuildErrorString(m_prs->m_pDatabase, m_prs->m_hstmt,
						FALSE);

					// Ignore data truncated messages
					if (e->m_strStateNativeOrigin.Find(_T("State:01004")) < 0)
					{
#ifdef _DEBUG
						if (afxTraceFlags & traceDatabase)
						{
							TRACE0("Warning: ODBC Success With Info, ");
							e->TraceErrorMessage(e->m_strError);
							e->TraceErrorMessage(e->m_strStateNativeOrigin);
						}
#endif // _DEBUG

						// Must be some other warning, should be finished
						lb.m_dwDataLength += lActualDataSize;
					}
					else
					{
						// Should only happen if further calls to SQLGetData necessary

						// Increment the length by the chunk size for subsequent SQLGetData call
						lb.m_dwDataLength += lChunkDataSize;

						// Recalculate chunk and alloc sizes
						lChunkDataSize = m_prs->GetLBFetchSize(lChunkDataSize);
						lReallocSize = m_prs->GetLBReallocSize(lReallocSize);
						// force loop to repeat
						lActualDataSize = SQL_NO_TOTAL;
					}

					*plSize = (long)lb.m_dwDataLength;
					e->Delete();
				}
				break;

			default:
				m_prs->ThrowDBException(nRetCode);
		}

	} while (lActualDataSize == SQL_NO_TOTAL);
}

BYTE* CFieldExchange::ReallocLongBinary(CLongBinary& lb, long lSizeRequired,
	long lReallocSize)
{
	// realloc max of lSizeRequired, lReallocSize (m_dwDataLength untouched)

	if (lSizeRequired < 0)
	{
		ASSERT(FALSE);
		lSizeRequired = 0;
	}

	HGLOBAL hOldData = NULL;

	// Allocate or Realloc as req'd
	if (lb.m_hData == NULL)
		lb.m_hData = ::GlobalAlloc(GMEM_MOVEABLE, lReallocSize);
	else
	{
		DWORD dwSize = ::GlobalSize(lb.m_hData);
		if (dwSize < (DWORD)lSizeRequired)
		{
			// Save the old handle in case ReAlloc fails
			hOldData = lb.m_hData;

			// Allocate more memory if necessary
			lb.m_hData = ::GlobalReAlloc(lb.m_hData,
				__max(lSizeRequired, lReallocSize), GMEM_MOVEABLE);
		}
	}

	// Validate the memory was allocated and can be locked
	if (lb.m_hData == NULL)
	{
		// Restore the old handle (not NULL if Realloc failed)
		lb.m_hData = hOldData;
		AfxThrowMemoryException();
	}

	BYTE* lpLongBinary = (BYTE*)::GlobalLock(lb.m_hData);
	if (lpLongBinary == NULL)
	{
		::GlobalFree(lb.m_hData);
		lb.m_hData = NULL;
		AfxThrowMemoryException();
	}

	return lpLongBinary;
}

//////////////////////////////////////////////////////////////////////////////
// Recordset Field Exchange Helpers


void AFXAPI AfxStoreField(CRecordset& rs, UINT nField, void* pvField)
{
	// Get the field data
	CFieldInfo* pInfo = &rs.m_rgFieldInfos[nField - 1];

	// Cache the status
	pInfo->m_bStatus = rs.GetFieldStatus(nField - 1);

	// Save the data
	if (!rs.IsFieldStatusNull(nField - 1))
	{
		// Don't need to save length for variable len
		// objects as CString and CByteArray track len internally
		long nDummyLength;

		if (pInfo->m_nDataType == AFX_RFX_BOOL ||
			pInfo->m_nDataType == AFX_RFX_BYTE ||
			pInfo->m_nDataType == AFX_RFX_INT ||
			pInfo->m_nDataType == AFX_RFX_LONG ||
			pInfo->m_nDataType == AFX_RFX_SINGLE)
		{
			// If caching data by value, pass a ref
			AfxCopyValueByRef(pvField, &pInfo->m_pvDataCache,
				&nDummyLength, pInfo->m_nDataType);
		}
		else
		{
			AfxCopyValueByRef(pvField, pInfo->m_pvDataCache,
				&nDummyLength, pInfo->m_nDataType);
		}
	}

#ifdef _DEBUG
	// Cache the bind address expected by ODBC
	switch(pInfo->m_nDataType)
	{
	default:
		// All types that are bound directly
		pInfo->m_pvBindAddress = pvField;
		break;

	case AFX_RFX_TEXT:
#ifdef _UNICODE
		pInfo->m_pvBindAddress = rs.m_pvFieldProxy[nField-1];
#else
		pInfo->m_pvBindAddress = ((CString*)pvField)->GetBuffer(0);
		((CString*)pvField)->ReleaseBuffer();
#endif
		break;

	case AFX_RFX_LPTSTR:
#ifdef _UNICODE
		pInfo->m_pvBindAddress = rs.m_pvFieldProxy[nField-1];
#else
		pInfo->m_pvBindAddress = pvField;
#endif
		break;

	case AFX_RFX_DATE:
	case AFX_RFX_OLEDATE:
		pInfo->m_pvBindAddress = rs.m_pvFieldProxy[nField-1];
		break;

	case AFX_RFX_BINARY:
		pInfo->m_pvBindAddress = ((CByteArray*)pvField)->GetData();
		break;
	}
#endif
}

void AFXAPI AfxLoadField(CRecordset& rs, UINT nField,
	void* pvField, long* plLength)
{
	// Get the field data
	CFieldInfo* pInfo = &rs.m_rgFieldInfos[nField - 1];

	// Assumes old field status cleared out
	rs.SetFieldStatus(nField - 1, pInfo->m_bStatus);

	// If not NULL, restore the value and the length
	if (!rs.IsFieldStatusNull(nField - 1))
	{
		if (pInfo->m_nDataType == AFX_RFX_BOOL ||
			pInfo->m_nDataType == AFX_RFX_BYTE ||
			pInfo->m_nDataType == AFX_RFX_INT ||
			pInfo->m_nDataType == AFX_RFX_LONG ||
			pInfo->m_nDataType == AFX_RFX_SINGLE)
		{
			// If caching data by value, pass a ref
			AfxCopyValueByRef(&pInfo->m_pvDataCache, pvField,
				plLength, pInfo->m_nDataType);
		}
		else
		{
			AfxCopyValueByRef(pInfo->m_pvDataCache, pvField,
				plLength, pInfo->m_nDataType);
		}
	}
	else
		*plLength = SQL_NULL_DATA;

#ifdef _DEBUG
	// Buffer address must not change - ODBC's SQLBindCol depends upon this
	if (pInfo->m_nDataType == AFX_RFX_BINARY)
	{
		// Change pvField to point to the data of the CByteArray
		pvField = ((CByteArray*)pvField)->GetData();
	}

	if (pInfo->m_pvBindAddress != pvField)
	{
		TRACE1("Error: field address (column %u) has changed!\n",
			nField);
		ASSERT(FALSE);
	}
#endif // _DEBUG
}

BOOL AFXAPI AfxCompareValueByRef(void* pvSrc, void* pvDest, int nSrcType)
{
	ASSERT(pvSrc != NULL);
	ASSERT(pvDest != NULL);

	BOOL bCompare = FALSE;

	switch(nSrcType)
	{

	case AFX_RFX_LONGBINARY:
		// Caching long binary Src not supported
	default:
		ASSERT(FALSE);
		break;

	case AFX_RFX_LPTSTR:
		if (lstrcmp((LPTSTR) pvDest, (LPTSTR) pvSrc) == 0)
			bCompare = TRUE;
		break;

	case AFX_RFX_TEXT:
		if (*(CString*)pvDest == *(CString*)pvSrc)
			bCompare = TRUE;
		break;

	case AFX_RFX_BINARY:
		{
			CByteArray* pByteArraySrc = (CByteArray*)pvSrc;
			CByteArray* pByteArrayDest = (CByteArray*)pvDest;

			// If sizes compare, compare the Src
			int nSize = pByteArraySrc->GetSize();
			if (nSize == pByteArrayDest->GetSize())
			{
				if (memcmp(&pByteArrayDest[0], &pByteArraySrc[0], nSize) == 0)
					bCompare = TRUE;
			}
		}
		break;
	case AFX_RFX_BOOL:
		if (*(BOOL*)pvDest == *(BOOL*)pvSrc)
			bCompare = TRUE;
		break;

	case AFX_RFX_BYTE:
		if (*(BYTE*)pvDest == *(BYTE*)pvSrc)
			bCompare = TRUE;
		break;

	case AFX_RFX_INT:
		if (*(int*)pvDest == *(int*)pvSrc)
			bCompare = TRUE;
		break;

	case AFX_RFX_LONG:
		if (*(long*)pvDest == *(long*)pvSrc)
			bCompare = TRUE;
		break;

	case AFX_RFX_SINGLE:
		if (*(float*)pvDest == *(float*)pvSrc)
			bCompare = TRUE;
		break;

	case AFX_RFX_DOUBLE:
		if (*(double*)pvDest == *(double*)pvSrc)
			bCompare = TRUE;
		break;

	case AFX_RFX_OLEDATE:
		if (*(COleDateTime*)pvDest == *(COleDateTime*)pvSrc)
			bCompare = TRUE;
		break;

	case AFX_RFX_DATE:
		if (*(CTime*)pvDest == *(CTime*)pvSrc)
			bCompare = TRUE;
		break;

	case AFX_RFX_TIMESTAMP:
		{
			TIMESTAMP_STRUCT* pSrc = (TIMESTAMP_STRUCT*)pvSrc;
			TIMESTAMP_STRUCT* pDest = (TIMESTAMP_STRUCT*)pvDest;
			if ((pSrc->year == pDest->year &&
				pSrc->month == pDest->month &&
				pSrc->day == pDest->day &&
				pSrc->hour == pDest->hour &&
				pSrc->minute == pDest->minute &&
				pSrc->second == pDest->second &&
				pSrc->fraction == pDest->fraction))
			{
				bCompare = TRUE;
			}
		}
		break;
	}

	return bCompare;
}

void AFXAPI AfxCopyValueByRef(void* pvSrc, void* pvDest, long* plLength, int nDestType)
{
	ASSERT(pvSrc != NULL);
	ASSERT(pvDest != NULL);
	ASSERT(plLength != NULL);

	switch (nDestType)
	{
	case AFX_RFX_LONGBINARY:
		// Caching long binary Dest not supported
	default:
		ASSERT(FALSE);
		break;

	case AFX_RFX_LPTSTR:
		lstrcpy((LPTSTR) pvDest, (LPTSTR) pvSrc);
		*plLength = lstrlen((LPTSTR) pvDest);
		break;

	case AFX_RFX_TEXT:
		*(CString*)pvDest = *(CString*)pvSrc;
		*plLength = ((CString*)pvSrc)->GetLength();
		break;

	case AFX_RFX_BINARY:
		((CByteArray*)pvDest)->Copy(*(CByteArray*)pvSrc);
		*plLength = ((CByteArray*)pvSrc)->GetSize();
		break;

	case AFX_RFX_BOOL:
		*(BOOL*)pvDest = *(BOOL*)pvSrc;
		*plLength = sizeof(BOOL);
		break;

	case AFX_RFX_BYTE:
		*(BYTE*)pvDest = *(BYTE*)pvSrc;
		*plLength = sizeof(BYTE);
		break;

	case AFX_RFX_INT:
		*(int*)pvDest = *(int*)pvSrc;
		*plLength = sizeof(int);
		break;

	case AFX_RFX_LONG:
		*(long*)pvDest = *(long*)pvSrc;
		*plLength = sizeof(long);
		break;

	case AFX_RFX_SINGLE:
		*(float*)pvDest = *(float*)pvSrc;
		*plLength = sizeof(float);
		break;

	case AFX_RFX_DOUBLE:
		*(double*)pvDest = *(double*)pvSrc;
		*plLength = sizeof(double);
		break;

	case AFX_RFX_DATE:
		*(CTime*)pvDest = *(CTime*)pvSrc;
		*plLength = sizeof(TIMESTAMP_STRUCT);
		break;

	case AFX_RFX_OLEDATE:
		*(COleDateTime*)pvDest = *(COleDateTime*)pvSrc;
		*plLength = sizeof(TIMESTAMP_STRUCT);
		break;

	case AFX_RFX_TIMESTAMP:
		{
			TIMESTAMP_STRUCT* pDest = (TIMESTAMP_STRUCT*)pvDest;
			TIMESTAMP_STRUCT* pSrc = (TIMESTAMP_STRUCT*)pvSrc;

			pDest->year = pSrc->year;
			pDest->month = pSrc->month;
			pDest->day = pSrc->day;
			pDest->hour = pSrc->hour;
			pDest->minute = pSrc->minute;
			pDest->second = pSrc->second;
			pDest->fraction = pSrc->fraction;

			*plLength = sizeof(TIMESTAMP_STRUCT);
		}
		break;
	}
}

//////////////////////////////////////////////////////////////////////////////
// Bulk Recordset Field Exchange

void AFXAPI RFX_Text_Bulk(CFieldExchange* pFX, LPCTSTR szName,
	LPSTR* prgStrVals, long** prgLengths, int nMaxLength)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	switch (pFX->m_nOperation)
	{
	case CFieldExchange::AllocMultiRowBuffer:
		{
			// The buffer pointer better be initialized to NULL
			// or cleanup in exceptional cases mail fail
			ASSERT(*prgStrVals == NULL);
			ASSERT(*prgLengths == NULL);

			int nRowsetSize = pFX->m_prs->GetRowsetSize();

			// Allocate buffers to hold data and length
			*prgStrVals = new char[nRowsetSize * nMaxLength];
			*prgLengths = new long[nRowsetSize];
		}
		break;

	case CFieldExchange::DeleteMultiRowBuffer:
		delete [] *prgStrVals;
		*prgStrVals = NULL;

		delete [] *prgLengths;
		*prgLengths = NULL;
		break;

	default:
		AfxRFXBulkDefault(pFX, szName, *prgStrVals, *prgLengths,
			SQL_C_CHAR, nMaxLength);
		break;
	}
}

void AFXAPI RFX_Bool_Bulk(CFieldExchange* pFX, LPCTSTR szName,
	BOOL** prgBoolVals, long** prgLengths)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	switch (pFX->m_nOperation)
	{
	case CFieldExchange::AllocMultiRowBuffer:
		{
			// The buffer pointer better be initialized to NULL
			// or cleanup in exceptional cases mail fail
			ASSERT(*prgBoolVals == NULL);
			ASSERT(*prgLengths == NULL);

			int nRowsetSize = pFX->m_prs->GetRowsetSize();

			// Allocate buffers to hold data and length
			*prgBoolVals = new BOOL[nRowsetSize];
			*prgLengths = new long[nRowsetSize];
		}
		break;

	case CFieldExchange::DeleteMultiRowBuffer:
		delete [] *prgBoolVals;
		*prgBoolVals = NULL;

		delete [] *prgLengths;
		*prgLengths = NULL;
		break;

	default:
		AfxRFXBulkDefault(pFX, szName, *prgBoolVals, *prgLengths,
			SQL_C_LONG, sizeof(BOOL));
		break;
	}
}

void AFXAPI RFX_Int_Bulk(CFieldExchange* pFX, LPCTSTR szName,
	int** prgIntVals, long** prgLengths)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	switch (pFX->m_nOperation)
	{
	case CFieldExchange::AllocMultiRowBuffer:
		{
			// The buffer pointer better be initialized to NULL
			// or cleanup in exceptional cases mail fail
			ASSERT(*prgIntVals == NULL);
			ASSERT(*prgLengths == NULL);

			int nRowsetSize = pFX->m_prs->GetRowsetSize();

			// Allocate buffers to hold data and length
			*prgIntVals = new int[nRowsetSize];
			*prgLengths = new long[nRowsetSize];
		}
		break;

	case CFieldExchange::DeleteMultiRowBuffer:
		delete [] *prgIntVals;
		*prgIntVals = NULL;

		delete [] *prgLengths;
		*prgLengths = NULL;
		break;

	default:
		AfxRFXBulkDefault(pFX, szName, *prgIntVals, *prgLengths,
			SQL_C_LONG, sizeof(int));
		break;
	}
}

void AFXAPI RFX_Long_Bulk(CFieldExchange* pFX, LPCTSTR szName,
	long** prgLongVals, long** prgLengths)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	switch (pFX->m_nOperation)
	{
	case CFieldExchange::AllocMultiRowBuffer:
		{
			// The buffer pointer better be initialized to NULL
			// or cleanup in exceptional cases mail fail
			ASSERT(*prgLongVals == NULL);
			ASSERT(*prgLengths == NULL);

			int nRowsetSize = pFX->m_prs->GetRowsetSize();

			// Allocate buffers to hold data and length
			*prgLongVals = new long[nRowsetSize];
			*prgLengths = new long[nRowsetSize];
		}
		break;

	case CFieldExchange::DeleteMultiRowBuffer:
		delete [] *prgLongVals;
		*prgLongVals = NULL;

		delete [] *prgLengths;
		*prgLengths = NULL;
		break;

	default:
		AfxRFXBulkDefault(pFX, szName, *prgLongVals, *prgLengths,
			SQL_C_LONG, sizeof(long));
		break;
	}
}

void AFXAPI RFX_Date_Bulk(CFieldExchange* pFX, LPCTSTR szName,
	TIMESTAMP_STRUCT** prgTSVals, long** prgLengths)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	switch (pFX->m_nOperation)
	{
	case CFieldExchange::AllocMultiRowBuffer:
		{
			// The buffer pointer better be initialized to NULL
			// or cleanup in exceptional cases mail fail
			ASSERT(*prgTSVals == NULL);
			ASSERT(*prgLengths == NULL);

			int nRowsetSize = pFX->m_prs->GetRowsetSize();

			// Allocate buffers to hold data and length
			*prgTSVals = new TIMESTAMP_STRUCT[nRowsetSize];
			*prgLengths = new long[nRowsetSize];
		}
		break;

	case CFieldExchange::DeleteMultiRowBuffer:
		delete [] *prgTSVals;
		*prgTSVals = NULL;

		delete [] *prgLengths;
		*prgLengths = NULL;
		break;

	default:
		AfxRFXBulkDefault(pFX, szName, *prgTSVals, *prgLengths,
			SQL_C_TIMESTAMP, sizeof(TIMESTAMP_STRUCT));
		break;
	}
}

void AFXAPI RFX_Byte_Bulk(CFieldExchange* pFX, LPCTSTR szName,
	BYTE** prgByteVals, long** prgLengths)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	switch (pFX->m_nOperation)
	{
	case CFieldExchange::AllocMultiRowBuffer:
		{
			// The buffer pointer better be initialized to NULL
			// or cleanup in exceptional cases mail fail
			ASSERT(*prgByteVals == NULL);
			ASSERT(*prgLengths == NULL);

			int nRowsetSize = pFX->m_prs->GetRowsetSize();

			// Allocate buffers to hold data and length
			*prgByteVals = new BYTE[nRowsetSize];
			*prgLengths = new long[nRowsetSize];
		}
		break;

	case CFieldExchange::DeleteMultiRowBuffer:
		delete [] *prgByteVals;
		*prgByteVals = NULL;

		delete [] *prgLengths;
		*prgLengths = NULL;
		break;

	default:
		AfxRFXBulkDefault(pFX, szName, *prgByteVals, *prgLengths,
			SQL_C_TINYINT, sizeof(BYTE));
		break;
	}
}

void AFXAPI RFX_Binary_Bulk(CFieldExchange* pFX, LPCTSTR szName,
	BYTE** prgByteVals, long** prgLengths, int nMaxLength)
{
	ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
	ASSERT(AfxIsValidString(szName));

	UINT nField;
	if (!pFX->IsFieldType(&nField))
		return;

	switch (pFX->m_nOperation)
	{
	case CFieldExchange::AllocMultiRowBuffer:
		{
			// The buffer pointer better be initialized to NULL
			// or cleanup in exceptional cases mail fail
			ASSERT(*prgByteVals == NULL);
			ASSERT(*prgLengths == NULL);

			int nRowsetSize = pFX->m_prs->GetRowsetSize();

			// Allocate buffers to hold data and length
			*prgByteVals = new BYTE[nRowsetSize * nMaxLength];
			*prgLengths = new long[nRowsetSize];
		}
		break;

	case CFieldExchange::DeleteMultiRowBuffer:
		delete [] *prgByteVals;
		*prgByteVals = NULL;

		delete [] *prgLengths;
		*prgLengths = NULL;
		break;

	default:
		AfxRFXBulkDefault(pFX, szName, *prgByteVals, *prgLengths,
			SQL_C_BINARY, nMaxLength);
		break;
	}
}

void AFXAPI AfxRFXBulkDefault(CFieldExchange* pFX, LPCTSTR szName,
	void* pv, long* rgLengths, int nCType, UINT cbValue)
{
	RETCODE nRetCode;

	switch(pFX->m_nOperation)
	{
	default:
		// Operation not valid for bulk fetch
		ASSERT(FALSE);
		return;

	case CFieldExchange::Name:
		// We require a name
		ASSERT(lstrlen(szName) != 0);

		*pFX->m_pstr += szName;
		*pFX->m_pstr += pFX->m_lpszSeparator;
		break;

	case CFieldExchange::BindFieldToColumn:
		AFX_SQL_SYNC(::SQLBindCol(pFX->m_prs->m_hstmt,
			(UWORD)pFX->m_nFields, (SWORD)nCType, pv, cbValue, rgLengths));
		if (!pFX->m_prs->Check(nRetCode))
			pFX->m_prs->ThrowDBException(nRetCode);
		break;
	}
}

//////////////////////////////////////////////////////////////////////////////
// Inline function declarations expanded out-of-line

#ifndef _AFX_ENABLE_INLINES

static char _szAfxDbInl[] = "afxdb.inl";
#undef THIS_FILE
#define THIS_FILE _szAfxDbInl
#define _AFXDBRFX_INLINE
#include "afxdb.inl"

#endif

#ifdef AFX_INIT_SEG
#pragma code_seg(AFX_INIT_SEG)
#endif

/////////////////////////////////////////////////////////////////////////////
