Click here to Skip to main content
15,881,742 members
Articles / Desktop Programming / Win32

dbAx: a C++ Library for ActiveX Data Objects (ADO)

Rate me:
Please Sign up or sign in to vote.
4.93/5 (24 votes)
21 Jan 2009CPOL4 min read 103.7K   2.4K   98  
C++ class wrapper for ADO
#include "StdAfx.h"
#include "AxLib.h"

using namespace dbAx;

///////////////////////////////////////////////////////////////
//	Construction

CAxRecordset::CAxRecordset()
{
  m_pConnection = NULL;
  m_pCommand = NULL;
  m_piConnection = NULL;
  m_piRecordset = NULL;
  m_pFields= NULL;
  m_dwEvents = 0;
  m_eCursorType = (CursorTypeEnum)-2;
  m_eLockType = (LockTypeEnum)-2;
}

CAxRecordset::~CAxRecordset()
{
  if ( _IsOpen() )
    Close();

  if( m_piRecordset )
    m_piRecordset->Release();
  m_piRecordset = NULL;
}

///////////////////////////////////////////////////////////////
//	Initialization

// Method: Create
//   Desc: Creates and attacheds the underlying ADO Recordset object.
//         Create must be called before using the CAxRecordset object.
//
//   Args: none
//
// Return: void
//
//   v1.1: Moved call to Create internal to the Open method. Client
//         application no longer needs to call Create(). See note
//         in AxLib.h

HRESULT CAxRecordset::Create()
{
  HRESULT hr = CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_INPROC_SERVER, 
              IID_IADORecordset15, (LPVOID *)&m_piRecordset);

	if FAILED( hr )
  {
    if ( m_piRecordset != NULL )
    {
      m_piRecordset->Release();
      m_piRecordset = NULL;
    }
  }

  return ( hr );
}

///////////////////////////////////////////////////////////////
//	AxLibSpecific

// Method: _ClearRecordsetEvents
//   Desc: Releases the ADO Events sink interface.
//
//   Args: none
//
// Return: void
//
void CAxRecordset::_ClearRecordsetEvents()
{
  if ( m_piRecordset == NULL || m_dwEvents == 0 )
    return;

  IConnectionPointContainer *pCPC = NULL;
  IConnectionPoint          *pCP = NULL;
  HRESULT hr = m_piRecordset->QueryInterface(__uuidof(IConnectionPointContainer),
    (LPVOID*)&pCPC);
  if FAILED(hr) 
	  ThrowAxException(AXLIB_ERROR_NONE, _T("Failed to create connection object"));

  hr = pCPC->FindConnectionPoint(__uuidof(RecordsetEvents), &pCP);
  pCPC->Release();
  if FAILED(hr)
		ThrowAxException(AXLIB_ERROR_NONE, _T("Failed to create connection object"));

  hr = pCP->Unadvise(m_dwEvents);
  pCP->Release();
  m_dwEvents--;
}

// Method: _IsEmpty
//   Desc: Indicates if there are any records in the recordset.
//
//   Args: none
//
// Return: True if the recordset is empty. Otherwise, false.
//
bool CAxRecordset::_IsEmpty()
{
	VALID_ADO_OBJECT(m_piRecordset);
	VARIANT_BOOL vbBOF = 0;
  VARIANT_BOOL vbEOF = 0;

	m_piRecordset->get_BOF(&vbBOF);
	m_piRecordset->get_EOF(&vbEOF);

  return ( (vbBOF<0) && (vbEOF<0) );
}

// Method: _IsOpen
//   Desc: Indicates if the recordset is currently open.
//
//   Args: none
//
// Return: True if the recordset is open. Otherwise, false.
//
bool CAxRecordset::_IsOpen()
{
  if ( m_piRecordset == NULL )
    return (FALSE);

	VALID_ADO_OBJECT(m_piRecordset);

  long lState;
  if FAILED( m_piRecordset->get_State(&lState) )
		ThrowAxException(AXLIB_ERROR_NONE);

  return (lState == adStateOpen);
}

// Method: _SetRecordsetEvents
//   Desc: Attaches the ADO Events sink interface allowing client
//         applications to handle ADO Recordset events. The host
//         program provides a specific implementation of the
//         CAxRecordsetEvents class to handle and respond to ADO
//         events that may be raised.
//
//   Args: CAxRecordsetEvents*
//          A reference to an AxLib Events object derive from
//          CAxRecordsetEvents.
//
// Return: void
//
void CAxRecordset::_SetRecordsetEvents(CAxRecordsetEvents* pEvents)
{
    IConnectionPointContainer *pCPC = NULL;
    IConnectionPoint          *pCP = NULL;
    HRESULT hr = m_piRecordset->QueryInterface(__uuidof(IConnectionPointContainer),
      (LPVOID*)&pCPC);
    if FAILED(hr) 
			ThrowAxException(AXLIB_ERROR_NONE, _T("Failed to create connection object"));

    hr = pCPC->FindConnectionPoint(__uuidof(RecordsetEvents), &pCP);
    pCPC->Release();
    if FAILED(hr)
			ThrowAxException(AXLIB_ERROR_NONE, _T("Failed to create connection object"));

    hr = pCP->Advise(pEvents, &m_dwEvents);
    pCP->Release();
}


///////////////////////////////////////////////////////////////
//	Collections

// Method: Fields
//   Desc: Returns a reference to the ADO Filelds collection.
//
//   Args: none
//
// Return: ADOFields*
//
ADOFields* CAxRecordset::Fields()
{
  return (m_pFields);
}

// Method: Properties
//   Desc: Returns a reference to the ADO Properties collection.
//
//   Args: none
//
// Return: ADOProperties*
//
ADOProperties* CAxRecordset::Properties()
{
	VALID_ADO_OBJECT(m_piRecordset);
  ADOProperties *pProps;

  HRESULT hr = m_piRecordset->get_Properties(&pProps);
  if FAILED( hr )
		ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Properties"), hr);

  return (pProps);
}


///////////////////////////////////////////////////////////////
//	Methods

// Method: AddNew
//   Desc: Creates a new blank record in the recordset. After
//         calling AddNew, the client application typically
//         completes the member variables of the CAxRecordset
//         derive class then calls the Update method to post
//         the new recordset information to the database.
//
//   Args: none
//
// Return: void
//
void CAxRecordset::AddNew()
{
	VALID_ADO_OBJECT(m_piRecordset);
  _variant_t vNames(DISP_E_PARAMNOTFOUND, VT_ERROR);
  _variant_t vValues(DISP_E_PARAMNOTFOUND, VT_ERROR);

  if ( Supports(adAddNew) )
  {
    HRESULT hr = m_piRecordset->AddNew(vNames, vValues);
	  if FAILED( hr )
		  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Properties"), hr);
  }
}

// Method: Cancel
//   Desc: Cancels an asynchronous operation for the recordset object.
//
//   Args: none
//
// Return: void
//
void CAxRecordset::Cancel()
{
}

void CAxRecordset::CancelBatch()
{
}

