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

#include "stdafx.h"
#include "Notifier.h"
#include "RSColumn.h"
#include "RSSource.h"
#include "CursMain.h"
#include "CursBase.h"
#include "CursMeta.h"
#include "fastguid.h"
#include "resource.h"

SZTHISFILE


//=--------------------------------------------------------------------------=
// CVDMetadataCursor - Constructor
//
CVDMetadataCursor::CVDMetadataCursor()
{
    m_dwRefCount    = 1;
    m_lCurrentRow   = -1;   // before first

    m_ulColumns     = 0;
    m_pColumns      = NULL;

    m_ulMetaColumns = 0;
    m_pMetaColumns  = NULL;

#ifdef _DEBUG
    g_cVDMetadataCursorCreated++;
#endif
}

//=--------------------------------------------------------------------------=
// ~CVDMetadataCursor - Destructor
//
CVDMetadataCursor::~CVDMetadataCursor()
{
#ifdef _DEBUG
    g_cVDMetadataCursorDestroyed++;
#endif
}

//=--------------------------------------------------------------------------=
// RowToBookmark - Convert row to bookmark
//=--------------------------------------------------------------------------=
//
// Parameters:
//    lRow          - [in]  a row number
//    pcbBookmark   - [out] a pointer to memory in which to return the length
//                          in bytes of the corresponding bookmark
//    pBookmark     - [out] a pointer to memory in which to return the bookmark
//
// Notes:
//
void CVDMetadataCursor::RowToBookmark(LONG lRow, ULONG * pcbBookmark, void * pBookmark) const
{

    if (lRow < 0)
    {
        *pcbBookmark = CURSOR_DB_BMK_SIZE;
    	memcpy(pBookmark, &CURSOR_DBBMK_BEGINNING, CURSOR_DB_BMK_SIZE);
    }
    else if (lRow >= (LONG)m_ulColumns)
    {
        *pcbBookmark = CURSOR_DB_BMK_SIZE;
    	memcpy(pBookmark, &CURSOR_DBBMK_END, CURSOR_DB_BMK_SIZE);
    }
    else
    {
        *pcbBookmark = sizeof(LONG);
    	memcpy(pBookmark, &lRow, sizeof(LONG));
    }

}
//=--------------------------------------------------------------------------=
// BookmarkToRow - Convert bookmark to row
//=--------------------------------------------------------------------------=
//
// Parameters:
//    cbBookmark    - [in]  the length in bytes of the bookmark
//    pBookmark     - [in]  a pointer to the bookmark
//    pRow          - [out] a pointer to memory in which to return the
//                          corresponding row
//
// Output:
//    BOOL          - TRUE if successful
//
// Notes:
//
BOOL CVDMetadataCursor::BookmarkToRow(ULONG cbBookmark, void * pBookmark, LONG * plRow) const
{
    BOOL fResult = FALSE;

    if (cbBookmark == CURSOR_DB_BMK_SIZE)
    {
        if (memcmp(pBookmark, &CURSOR_DBBMK_BEGINNING, CURSOR_DB_BMK_SIZE) == 0)
        {
            *plRow = -1;
            fResult = TRUE;
        }
        else if (memcmp(pBookmark, &CURSOR_DBBMK_END, CURSOR_DB_BMK_SIZE) == 0)
        {
            *plRow = (LONG)m_ulColumns;
            fResult = TRUE;
        }
        else if (memcmp(pBookmark, &CURSOR_DBBMK_CURRENT, CURSOR_DB_BMK_SIZE) == 0)
        {
            *plRow = m_lCurrentRow;
            fResult = TRUE;
        }
    }
    else
    if (cbBookmark == sizeof(LONG))
    {
        memcpy(plRow, pBookmark, sizeof(LONG));
        if (*plRow >= 0 && *plRow < (LONG)m_ulColumns)
            fResult = TRUE;
    }

    return fResult;
}

//=--------------------------------------------------------------------------=
// ReturnData_I4 - Coerce I4 data into buffers
//=--------------------------------------------------------------------------=
// This function coerces the specified data into supplied buffers
//
// Parameters:
//    dwData            - [in] the 4-byte data
//    pCursorBinding    - [in] the cursor binding describing the format of the
//                             returned information
//    pData             - [in] a pointer to the fixed area buffer
//    pVarData          - [in] a pointer to the variable length buffer
//
// Output:
//    ULONG - the number of bytes used in variable length buffer
//
// Notes:
//
ULONG CVDMetadataCursor::ReturnData_I4(DWORD dwData, CURSOR_DBCOLUMNBINDING * pCursorBinding,
    BYTE * pData, BYTE * pVarData)
{
    ULONG cbVarData = 0;

    if (pCursorBinding->dwBinding == CURSOR_DBBINDING_DEFAULT)
    {
        if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
            *(DWORD*)(pData + pCursorBinding->obData) = dwData;
    }
    else if (pCursorBinding->dwBinding == CURSOR_DBBINDING_VARIANT)
    {
        if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
        {
            CURSOR_DBVARIANT * pVariant = (CURSOR_DBVARIANT*)(pData + pCursorBinding->obData);

            VariantInit((VARIANT*)pVariant);

            pVariant->vt    = CURSOR_DBTYPE_I4;
            pVariant->lVal  = dwData;
        }
    }

    if (pCursorBinding->obVarDataLen != CURSOR_DB_NOVALUE)
        *(ULONG*)(pData + pCursorBinding->obVarDataLen) = 0;

    if (pCursorBinding->obInfo != CURSOR_DB_NOVALUE)
        *(DWORD*)(pData + pCursorBinding->obInfo) = CURSOR_DB_NOINFO;

    return cbVarData;
}

