//---------------------------------------------------------------------------
// CursorMain.cpp : CursorMain implementation
//
// Copyright (c) 1996 Microsoft Corporation, All Rights Reserved
// Developed by Sheridan Software Systems, Inc.
//---------------------------------------------------------------------------

#include "stdafx.h"
#include "MSR2C.h"
#include "Notifier.h"
#include "RSColumn.h"
#include "RSSource.h"
#include "CursMain.h"
#include "ColUpdat.h"
#include "CursPos.h"
#include "CursBase.h"
#include "enumcnpt.h"
#include "Cursor.h"
#include "Bookmark.h"
#include "fastguid.h"

SZTHISFILE

#include "ARRAY_P.inl"

// static data
DWORD               CVDCursorMain::s_dwMetaRefCount   = 0;
ULONG               CVDCursorMain::s_ulMetaColumns    = 0;
CVDRowsetColumn *   CVDCursorMain::s_rgMetaColumns    = NULL;


//=--------------------------------------------------------------------------=
// CVDCursorMain - Constructor
//
CVDCursorMain::CVDCursorMain(LCID lcid) : m_resourceDLL(lcid)
{
    m_fWeAddedMetaRef		    = FALSE;
	m_fPassivated			    = FALSE;
	m_fColumnsRowsetSupported   = FALSE;
    m_fInternalInsertRow        = FALSE;
    m_fInternalDeleteRows       = FALSE;
    m_fInternalSetData          = FALSE;

	m_fLiteralBookmarks		    = FALSE;			
	m_fOrderedBookmarks		    = FALSE;
    m_fBookmarkSkipped          = FALSE;			

	m_fConnected			    = FALSE;
	m_dwAdviseCookie		    = 0;
    m_ulColumns				    = 0;
    m_rgColumns				    = NULL;

    m_cbMaxBookmark			    = 0;

	VDUpdateObjectCount(1);  // update object count to prevent dll from being unloaded

#ifdef _DEBUG
    g_cVDCursorMainCreated++;
#endif
}

//=--------------------------------------------------------------------------=
// ~CVDCursorMain - Destructor
//
CVDCursorMain::~CVDCursorMain()
{
	Passivate();

	VDUpdateObjectCount(-1);  // update object count to allow dll to be unloaded

#ifdef _DEBUG
    g_cVDCursorMainDestroyed++;
#endif
}

//=--------------------------------------------------------------------------=
// Pasivate when external ref count gets to zero
//
void CVDCursorMain::Passivate()
{

	if (m_fPassivated)
		return;

	m_fPassivated			= TRUE;

    if (IsRowsetValid())
	{
		if (m_hAccessorBM)
			GetAccessor()->ReleaseAccessor(m_hAccessorBM, NULL);

		if (m_fConnected)
			DisconnectIRowsetNotify();
	}

    DestroyColumns();
    DestroyMetaColumns();

}