// Method: CancelUpdate
//   Desc: Cancels editing of a record. A call to the CancelUpdate method
//         prior to calling Update will cancel any changes made to the
//         record since editing began. Additionally, if AddNew was called,
//         the creation of a new record is also canceled. With respect
//         to AxLib, a call to CancelUpdate is generally not needed since
//         changes are made to the local member variables. Any need to
//         cancel updating of the record would be handled in the client
//         application before making a call to the Update method.
//
//   Args: none
//
// Return: void
//
void CAxRecordset::CancelUpdate()
{
	VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;
	EditModeEnum	lEditMode;

  hr = m_piRecordset->get_EditMode(&lEditMode);
  if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::CancelUpdate"), hr);

  if ( lEditMode == adEditInProgress || lEditMode == adEditAdd )
  {
    hr = m_piRecordset->CancelUpdate();
    if FAILED( hr )
      ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::CancelUpdate"), hr);
  }
}

// Method: Clone
//   Desc: Return a duplicate copy of the underlying ADO Recordset object.
//
//   Args: eLockType
//     adLockUnspecified, (default)
//     adLockReadOnly
//
// Return: ADORecordset*
//
ADORecordset* CAxRecordset::Clone(LockTypeEnum eLockType)
{
	VALID_ADO_OBJECT(m_piRecordset);
  ADORecordset* pRec;

  HRESULT hr = m_piRecordset->Clone(eLockType, &pRec);
  if FAILED( hr )
      ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Clone"), hr);

  return (pRec);
}

// Method: Close
//   Desc: Closes and open recordset. All dependent objects such as 
//         CAxRecordsetEvents, CAxCommand objects and the underlying
//         ADO Recordset object are closed as well.
//
//   Args: none
//
// Return: void
//
void CAxRecordset::Close()
{
	VALID_ADO_OBJECT(m_piRecordset);

  //Cleanup any related Command objects
  if ( m_pCommand )
  {
		if ( m_pCommand->m_piParameters )
		{
			m_pCommand->m_piParameters->Release();
			m_pCommand->m_piParameters = NULL;
		}
  	m_pCommand->m_piCommand->Release();
		m_pCommand->m_piCommand = NULL;
  }

  //Release Recordset events
  _ClearRecordsetEvents();

	if ( m_piRecordset && _IsOpen() )
	{
		m_piRecordset->MoveFirst();
		HRESULT hr = m_piRecordset->Close();
    
		if FAILED( hr )
      ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Close"), hr);
	}
	
//	m_piRecordset->Release();
//	m_piRecordset = NULL;
}

// Method: CompareBookmarks
//   Desc: Determines the position of two bookmarks relative to one
//         another. A CompareEnum value is returned that indicates which
//         bookmark comes first in ordinal position.
//
//   Args: vtBkMark1, vtBkMark2
//          Bookmark variants to be compared.
//
// Return: A CompareEnum enumeration
//          adCompareLessThan	= 0,
//          adCompareEqual	= 1,
//          adCompareGreaterThan	= 2,
//          adCompareNotEqual	= 3,
//          adCompareNotComparable	= 4
//
CompareEnum CAxRecordset::CompareBookmarks(_variant_t vtBkMark1, _variant_t vtBkMark2)
{
 	VALID_ADO_OBJECT(m_piRecordset);
  CompareEnum eComp;

  HRESULT hr = m_piRecordset->CompareBookmarks(vtBkMark1, vtBkMark2, &eComp);
  if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::CompareBookmarks"), hr);

  return (eComp);
}

// Method: Delete 
//   Desc: Deletes specified records in the recordset. In general the
//         client application will use the default of adAffectCurrent
//         to delete the current record only.
//
//   Args: eAffect
//          An AffectEnum value:
//            adAffectCurrent	= 1,
//            adAffectGroup	= 2,
//            adAffectAll	= 3,
//           dAffectAllChapters	= 4
//
// Return: void
//
void CAxRecordset::Delete(AffectEnum eAffect)
{
	VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;
	EditModeEnum	lEditMode;

	//Watch for empty recordset
	if ( _IsEmpty() )
		return;

	//Delete method depends on current mode
  hr = m_piRecordset->get_EditMode(&lEditMode);
	if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Delete"), hr);

	switch (lEditMode)
  {
	case adEditNone: // Just delete it
    hr = m_piRecordset->Delete(eAffect);
	  if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Delete"), hr);
		break;

	case adEditInProgress: //Forget changes
    hr = m_piRecordset->CancelUpdate();
		if FAILED( hr )
      ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Delete"), hr);

    hr = m_piRecordset->Delete(eAffect);
	  if FAILED( hr )
      ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Delete"), hr);
		break;

	case adEditAdd: //If new record, go back to last known
    hr = m_piRecordset->CancelUpdate();
		if FAILED( hr )
      ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Delete"), hr);
    
    hr = m_piRecordset->put_Bookmark(m_varLastGoodRecord);
		if FAILED( hr )
      ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Delete"), hr);
    break;
	}
}

// Method: Find 
//   Desc: Performs a search based on a single column search criteria.
//         If successful, the record pointer is positioned on the 
//         first record to match the search criteria. The search
//         criteria matches that of the SQL WHERE clause, such as
//         "AccountID = 'ABC'". The client application should set the
//         record pointer by making a call to Move, MoveFirst, etc.
//
//   Args: strCriteria - search criteria
//         nSkip - number of rows to skip before searching
//         SearchDirectionEnum - determines the direction of the search
//            adSearchForward	= 1,
//            adSearchBackward = -1
//         pvtStart - bookmark used as a starting point for the search
//
// Return: true if match is found, otherwise false
//

bool CAxRecordset::Find(LPCTSTR lpszCriteria, long nSkip,
	         SearchDirectionEnum eSearchDirection, _variant_t* pvtStart)
{
	VALID_ADO_OBJECT(m_piRecordset);
	HRESULT hr;
	_bstr_t bstrCriteria(lpszCriteria);

  if ( _IsEmpty() )
    return ( false );

  if ( pvtStart )
	  hr = m_piRecordset->Find(bstrCriteria, nSkip, eSearchDirection, *pvtStart);
  else
	  hr = m_piRecordset->Find(bstrCriteria, nSkip, eSearchDirection, _variant_t(AbsolutePosition()));

  if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Find"), hr);

  if ( IsBOF() || IsEOF() )
    return ( false );

	DoFieldExchange(FALSE);
	return ( true );
}

void CAxRecordset::GetRows()
{
}

void CAxRecordset::GetString()
{
}

// Method: Move 
//   Desc: Moves a specified number of records forward or backward
//         from the current position. If the resultant move is beyond
//         the end of the recordset or past the beginning, the 
//         record point is positioned just after the last record or
//         just before the first record, i.e. BOF/EOF.
//
//   Args: nNumRecs - number of records to move from the current position
//                    or that specified by the pvtStartRec bookmark
//         pvtStartRec - bookmark specifing the starting record position.
//                       The default is the current record, adBookmarkCurrent
//
// Return: void
//
void CAxRecordset::Move(long nNumRecs, _variant_t* pvtStartRec)
{
	VALID_ADO_OBJECT(m_piRecordset);

  HRESULT hr = S_OK;
  
  if ( pvtStartRec )
    hr = m_piRecordset->Move(nNumRecs, *pvtStartRec);
  else
    hr = m_piRecordset->Move(nNumRecs, _variant_t(0L));

	if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Move"), hr);
		
  if ( !IsBOF() && !IsEOF() )
	  DoFieldExchange(FALSE);
}

// Method: MoveFirst
//   Desc: Sets the record pointer to the first record in the recordset.
//
//   Args: none 
//
// Return: void
//
void CAxRecordset::MoveFirst()
{
	VALID_ADO_OBJECT(m_piRecordset);

	if ( _IsEmpty() )
    return;

  HRESULT hr = m_piRecordset->MoveFirst();
	if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::MoveFirst"), hr);

	DoFieldExchange(FALSE);
}