//=--------------------------------------------------------------------------=
// ReturnData_BOOL - Coerce BOOL data into buffers
//=--------------------------------------------------------------------------=
// This function coerces the specified data into supplied buffers
//
// Parameters:
//    fData             - [in] the boolean data
//    pCursorBinding    - [in] the cursor binding describing the format of the
//                             returned information
//    pData             - [in] a pointer to the fixed area buffer
//    pVarData          - [in] a pointer to the variable length buffer
//
// Output:
//    ULONG - the number of bytes used in variable length buffer
//
// Notes:
//
ULONG CVDMetadataCursor::ReturnData_BOOL(VARIANT_BOOL fData, CURSOR_DBCOLUMNBINDING * pCursorBinding,
    BYTE * pData, BYTE * pVarData)
{
    ULONG cbVarData = 0;

    if (pCursorBinding->dwBinding == CURSOR_DBBINDING_DEFAULT)
    {
        if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
            *(VARIANT_BOOL*)(pData + pCursorBinding->obData) = fData;
    }
    else if (pCursorBinding->dwBinding == CURSOR_DBBINDING_VARIANT)
    {
        if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
        {
            CURSOR_DBVARIANT * pVariant = (CURSOR_DBVARIANT*)(pData + pCursorBinding->obData);

            VariantInit((VARIANT*)pVariant);

            pVariant->vt        = CURSOR_DBTYPE_BOOL;
            pVariant->boolVal   = fData;
        }
    }

    if (pCursorBinding->obVarDataLen != CURSOR_DB_NOVALUE)
        *(ULONG*)(pData + pCursorBinding->obVarDataLen) = 0;

    if (pCursorBinding->obInfo != CURSOR_DB_NOVALUE)
        *(DWORD*)(pData + pCursorBinding->obInfo) = CURSOR_DB_NOINFO;

    return cbVarData;
}

//=--------------------------------------------------------------------------=
// ReturnData_LPWSTR - Coerce LPWSTR data into buffers
//=--------------------------------------------------------------------------=
// This function coerces the specified data into supplied buffers
//
// Parameters:
//    pwszData          - [in] the string data
//    pCursorBinding    - [in] the cursor binding describing the format of the
//                             returned information
//    pData             - [in] a pointer to the fixed area buffer
//    pVarData          - [in] a pointer to the variable length buffer
//
// Output:
//    ULONG - the number of bytes used in variable length buffer
//
// Notes:
//
ULONG CVDMetadataCursor::ReturnData_LPWSTR(WCHAR * pwszData, CURSOR_DBCOLUMNBINDING * pCursorBinding,
    BYTE * pData, BYTE * pVarData)
{
    ULONG cbVarData = 0;

    ULONG cbLength = 0;
    DWORD dwInfo = CURSOR_DB_NOINFO;

    if (pCursorBinding->dwBinding == CURSOR_DBBINDING_DEFAULT)
    {
        if (pCursorBinding->dwDataType == CURSOR_DBTYPE_CHARS)
        {
            if (pwszData)
                cbLength = GET_MBCSLEN_FROMWIDE(pwszData);

            if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
            {
                if (pwszData)
                {
                    MAKE_MBCSPTR_FROMWIDE(pszData, pwszData);

                    memcpy(pData + pCursorBinding->obData, pszData, min(pCursorBinding->cbMaxLen, cbLength));

                    if (pCursorBinding->cbMaxLen < cbLength)
                        dwInfo = CURSOR_DB_TRUNCATED;
                }
                else
				{
                    *(CHAR*)(pData + pCursorBinding->obData) = 0;
					dwInfo = CURSOR_DB_NULL;
				}
            }
        }
        else if (pCursorBinding->dwDataType == CURSOR_DBTYPE_WCHARS)
        {
            if (pwszData)
                cbLength = (lstrlenW(pwszData) + 1) * sizeof(WCHAR);

            if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
            {
                if (pwszData)
                {
                    memcpy(pData + pCursorBinding->obData, pwszData, min(pCursorBinding->cbMaxLen, cbLength));

                    if (pCursorBinding->cbMaxLen < cbLength)
                        dwInfo = CURSOR_DB_TRUNCATED;
                }
                else
				{
                    *(WCHAR*)(pData + pCursorBinding->obData) = 0;
					dwInfo = CURSOR_DB_NULL;
				}
            }
        }
        else if (pCursorBinding->dwDataType == CURSOR_DBTYPE_LPSTR)
        {
            if (pwszData)
                cbLength = GET_MBCSLEN_FROMWIDE(pwszData);

            if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
            {
                if (pwszData)
                {
                    MAKE_MBCSPTR_FROMWIDE(pszData, pwszData);

                    *(LPSTR*)(pData + pCursorBinding->obData) = (LPSTR)pVarData;

                    if (pCursorBinding->cbMaxLen == CURSOR_DB_NOMAXLENGTH)
                    {
                        memcpy(pVarData, pszData, cbLength);
	
	                    cbVarData = cbLength;
                    }
                    else
                    {
                        memcpy(pVarData, pszData, min(pCursorBinding->cbMaxLen, cbLength));

	                    cbVarData = min(pCursorBinding->cbMaxLen, cbLength);

                        if (pCursorBinding->cbMaxLen < cbLength)
                            dwInfo = CURSOR_DB_TRUNCATED;
                    }
                }
                else
				{
                    *(LPSTR*)(pData + pCursorBinding->obData) = NULL;
					dwInfo = CURSOR_DB_NULL;
				}
            }
        }
        else if (pCursorBinding->dwDataType == CURSOR_DBTYPE_LPWSTR)
        {
            if (pwszData)
                cbLength = (lstrlenW(pwszData) + 1) * sizeof(WCHAR);

            if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
            {
                if (pwszData)
                {
                    *(LPWSTR*)(pData + pCursorBinding->obData) = (LPWSTR)pVarData;

                    if (pCursorBinding->cbMaxLen == CURSOR_DB_NOMAXLENGTH)
                    {
                        memcpy(pVarData, pwszData, cbLength);

	                    cbVarData = cbLength;
                    }
                    else
                    {
                        memcpy(pVarData, pwszData, min(pCursorBinding->cbMaxLen, cbLength));

	                    cbVarData = min(pCursorBinding->cbMaxLen, cbLength);

                        if (pCursorBinding->cbMaxLen < cbLength)
                            dwInfo = CURSOR_DB_TRUNCATED;
                    }
                }
                else
				{
                    *(LPWSTR*)(pData + pCursorBinding->obData) = NULL;
					dwInfo = CURSOR_DB_NULL;
				}
            }
        }
    }
    else if (pCursorBinding->dwBinding == CURSOR_DBBINDING_VARIANT)
    {
        if (pCursorBinding->dwDataType == CURSOR_DBTYPE_LPSTR)
        {
            if (pwszData)
                cbLength = GET_MBCSLEN_FROMWIDE(pwszData);

            if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
            {
                CURSOR_DBVARIANT * pVariant = (CURSOR_DBVARIANT*)(pData + pCursorBinding->obData);

                VariantInit((VARIANT*)pVariant);

                if (pwszData)
                {
                    MAKE_MBCSPTR_FROMWIDE(pszData, pwszData);

                    pVariant->vt        = CURSOR_DBTYPE_LPSTR;
                    pVariant->pszVal    = (LPSTR)pVarData;

                    if (pCursorBinding->cbMaxLen == CURSOR_DB_NOMAXLENGTH)
                    {
                        memcpy(pVarData, pszData, cbLength);

	                    cbVarData = cbLength;
                    }
                    else
                    {
                        memcpy(pVarData, pszData, min(pCursorBinding->cbMaxLen, cbLength));

	                    cbVarData = min(pCursorBinding->cbMaxLen, cbLength);

                        if (pCursorBinding->cbMaxLen < cbLength)
                            dwInfo = CURSOR_DB_TRUNCATED;
                    }
                }
                else
				{
                    pVariant->vt = VT_NULL;
					dwInfo = CURSOR_DB_NULL;
				}
            }
        }
        else if (pCursorBinding->dwDataType == CURSOR_DBTYPE_LPWSTR)
        {
            if (pwszData)
                cbLength = (lstrlenW(pwszData) + 1) * sizeof(WCHAR);

            if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
            {
                CURSOR_DBVARIANT * pVariant = (CURSOR_DBVARIANT*)(pData + pCursorBinding->obData);

                VariantInit((VARIANT*)pVariant);

                if (pwszData)
                {
                    pVariant->vt        = CURSOR_DBTYPE_LPWSTR;
                    pVariant->pwszVal   = (LPWSTR)pVarData;

                    if (pCursorBinding->cbMaxLen == CURSOR_DB_NOMAXLENGTH)
                    {
                        memcpy(pVarData, pwszData, cbLength);

	                    cbVarData = cbLength;
                    }
                    else
                    {
                        memcpy(pVarData, pwszData, min(pCursorBinding->cbMaxLen, cbLength));

	                    cbVarData = min(pCursorBinding->cbMaxLen, cbLength);

                        if (pCursorBinding->cbMaxLen < cbLength)
                            dwInfo = CURSOR_DB_TRUNCATED;
                    }
                }
                else
				{
                    pVariant->vt = VT_NULL;
					dwInfo = CURSOR_DB_NULL;
				}
            }
        }
        else if (pCursorBinding->dwDataType == VT_BSTR)
        {
            if (pwszData)
                cbLength = (lstrlenW(pwszData) + 1) * sizeof(WCHAR);

            if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
			{
                CURSOR_DBVARIANT * pVariant = (CURSOR_DBVARIANT*)(pData + pCursorBinding->obData);

                VariantInit((VARIANT*)pVariant);

                if (pwszData)
				{
                    pVariant->vt        = VT_BSTR;
                    pVariant->pwszVal   = SysAllocString(pwszData);
				}
                else
				{
                    pVariant->vt = VT_NULL;
					dwInfo = CURSOR_DB_NULL;
				}
			}
		}
	}

    if (pCursorBinding->obVarDataLen != CURSOR_DB_NOVALUE)
        *(ULONG*)(pData + pCursorBinding->obVarDataLen) = cbLength;

    if (pCursorBinding->obInfo != CURSOR_DB_NOVALUE)
        *(DWORD*)(pData + pCursorBinding->obInfo) = dwInfo;

    return cbVarData;
}