//=--------------------------------------------------------------------------=
// Create - Create cursor provider from row position or rowset
//=--------------------------------------------------------------------------=
// This function creates and initializes a new cursor main object
//
// Parameters:
//    pRowPosition  - [in]  original IRowPosition provider (may be NULL)
//    pRowset       - [in]  original IRowset provider
//    ppCursor      - [out] resulting ICursor provider
//    lcid          - [in]  locale identifier
//
// Output:
//    HRESULT - S_OK if successful
//              E_INVALIDARG bad parameter
//              E_OUTOFMEMORY not enough memory
//              VD_E_CANNOTCONNECTIROWSETNOTIFY unable to connect IRowsetNotify
//
// Notes:
//
HRESULT CVDCursorMain::Create(IRowPosition* pRowPosition, IRowset * pRowset, ICursor ** ppCursor, LCID lcid)
{
    ASSERT_POINTER(pRowset, IRowset)
    ASSERT_POINTER(ppCursor, ICursor*)

    if (!pRowset || !ppCursor)
        return E_INVALIDARG;

    // create new cursor main object
    CVDCursorMain * pCursorMain = new CVDCursorMain(lcid);

    if (!pCursorMain)
        return E_OUTOFMEMORY;

    // initialize rowset source
    HRESULT hr = pCursorMain->Initialize(pRowset);

    if (FAILED(hr))
    {
        pCursorMain->Release();
        return hr;
    }

    // create array of column objects
    hr = pCursorMain->CreateColumns();

    if (FAILED(hr))
    {
        pCursorMain->Release();
        return hr;
    }

    // create array of meta-column objects
    hr = pCursorMain->CreateMetaColumns();

    if (FAILED(hr))
    {
        pCursorMain->Release();
        return hr;
    }

	// create bookmark accessor
    DBBINDING rgBindings[1];
	DBBINDSTATUS rgStatus[1];

	memset(rgBindings, 0, sizeof(DBBINDING));

	rgBindings[0].iOrdinal      = 0;
    rgBindings[0].obValue       = 4;
    rgBindings[0].obLength      = 0;
    rgBindings[0].dwPart        = DBPART_VALUE | DBPART_LENGTH;
    rgBindings[0].dwMemOwner    = DBMEMOWNER_CLIENTOWNED;
    rgBindings[0].cbMaxLen      = pCursorMain->GetMaxBookmarkLen();
    rgBindings[0].wType         = DBTYPE_BYTES;

	hr = pCursorMain->GetAccessor()->CreateAccessor(DBACCESSOR_ROWDATA,
													1,
													rgBindings,
													0,
													&pCursorMain->m_hAccessorBM,
													rgStatus);
    if (FAILED(hr))
    {
        pCursorMain->Release();
        return VD_E_CANNOTCREATEBOOKMARKACCESSOR;
    }

    // create new cursor position object
    CVDCursorPosition * pCursorPosition;

    hr = CVDCursorPosition::Create(pRowPosition, pCursorMain, &pCursorPosition, &pCursorMain->m_resourceDLL);

    if (FAILED(hr))
    {
        pCursorMain->Release();
        return hr;
    }

    // create new cursor object
    CVDCursor * pCursor;

    hr = CVDCursor::Create(pCursorPosition, &pCursor, &pCursorMain->m_resourceDLL);

    if (FAILED(hr))
    {
        ((CVDNotifier*)pCursorPosition)->Release();
        pCursorMain->Release();
        return hr;
    }

    // connect IRowsetNotify
	hr = pCursorMain->ConnectIRowsetNotify();

	if (SUCCEEDED(hr))
		pCursorMain->m_fConnected = TRUE;

    // check rowset properties
    BOOL fCanHoldRows = TRUE;

	IRowsetInfo * pRowsetInfo = pCursorMain->GetRowsetInfo();

	if (pRowsetInfo)
	{
		DBPROPID propids[] = { DBPROP_LITERALBOOKMARKS,
							   DBPROP_ORDEREDBOOKMARKS,
                               DBPROP_BOOKMARKSKIPPED,
                               DBPROP_CANHOLDROWS };

		const DBPROPIDSET propsetids[] = { propids, 4, {0,0,0,0} };
		memcpy((void*)&propsetids[0].guidPropertySet, &DBPROPSET_ROWSET, sizeof(DBPROPSET_ROWSET));

		ULONG cPropertySets = 0;
		DBPROPSET * propset = NULL;
		hr = pRowsetInfo->GetProperties(1, propsetids, &cPropertySets, &propset);

		if (SUCCEEDED(hr) && propset && propset->rgProperties)
		{
			if (DBPROPSTATUS_OK == propset->rgProperties[0].dwStatus)
				pCursorMain->m_fLiteralBookmarks = V_BOOL(&propset->rgProperties[0].vValue);

			if (DBPROPSTATUS_OK == propset->rgProperties[1].dwStatus)
				pCursorMain->m_fOrderedBookmarks = V_BOOL(&propset->rgProperties[1].vValue);

			if (DBPROPSTATUS_OK == propset->rgProperties[2].dwStatus)
				pCursorMain->m_fBookmarkSkipped = V_BOOL(&propset->rgProperties[2].vValue);

			if (DBPROPSTATUS_OK == propset->rgProperties[3].dwStatus)
				fCanHoldRows = V_BOOL(&propset->rgProperties[3].vValue);
		}

		if (propset)
		{
			if (propset->rgProperties)
				g_pMalloc->Free(propset->rgProperties);

			g_pMalloc->Free(propset);
		}
	}

    // release our references
    pCursorMain->Release();
    ((CVDNotifier*)pCursorPosition)->Release();

    // check for required property
    if (!fCanHoldRows)
    {
        pCursor->Release();
        return VD_E_REQUIREDPROPERTYNOTSUPPORTED;
    }

    // we're done
    *ppCursor = pCursor;

    return S_OK;
}

//=--------------------------------------------------------------------------=
// Create - Create cursor provider from rowset
//=--------------------------------------------------------------------------=
// This function creates and initializes a new cursor main object
//
// Parameters:
//    pRowset       - [in]  original IRowset provider
//    ppCursor      - [out] resulting ICursor provider
//    lcid          - [in]  locale identifier
//
// Output:
//    HRESULT - S_OK if successful
//              E_INVALIDARG bad parameter
//              E_OUTOFMEMORY not enough memory
//              VD_E_CANNOTCONNECTIROWSETNOTIFY unable to connect IRowsetNotify
//
// Notes:
//
HRESULT CVDCursorMain::Create(IRowset * pRowset, ICursor ** ppCursor, LCID lcid)
{
    ASSERT_POINTER(pRowset, IRowset)
    ASSERT_POINTER(ppCursor, ICursor*)

    if (!pRowset || !ppCursor)
        return E_INVALIDARG;

	// create cursor as done before row position
	return Create(NULL, pRowset, ppCursor, lcid);
}

//=--------------------------------------------------------------------------=
// Create - Create cursor provider from row position
//=--------------------------------------------------------------------------=
// This function creates and initializes a new cursor main object
//
// Parameters:
//    pRowPosition  - [in]  original IRowPosition provider
//    ppCursor      - [out] resulting ICursor provider
//    lcid          - [in]  locale identifier
//
// Output:
//    HRESULT - S_OK if successful
//              E_INVALIDARG bad parameter
//              E_OUTOFMEMORY not enough memory
//              VD_E_CANNOTCONNECTIROWSETNOTIFY unable to connect IRowPositionNotify
//				VD_E_CANNOTGETROWSETINTERFACE unable to get IRowset
//
// Notes:
//
HRESULT CVDCursorMain::Create(IRowPosition * pRowPosition, ICursor ** ppCursor, LCID lcid)
{
    ASSERT_POINTER(pRowPosition, IRowPosition)
    ASSERT_POINTER(ppCursor, ICursor*)

    if (!pRowPosition || !ppCursor)
        return E_INVALIDARG;

	IRowset * pRowset;

	// get IRowset from IRowPosition
	HRESULT hr = pRowPosition->GetRowset(IID_IRowset, (IUnknown**)&pRowset);

	if (FAILED(hr))
		return VD_E_CANNOTGETROWSETINTERFACE;

	// create cursor with new row position parameter
	hr = Create(pRowPosition, pRowset, ppCursor, lcid);

	pRowset->Release();

    // we're done
	return hr;
}