// Method: MoveLast
//   Desc: Sets the record pointer to the last record in the recordset.
//
//   Args: none 
//
// Return: void
//
void CAxRecordset::MoveLast()
{
	VALID_ADO_OBJECT(m_piRecordset);

	if ( _IsEmpty() )
    return;

	HRESULT hr = m_piRecordset->MoveLast();
	if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::MoveLast"), hr);

	DoFieldExchange(FALSE);
}

// Method: MoveNext
//   Desc: Sets the record pointer to the next record in the recordset.
//         If the record pointer is on the last record before a call to
//         MoveNext, the record pointer is moved beyond the last record
//         and the EOF property is set to true.
//
//   Args: none 
//
// Return: void
//
void CAxRecordset::MoveNext()
{
	VALID_ADO_OBJECT(m_piRecordset);

//  if ( _IsEmpty() || IsEOF() )
//    if ( IsEOF() )
//    return;

  HRESULT hr = m_piRecordset->MoveNext();
	if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::MoveNext"), hr);

	if ( !IsEOF() )
		DoFieldExchange(FALSE);
}

// Method: MovePrevious
//   Desc: Sets the record pointer to the previous record in the recordset.
//         If the record pointer is on the first record before a call to
//         MovePrevious, the record pointer is moved to a position before
//         the first record and the BOF property is set to true.
//
//   Args: none 
//
// Return: void
//
void CAxRecordset::MovePrevious()
{
	VALID_ADO_OBJECT(m_piRecordset);

//  if ( _IsEmpty() || IsBOF() )
//    return;

  HRESULT hr = m_piRecordset->MovePrevious();
  if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::MovePrevious"), hr);

	if ( !IsBOF() )
		DoFieldExchange(FALSE);
}

// Method: NextRecordset
//   Desc: Not supported in this version of AxLib
//
void CAxRecordset::NextRecordset()
{
}

// Method: Open
//   Desc: The first version of the Open method accepts a CAxCommand object
//         whereby a stored procedure is used to return recordset results.
//         A derived CAxRecordset object must exist and be initialized before
//         calling the Open method. If the method fails, a CAxException is
//         thrown.
//
//   Args: 
//        pCmd: Reference to a CAxCommand object.
//       cType: Cursor type to use, default is adOpenForwardOnly
//      cLocat: Cursor location, default is adUseClient
//       lType: Lock type to use, default is adLockOptimistic
//    nOptions: Command options, default is adCmdUnspecified
//
// Return: void
//
void CAxRecordset::Open(CAxCommand* pCmd, 
				                CursorTypeEnum eCursorType,
				                CursorLocationEnum eCursorLocation,
				                LockTypeEnum eLockType,
				                long nOptions)
{
  if ( m_piRecordset == NULL )
  {
    HRESULT hr = Create();
    if  FAILED ( hr )
      ThrowAxException(AXLIB_ERROR_INIT, _T("CAxRecordset::Open"), hr);
  }

  VALID_ADO_OBJECT(m_piRecordset);
  VARIANT vNull, vCmd;
  HRESULT hr = S_OK;

	vNull.vt = VT_ERROR;
	vNull.scode = DISP_E_PARAMNOTFOUND;

	vCmd.vt = VT_DISPATCH;
	vCmd.pdispVal = pCmd->m_piCommand;

  //Capture references to controlling objects
  m_pConnection = pCmd->ActiveConnection();
  m_pCommand = pCmd;
  m_piConnection = m_pConnection->_GetActiveConnection();

  //Create command object's parameters (one time only) and Update
  //Note: v1.2.0 now calls the _CreateParameters method from within the
  //CAxCommand::Create method
  m_pCommand->_UpdateParameters();

  CursorTypeEnum ct = (m_eCursorType == -2) ? eCursorType : m_eCursorType;
  LockTypeEnum lt = (m_eLockType == -2) ? eLockType : m_eLockType;
  CursorLocation(eCursorLocation);

  hr = m_piRecordset->Open(vCmd, vNull, ct, lt, nOptions);
	if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Open"), hr);

  hr = m_piRecordset->get_Fields(&m_pFields);
	if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Open"), hr);

	DoFieldExchange(FALSE);
}

// Method: Open
//   Desc: The second version of the Open method uses a source argument in
//         place of a CAxCommand object. A typical source statement would 
//         be "SELECT * From Accounts ORDER By AccountID"
//
// v1.1.1: Added call to CursorLocation
//
//   Args: 
//        szSource: Valid SQL Source statement
//     pConnection: Reference to an existing CAxConnection object
//           cType: Cursor type to use, default is adOpenForwardOnly
//          cLocat: Cursor location, default is adUseClient
//           lType: Lock type to use, default is adLockOptimistic
//        nOptions: Command options, default is adCmdUnspecified
//
// Return: void
//
void CAxRecordset::Open(LPCTSTR szSource, CAxConnection *pConnection,
				                CursorTypeEnum eCursorType,
				                CursorLocationEnum eCursorLocation,
				                LockTypeEnum eLockType,
				                long nOptions)
{
  if ( m_piRecordset == NULL )
  {
    HRESULT hr = Create();
    if  FAILED ( hr )
      ThrowAxException(AXLIB_ERROR_INIT, _T("CAxRecordset::Open"), hr);
  }

  VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;

	if ( szSource != NULL )
    m_strSourceString = szSource;

	if ( pConnection != NULL )
		m_piConnection = pConnection->_GetActiveConnection();

	ASSERT( !m_strSourceString.IsEmpty() );
	ASSERT( m_piConnection != NULL );

  hr =  m_piRecordset->put_Source(m_strSourceString.AllocSysString());
	if FAILED( hr )
	{
		m_piRecordset->Release();
		m_piRecordset = NULL;
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Open"), hr);
	}

  hr = m_piRecordset->putref_ActiveConnection((ADOConnection*)m_piConnection);
	if FAILED( hr )
	{
		m_piRecordset->Release();
		m_piRecordset = NULL;
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Open"), hr);
	}

  CursorTypeEnum ct = (m_eCursorType == -2) ? eCursorType : m_eCursorType;
  LockTypeEnum lt = (m_eLockType == -2) ? eLockType : m_eLockType;
  CursorLocation(eCursorLocation);

  hr = m_piRecordset->Open(_variant_t(szSource), _variant_t(m_piConnection),
    ct, lt, nOptions);

	if FAILED( hr )
	{
		m_piRecordset->Release();
		m_piRecordset = NULL;
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Open"), hr);
	}

  hr = m_piRecordset->get_Fields(&m_pFields);
	if FAILED( hr )
	{
		m_piRecordset->Release();
		m_piRecordset = NULL;
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Open"), hr);
	}

  DoFieldExchange(FALSE);
}

// Method: Requery
//   Desc: The Requery method re-executes the command that was used to
//         originally open the recordset. If the recordset was opened
//         using a command object (i.e. CAxCommand), Requery is used to
//         update the recordset after changing one or more parameter
//         values of the command.
//
//   Args: 
//      eExecuteOption: Execute option, default is adOptionUnspecified
//        adOptionUnspecified	= -1,
//        adAsyncExecute	= 0x10,
//        adAsyncFetch	= 0x20,
//        adAsyncFetchNonBlocking	= 0x40,
//        adExecuteNoRecords	= 0x80
//
// Return: void
//
void CAxRecordset::Requery(ExecuteOptionEnum eExecuteOption)
{
	VALID_ADO_OBJECT(m_piRecordset);

  if ( m_pCommand )
    m_pCommand->_UpdateParameters();

  HRESULT hr = m_piRecordset->Requery(eExecuteOption);
	if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Requery"), hr);
  
	DoFieldExchange(FALSE);
}