//=--------------------------------------------------------------------------=
// ReturnData_DBCOLUMNID - Coerce DBCOLUMNID data into buffers
//=--------------------------------------------------------------------------=
// This function coerces the specified data into supplied buffers
//
// Parameters:
//    cursorColumnID    - [in] the cursor column identifier
//    pCursorBinding    - [in] the cursor binding describing the format of the
//                             returned information
//    pData             - [in] a pointer to the fixed area buffer
//    pVarData          - [in] a pointer to the variable length buffer
//
// Output:
//    ULONG - the number of bytes used in variable length buffer
//
// Notes:
//
ULONG CVDMetadataCursor::ReturnData_DBCOLUMNID(CURSOR_DBCOLUMNID cursorColumnID, CURSOR_DBCOLUMNBINDING * pCursorBinding,
    BYTE * pData, BYTE * pVarData)
{
    ULONG cbVarData = 0;

    if (pCursorBinding->dwBinding == CURSOR_DBBINDING_DEFAULT)
    {
        if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
            *(CURSOR_DBCOLUMNID*)(pData + pCursorBinding->obData) = cursorColumnID;
    }
    else if (pCursorBinding->dwBinding == CURSOR_DBBINDING_VARIANT)
    {
        if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
        {
            CURSOR_DBVARIANT * pVariant = (CURSOR_DBVARIANT*)(pData + pCursorBinding->obData);
            CURSOR_DBCOLUMNID * pCursorColumnID = (CURSOR_DBCOLUMNID*)pVarData;

            VariantInit((VARIANT*)pVariant);

            pVariant->vt        = CURSOR_DBTYPE_COLUMNID;
            pVariant->pColumnid = pCursorColumnID;

            *pCursorColumnID = cursorColumnID;

            cbVarData = sizeof(CURSOR_DBCOLUMNID);
        }
    }

    if (pCursorBinding->obVarDataLen != CURSOR_DB_NOVALUE)
        *(DWORD*)(pData + pCursorBinding->obVarDataLen) = 0;

    if (pCursorBinding->obInfo != CURSOR_DB_NOVALUE)
        *(DWORD*)(pData + pCursorBinding->obInfo) = CURSOR_DB_NOINFO;

    return cbVarData;
}