typedef struct tagVDMETADATA_METADATA
	{
        const CURSOR_DBCOLUMNID * pCursorColumnID;
		ULONG	cbMaxLength;
		CHAR *	pszName;
		DWORD	dwCursorType;
	} VDMETADATA_METADATA;

#define MAX_METADATA_COLUMNS 21

static const VDMETADATA_METADATA g_MetaDataMetaData[MAX_METADATA_COLUMNS] =
{
    // Bookmark column
	{ &CURSOR_COLUMN_BMKTEMPORARY,		sizeof(ULONG),				NULL,					CURSOR_DBTYPE_BLOB },
    // data columns
	{ &CURSOR_COLUMN_COLUMNID,			sizeof(CURSOR_DBCOLUMNID),	"COLUMN_COLUMNID",		CURSOR_DBTYPE_COLUMNID },
	{ &CURSOR_COLUMN_DATACOLUMN,		sizeof(VARIANT_BOOL),		"COLUMN_DATACOLUMN",	CURSOR_DBTYPE_BOOL },
	{ &CURSOR_COLUMN_ENTRYIDMAXLENGTH,	sizeof(ULONG),				"COLUMN_ENTRYIDMAXLENGTH",CURSOR_DBTYPE_I4 },
	{ &CURSOR_COLUMN_FIXED,				sizeof(VARIANT_BOOL),		"COLUMN_FIXED",			CURSOR_DBTYPE_BOOL },
	{ &CURSOR_COLUMN_MAXLENGTH,			sizeof(ULONG),				"COLUMN_MAXLENGTH",		CURSOR_DBTYPE_I4 },
	{ &CURSOR_COLUMN_NAME,				256,						"COLUMN_NAME",			VT_LPWSTR },
	{ &CURSOR_COLUMN_NULLABLE,			sizeof(VARIANT_BOOL),		"COLUMN_NULLABLE",		CURSOR_DBTYPE_BOOL },
	{ &CURSOR_COLUMN_NUMBER,			sizeof(ULONG),				"COLUMN_NUMBER",		CURSOR_DBTYPE_I4 },
	{ &CURSOR_COLUMN_SCALE,				sizeof(ULONG),				"COLUMN_SCALE",			CURSOR_DBTYPE_I4 },
	{ &CURSOR_COLUMN_TYPE,				sizeof(ULONG),				"COLUMN_TYPE",			CURSOR_DBTYPE_I4 },
	{ &CURSOR_COLUMN_UPDATABLE,			sizeof(ULONG),				"COLUMN_UPDATABLE",		CURSOR_DBTYPE_I4 },
	{ &CURSOR_COLUMN_BINDTYPE,			sizeof(ULONG),				"COLUMN_BINDTYPE",		CURSOR_DBTYPE_I4 },
    // optional metadata columns - supported with IColumnsRowset only)
	{ &CURSOR_COLUMN_AUTOINCREMENT,		sizeof(VARIANT_BOOL),		"COLUMN_AUTOINCREMENT",	CURSOR_DBTYPE_BOOL },
	{ &CURSOR_COLUMN_BASECOLUMNNAME,	256,						"COLUMN_BASECOLUMNNAME",VT_LPWSTR },
	{ &CURSOR_COLUMN_BASENAME,			256,						"COLUMN_BASENAME",		VT_LPWSTR },
	{ &CURSOR_COLUMN_COLLATINGORDER,	sizeof(LCID),				"COLUMN_COLLATINGORDER",CURSOR_DBTYPE_I4 },
	{ &CURSOR_COLUMN_DEFAULTVALUE,		256,						"COLUMN_DEFAULTVALUE",	VT_LPWSTR },
	{ &CURSOR_COLUMN_HASDEFAULT,		sizeof(VARIANT_BOOL),		"COLUMN_HASDEFAULT",	CURSOR_DBTYPE_BOOL },
	{ &CURSOR_COLUMN_CASESENSITIVE,		sizeof(VARIANT_BOOL),		"COLUMN_CASESENSITIVE",	CURSOR_DBTYPE_BOOL },
	{ &CURSOR_COLUMN_UNIQUE,			sizeof(VARIANT_BOOL),		"COLUMN_UNIQUE",		CURSOR_DBTYPE_BOOL },
};

//=--------------------------------------------------------------------------=
// CreateMetaColumns - Create array of meta-column objects
//
HRESULT CVDCursorMain::CreateMetaColumns()
{
    HRESULT hr = S_OK;

    EnterCriticalSection(&g_CriticalSection);

    if (!s_dwMetaRefCount)
    {
		// allocate a static aray of metadata metadata columns
        s_rgMetaColumns = new CVDRowsetColumn[MAX_METADATA_COLUMNS];

        if (!s_rgMetaColumns)
        {
            hr = E_OUTOFMEMORY;
            goto cleanup;
        }

        s_ulMetaColumns = MAX_METADATA_COLUMNS; // number of columns for IColumnsInfo

		// initialize the array elments from the static g_MetaDataMetaData table
		for (int i = 0; i < MAX_METADATA_COLUMNS; i++)
		{
	        s_rgMetaColumns[i].Initialize(g_MetaDataMetaData[i].pCursorColumnID,
										  (BOOL)i, // false for 1st column (bookmark) TRUE for all other columns
										  g_MetaDataMetaData[i].cbMaxLength,
										  g_MetaDataMetaData[i].pszName,
										  g_MetaDataMetaData[i].dwCursorType,
										  i );	// ordinal number
		}
    }

    s_dwMetaRefCount++;

    m_fWeAddedMetaRef = TRUE;

cleanup:
    LeaveCriticalSection(&g_CriticalSection);

    return hr;
}