// Method: Resync
//   Desc: The Resync method is used to resychronize the the records
//         with the data provider. Resync is typically used when the
//         recordset was opened using a Static or Forward only cursor
//         and the client application needs to check is any other users
//         have made changes.
//
//   Args: 
//      eAffectRecords: Affect records options, default is adAffectAll
//        adAffectCurrent	= 1,
//        adAffectGroup	= 2,
//        adAffectAll	= 3,
//        adAffectAllChapters	= 4
//
//      eResyncValues: Resync option, default is adResyncAllValues
//        adResyncUnderlyingValues	= 1,
//        adResyncAllValues	= 2
//
// Return: void
//
void CAxRecordset::Resync(AffectEnum eAffectRecords,
                                ResyncEnum eResyncValues) 
{
	VALID_ADO_OBJECT(m_piRecordset);

  HRESULT hr = m_piRecordset->Resync(eAffectRecords, eResyncValues);
	if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Resync"), hr);
}

// Method: Save
//   Desc: Saves the recordset to a file. The AxLib version differs
//         from ADO in that a file name is used as the destination
//         argument rather than a variant. Therefore, only saving
//         to a file is supported.
//
//   Args: 
//      lpszFileName: File name to save recordset data to.
//    ePersistFormat: Persist format option, default is adPersistADTG
//      adPersistADTG	= 0,
//      adPersistXML	= 1
//
// Return: void
//
void CAxRecordset::Save(LPCTSTR lpszFileName,
                              PersistFormatEnum ePersistFormat) 
{
#if _MSC_VER == 1200
  _bstr_t bstrFileName(lpszFileName);
  HRESULT hr = m_piRecordset->Save(bstrFileName, ePersistFormat);
#else
  #ifdef _UNICODE
    _variant_t varFile(lpszFileName);
  #else
    _variant_t varFile(M2W(lpszFileName));
  #endif
  HRESULT hr = m_piRecordset->Save(varFile, ePersistFormat);
#endif

  if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Save"), hr);
}


// Method: Supports
//   Desc: Determines if the data provider supports a certain
//         functionality.
//
//   Args: 
//      eCursorOption: Cursor option to validate
//        adHoldRecords	= 0x100,
//        adMovePrevious	= 0x200,
//        adAddNew	= 0x1000400,
//        adDelete	= 0x1000800,
//        adUpdate	= 0x1008000,
//        adBookmark	= 0x2000,
//        adApproxPosition	= 0x4000,
//        adUpdateBatch	= 0x10000,
//        adResync	= 0x20000,
//        adNotify	= 0x40000,
//        adFind	= 0x80000
//
// Return: If the data provider supports the indicated option true,
//         otherwise false.
//
bool CAxRecordset::Supports(CursorOptionEnum eCursorOption)
{
	VALID_ADO_OBJECT(m_piRecordset);
	VARIANT_BOOL vSucceeded;
	
  HRESULT hr = m_piRecordset->Supports(eCursorOption, &vSucceeded);
	if FAILED( hr )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Supports"), hr);

	return ( vSucceeded != 0 );
}

// Method: Update
//   Desc: Posts changes made to the recordset back to the server. The
//         Update method calls the virtual function DoFieldExchange in
//         the derived CAxRecordset object to write individual field
//         data back to the data provider.
//
//   Args: none
//
// Return: void
//
void CAxRecordset::Update()
{
	VALID_ADO_OBJECT(m_piRecordset);
	_variant_t vNames(DISP_E_PARAMNOTFOUND, VT_ERROR);
	_variant_t vValues(DISP_E_PARAMNOTFOUND, VT_ERROR);

	DoFieldExchange(TRUE);

	//Use 'adResyncUnderlyingValues' on Resync to avoid errors on compound
	//Recordsets (i.e. INNER/OUTER Joins of tables)
  HRESULT hr = S_OK;
	if ( Supports(adResync) )
	{
		if FAILED( m_piRecordset->Resync(adAffectCurrent, adResyncUnderlyingValues) )
		{
      hr = m_piRecordset->Resync(adAffectCurrent, adResyncAllValues);
			if FAILED( hr )
        ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Update"), hr);
		}
	}

	if ( Supports(adUpdate) )
	{
    hr = m_piRecordset->Update(vNames, vValues);
		if FAILED( hr )
      ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Update"), hr);
	}
}


///////////////////////////////////////////////////////////////
//	Properties

// Method: AbsolutePage
//   Desc: Sets/Returns the current page in the record set.
//         Call method with no argument to return current setting.
//
//   Args: nPage
//          Page to set active. Page value must be between
//          1 and the total page count. Page count is 
//          returned by the PageCount() method.
//
// Return: Current page or PositionEnum value.
//            adPosUnknown	= -1,
//            adPosBOF	= -2,
//            adPosEOF	= -3
//
long CAxRecordset::AbsolutePage(long nPageNum)
{
	VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;

  if ( nPageNum < 0 || nPageNum > PageCount() )
		  ThrowAxException(AXLIB_ERROR_INVALID_POS, _T("CAxRecordset::AbsolutePage"));

  if ( nPageNum > 0 )
  {
    hr = m_piRecordset->put_AbsolutePage((PositionEnum)nPageNum);
	  if FAILED( hr )
		  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::AbsolutePage"), hr);
  }

	PositionEnum pageNum;
  hr = m_piRecordset->get_AbsolutePage(&pageNum);
	if FAILED( hr )
		ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::AbsolutePage"), hr);

	return ((PositionEnum)pageNum);
}

// Method: AbsolutePage
//   Desc: Sets/Returns the current record position.
//         Call method with no argument to return current setting.
//
//   Args: nPosition
//          Position to set the current record. Position is 1' based.
//          Position is between 1 and the record count. Record count
//          is returned by the RecordCount() method.
//
// Return: Current record position (ordinal value) or PositionEnum value.
//            adPosUnknown	= -1,
//            adPosBOF	= -2,
//            adPosEOF	= -3
//
long CAxRecordset::AbsolutePosition(long nPosition)
{
	VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;
  _variant_t* pvtBookmark = NULL;

  if ( _IsEmpty() )
    return ( 0L );

  if ( nPosition < 0 || nPosition > RecordCount() )
		  ThrowAxException(AXLIB_ERROR_INVALID_POS, _T("CAxRecordset::AbsolutePosition"));

  if ( nPosition > 0 )
  {
		pvtBookmark = Bookmark();
    hr = m_piRecordset->put_AbsolutePosition(PositionEnum(nPosition));
		if FAILED( hr )
		{
			if ( pvtBookmark->vt != VT_EMPTY )
				m_piRecordset->put_Bookmark(*pvtBookmark);
		 
		  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::AbsolutePosition"), hr);
		}

		delete pvtBookmark;
    DoFieldExchange(FALSE);
  }

	PositionEnum recPos;

  hr = m_piRecordset->get_AbsolutePosition(&recPos);
	if FAILED( hr )
		ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::AbsolutePosition"), hr);

	return ((long)recPos);
}