//=--------------------------------------------------------------------------=
// ReturnData_Bookmark - Coerce bookmark data into buffers
//=--------------------------------------------------------------------------=
// This function coerces the specified data into supplied buffers
//
// Parameters:
//    lRow              - [in] the current row
//    pCursorBinding    - [in] the cursor binding describing the format of the
//                             returned information
//    pData             - [in] a pointer to the fixed area buffer
//    pVarData          - [in] a pointer to the variable length buffer
//
// Output:
//    ULONG - the number of bytes used in variable length buffer
//
// Notes:
//
ULONG CVDMetadataCursor::ReturnData_Bookmark(LONG lRow, CURSOR_DBCOLUMNBINDING * pCursorBinding,
    BYTE * pData, BYTE * pVarData)
{
    ULONG cbVarData = 0;

    ULONG cbLength = sizeof(LONG);
    DWORD dwInfo = CURSOR_DB_NOINFO;

    if (pCursorBinding->dwBinding == CURSOR_DBBINDING_DEFAULT)
    {
        if (pCursorBinding->dwDataType == CURSOR_DBTYPE_BYTES)
        {
            if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
            {
                memcpy(pData + pCursorBinding->obData, &lRow, min(pCursorBinding->cbMaxLen, cbLength));

                if (pCursorBinding->cbMaxLen < cbLength)
                    dwInfo = CURSOR_DB_TRUNCATED;
            }
        }
        else if (pCursorBinding->dwDataType == CURSOR_DBTYPE_BLOB)
        {
            if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
            {
                *(ULONG*)(pData + pCursorBinding->obData) = cbLength;
                *(LPBYTE*)(pData + pCursorBinding->obData + sizeof(ULONG)) = (LPBYTE)pVarData;

                if (pCursorBinding->cbMaxLen == CURSOR_DB_NOMAXLENGTH)
                {
                    memcpy((LPBYTE)pVarData, &lRow, cbLength);

	                cbVarData = cbLength;
                }
                else
                {
                    memcpy(pVarData, &lRow, min(pCursorBinding->cbMaxLen, cbLength));

	                cbVarData = min(pCursorBinding->cbMaxLen, cbLength);

                    if (pCursorBinding->cbMaxLen < cbLength)
                        dwInfo = CURSOR_DB_TRUNCATED;
                }
            }
        }
    }
    else if (pCursorBinding->dwBinding == CURSOR_DBBINDING_VARIANT)
    {
        if (pCursorBinding->dwDataType == CURSOR_DBTYPE_BLOB)
        {
            if (pCursorBinding->obData != CURSOR_DB_NOVALUE)
            {
                CURSOR_DBVARIANT * pVariant = (CURSOR_DBVARIANT*)(pData + pCursorBinding->obData);

                VariantInit((VARIANT*)pVariant);

                pVariant->vt                = CURSOR_DBTYPE_BLOB;
                pVariant->blob.cbSize       = cbLength;
                pVariant->blob.pBlobData    = (LPBYTE)pVarData;

                if (pCursorBinding->cbMaxLen == CURSOR_DB_NOMAXLENGTH)
                {
                    memcpy((LPBYTE)pVarData, &lRow, cbLength);

	                cbVarData = cbLength;
                }
                else
                {
                    memcpy(pVarData, &lRow, min(pCursorBinding->cbMaxLen, cbLength));

	                cbVarData = min(pCursorBinding->cbMaxLen, cbLength);

                    if (pCursorBinding->cbMaxLen < cbLength)
                        dwInfo = CURSOR_DB_TRUNCATED;
                }
            }
        }
    }

    if (pCursorBinding->obVarDataLen != CURSOR_DB_NOVALUE)
        *(ULONG*)(pData + pCursorBinding->obVarDataLen) = cbLength;

    if (pCursorBinding->obInfo != CURSOR_DB_NOVALUE)
        *(DWORD*)(pData + pCursorBinding->obInfo) = dwInfo;

    return cbVarData;
}

//=--------------------------------------------------------------------------=
// Create - Create metadata cursor object
//=--------------------------------------------------------------------------=
// This function creates and initializes a new metadata cursor object
//
// Parameters:
//    ulColumns             - [in]  the number of rowset columns
//    pColumns              - [in]  a pointer to rowset columns where to
//                                  retrieve metadata
//    ulMetaColumns         - [in]  the number of rowset meta-columns (can be 0)
//    pMetaColumns          - [in]  a pointer to rowset meta-columns where to
//                                  retrieve metadata (can be NULL)
//    ppMetaDataCursor      - [out] a pointer in which to return pointer to
//                                  metadata cursor object
//    pResourceDLL          - [in]  a pointer which keeps track of resource DLL
//
// Output:
//    HRESULT - S_OK if successful
//              E_INVALIDARG bad parameter
//              E_OUTOFMEMORY not enough memory to create object
//
// Notes:
//
HRESULT CVDMetadataCursor::Create(ULONG ulColumns, CVDRowsetColumn * pColumns, ULONG ulMetaColumns,
    CVDRowsetColumn * pMetaColumns, CVDMetadataCursor ** ppMetadataCursor, CVDResourceDLL * pResourceDLL)
{
    ASSERT_POINTER(pColumns, CVDRowsetColumn)
    ASSERT_NULL_OR_POINTER(pMetaColumns, CVDRowsetColumn)
    ASSERT_POINTER(ppMetadataCursor, CVDMetadataCursor*)
    ASSERT_POINTER(pResourceDLL, CVDResourceDLL)

    if (!ppMetadataCursor || !pColumns)
        return E_INVALIDARG;

    *ppMetadataCursor = NULL;

    CVDMetadataCursor * pMetadataCursor = new CVDMetadataCursor();

    if (!pMetadataCursor)
        return E_OUTOFMEMORY;

    pMetadataCursor->m_ulColumns        = ulColumns;
    pMetadataCursor->m_pColumns         = pColumns;
    pMetadataCursor->m_ulMetaColumns    = ulMetaColumns;
    pMetadataCursor->m_pMetaColumns     = pMetaColumns;
    pMetadataCursor->m_pResourceDLL     = pResourceDLL;

    *ppMetadataCursor = pMetadataCursor;

    return S_OK;
}

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

    if (!ppvObjOut)
        return E_INVALIDARG;

    *ppvObjOut = NULL;

    switch (riid.Data1)
    {
        QI_INTERFACE_SUPPORTED((ICursor*)this, IUnknown);
        QI_INTERFACE_SUPPORTED(this, ICursor);
        QI_INTERFACE_SUPPORTED(this, ICursorMove);
        QI_INTERFACE_SUPPORTED(this, ICursorScroll);
		QI_INTERFACE_SUPPORTED(this, ISupportErrorInfo);
    }

    if (NULL == *ppvObjOut)
        return E_NOINTERFACE;

    AddRef();

    return S_OK;
}