//=--------------------------------------------------------------------------=
// DestroyMetaColumns - Destroy array of meta-columns objects
//
void CVDCursorMain::DestroyMetaColumns()
{
    EnterCriticalSection(&g_CriticalSection);

    if (m_fWeAddedMetaRef)
    {
        s_dwMetaRefCount--;

        if (!s_dwMetaRefCount)
        {
            delete [] s_rgMetaColumns;

            s_ulMetaColumns = 0;
            s_rgMetaColumns = NULL;
        }
    }

    LeaveCriticalSection(&g_CriticalSection);
}

//=--------------------------------------------------------------------------=
// CreateColumns - Create array of column objects
//
HRESULT CVDCursorMain::CreateColumns()
{
    IColumnsInfo * pColumnsInfo;

    // try to get IRowset's simple metadata interface
    HRESULT hr = m_pRowset->QueryInterface(IID_IColumnsInfo, (void**)&pColumnsInfo);

    if (FAILED(hr))
        return VD_E_CANNOTGETMANDATORYINTERFACE;

    ULONG cColumns          = 0;
    DBCOLUMNINFO * pInfo    = NULL;
    WCHAR * pStringsBuffer  = NULL;

    // now get column information
    hr = pColumnsInfo->GetColumnInfo(&cColumns, &pInfo, &pStringsBuffer);

    if (FAILED(hr))
    {
        pColumnsInfo->Release();
        return VD_E_CANNOTGETCOLUMNINFO;
    }

    // store column count
	// note cColumns includes the bookmark column (0)
    m_ulColumns = cColumns;

    // add one for CURSOR_COLUMN_BMK_CURSOR
    m_ulColumns++;

    // if rowset supports DBPROP_BOOKMARKSKIPPED add two for
    // CURSOR_COLUMN_BMK_TEMPORARYREL and CURSOR_COLUMN_BMK_CURSORREL
    if (m_fBookmarkSkipped)
        m_ulColumns += 2;

    // create array of rowset column objects
    m_rgColumns = new CVDRowsetColumn[m_ulColumns];

    if (!m_rgColumns)
    {
        if (pInfo)
            g_pMalloc->Free(pInfo);

        if (pStringsBuffer)
            g_pMalloc->Free(pStringsBuffer);

        pColumnsInfo->Release();

        return E_OUTOFMEMORY;
    }

    ULONG ulCursorOrdinal = 0;

    // get maximum length of bookmarks
    m_cbMaxBookmark = pInfo[0].ulColumnSize;

    // initialize data column(s)
    for (ULONG ulCol = 1; ulCol < cColumns; ulCol++)
    {
        m_rgColumns[ulCursorOrdinal].Initialize(ulCol, ulCursorOrdinal, &pInfo[ulCol], m_cbMaxBookmark);
        ulCursorOrdinal++;
    }

    // initialize bookmark columns
    pInfo[0].pwszName = NULL;   // ICursor requires bookmark columns have a NULL name

    m_rgColumns[ulCursorOrdinal].Initialize(0, ulCursorOrdinal, &pInfo[0], m_cbMaxBookmark,
        (CURSOR_DBCOLUMNID*)&CURSOR_COLUMN_BMKTEMPORARY);
    ulCursorOrdinal++;

    m_rgColumns[ulCursorOrdinal].Initialize(0, ulCursorOrdinal, &pInfo[0], m_cbMaxBookmark,
        (CURSOR_DBCOLUMNID*)&CURSOR_COLUMN_BMKCURSOR);
    ulCursorOrdinal++;

    if (m_fBookmarkSkipped)
    {
        m_rgColumns[ulCursorOrdinal].Initialize(0, ulCursorOrdinal, &pInfo[0], m_cbMaxBookmark,
            (CURSOR_DBCOLUMNID*)&CURSOR_COLUMN_BMKTEMPORARYREL);
        ulCursorOrdinal++;

        m_rgColumns[ulCursorOrdinal].Initialize(0, ulCursorOrdinal, &pInfo[0], m_cbMaxBookmark,
            (CURSOR_DBCOLUMNID*)&CURSOR_COLUMN_BMKCURSORREL);
        ulCursorOrdinal++;
    }

    // free resources
    if (pInfo)
        g_pMalloc->Free(pInfo);

    if (pStringsBuffer)
        g_pMalloc->Free(pStringsBuffer);

    pColumnsInfo->Release();

    InitOptionalMetadata(cColumns);

    return S_OK;
}