// Method: ActiveCommand
//   Desc: Returns a reference to the associated CAxCommand object provided
//         the record set was initially opened by means of a CAxCommand.
//
//   Args: none
//
// Return: Reference to ADOCommand object. NULL if record set was
//         opened directly.
//
CAxCommand* CAxRecordset::ActiveCommand()
{
  return (m_pCommand);
}

// Method: ActiveConnection
//   Desc: Returns a reference to the associated CAxConnection object. 
//
//   Args: none
//
// Return: Reference to ADOConnection object.
//
CAxConnection* CAxRecordset::ActiveConnection()
{
  return (m_pConnection);
}

// Method: IsBOF - Proxy for ADO's BOF method
//   Desc: Determine if the current record pointer is before
//         the first record.
//
//   Args: none
//
// Return: Returns true is the record pointer is located before the
//         first record or if the recordset contains no records.
//         Otherwise false.
//
bool CAxRecordset::IsBOF()
{
	VALID_ADO_OBJECT(m_piRecordset);
	VARIANT_BOOL vbBOF = 0;

  HRESULT hr = m_piRecordset->get_BOF(&vbBOF);
	if FAILED( hr )
		ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::IsBOF"), hr);

	return ( vbBOF < 0 );
}

// Method: Bookmark
//   Desc: Set/Return a unique record identifier in the form of a 
//         _variant_t class. If the Bookmark method is called with
//         no arguments, a new variant is created and returned to
//         the caller. The host application needs to save this
//         refernece and delete it when no longer needed. Otherwise,
//         a memory leak will occur. If a variant argument is supplied,
//         Bookmark will attempt to restore the record position as 
//         specified in the variant argument.
//
//   Args: _variant_t*
//           Reference to an existing variant type. This is generally
//           the variant reference that was returned from a previous
//           call to Bookmark
//
// Return: Reference to a newly created instance of a _variant_t class
//         if the method was called with no argument. Otherwise, NULL
//
_variant_t* CAxRecordset::Bookmark(_variant_t* pvtBookmark)
{
	VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;
  _variant_t *pvtBkMrk = NULL;

  if ( pvtBookmark && pvtBookmark->vt == VT_R8 )
  {
    hr = m_piRecordset->put_Bookmark(*pvtBookmark);
	  if FAILED( hr )
		  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Bookmark"), hr);

  	DoFieldExchange(FALSE);
  }
  else
  {
    if ( AbsolutePosition() > 0 )
    {
      pvtBkMrk  = new _variant_t;
      hr = m_piRecordset->get_Bookmark(pvtBkMrk );
      if FAILED( hr )
      {
        delete pvtBkMrk;
		    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Bookmark"), hr);
      }
    }
  }

  return (pvtBkMrk );
}

// Method: CacheSize
//   Desc: Set/Return the number of records that are kept in
//         local memory. The default value is 1. Can be changed
//         throughout the life of the record set object.
//
//   Args: nCacheSize
//           Specified the number of records to keep in local memory.
//           The value must be 1 or greater.
//
// Return: The current CacheSize setting.
//
long CAxRecordset::CacheSize(long nCacheSize)
{
	VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;

  if ( nCacheSize > 0 )
  {
    hr = m_piRecordset->put_CacheSize(nCacheSize);
	  if FAILED( hr )
		  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::CacheSize"), hr);
  }

  long cacheSize;
  hr = m_piRecordset->get_CacheSize(&cacheSize);
  if FAILED( hr )
		ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::CacheSize"), hr);

  return (cacheSize);
}

// Method: CursorLocation
//   Desc: Set/Return the location of the cursor service.
//         Read-Write while the record set is closed. Read-only
//         if the record set is open. Default is adUseServer.
//         Host applications will generally want to set this
//         to adUseClient.
//
//   Args: pCursorLocation
//           Reference to a CursorLocationEnum variable.
//            adUseNone	= 1,
//            adUseServer	= 2,
//            adUseClient	= 3,
//            adUseClientBatch	= 3
//
// Return: The current CursorLocationEnum setting.
//
CursorLocationEnum CAxRecordset::CursorLocation(long eCursorLocation)
{
	VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;

  if ( eCursorLocation >= adUseNone && eCursorLocation <= adUseClient )
  {
    hr = m_piRecordset->put_CursorLocation((CursorLocationEnum)eCursorLocation);
	  if FAILED( hr )
		  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::CursorLocation"), hr);
  }

  CursorLocationEnum curLoc;
  hr = m_piRecordset->get_CursorLocation(&curLoc);
	if FAILED( hr )
		ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::CursorLocation"), hr);

  return (curLoc);
}

// Method: CursorType
//   Desc: Set/Return the type of cursor used for the recordset.
//         Read-Write while the recordset is closed. Read-only
//         if the recordset is open. Default is adOpenForwardOnly.
//
//   Args: pCursorLocation
//           Reference to a CursorTypeEnum variable.
//            adOpenUnspecified	= -1,
//            adOpenForwardOnly	= 0,
//            adOpenKeyset	= 1,
//            adOpenDynamic	= 2,
//            adOpenStatic	= 3
//
// Return: The current CursorTypeEnum setting.
//
CursorTypeEnum CAxRecordset::CursorType(CursorTypeEnum eCursorType)
{
	VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;

  if ( eCursorType >= adOpenForwardOnly && eCursorType <= adOpenStatic )
  {
    hr = m_piRecordset->put_CursorType(eCursorType);
	  if FAILED( hr )
		  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::CursorType"), hr);
  }

  hr = m_piRecordset->get_CursorType(&m_eCursorType);
	if FAILED( hr )
		ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::CursorType"), hr);

   
	return (m_eCursorType);
}

// Method: EditMode
//   Desc: Return the editing status for the current record.
//
//   Args: none
//
// Return: The current EditModeEnum setting.
//          adEditNone	= 0,
//          adEditInProgress	= 0x1,
//          adEditAdd	= 0x2,
//          adEditDelete	= 0x4
//
EditModeEnum CAxRecordset::EditMode()
{
	VALID_ADO_OBJECT(m_piRecordset);
	EditModeEnum editMode;

  HRESULT hr = m_piRecordset->get_EditMode(&editMode);
	if FAILED( hr )
		ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::EditMode"), hr);

	return (editMode);
}

// Method: IsEOF - Proxy for ADO's EOF method
//   Desc: Determine if the current record pointer is immediately
//         after the last record.
//
//   Args: none
//
// Return: Returns true is the record pointer is located after the
//         last record or if the recordset contains no records.
//         Otherwise false.
//
bool CAxRecordset::IsEOF()
{
	VALID_ADO_OBJECT(m_piRecordset);
	VARIANT_BOOL vbEOF = 0;

	if FAILED( m_piRecordset->get_EOF(&vbEOF) )
		return false;

	return ( vbEOF < 0);
}
 