//=--------------------------------------------------------------------------=
// IUnknown AddRef
//
ULONG CVDMetadataCursor::AddRef(void)
{
   return ++m_dwRefCount;
}

//=--------------------------------------------------------------------------=
// IUnknown Release
//
ULONG CVDMetadataCursor::Release(void)
{
    if (1 > --m_dwRefCount)
    {
        delete this;
        return 0;
    }

    return m_dwRefCount;
}

//=--------------------------------------------------------------------------=
// ICursor methods implemented
//=--------------------------------------------------------------------------=
//=--------------------------------------------------------------------------=
// ICursor GetColumnsCursor
//=--------------------------------------------------------------------------=
// Creates a cursor containing information about the current cursor
//
// Parameters:
//    riid              - [in]  the interface ID to which to return a pointer
//    ppvColumnsCursor  - [out] a pointer to memory in which to return the
//                              interface pointer
//    pcRows            - [out] a pointer to memory in which to return the
//                              number of rows in the metadata cursor
//
// Output:
//    HRESULT - S_OK if successful
//              E_FAIL can't create cursor
//              E_INVALIDARG bad parameter
//              E_OUTOFMEMORY not enough memory
//              E_NOINTERFACE interface not available
//
// Notes:
//    This function only succeeds when creating a meta-metadata cursor.
//
HRESULT CVDMetadataCursor::GetColumnsCursor(REFIID riid, IUnknown **ppvColumnsCursor, ULONG *pcRows)
{
    ASSERT_POINTER(ppvColumnsCursor, IUnknown*)
    ASSERT_NULL_OR_POINTER(pcRows, ULONG)

    if (!ppvColumnsCursor)
    {
        VDSetErrorInfo(IDS_ERR_INVALIDARG, IID_ICursor, m_pResourceDLL);
        return E_INVALIDARG;
    }

    // init out parameters
    *ppvColumnsCursor = NULL;

    if (pcRows)
        *pcRows = 0;

    if (!m_ulMetaColumns)   // can't create meta-meta-metadata cursor
    {
        VDSetErrorInfo(IDS_ERR_CANTCREATEMETACURSOR, IID_ICursor, m_pResourceDLL);
        return E_FAIL;
    }

    // make sure caller asked for an available interface
    if (riid != IID_IUnknown && riid != IID_ICursor && riid != IID_ICursorMove && riid != IID_ICursorScroll)
    {
        VDSetErrorInfo(IDS_ERR_NOINTERFACE, IID_ICursor, m_pResourceDLL);
        return E_NOINTERFACE;
    }

    // create meta-metadata cursor
    CVDMetadataCursor * pMetadataCursor;

    HRESULT hr = CVDMetadataCursor::Create(m_ulMetaColumns, m_pMetaColumns, 0, 0, &pMetadataCursor, m_pResourceDLL);

    if (FAILED(hr)) // the only reason for failing here is an out of memory condition
    {
        VDSetErrorInfo(IDS_ERR_OUTOFMEMORY, IID_ICursor, m_pResourceDLL);
        return hr;
    }

    *ppvColumnsCursor = (ICursor*)pMetadataCursor;

    if (pcRows)
        *pcRows = m_ulMetaColumns;

    return S_OK;
}

//=--------------------------------------------------------------------------=
// ICursor SetBindings
//=--------------------------------------------------------------------------=
// Replaces the existing column bindings or adds new column bindings to the
// existing ones
//
// Parameters:
//    cCol              - [in] the number of columns to bind
//    rgBoundColumns    - [in] an array of column bindings, one for each
//                             column for which data is to be returned
//    cbRowLength       - [in] the number of bytes of inline memory in a
//                             single row of data
//    dwFlags           - [in] a flag that specifies whether to replace the
//                             existing column bindings or add to them
//
// Output:
//    HRESULT - S_OK if successful
//              E_INVALIDARG bad parameter
//              E_OUTOFMEMORY not enough memory
//              CURSOR_DB_E_BADBINDINFO bad binding information
//              CURSOR_DB_E_COLUMNUNAVAILABLE columnID is not available
//              CURSOR_DB_E_ROWTOOSHORT cbRowLength was less than the minumum (and not zero)
//
// Notes:
//
HRESULT CVDMetadataCursor::SetBindings(ULONG cCol, CURSOR_DBCOLUMNBINDING rgBoundColumns[], ULONG cbRowLength, DWORD dwFlags)
{
    ASSERT_NULL_OR_POINTER(rgBoundColumns, CURSOR_DBCOLUMNBINDING)

    if (!cCol && dwFlags == CURSOR_DBCOLUMNBINDOPTS_ADD)
        return S_OK;

    if (cCol && !rgBoundColumns)
    {
        VDSetErrorInfo(IDS_ERR_INVALIDARG, IID_ICursor, m_pResourceDLL);
        return E_INVALIDARG;
    }

    if (dwFlags != CURSOR_DBCOLUMNBINDOPTS_REPLACE && dwFlags != CURSOR_DBCOLUMNBINDOPTS_ADD)
    {
        VDSetErrorInfo(IDS_ERR_INVALIDARG, IID_ICursor, m_pResourceDLL);
        return E_INVALIDARG;
    }

    // make sure the bindings are okay
    ULONG ulColumns = m_ulMetaColumns;
    CVDRowsetColumn * pColumns = m_pMetaColumns;

    if (!pColumns)
    {
        ulColumns = m_ulColumns;
        pColumns = m_pColumns;
    }

    ULONG cbNewRowLength;
    ULONG cbNewVarRowLength;

    HRESULT hr = ValidateCursorBindings(ulColumns, pColumns, cCol, rgBoundColumns, cbRowLength, dwFlags,
        &cbNewRowLength, &cbNewVarRowLength);

    if (SUCCEEDED(hr))  // if so, then set them in cursor
    {
        hr = CVDCursorBase::SetBindings(cCol, rgBoundColumns, cbRowLength, dwFlags);

        if (SUCCEEDED(hr))  // store new row lengths computed during validation
        {
            m_cbRowLength = cbNewRowLength;
            m_cbVarRowLength = cbNewVarRowLength;
        }
    }

    return hr;
}