//=--------------------------------------------------------------------------=
// InitOptionalMetadata - gets additional metadata from IColumnsRowset (if available)
//
void CVDCursorMain::InitOptionalMetadata(ULONG cColumns)
{
	// we should return if there is only a bookmark column
	if (cColumns < 2)
		return;

	IColumnsRowset * pColumnsRowset = NULL;

    // try to get IColumnsRowset interface
    HRESULT hr = m_pRowset->QueryInterface(IID_IColumnsRowset, (void**)&pColumnsRowset);

    if (FAILED(hr))
        return;

	IRowset * pRowset	= NULL;
	IColumnsInfo * pColumnsInfo = NULL;
	IAccessor * pAccessor = NULL;

	ULONG	cOptColumnsAvailable = 0;
	DBID *	rgOptColumnsAvailable = NULL;
	DBID *	pOptColumnsAvailable = NULL;  // work ptr

	ULONG	cOptColumns = 0;
	DBID *	rgOptColumns = NULL;
	DBID *	pOptColumns = NULL;	 //work ptr

	ULONG	ulBuffLen = 0;
	BYTE *	pBuff = NULL;
	HACCESSOR hAccessor;
	BOOL	fAccessorCreated = FALSE;

	HROW *	rgRows = NULL;
	ULONG	cRowsObtained = 0;

	// we are only interested in a few of the optional columns
	ULONG		rgColumnPropids[VD_COLUMNSROWSET_MAX_OPT_COLUMNS];
	ULONG		rgOrdinals[VD_COLUMNSROWSET_MAX_OPT_COLUMNS];
	DBBINDING	rgBindings[VD_COLUMNSROWSET_MAX_OPT_COLUMNS];
	DBBINDING * pBinding = NULL; // work ptr
	BOOL		fMatched;
	GUID		guidCID	= DBCIDGUID;

	ULONG	cColumnsMatched = 0;
	ULONG	i, j;

    // get array of available columns
	hr = pColumnsRowset->GetAvailableColumns(&cOptColumnsAvailable, &rgOptColumnsAvailable);

    if (FAILED(hr) || 0 == cOptColumnsAvailable)
		goto cleanup;

	ASSERT_(rgOptColumnsAvailable);

	// allocate enough DBIDs for the lesser of the total available columns or the total number of
	// columns we're interested in
	rgOptColumns = (DBID *)g_pMalloc->Alloc(min(cOptColumnsAvailable, VD_COLUMNSROWSET_MAX_OPT_COLUMNS)
											* sizeof(DBID));

	if (!rgOptColumns)
		goto cleanup;

	// initalize work pointers
	pOptColumnsAvailable	= rgOptColumnsAvailable;
	pOptColumns				= rgOptColumns;			
	pBinding				= rgBindings;

	memset(pBinding, 0, sizeof(DBBINDING) * VD_COLUMNSROWSET_MAX_OPT_COLUMNS);

	// search available columns for the ones we are interested in copying them into rgOptColumns
	for (i = 0; i < cOptColumnsAvailable && cColumnsMatched < VD_COLUMNSROWSET_MAX_OPT_COLUMNS; i++)
	{
		fMatched = FALSE; // initialize to false
		if (DBKIND_GUID_PROPID == pOptColumnsAvailable->eKind	&&
			DO_GUIDS_MATCH(pOptColumnsAvailable->uGuid.guid, guidCID))
		{
			switch (pOptColumnsAvailable->uName.ulPropid)
			{
				case 12: //DBCOLUMN_COLLATINGSEQUENCE     = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)12};
					pBinding->obValue     = ulBuffLen;
					ulBuffLen			 +=	sizeof(ULONG);
					pBinding->obStatus    = ulBuffLen;
					ulBuffLen			 +=	sizeof(DBSTATUS);
					pBinding->dwPart      = DBPART_VALUE | DBPART_STATUS;
					pBinding->dwMemOwner  = DBMEMOWNER_CLIENTOWNED;
					pBinding->wType       = DBTYPE_I4;
					fMatched			  = TRUE;
					break;

				//string properties
				case 10: //DBCOLUMN_BASECOLUMNNAME        = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)10};
				case 11: //DBCOLUMN_BASETABLENAME         = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)11};
				case 14: //DBCOLUMN_DEFAULTVALUE          = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)14};
					pBinding->obValue     = ulBuffLen;
					ulBuffLen			 +=	512;
					pBinding->obLength    = ulBuffLen;
					ulBuffLen			 +=	sizeof(ULONG);
					pBinding->obStatus    = ulBuffLen;
					ulBuffLen			 +=	sizeof(DBSTATUS);
					pBinding->dwPart      = DBPART_VALUE | DBPART_LENGTH |DBPART_STATUS;
					pBinding->dwMemOwner  = DBMEMOWNER_CLIENTOWNED;
					pBinding->cbMaxLen    = 512;
					pBinding->wType       = DBTYPE_WSTR;
					fMatched			  = TRUE;
					break;

					// bool properties
				case 16: //DBCOLUMN_HASDEFAULT            = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)16};
				case 17: //DBCOLUMN_ISAUTOINCREMENT       = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)17};
				case 18: //DBCOLUMN_ISCASESENSITIVE       = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)18};
				case 21: //DBCOLUMN_ISUNIQUE              = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)21};
					pBinding->obValue     = ulBuffLen;
					ulBuffLen			 +=	sizeof(VARIANT_BOOL);
					pBinding->obStatus    = ulBuffLen;
					ulBuffLen			 +=	sizeof(DBSTATUS);
					pBinding->dwPart      = DBPART_VALUE | DBPART_STATUS;
					pBinding->dwMemOwner  = DBMEMOWNER_CLIENTOWNED;
					pBinding->wType       = DBTYPE_BOOL;
					fMatched			  = TRUE;
					break;
			}
		}

		if (fMatched)
		{
			rgColumnPropids[cColumnsMatched]	= pOptColumnsAvailable->uName.ulPropid;
			*pOptColumns						= *pOptColumnsAvailable;
			pBinding++;
			pOptColumns++;
			cColumnsMatched++;
		}
		pOptColumnsAvailable++;
	}

	if (!cColumnsMatched)
		goto cleanup;

    // get column's rowset
	hr = pColumnsRowset->GetColumnsRowset(NULL,
											cColumnsMatched,
											rgOptColumns,
											IID_IRowset,
											0,
											NULL,
											(IUnknown**)&pRowset);

    if FAILED(hr)
	{
		ASSERT_(FALSE);
		goto cleanup;
	}

    // get IColumnsInfo interface on column's rowset
    hr = pRowset->QueryInterface(IID_IColumnsInfo, (void**)&pColumnsInfo);

    if (FAILED(hr))
	{
		ASSERT_(FALSE);
		goto cleanup;
	}

	// get ordinals for our optional columns
	hr = pColumnsInfo->MapColumnIDs(cColumnsMatched, rgOptColumns, rgOrdinals);

    if (S_OK != hr)
	{
		ASSERT_(FALSE);
		goto cleanup;
	}

	// update binding structures with ordinals
	for (i = 0; i < cColumnsMatched; i++)
		rgBindings[i].iOrdinal    = rgOrdinals[i];

    // get IAccessor interface on column's rowset
    hr = pRowset->QueryInterface(IID_IAccessor, (void**)&pAccessor);

    if (FAILED(hr))
	{
		ASSERT_(FALSE);
		goto cleanup;
	}

    // create accessor based on rgBindings array
	hr = pAccessor->CreateAccessor(DBACCESSOR_ROWDATA,
									cColumnsMatched,
									rgBindings,
									ulBuffLen,
									&hAccessor,
									NULL);

    if (S_OK != hr)
	{
		ASSERT_(FALSE);
		goto cleanup;
	}

	// set flag that accessor was successfully created (used during cleanup)
	fAccessorCreated = TRUE;

	// allocate a buffer to hold the metadata
	pBuff = (BYTE *)g_pMalloc->Alloc(ulBuffLen);

	if (!pBuff)
	{
		ASSERT_(FALSE);
		goto cleanup;
	}
									
	// get all rows (each row represents a column in the original rowset)
	// except the first row which represents the bookmark column
	hr = pRowset->GetNextRows(0, // reserved
							  1, // skip the bookmark row
							  cColumns - 1,	// get 1 less than cColumns to account for bookmark row
							  &cRowsObtained, // return count of rows obtanied
							  &rgRows);

    if (FAILED(hr) || !cRowsObtained)
	{
		ASSERT_(FALSE);
		goto cleanup;
	}

	BYTE *		pValue;

	// loop through all rows obtained
	for (i = 0; i < cRowsObtained; i++)
	{
		// call GetData to get the metadata for this row (which represents a column in the orig rowset)
		hr = pRowset->GetData(rgRows[i], hAccessor, pBuff);
		if SUCCEEDED(hr)
		{
			// now update the CVDRowsetColumn object (that this row represents)
			// with the values returned from GetData
			for (j = 0; j < cColumnsMatched; j++)
			{
				if (DBBINDSTATUS_OK != *(DBSTATUS*)(pBuff + rgBindings[j].obStatus))
					continue;

				// set pValue to point into buffer at correct offset
				pValue = pBuff + rgBindings[j].obValue;

				switch (rgColumnPropids[j])
				{
					case 12: //DBCOLUMN_COLLATINGSEQUENCE     = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)12};
						m_rgColumns[i].SetCollatingOrder(*(LCID*)pValue);
						break;

					//string properties
					case 10: //DBCOLUMN_BASECOLUMNNAME        = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)10};
						m_rgColumns[i].SetBaseColumnName((WCHAR*)pValue, *(ULONG*)(pBuff + rgBindings[j].obLength));
						break;
					case 11: //DBCOLUMN_BASETABLENAME         = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)11};
						m_rgColumns[i].SetBaseName((WCHAR*)pValue, *(ULONG*)(pBuff + rgBindings[j].obLength));
						break;
					case 14: //DBCOLUMN_DEFAULTVALUE          = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)14};
						m_rgColumns[i].SetDefaultValue((WCHAR*)pValue, *(ULONG*)(pBuff + rgBindings[j].obLength));
						break;

						// bool properties
					case 16: //DBCOLUMN_HASDEFAULT            = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)16};
						m_rgColumns[i].SetHasDefault(*(VARIANT_BOOL*)pValue);
						break;
					case 17: //DBCOLUMN_ISAUTOINCREMENT       = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)17};
						m_rgColumns[i].SetAutoIncrement(*(VARIANT_BOOL*)pValue);
						break;
					case 18: //DBCOLUMN_ISCASESENSITIVE       = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)18};
						m_rgColumns[i].SetCaseSensitive(*(VARIANT_BOOL*)pValue);
						break;
					case 21: //DBCOLUMN_ISUNIQUE              = {DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)21};
						m_rgColumns[i].SetUnique(*(VARIANT_BOOL*)pValue);
						break;
					default:
						ASSERT_(FALSE);
						break;
				}
			}
		}
		else
			ASSERT_(FALSE);
	}

	m_fColumnsRowsetSupported = TRUE;

cleanup:


	if (pBuff)
		g_pMalloc->Free(pBuff);

	if (pAccessor)
	{
		if (fAccessorCreated)
			pAccessor->ReleaseAccessor(hAccessor, NULL);
		pAccessor->Release();
	}

	if (pRowset)
	{
		if (cRowsObtained)
		{
			pRowset->ReleaseRows(cRowsObtained, rgRows, NULL, NULL, NULL);
			ASSERT_(rgRows);
			g_pMalloc->Free(rgRows);
		}
		pRowset->Release();
	}

	if (pColumnsInfo)
		pColumnsInfo->Release();

    if (rgOptColumnsAvailable)
		g_pMalloc->Free(rgOptColumnsAvailable);

    if (rgOptColumns)
		g_pMalloc->Free(rgOptColumns);
	
	if (pColumnsRowset)	
		pColumnsRowset->Release();

}