// Method: Filter a set of records from a current recordset.
//   Desc: Sets/Returns the current filter. A variant value
//         is used which may contain a filter string (VT_BSTR)
//         or a predefined FilterGroupEnum (VT_I2). The client
//         application is responsible for determining, setting
//         and extracting variant values. If a filter string
//         is supplied, use a statement similar to that found
//         in an SQL WHERE clause, i.e. _T("AccountID Like 'BA%'")
//         Call method with no argument to get the current setting.
//
//   Args: *pvtFilter a reference to a _variant_t type.
//          If using FilterGroupEnum, values may be:
//            adFilterNone	= 0,
//            adFilterPendingRecords	= 1,
//            adFilterAffectedRecords	= 2,
//            adFilterFetchedRecords	= 3,
//            adFilterPredicate	= 4,
//            adFilterConflictingRecords	= 5
//
// Return:  Current filter string.
//
_variant_t CAxRecordset::Filter(_variant_t *pvtFilter)
{
	VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;

  if ( pvtFilter )
  {
    hr = m_piRecordset->put_Filter(*pvtFilter);
	  if FAILED( hr )
		  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Filter"), hr);
  }

  VARIANT vtFilter;

  hr = m_piRecordset->get_Filter(&vtFilter);
	if FAILED( hr )
		ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Filter"), hr);

  return (vtFilter);
}

//Not supported on VS6
#if _MSC_VER > 1200
LPCTSTR CAxRecordset::Index(LPCTSTR lpszIndex)
{
  VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;

  if ( Supports(adIndex) )
  {
    hr = m_piRecordset->put_Index(_bstr_t(lpszIndex));
    if FAILED( hr )
	    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Index"), hr);
  }

  BSTR bstrIndex;
  hr = m_piRecordset->get_Index(&bstrIndex);
  if FAILED( hr )
	  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Index"), hr);

#ifdef _UNICODE
  return (_bstr_t(bstrIndex).operator const wchar_t *());
#else
  return (_bstr_t(bstrIndex).operator const char*());
#endif
}
#endif

// Method: LockType
//   Desc: Sets/Returns the type of lock on records during edits.
//         Call method with no argument to return current setting.
//         Read-Write when recordset is closed. Read-only when
//         recordset is open.
//   Args: pLockType - Reference to a LockTypeEnum variable.
//          adLockUnspecified	= -1,
//          adLockReadOnly	= 1,
//          adLockPessimistic	= 2,
//          adLockOptimistic	= 3,
//          adLockBatchOptimistic	= 4
//
// Return:  Current LockTypeEnum setting.
//
LockTypeEnum CAxRecordset::LockType(LockTypeEnum eLockType)
{
	VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;

  if ( eLockType != adLockUnspecified )
  {
    hr = m_piRecordset->put_LockType(eLockType);
	  if FAILED( hr )
		  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Filter"), hr);
  }

	LockTypeEnum eLT;
  hr = m_piRecordset->get_LockType(&eLT);
	if FAILED( hr )
		ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Filter"), hr);

  return (eLT);
}

// Method: MarshalOptions
//   Desc: Sets/Returns the option as to which records should be
//         marshaled back to the server.
//
//   Args: *pOption - Reference to a MarshalOptionsEnum variable.
//          adMarshalAll	= 0,
//          adMarshalModifiedOnly	= 1
//
// Return: Current MarshalOptionsEnum value.
//
MarshalOptionsEnum CAxRecordset::MarshalOptions(long eOptions)
{
	VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;

  if ( eOptions >= adMarshalAll && eOptions <= adMarshalModifiedOnly )
  {
    hr = m_piRecordset->put_MarshalOptions((MarshalOptionsEnum)eOptions);
	  if FAILED( hr )
		  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::MarshalOptions"), hr);
  }

  MarshalOptionsEnum opt;
  hr = m_piRecordset->get_MarshalOptions(&opt);
	if FAILED( hr )
		ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::MarshalOptions"), hr);

  return (opt);
}

// Method: MaxRecords
//   Desc: Sets/Returns the maximum number of records that are
//         returned to a recordset when executing a query.
//         Read-Write when a recordset is closed. Read-only when
//         open. A value of 0 results in all valid records
//         being returned.
//
//   Args: nMaxRecords - Maximum number of records to return.
//
// Return: Current maximum records setting.
//
long CAxRecordset::MaxRecords(long nMaxRecords)
{
	VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;

  if ( nMaxRecords >= 0 )
  {
    //Can only set max records if recordset is closed
    if ( !_IsOpen() )
    {
      hr = m_piRecordset->put_MaxRecords(nMaxRecords);
      if FAILED( hr )
		    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::MaxRecords"), hr);
    }
  }

  long max;
  hr = m_piRecordset->get_MaxRecords(&max);
  if FAILED( hr )
		ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::MaxRecords"), hr);

  return (max);
}

// Method: PageCount
//   Desc: Returns the number of logical pages in the recordset.
//
//   Args: none
//
// Return: Number of logical pages.
//
long CAxRecordset::PageCount()
{
	VALID_ADO_OBJECT(m_piRecordset);
  long count;

  HRESULT hr = m_piRecordset->get_PageCount(&count);
  if FAILED( hr )
	  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::PageCount"), hr);
  
  return (count);
}

// Method: PageSize
//   Desc: Sets/Returns the number of records per logical page.
//
//   Args: nPageSize
//          Number of records in a logical page. Must be greater
//          than 0.
//
// Return: Number of records in a page.
//
long CAxRecordset::PageSize(long nPageSize)
{
	VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;

  if ( nPageSize > 0 )
  {
    hr = m_piRecordset->put_PageSize(nPageSize);
    if FAILED( hr )
	    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::PageSize"), hr);
  }

  long pageSize;
  hr = m_piRecordset->get_PageSize(&pageSize);
  if FAILED( hr )
	  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::PageSize"), hr);

  return (pageSize);
}

// Method: RecordCount
//   Desc: Returns the number of records in the recordset.
//
//   Args: none
//
// Return: Number of records.
//
long CAxRecordset::RecordCount()
{
	VALID_ADO_OBJECT(m_piRecordset);
	long nRecordCount;

  HRESULT hr = m_piRecordset->get_RecordCount(&nRecordCount);
	if FAILED( hr )
	  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::RecordCount"), hr);

	return (nRecordCount);
}

// Method: Sort
//   Desc: Set/Return a sort on one or more fields in a recordset.
//
//   Args: lpszSort
//          If non-NULL, identifies the name(s) of the fields
//          on which to sort. The syntax is the same as for
//          the SORT clause in an SQL statement. An empty
//          string value removes any existing sort. The
//          CursorLocation property must be set to adUseClient.
//
// Return: The current sort string.
//
LPCTSTR CAxRecordset::Sort(LPCTSTR lpszSort)
{
	VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;

  if ( lpszSort )
  {
    hr = m_piRecordset->put_Sort(_bstr_t(lpszSort));
	  if FAILED( hr )
	    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Sort"), hr);
  }

  BSTR bstrSort;
  hr = m_piRecordset->get_Sort(&bstrSort);
  if FAILED( hr )
	  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Sort"), hr);

#ifdef _UNICODE
  return (_bstr_t(bstrSort).operator const wchar_t *());
#else
  return (_bstr_t(bstrSort).operator const char*());
#endif
}

// Method: Source
//   Desc: Set/Return the Source property for the recordset.
//
//   Args: lpszSource
//          Sets/Returns the source for the recordset data.
//          Typically the source is specified in the Open
//          method.
//
// Return: The current data source.
//
LPCTSTR CAxRecordset::Source(LPCTSTR lpszSource)
{
	VALID_ADO_OBJECT(m_piRecordset);
  HRESULT hr = S_OK;

  if ( lpszSource  && !_IsOpen() )
  {
    hr = m_piRecordset->put_Source(_bstr_t(lpszSource));
	  if FAILED( hr )
	    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Source"), hr);
  }

  _variant_t vtSource;
  hr =  m_piRecordset->get_Source(&vtSource);
  if FAILED( hr )
	  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Source"), hr);

#ifdef _UNICODE
  return (_bstr_t(vtSource.bstrVal).operator const wchar_t *());
#else
  return (_bstr_t(vtSource.bstrVal).operator const char *());
#endif
}