//=--------------------------------------------------------------------------=
// ICursor GetNextRows
//=--------------------------------------------------------------------------=
// Fetches the specified number of rows starting with the row after the
// current one
//
// Parameters:
//    udlRowsToSkip     - [in]      the number of rows to skip before fetching
//    pFetchParams      - [in, out] a pointer to fetch rows structure
//
// Output:
//    HRESULT - S_OK if successful
//              CURSOR_DB_S_ENDOFCURSOR reached end of the cursor
//
// Notes:
//
HRESULT CVDMetadataCursor::GetNextRows(LARGE_INTEGER udlRowsToSkip, CURSOR_DBFETCHROWS *pFetchParams)
{
    ASSERT_NULL_OR_POINTER(pFetchParams, CURSOR_DBFETCHROWS)

    // return if caller doesn't supply fetch rows structure
    if (!pFetchParams)
        return S_OK;

    // init out parameter
    pFetchParams->cRowsReturned = 0;

    // return if caller didn't ask for any rows
    if (!pFetchParams->cRowsRequested)
        return S_OK;

    // make sure fetch flags has only valid values
    if (pFetchParams->dwFlags != CURSOR_DBROWFETCH_DEFAULT &&
        pFetchParams->dwFlags != CURSOR_DBROWFETCH_CALLEEALLOCATES &&
        pFetchParams->dwFlags != CURSOR_DBROWFETCH_FORCEREFRESH &&
        pFetchParams->dwFlags != (CURSOR_DBROWFETCH_CALLEEALLOCATES | CURSOR_DBROWFETCH_FORCEREFRESH))
        return CURSOR_DB_E_BADFETCHINFO;

    // if memory was caller allocated, make sure caller supplied data pointer
    if (!(pFetchParams->dwFlags & CURSOR_DBROWFETCH_CALLEEALLOCATES) && !pFetchParams->pData)
        return CURSOR_DB_E_BADFETCHINFO;

    // if memory was caller allocated, make sure caller supplied var-data pointer and size if needed
    if (!(pFetchParams->dwFlags & CURSOR_DBROWFETCH_CALLEEALLOCATES) && m_fNeedVarData &&
        (!pFetchParams->pVarData || !pFetchParams->cbVarData))
        return CURSOR_DB_E_BADFETCHINFO;

    // allocate necessary memory
    if (pFetchParams->dwFlags & CURSOR_DBROWFETCH_CALLEEALLOCATES)
    {
        // inline memory
        pFetchParams->pData = g_pMalloc->Alloc(pFetchParams->cRowsRequested * m_cbRowLength);

        if (!pFetchParams->pData)
        {
			VDSetErrorInfo(IDS_ERR_OUTOFMEMORY, IID_ICursor, m_pResourceDLL);
            return E_OUTOFMEMORY;
        }

        if (m_fNeedVarData)
        {
            // out-of-line memory
            pFetchParams->pVarData = g_pMalloc->Alloc(pFetchParams->cRowsRequested * m_cbVarRowLength);

            if (!pFetchParams->pData)
            {
                g_pMalloc->Free(pFetchParams->pData);
                pFetchParams->pData = NULL;
			    VDSetErrorInfo(IDS_ERR_OUTOFMEMORY, IID_ICursor, m_pResourceDLL);
                return E_OUTOFMEMORY;
            }
        }
        else
            pFetchParams->pVarData = NULL;
    }

    // fetch data
    HRESULT hrFetch = S_OK;
    CVDRowsetColumn * pColumn;
    CURSOR_DBCOLUMNID cursorColumnID;
    CURSOR_DBCOLUMNBINDING * pCursorBinding;
    BYTE * pData = (BYTE*)pFetchParams->pData;
    BYTE * pVarData = (BYTE*)pFetchParams->pVarData;

    // iterate through rows
    for (ULONG ulRow = 0; ulRow < pFetchParams->cRowsRequested; ulRow++)
    {
        // increment row
        m_lCurrentRow++;

        // make sure we didn't hit end of table
        if (m_lCurrentRow >= (LONG)m_ulColumns)
        {
            m_lCurrentRow = (LONG)m_ulColumns;
            hrFetch = CURSOR_DB_S_ENDOFCURSOR;
            goto DoneFetchingMetaData;
        }

        pCursorBinding = m_pCursorBindings;
        pColumn = &m_pColumns[m_lCurrentRow];

        // iterate through bindings
        for (ULONG ulBind = 0; ulBind < m_ulCursorBindings; ulBind++)
        {
            cursorColumnID = pCursorBinding->columnID;

            // return requested data
            if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_BINDTYPE))
            {
                pVarData += ReturnData_I4(pColumn->GetBindType(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_COLUMNID))
            {
                pVarData += ReturnData_DBCOLUMNID(pColumn->GetCursorColumnID(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_DATACOLUMN))
            {
                pVarData += ReturnData_BOOL(pColumn->GetDataColumn(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_ENTRYIDMAXLENGTH))
            {
                pVarData += ReturnData_I4(pColumn->GetEntryIDMaxLength(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_FIXED))
            {
                pVarData += ReturnData_BOOL(pColumn->GetFixed(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_MAXLENGTH))
            {
                pVarData += ReturnData_I4(pColumn->GetMaxLength(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_NAME))
            {
                pVarData += ReturnData_LPWSTR(pColumn->GetName(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_NUMBER))
            {
                pVarData += ReturnData_I4(pColumn->GetNumber(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_NULLABLE))
            {
                pVarData += ReturnData_BOOL(pColumn->GetNullable(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_SCALE))
            {
                pVarData += ReturnData_I4(pColumn->GetScale(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_TYPE))
            {
                pVarData += ReturnData_I4(pColumn->GetCursorType(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_UPDATABLE))
            {
                pVarData += ReturnData_I4(pColumn->GetUpdatable(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_UNIQUE))
            {
                pVarData += ReturnData_BOOL(pColumn->GetUnique(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_CASESENSITIVE))
            {
                pVarData += ReturnData_BOOL(pColumn->GetCaseSensitive(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_AUTOINCREMENT))
            {
                pVarData += ReturnData_BOOL(pColumn->GetAutoIncrement(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_HASDEFAULT))
            {
                pVarData += ReturnData_BOOL(pColumn->GetHasDefault(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_COLLATINGORDER))
            {
                pVarData += ReturnData_I4(pColumn->GetCollatingOrder(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_BASENAME))
            {
                pVarData += ReturnData_LPWSTR(pColumn->GetBaseName(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_BASECOLUMNNAME))
            {
                pVarData += ReturnData_LPWSTR(pColumn->GetBaseColumnName(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_DEFAULTVALUE))
            {
                pVarData += ReturnData_LPWSTR(pColumn->GetDefaultValue(), pCursorBinding, pData, pVarData);
            }
            else if (IsEqualCursorColumnID(cursorColumnID, CURSOR_COLUMN_BMKTEMPORARY))
            {
                pVarData += ReturnData_Bookmark(m_lCurrentRow, pCursorBinding, pData, pVarData);
            }

            pCursorBinding++;
        }

        // increment returned row count
        pFetchParams->cRowsReturned++;
        pData += m_cbRowLength;
    }

DoneFetchingMetaData:
    // cleanup memory allocations if we did not retrieve any rows
    if (pFetchParams->dwFlags & CURSOR_DBROWFETCH_CALLEEALLOCATES && !pFetchParams->cRowsReturned)
    {
        if (pFetchParams->pData)
        {
            g_pMalloc->Free(pFetchParams->pData);
            pFetchParams->pData = NULL;
        }

        if (pFetchParams->pVarData)
        {
            g_pMalloc->Free(pFetchParams->pVarData);
            pFetchParams->pVarData = NULL;
        }
    }

    return hrFetch;
}

//=--------------------------------------------------------------------------=
// ICursor Requery
//=--------------------------------------------------------------------------=
// Repopulates the cursor based on its original definition
//
// Parameters:
//              none
//
// Output:
//    HRESULT - S_OK if successful
//
// Notes:
//
HRESULT CVDMetadataCursor::Requery(void)
{
    m_lCurrentRow = -1;

    return S_OK;
}

//=--------------------------------------------------------------------------=
// ICursorMove methods implemented
//=--------------------------------------------------------------------------=
//=--------------------------------------------------------------------------=
// ICursorMove Move
//=--------------------------------------------------------------------------=
// Moves the current row to a new row within the cursor and optionally fetches
// rows from that new position
//
// Parameters:
//    cbBookmark    - [in]      length in bytes of the bookmark
//    pBookmark     - [in]      a pointer to a bookmark which serves as the
//                              origin for the calculation that determines the
//                              target row
//    dlOffset      - [in]      a signed count of the rows from the origin
//                              bookmark to the target row
//    pFetchParams  - [in, out] a pointer to fetch rows structure
//
// Output:
//    HRESULT - S_OK if successful
//              E_INVALIDARG bad parameter
//
// Notes:
//
HRESULT CVDMetadataCursor::Move(ULONG cbBookmark, void *pBookmark, LARGE_INTEGER dlOffset, CURSOR_DBFETCHROWS *pFetchParams)
{
    ASSERT_POINTER(pBookmark, BYTE)
    ASSERT_NULL_OR_POINTER(pFetchParams, CURSOR_DBFETCHROWS)

    if (!cbBookmark || !pBookmark)
    {
        VDSetErrorInfo(IDS_ERR_INVALIDARG, IID_ICursor, m_pResourceDLL);
        return E_INVALIDARG;
    }

	if (!BookmarkToRow(cbBookmark, pBookmark, &m_lCurrentRow))
    {
        VDSetErrorInfo(IDS_ERR_BADBOOKMARK, IID_ICursor, m_pResourceDLL);
        return CURSOR_DB_E_BADBOOKMARK;
    }

    m_lCurrentRow += (LONG)dlOffset.LowPart;

	if (m_lCurrentRow < -1)
	{
		m_lCurrentRow = -1;
		return CURSOR_DB_S_ENDOFCURSOR;
	}
	else
	if (m_lCurrentRow >= (LONG)m_ulColumns)
	{
		m_lCurrentRow = (LONG)m_ulColumns;
		return CURSOR_DB_S_ENDOFCURSOR;
	}

	if (!pFetchParams)
		return S_OK;

	// since get next rows starts from the row after the current row we must
	// back up one row
	m_lCurrentRow--;
	if (m_lCurrentRow < -1)
		m_lCurrentRow	= -1;

    return CVDMetadataCursor::GetNextRows(g_liZero, pFetchParams);
}

//=--------------------------------------------------------------------------=
// ICursorMove GetBookmark
//=--------------------------------------------------------------------------=
// Returns the bookmark of the current row
//
// Parameters:
//    pBookmarkType - [in]  a pointer to the type of bookmark desired
//    cbMaxSize     - [in]  length in bytes of the client buffer to put the
//                          returned bookmark into
//    pcbBookmark   - [out] a pointer to memory in which to return the actual
//                          length of the returned bookmark
//    pBookmark     - [out] a pointer to client buffer to put the returned
//                          bookmark into
//
// Output:
//    HRESULT - S_OK if successful
//              E_INVALIDARG bad parameter
//
// Notes:
//
HRESULT CVDMetadataCursor::GetBookmark(CURSOR_DBCOLUMNID *pBookmarkType,
									   ULONG cbMaxSize,
									   ULONG *pcbBookmark,
									   void *pBookmark)
{
    ASSERT_POINTER(pBookmarkType, CURSOR_DBCOLUMNID)
    ASSERT_POINTER(pcbBookmark, ULONG)
    ASSERT_POINTER(pBookmark, BYTE)

    if (!pBookmarkType || !pcbBookmark || !pBookmark)
    {
        VDSetErrorInfo(IDS_ERR_INVALIDARG, IID_ICursor, m_pResourceDLL);
        return E_INVALIDARG;
    }

    if (cbMaxSize < sizeof(LONG))
    {
        VDSetErrorInfo(IDS_ERR_BUFFERTOOSMALL, IID_ICursor, m_pResourceDLL);
        return CURSOR_DB_E_BUFFERTOOSMALL;
    }

	RowToBookmark(m_lCurrentRow, pcbBookmark, pBookmark);

    return S_OK;
}

//=--------------------------------------------------------------------------=
// ICursorMove Clone
//=--------------------------------------------------------------------------=
// Returns a clone of the cursor
//
// Parameters:
//    dwFlags           - [in]  a flag that specifies the clone options
//    riid              - [in]  the interface desired for the returned clone
//    ppvClonedCursor   - [out] a pointer to memory in which to return newly
//                              created clone pointer
//
// Output:
//    HRESULT - S_OK if successful
//
// Notes:
//
HRESULT CVDMetadataCursor::Clone(DWORD dwFlags, REFIID riid, IUnknown **ppvClonedCursor)
{

    CVDMetadataCursor * pMetaCursor = 0;

	HRESULT hr = CVDMetadataCursor::Create(m_ulColumns,
										m_pColumns,
										m_ulMetaColumns,
										m_pMetaColumns,
										&pMetaCursor,
										m_pResourceDLL);

    *ppvClonedCursor = (ICursor*)pMetaCursor;

    return hr;
}

//=--------------------------------------------------------------------------=
// ICursorScroll methods implemented
//=--------------------------------------------------------------------------=
//=--------------------------------------------------------------------------=
// ICursorScroll Scroll
//=--------------------------------------------------------------------------=
// Moves the current row to a new row within the cursor, specified as a
// fraction, and optionally fetches rows from that new position
//
// Parameters:
//    ulNumerator   - [in]      the numerator of the fraction that states the
//                              position to scroll to in the cursor
//    ulDenominator - [in]      the denominator of that same fraction
//    pFetchParams  - [in, out] a pointer to fetch rows structure
//
// Output:
//    HRESULT - S_OK if successful
//              CURSOR_DB_E_BADFRACTION - bad fraction
//
// Notes:
//
HRESULT CVDMetadataCursor::Scroll(ULONG ulNumerator, ULONG ulDenominator, CURSOR_DBFETCHROWS *pFetchParams)
{
    ASSERT_NULL_OR_POINTER(pFetchParams, CURSOR_DBFETCHROWS)

    if (!ulDenominator) // division by zero is a bad thing!
    {
        // this is a Viaduct1 error message, which doesn't really apply
        VDSetErrorInfo(IDS_ERR_BADFRACTION, IID_ICursor, m_pResourceDLL);
        return CURSOR_DB_E_BADFRACTION;
    }

    m_lCurrentRow = (LONG)((ulNumerator * m_ulColumns) / ulDenominator);

	if (m_lCurrentRow >= (LONG)m_ulColumns)
		m_lCurrentRow = (LONG)m_ulColumns - 1;

	if (!pFetchParams)
		return S_OK;

	// since get next rows starts from the row after the current row we must
	// back up one row
	m_lCurrentRow--;
	if (m_lCurrentRow < -1)
		m_lCurrentRow = -1;

    return CVDMetadataCursor::GetNextRows(g_liZero, pFetchParams);
}

//=--------------------------------------------------------------------------=
// ICursorScroll GetApproximatePosition
//=--------------------------------------------------------------------------=
// Returns the approximate location of a bookmark within the cursor, specified
// as a fraction
//
// Parameters:
//    cbBookmark        - [in]  length in bytes of the bookmark
//    pBookmark         - [in]  a pointer to the bookmark
//    pulNumerator      - [out] a pointer to memory in which to return the
//                              numerator of the faction that defines the
//                              approximate position of the bookmark
//    pulDenominator    - [out] a pointer to memory in which to return the
//                              denominator of that same faction
//
// Output:
//    HRESULT - S_OK if successful
//              E_INVALIDARG bad parameter
//
// Notes:
//
HRESULT CVDMetadataCursor::GetApproximatePosition(ULONG cbBookmark, void *pBookmark, ULONG *pulNumerator, ULONG *pulDenominator)
{
    ASSERT_POINTER(pBookmark, BYTE)
    ASSERT_POINTER(pulNumerator, ULONG)
    ASSERT_POINTER(pulDenominator, ULONG)

    if (!pBookmark || !pulNumerator || !pulDenominator)
    {
        VDSetErrorInfo(IDS_ERR_INVALIDARG, IID_ICursor, m_pResourceDLL);
        return E_INVALIDARG;
    }

	LONG lRow;

	if (!BookmarkToRow(cbBookmark, pBookmark, &lRow))
    {
        VDSetErrorInfo(IDS_ERR_BADBOOKMARK, IID_ICursor, m_pResourceDLL);
        return CURSOR_DB_E_BADBOOKMARK;
    }

    *pulNumerator = lRow + 1;
    *pulDenominator = m_ulColumns ? m_ulColumns : 1;

    return S_OK;
}

//=--------------------------------------------------------------------------=
// ICursorScroll GetApproximateCount
//=--------------------------------------------------------------------------=
// Returns the approximate number of rows in the cursor
//
// Parameters:
//    pudlApproxCount       - [out] a pointer to a buffer containing the
//                                  returned approximate count of the rows
//                                  in the cursor
//    pdwFullyPopuldated    - [out] a pointer to a buffer containing returned
//                                  flags indicating whether the cursor is fully
//                                  populated
//
// Output:
//    HRESULT - S_OK if successful
//              E_INVALIDARG bad parameter
//
// Notes:
//
HRESULT CVDMetadataCursor::GetApproximateCount(LARGE_INTEGER *pudlApproxCount, DWORD *pdwFullyPopulated)
{
    ASSERT_POINTER(pudlApproxCount, LARGE_INTEGER)
    ASSERT_NULL_OR_POINTER(pdwFullyPopulated, DWORD)

    if (!pudlApproxCount)
    {
        VDSetErrorInfo(IDS_ERR_INVALIDARG, IID_ICursor, m_pResourceDLL);
        return E_INVALIDARG;
    }

    pudlApproxCount->HighPart = 0;
    pudlApproxCount->LowPart  = m_ulColumns;

    if (pdwFullyPopulated)
        *pdwFullyPopulated = CURSOR_DBCURSORPOPULATED_FULLY;

    return S_OK;
}