//=--------------------------------------------------------------------------=
// DestroyColumns - Destroy array of column objects
//
void CVDCursorMain::DestroyColumns()
{
    delete [] m_rgColumns;

    m_ulColumns = 0;
    m_rgColumns = NULL;
}

//=--------------------------------------------------------------------------=
// ConnectIRowsetNotify - Connect IRowsetNotify interface
//
HRESULT CVDCursorMain::ConnectIRowsetNotify()
{
    IConnectionPointContainer * pConnectionPointContainer;

    HRESULT hr = GetRowset()->QueryInterface(IID_IConnectionPointContainer, (void**)&pConnectionPointContainer);

    if (FAILED(hr))
        return VD_E_CANNOTCONNECTIROWSETNOTIFY;

    IConnectionPoint * pConnectionPoint;

    hr = pConnectionPointContainer->FindConnectionPoint(IID_IRowsetNotify, &pConnectionPoint);

    if (FAILED(hr))
    {
        pConnectionPointContainer->Release();
        return VD_E_CANNOTCONNECTIROWSETNOTIFY;
    }

    hr = pConnectionPoint->Advise(&m_RowsetNotify, &m_dwAdviseCookie);

    pConnectionPointContainer->Release();
    pConnectionPoint->Release();

    return hr;
}

//=--------------------------------------------------------------------------=
// DisconnectIRowsetNotify - Disconnect IRowsetNotify interface
//
void CVDCursorMain::DisconnectIRowsetNotify()
{
    IConnectionPointContainer * pConnectionPointContainer;

    HRESULT hr = GetRowset()->QueryInterface(IID_IConnectionPointContainer, (void**)&pConnectionPointContainer);

    if (FAILED(hr))
        return;

    IConnectionPoint * pConnectionPoint;

    hr = pConnectionPointContainer->FindConnectionPoint(IID_IRowsetNotify, &pConnectionPoint);

    if (FAILED(hr))
    {
        pConnectionPointContainer->Release();
        return;
    }

    hr = pConnectionPoint->Unadvise(m_dwAdviseCookie);

    if (SUCCEEDED(hr))
        m_dwAdviseCookie = 0;   // clear connection point identifier

    pConnectionPointContainer->Release();
    pConnectionPoint->Release();
}