ObjectStateEnum CAxRecordset::State()
{
	VALID_ADO_OBJECT(m_piRecordset);
	long lState;

  HRESULT hr = m_piRecordset->get_State(&lState);
	if FAILED( hr )
	  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::State"), hr);

	return ((ObjectStateEnum)lState);
}

RecordStatusEnum CAxRecordset::Status()
{
	VALID_ADO_OBJECT(m_piRecordset);
	long lStatus;

  HRESULT hr = m_piRecordset->get_Status(&lStatus);
	if FAILED( hr )
	  ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::Status"), hr);

	return ((RecordStatusEnum)lStatus);
}


long CAxRecordset::FieldCount()
{
	VALID_ADO_OBJECT(m_piRecordset);
	long lFieldCount;

	if FAILED( m_pFields->get_Count(&lFieldCount) )
		ThrowAxException(AXLIB_ERROR_NONE);

	return lFieldCount;
}




//----------------------------------------------------------
// Bookmark


//----------------------------------------------------------
// Utility


//----------------------------------------------------------
// Data Exchange
HRESULT CAxRecordset::_getADOField(LPCTSTR lpszColumn, ADOField** ppFld)
{
	VALID_ADO_OBJECT(m_piRecordset);
	ASSERT( m_pFields );

  return ( m_pFields->get_Item(_variant_t(lpszColumn), ppFld) );
}

HRESULT CAxRecordset::_isUpdatable(ADOField* pFld)
{
  long lAttrib = 0;
  HRESULT hr = pFld->get_Attributes(&lAttrib);

  if ( FAILED(hr) )
    return ( hr );

	//If both the adFldUpdatable (4) and adFldUnknownUpdatable (8)
	//flags are false, as is the case with an Identity field, then
	//we need to avoid an attempt to write data back to the field.
  //Returning the E_ACCESSDENIED value which, in this instance,
  //should not be considered an actual error needing to be handled.
	if ( !(lAttrib & adFldUpdatable) && !(lAttrib & adFldUnknownUpdatable) )
    hr = ERROR_ACCESS_DENIED;

  return ( hr );
}


_variant_t* CAxRecordset::_GetFieldValue(LPCTSTR lpszColumn)
{
  m_hr = _setGetFieldValue(false, lpszColumn, VT_VARIANT, NULL);

  if FAILED(m_hr)
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::_GetFieldValue"), m_hr);

  return (&m_vtData);
}

_variant_t* CAxRecordset::_GetFieldValue(long nColumn)
{
	VALID_ADO_OBJECT(m_piRecordset);
	ASSERT( m_pFields );
  ADOField* pFld = NULL;

  m_hr = m_pFields->get_Item(_variant_t(nColumn), &pFld);
  if ( FAILED(m_hr) )
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::_GetFieldValue"), m_hr);

  m_vtData.Clear();
	m_vtData.vt = VT_ERROR;
	m_vtData.scode = DISP_E_PARAMNOTFOUND;

	m_hr = pFld->get_Value(&m_vtData);
  if ( FAILED(m_hr) )
  {
    m_vtData.Clear();
    ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::_GetFieldValue"), m_hr);
  }

  return (&m_vtData);
}

//------------------------------------------------------------------------
// Removed steps to set assumed values if the attempt to get a field
// value should fail. See revision notes under  v1.1.1 in AxLib.h.
HRESULT CAxRecordset::_getFldValue(ADOField* pFld, unsigned short varType, void** ppValue)
{
  HRESULT hr = S_OK;

  //If the table is empty or the field is empty
  //the variant's type is set to VT_NULL
  if ( _IsEmpty() )
  {
    m_vtData.vt = VT_NULL;
  }
  else
  {
    m_vtData.vt = VT_ERROR;
    m_vtData.scode = DISP_E_PARAMNOTFOUND;
    hr = pFld->get_Value(&m_vtData);
  }

  if ( *ppValue == NULL || (m_vtData.vt == VT_NULL) )
    return ( hr );

  if ( FAILED(hr) )
    return ( hr );

  switch ( varType )
  {
  case VT_I2 :
    (*(short*)*ppValue) = (short)m_vtData.iVal;
 //   (*(short*)*ppValue) = (m_vtData.vt == VT_NULL) ? 0 :
 //     (short)m_vtData.iVal;
    break;

  case VT_I4 :
      (*(int*)*ppValue) = (int)m_vtData.lVal;
 //   (*(int*)*ppValue) = (m_vtData.vt == VT_NULL) ? 0 :
 //     (int)m_vtData.lVal;
    break;

  case VT_I8 :
    (*(LONGLONG*)*ppValue) = (LONGLONG)(m_vtData.operator DECIMAL()).Lo64;
//    (*(LONGLONG*)*ppValue) = (m_vtData.vt == VT_NULL) ? 0 :
//      (LONGLONG)(m_vtData.operator DECIMAL()).Lo64;
    break;

  case VT_R4 : 
    (*(float*)*ppValue) = (float)m_vtData.fltVal;
//    (*(float*)*ppValue) = (m_vtData.vt == VT_NULL) ? 0L : 
//      (float)m_vtData.fltVal;
    break;

  case VT_R8 :
    (*(double*)*ppValue) = m_vtData.dblVal;
//    (*(double*)*ppValue) = (m_vtData.vt == VT_NULL) ? 0L :
//      m_vtData.dblVal;
    break;

  case VT_BOOL :
    (*(bool*)*ppValue) = (bool)(m_vtData.boolVal == -1);
 //   (*(bool*)*ppValue) = (m_vtData.vt == VT_NULL) ? false :
 //     (bool)(m_vtData.boolVal == -1);
    break;

  case VT_VARIANT :
    (*(_variant_t*)*ppValue) = m_vtData;
    break;

  case VT_BSTR :
//    if ( m_vtData.vt == VT_NULL )
//      ((CString*)*ppValue)->Empty();
//    else
      ((CString*)*ppValue)->operator =((LPCWSTR)m_vtData.bstrVal);
    break;

  case VT_CY :
//    if ( m_vtData.vt == VT_NULL )
//      (*(COleCurrency*)*ppValue).m_cur.int64 = 0L;
//    else
      (*(COleCurrency*)*ppValue) = m_vtData.cyVal;
    break;

  case VT_DATE :
//    if ( m_vtData.vt == VT_NULL )
//      (*(COleDateTime*)*ppValue) = COleDateTime();
//    else
      ((COleDateTime*)*ppValue)->operator =(m_vtData);
    break;

  case VT_DECIMAL :
//    if ( m_vtData.vt == VT_NULL )
//      (*(double*)*ppValue) = 0L;
 //   else
      hr = VarR8FromDec(&m_vtData.decVal, (double*)*ppValue);
    break;

  case VT_UI1 :
    (*(BYTE*)*ppValue) = m_vtData.bVal;
    break;

  case VT_ARRAY | VT_UI1 :
    //Place holder for Image data type to avoid default action
    break;

  default :
    m_vtData.Clear();
    break;
  }

  return ( hr );
}