//=--------------------------------------------------------------------------=
// IUnknown QueryInterface
//
HRESULT CVDCursorMain::QueryInterface(REFIID riid, void **ppvObjOut)
{
    ASSERT_POINTER(ppvObjOut, IUnknown*)

    if (!ppvObjOut)
        return E_INVALIDARG;

	*ppvObjOut = NULL;

	if (DO_GUIDS_MATCH(riid, IID_IUnknown))
		{
		*ppvObjOut = this;
		AddRef();
		return S_OK;
		}

	return E_NOINTERFACE;
}

//=--------------------------------------------------------------------------=
// IUnknown AddRef (needed to resolve ambiguity)
//
ULONG CVDCursorMain::AddRef(void)
{
    return CVDNotifier::AddRef();
}

//=--------------------------------------------------------------------------=
// IUnknown Release (needed to resolve ambiguity)
//
ULONG CVDCursorMain::Release(void)
{

	if (1 == m_dwRefCount)
		Passivate();  // unhook everything including notification sink

	if (1 > --m_dwRefCount)
	{
		if (0 == m_RowsetNotify.GetRefCount())
			delete this;
		return 0;
	}

	return m_dwRefCount;
}

//=--------------------------------------------------------------------------=
// IsSameRowAsNew - Determine if specified hRow is an addrow
//
BOOL CVDCursorMain::IsSameRowAsNew(HROW hrow)
{
	for (int k = 0; k < m_Children.GetSize(); k++)
    {
		if (((CVDCursorPosition*)(CVDNotifier*)m_Children[k])->IsSameRowAsNew(hrow) == S_OK)
			return TRUE;
	}

	return FALSE;
}

//=--------------------------------------------------------------------------=
// AddedRows - Get the number of add-rows in cursor
//
ULONG CVDCursorMain::AddedRows()
{
	ULONG cAdded = 0;

	for (int k = 0; k < m_Children.GetSize(); k++)
    {
		if (((CVDCursorPosition*)(CVDNotifier*)m_Children[k])->GetEditMode() == CURSOR_DBEDITMODE_ADD)
			cAdded++;
	}

	return cAdded;
}

//=--------------------------------------------------------------------------=
// IRowsetNotify Methods
//=--------------------------------------------------------------------------=
//=--------------------------------------------------------------------------=
// IRowsetNotify OnFieldChange
//=--------------------------------------------------------------------------=
// Forward to all CVDCursorPosition objects in our family
//
HRESULT CVDCursorMain::OnFieldChange(IRowset *pRowset,
									   HROW hRow,
									   ULONG cColumns,
									   ULONG rgColumns[],
									   DBREASON eReason,
									   DBEVENTPHASE ePhase,
									   BOOL fCantDeny)
{
	HRESULT hr = S_OK;

    // return if notification caused by internal rowset call
    if (m_fInternalSetData && eReason == DBREASON_COLUMN_SET)
        return hr;

	for (int k = 0; k < m_Children.GetSize(); k++)
    {
		hr = ((CVDCursorPosition*)(CVDNotifier*)m_Children[k])->OnFieldChange(pRowset,
															   hRow,
															   cColumns,
															   rgColumns,
															   eReason,
															   ePhase,
															   fCantDeny);
		if (hr)
			break;
	}

	return hr;

}

//=--------------------------------------------------------------------------=
// IRowsetNotify OnRowChange
//=--------------------------------------------------------------------------=
// Forward to all CVDCursorPosition objects in our family
//
HRESULT CVDCursorMain::OnRowChange(IRowset *pRowset,
									 ULONG cRows,
									 const HROW rghRows[],
									 DBREASON eReason,
									 DBEVENTPHASE ePhase,
									 BOOL fCantDeny)
{
	HRESULT hr = S_OK;

    // return if notification caused by internal rowset call (either insert or delete)
    if (m_fInternalInsertRow && eReason == DBREASON_ROW_INSERT || m_fInternalDeleteRows && eReason == DBREASON_ROW_DELETE)
        return hr;

	for (int k = 0; k < m_Children.GetSize(); k++)
    {
		hr = ((CVDCursorPosition*)(CVDNotifier*)m_Children[k])->OnRowChange(pRowset,
															   cRows,
															   rghRows,
															   eReason,
															   ePhase,
															   fCantDeny);
		if (hr)
			break;
    }

	return hr;
}

//=--------------------------------------------------------------------------=
// IRowsetNotify OnRowsetChange
//=--------------------------------------------------------------------------=
// Forward to all CVDCursorPosition objects in our family
//
HRESULT CVDCursorMain::OnRowsetChange(IRowset *pRowset,
										DBREASON eReason,
										DBEVENTPHASE ePhase,
										BOOL fCantDeny)
{
	HRESULT hr = S_OK;

	for (int k = 0; k < m_Children.GetSize(); k++)
    {
		hr = ((CVDCursorPosition*)(CVDNotifier*)m_Children[k])->OnRowsetChange(pRowset,
															   eReason,
															   ePhase,
															   fCantDeny);
		if (hr)
			break;
	}

	return hr;
}

//=--------------------------------------------------------------------------=
// CVDCursorMain::CVDRowsetNotify::m_pMainUnknown
//=--------------------------------------------------------------------------=
// this method is used when we're sitting in the private unknown object,
// and we need to get at the pointer for the main unknown.  basically, it's
// a little better to do this pointer arithmetic than have to store a pointer
// to the parent, etc.
//
inline CVDCursorMain *CVDCursorMain::CVDRowsetNotify::m_pMainUnknown
(
    void
)
{
    return (CVDCursorMain *)((LPBYTE)this - offsetof(CVDCursorMain, m_RowsetNotify));
}

//=--------------------------------------------------------------------------=
// CVDCursorMain::CVDRowsetNotify::QueryInterface
//=--------------------------------------------------------------------------=
// this is the non-delegating internal QI routine.
//
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//
STDMETHODIMP CVDCursorMain::CVDRowsetNotify::QueryInterface
(
    REFIID riid,
    void **ppvObjOut
)
{
	if (!ppvObjOut)
		return E_INVALIDARG;

	*ppvObjOut = NULL;

    if (DO_GUIDS_MATCH(riid, IID_IUnknown))
        *ppvObjOut = (IUnknown *)this;
	else
    if (DO_GUIDS_MATCH(riid, IID_IRowsetNotify))
        *ppvObjOut = (IUnknown *)this;

	if (*ppvObjOut)
	{
		m_cRef++;
        return S_OK;
	}

	return E_NOINTERFACE;

}

//=--------------------------------------------------------------------------=
// CVDCursorMain::CVDRowsetNotify::AddRef
//=--------------------------------------------------------------------------=
// adds a tick to the current reference count.
//
// Output:
//    ULONG        - the new reference count
//
// Notes:
//
ULONG CVDCursorMain::CVDRowsetNotify::AddRef
(
    void
)
{
    return ++m_cRef;
}

//=--------------------------------------------------------------------------=
// CVDCursorMain::CVDRowsetNotify::Release
//=--------------------------------------------------------------------------=
// removes a tick from the count, and delets the object if necessary
//
// Output:
//    ULONG         - remaining refs
//
// Notes:
//
ULONG CVDCursorMain::CVDRowsetNotify::Release
(
    void
)
{
    ULONG cRef = --m_cRef;

    if (!m_cRef && !m_pMainUnknown()->m_dwRefCount)
        delete m_pMainUnknown();

    return cRef;
}

//=--------------------------------------------------------------------------=
// IRowsetNotify Methods
//=--------------------------------------------------------------------------=
//=--------------------------------------------------------------------------=
// IRowsetNotify OnFieldChange
//=--------------------------------------------------------------------------=
// Forward to all CVDCursorPosition objects in our family
//
HRESULT CVDCursorMain::CVDRowsetNotify::OnFieldChange(IRowset *pRowset,
													   HROW hRow,
													   ULONG cColumns,
													   ULONG rgColumns[],
													   DBREASON eReason,
													   DBEVENTPHASE ePhase,
													   BOOL fCantDeny)
{
	
	return m_pMainUnknown()->OnFieldChange(pRowset,
											hRow,
											cColumns,
											rgColumns,
											eReason,
											ePhase,
											fCantDeny);
}

//=--------------------------------------------------------------------------=
// IRowsetNotify OnRowChange
//=--------------------------------------------------------------------------=
// Forward to all CVDCursorPosition objects in our family
//
HRESULT CVDCursorMain::CVDRowsetNotify::OnRowChange(IRowset *pRowset,
													 ULONG cRows,
													 const HROW rghRows[],
													 DBREASON eReason,
													 DBEVENTPHASE ePhase,
													 BOOL fCantDeny)
{
	return m_pMainUnknown()->OnRowChange(pRowset,
											cRows,
											rghRows,
											eReason,
											ePhase,
											fCantDeny);
}

//=--------------------------------------------------------------------------=
// IRowsetNotify OnRowsetChange
//=--------------------------------------------------------------------------=
// Forward to all CVDCursorPosition objects in our family
//
HRESULT CVDCursorMain::CVDRowsetNotify::OnRowsetChange(IRowset *pRowset,
														DBREASON eReason,
														DBEVENTPHASE ePhase,
														BOOL fCantDeny)
{
	return m_pMainUnknown()->OnRowsetChange(pRowset,
											eReason,
											ePhase,
											fCantDeny);
}