HRESULT CAxRecordset::_setFldValue(ADOField* pFld, unsigned short varType, void** ppValue)
{
  HRESULT hr = S_OK;

  m_vtData.vt = varType;

  switch ( varType )
  {
  case VT_I2 :
    m_vtData.iVal = *(short*)*ppValue;
    break;

  case VT_I4 :
    m_vtData.lVal = *(long*)*ppValue;
    break;

//VT_I8 not supported under VS6
  case VT_I8 :
#if _MSC_VER > 1200
    m_vtData.llVal = *(LONGLONG*)*ppValue;
#else
    m_vtData.cyVal.int64 = (*(LONGLONG*)*ppValue);
#endif
    break;

  case VT_R4 :
    m_vtData.fltVal = *(float*)*ppValue;
    break;

  case VT_R8 :
    m_vtData.dblVal = *(double*)*ppValue;
    break;

  case VT_BOOL :
    m_vtData.boolVal = (*(bool*)*ppValue) ? VARIANT_TRUE : VARIANT_FALSE;
    break;

  case VT_VARIANT :
    m_vtData = *(VARIANT*)*ppValue;
    break;

  case VT_BSTR :
    m_vtData.bstrVal = ((CString*)*ppValue)->AllocSysString();
    break;

  case VT_CY :
    m_vtData.cyVal = *(CURRENCY*)*ppValue;
    break;

  case VT_DATE :
    hr = VarDateFromR8((*(DATE*)*ppValue), &m_vtData.date);
    break;

  case VT_DECIMAL :
    hr = VarDecFromR8((*(double*)*ppValue), &m_vtData.decVal);
    break;

  case VT_UI1 :
    m_vtData.bVal = *(BYTE*)*ppValue;
    break;

  case VT_ARRAY | VT_UI1 :
    //Place holder for Image data type to avoid default action
    break;

  default :
    m_vtData.Clear();
    break;
  }

  if ( SUCCEEDED(hr) )
      hr = pFld->put_Value(m_vtData);

  return ( hr );
}

HRESULT CAxRecordset::_setGetFieldValue(bool bSave, LPCTSTR lpszColumn, 
                                        unsigned short varType, void* pvValue)
{
  ADOField* pADOFld = NULL;
  m_hr = S_OK;

  //If we get an error here, assume the query
  //that resulted in the returned recordset
  //simply did not specify the field in question
  //rather than a bad field definition. The
  //ERROR_INVALID_ACCESS result is returned which
  //is not considered a fatal error.
  m_hr = _getADOField(lpszColumn, &pADOFld);
  if FAILED(m_hr)
    return ( (HRESULT)ERROR_INVALID_ACCESS );

  m_hr = _isUpdatable(pADOFld);
  if FAILED(m_hr)
    return ( m_hr );

  if ( bSave && (m_hr != ERROR_ACCESS_DENIED) )
    m_hr = _setFldValue(pADOFld, varType, &pvValue);
  else
    m_hr = _getFldValue(pADOFld, varType, &pvValue);

  return ( m_hr );
}


//Max size is 8k
void CAxRecordset::FX_Binary(bool bSave, LPCTSTR lpszColumn, BYTE** ppValue)
{
  if ( bSave && *ppValue != NULL )
  { 
    BINDATAINFOHEADER binHeader;
    memcpy((void*)&binHeader, (const void*)*ppValue, sizeof(BINDATAINFOHEADER));
    if ( binHeader.imgSize > 8192 )
		  ThrowAxException(AXLIB_ERROR_BUF_SIZE);
  }
  
  FX_Image(bSave, lpszColumn, ppValue);
}


//Max size is 2^31 - 1 (2,147,483,647) bytes
void CAxRecordset::FX_Image(bool bSave, LPCTSTR lpszColumn, BYTE** ppValue)
{
	HRESULT hr = S_OK;
	BYTE HUGEP *pData = NULL;
  m_vtData.Clear();

  if ( bSave )
  {
    if ( *ppValue == NULL )
      return;

    //Read the header information
 	  BINDATAINFOHEADER binHeader;
    BYTE* pBuf = *ppValue;

	  memcpy((void*)&binHeader, (const void*)pBuf, sizeof(BINDATAINFOHEADER));
    long lBufSize = sizeof(BINDATAINFOHEADER) + binHeader.imgSize;
   
		SAFEARRAYBOUND rgsabound[1];
		rgsabound[0].lLbound = 0;
		rgsabound[0].cElements = lBufSize;

    m_vtData.vt = VT_ARRAY | VT_UI1;
    m_vtData.parray = SafeArrayCreate(VT_UI1, 1, rgsabound);

		if ( m_vtData.parray != NULL )
		{
			hr = SafeArrayAccessData(m_vtData.parray, (void HUGEP* FAR*)&pData);
			if ( SUCCEEDED(hr) )
			{
				memcpy((void*)pData, (const void*)pBuf, lBufSize);
				hr = SafeArrayUnaccessData(m_vtData.parray);
  
        hr = _setGetFieldValue(bSave, lpszColumn, VT_ARRAY | VT_UI1, NULL);

        if ( FAILED(m_hr) )
          ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::FX_Image"), hr);

        //Clear the variant to free memory used by the parray member
        m_vtData.Clear();
			}
		}
  }
  else
  {
   if ( *ppValue != NULL )
    {
      delete [] *ppValue;
      *ppValue = NULL;
    }
    hr = _setGetFieldValue(bSave, lpszColumn, VT_ARRAY | VT_UI1, NULL);

    if ( FAILED(hr) )
      ThrowAxException(AXLIB_ERROR_NONE, _T("CAxRecordset::FX_Image"), hr);

		if ( m_vtData.vt == VT_NULL )
			return;

    long lUBound;
		hr = SafeArrayGetUBound(m_vtData.parray, 1, &lUBound);
		if ( SUCCEEDED(hr) )
		{
			hr = SafeArrayAccessData(m_vtData.parray, (void HUGEP* FAR*)&pData);
			if ( SUCCEEDED(hr) )
			{
				long lByteCount = lUBound + 1;	//UBound is 0's based
				*ppValue = new BYTE[lByteCount];
				ZeroMemory((void*)*ppValue, lByteCount);

				memcpy((void*)*ppValue, (const void*)pData, lByteCount);

				SafeArrayUnaccessData(m_vtData.parray);
			}
		}
  }

}


//Max size is 8K
void CAxRecordset::FX_VarBinary(bool bSave, LPCTSTR lpszColumn, BYTE** ppValue)
{
  if ( bSave && *ppValue != NULL )
  { 
    BINDATAINFOHEADER binHeader;
    memcpy((void*)&binHeader, (const void*)*ppValue, sizeof(BINDATAINFOHEADER));
    if ( binHeader.imgSize > 8192 )
		  ThrowAxException(AXLIB_ERROR_BUF_SIZE);
  }
  
  FX_Image(bSave, lpszColumn, ppValue);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Unknown
I work almost exclusively with C++ and database applications and manage the IT concerns for the engineering department of a large food processing company. As such, I get involved with add-ons to existing commercial software, SCADA, interfacing with industrial components (PLC’s, automated weight checking machines, etc.) and other pet projects (currently working on v4.0 of my own document management app).

I get totally fed-up, frustrated and thoroughly delighted with programming and computers, but always remember the sage words of an old lab tech from my Air Force days; “There was never a statue erected to the man who left well enough alone.” I’ll be at it again tomorrow!

Comments and Discussions