//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1995 - 2000
//
// File:        ScrlSort.cxx
//
// Contents:    Sorted, fully scrollable, distributed rowset.
//
// Classes:     CScrollableSorted
//
// History:     05-Jun-95       KyleP       Created
//              14-JAN-97       KrishnaN    Brought it back to conform to 1.0 spec
//
//  Notes: Some of the distributed versions of the Ole DB interfaces simply 
//         call into the regular implementations. In such cases, we'll avoid
//         posting oledb errors because the underlying call had already done
//         that.
//
//----------------------------------------------------------------------------

#include <pch.cxx>
#pragma hdrstop

#include "scrlsort.hxx"
#include "disacc.hxx"
#include "disbmk.hxx"


// Rowset object Interfaces that support Ole DB error objects
static const IID * apRowsetErrorIFs[] =
{
        &IID_IAccessor,
        &IID_IColumnsInfo,
        &IID_IConvertType,
        &IID_IRowset,
        &IID_IRowsetInfo,
        &IID_IDBAsynchStatus,
        &IID_IRowsetWatchRegion,
        &IID_IRowsetAsynch,
        &IID_IRowsetQueryStatus,
        //&IID_IColumnsRowset,
        &IID_IConnectionPointContainer,
        &IID_IRowsetIdentity,
        &IID_IRowsetLocate,
        //&IID_IRowsetResynch,
        &IID_IRowsetScroll,
        //&IID_IRowsetUpdate,
        //&IID_ISupportErrorInfo
};

static const ULONG cRowsetErrorIFs  = sizeof(apRowsetErrorIFs)/sizeof(apRowsetErrorIFs[0]);

//
// IUnknown methods.
//

//+-------------------------------------------------------------------------
//
//  Method:     CScrollableSorted::RealQueryInterface
//
//  Synopsis:   Rebind to other interface
//
//  Arguments:  [riid]  -- IID of new interface
//              [ppiuk] -- New interface * returned here
//
//  Returns:    S_OK if bind succeeded, E_NOINTERFACE if bind failed
//
//  History:    10-Apr-1995     KyleP   Created
//
//  Notes:      ref count is incremented inside QueryInterface      
//
//--------------------------------------------------------------------------

SCODE  CScrollableSorted::RealQueryInterface( REFIID riid, VOID **ppiuk )
{
    SCODE sc = S_OK;

    *ppiuk = 0;

    // note -- IID_IUnknown covered in QueryInterface

    if ( riid == IID_IRowset )
    {
        *ppiuk = (void *)((IRowset *)this);
    }
    else if (IID_ISupportErrorInfo == riid)
    {
        *ppiuk = (void *) ((IUnknown *) (ISupportErrorInfo *) &_DBErrorObj);
    }
    else if ( riid == IID_IRowsetLocate )
    {
        *ppiuk = (void *)((IRowsetLocate *)this);
    }
    else if ( riid == IID_IRowsetScroll )
    {
        *ppiuk = (void *)((IRowsetScroll *)this);
    }
    else if ( riid == IID_IRowsetExactScroll )
    {
        *ppiuk = (void *)((IRowsetExactScroll *)this);
    }
    else if ( riid == IID_IColumnsInfo )
    {
        *ppiuk = (void *)((IColumnsInfo *)this);
    }
    else if ( riid == IID_IAccessor )
    {
        *ppiuk = (void *)((IAccessor *)this);
    }
    else if ( riid == IID_IRowsetIdentity )
    {
        *ppiuk = (void *)((IRowsetIdentity *)this);
    }
    else if ( riid == IID_IRowsetInfo )
    {
        *ppiuk = (void *)((IRowsetInfo *)this);
    }
    else if ( riid == IID_IRowsetAsynch )
    {
        sc = E_NOINTERFACE;
        //
        //  Support IRowsetAsynch if any of the child rowsets do.
        //
        IRowsetAsynch * pra = 0;
        for (unsigned iChild=0; iChild < _rowset._cChild; iChild++ )
        {
            sc = Get(iChild)->QueryInterface (IID_IRowsetAsynch, (void**) &pra);
            if (SUCCEEDED(sc))
            {
                pra->Release();
                *ppiuk = (void *)((IRowsetAsynch *)this);
                break;
            }
        }
    }
    else if ( riid == IID_IRowsetWatchRegion )
    {
        *ppiuk = (void *) (IRowsetWatchRegion *) this;
    }
    else if ( riid == IID_IRowsetWatchAll )
    {
        *ppiuk = (void *) (IRowsetWatchAll *) this;
    }
    else if ( riid == IID_IDBAsynchStatus )
    {
        *ppiuk = (void *) (IDBAsynchStatus *) this;
    }
    else if ( riid == IID_IConnectionPointContainer )
    {
        sc = _rowset._SetupConnectionPointContainer( this, ppiuk );
    }
    else if ( riid == IID_IRowsetQueryStatus )
    {
        *ppiuk = (void *)((IRowsetQueryStatus *)this);
    }
    else
    {
        sc = E_NOINTERFACE;
    }

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::GetData, public
//
//  Synopsis:   Fetch data for a row.
//
//  Arguments:  [hRow]      -- Handle to row
//              [hAccessor] -- Accessor to use for fetch.
//              [pData]     -- Data goes here.
//
//  History:    03-Apr-95   KyleP       Created.
//
//  Notes:      This method is virtually identical to the one in its base
//              class.  The difference is that this class tracks HROWs
//              for all child cursors, to use as bookmark hints.
//
//  Notes: Need to have Ole DB error handling here because an exception could
//         happen, resulting in a local error.
//
//----------------------------------------------------------------------------

SCODE CScrollableSorted::GetData( HROW              hRow,
                                  HACCESSOR         hAccessor,
                                  void *            pData )
{
    _DBErrorObj.ClearErrorInfo();

    SCODE sc;

    TRY
    {
        unsigned iChild;

        HROW * ahrow = _rowset._RowManager.GetChildAndHROWs( hRow, iChild );

        CDistributedAccessor * pAcc = (CDistributedAccessor *)_rowset._aAccessors.Convert(hAccessor);

        sc = pAcc->GetData( iChild, ahrow, pData );
    }
    CATCH( CException, e )
    {
        vqDebugOut(( DEB_ERROR, "CScrollableSorted::GetData -- caught 0x%x\n", e.GetErrorCode() ));
        sc = e.GetErrorCode();
    }
    END_CATCH

    if (FAILED(sc))
        _DBErrorObj.PostHResult(sc, IID_IRowset);

    return sc;
}

//
// IRowsetLocate methods
//

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::Compare, public
//
//  Synopsis:   Compare bookmarks.
//
//  Arguments:  [hChapter]      -- Chapter.
//              [cbBM1]         -- Size of [pBM1].
//              [pBM1]          -- First bookmark.
//              [cbBM2]         -- Size of [pBM2].
//              [pBM2]          -- Second bookmark.
//              [pdwComparison] -- Result of comparison returned here.
//
//  History:    03-Apr-95   KyleP       Created.
//
//  Notes:      Only equality test supported.
//
//  Notes: Need to have Ole DB error handling here because an exception could
//         happen, resulting in a local error.
//
//----------------------------------------------------------------------------

STDMETHODIMP CScrollableSorted::Compare( HCHAPTER          hChapter,
                                         DBBKMARK          cbBM1,
                                         const BYTE *      pBM1,
                                         DBBKMARK          cbBM2,
                                         const BYTE *      pBM2,
                                         DBCOMPARE *       pdwComparison )
{
    _DBErrorObj.ClearErrorInfo();

    SCODE sc = S_OK;

    TRY
    {
        CDistributedBookmark bmk1( cbBM1,
                                   (BYTE const *)pBM1,
                                   _rowset._cbBookmark,
                                   _rowset._cChild );
        CDistributedBookmark bmk2( cbBM2,
                                   (BYTE const *)pBM2,
                                   _rowset._cbBookmark,
                                   _rowset._cChild );

        if ( bmk1.Index() != bmk2.Index() )
            *pdwComparison = DBCOMPARE_NE;
        else
        {
            sc = Get( bmk1.Index() )->Compare( hChapter,
                                               bmk1.GetSize(),
                                               bmk1.Get(),
                                               bmk2.GetSize(),
                                               bmk2.Get(),
                                               pdwComparison );

            if ( SUCCEEDED(sc) )
            {
                if ( *pdwComparison != DBCOMPARE_EQ &&
                     *pdwComparison != DBCOMPARE_NOTCOMPARABLE )
                {
                    *pdwComparison = DBCOMPARE_NE;
                }
            }
        }
    }
    CATCH( CException, e )
    {
        sc = e.GetErrorCode();
        vqDebugOut(( DEB_ERROR, "CScrollableSorted::Compare returned 0x%x\n", sc ));
    }
    END_CATCH

    if (FAILED(sc))
        _DBErrorObj.PostHResult(sc, IID_IRowsetLocate);

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::GetRowsAt, public
//
//  Synopsis:   Fetch rows from specified starting location.
//
//  Arguments:  [hChapter]       -- Chapter.
//              [cbBookmark]     -- Size of [pBookmark]
//              [pBookmark]      -- Bookmark of starting fetch position.
//              [lRowsOffset]    -- Offset from bookmark to start fetch.
//              [cRows]          -- Count of rows requested
//              [pcRowsObtained] -- Count of rows in [rrghRows] returned here.
//              [rrghRows]       -- HROWs returned here.
//
//  History:    03-Apr-95   KyleP       Created.
//
//  Notes:      Backwards fetch not supported.
//
//  Notes: Need to have Ole DB error handling here because an exception could
//         happen, resulting in a local error.
//
//----------------------------------------------------------------------------

STDMETHODIMP CScrollableSorted::GetRowsAt( HWATCHREGION  hRegion,
                                           HCHAPTER hChapter,
                                           DBBKMARK cbBookmark,
                                           const BYTE * pBookmark,
                                           DBROWOFFSET lRowsOffset,
                                           DBROWCOUNT cRows,
                                           DBCOUNTITEM * pcRowsObtained,
                                           HROW * rrghRows[])
{
    _DBErrorObj.ClearErrorInfo();

    SCODE sc = S_OK;
    BOOL  fAllocated = FALSE;
    ULONG cTotalRowsObtained = 0;

    Win4Assert( 0 == hChapter && "Chapter support not yet implemented" );

    if (0 == cRows) // nothing to fetch
       return S_OK;

    // Underlying routines are not checking for these errors, so have to
    if (0 == cbBookmark || 0 == pBookmark || 0 == pcRowsObtained || 0 == rrghRows )
    {
       vqDebugOut((DEB_IERROR, "CScrollableSorted::GetRowsAt: Invalid Argument(s)\n"));
       return _DBErrorObj.PostHResult(E_INVALIDARG, IID_IRowsetLocate);
    }

    *pcRowsObtained = 0;

    TRY
    {
        fAllocated = SetupFetch( cRows, rrghRows );

        //
        // Seek to position.  Special cases are beginning and end of rowset.
        //

        sc = Seek( cbBookmark, pBookmark, lRowsOffset );

        if ( SUCCEEDED( sc ) )
        {
            sc = StandardFetch( cRows, pcRowsObtained, *rrghRows );

            if ( SUCCEEDED( sc ) &&
                 !_rowset._xChildNotify.IsNull() &&
                 *pcRowsObtained != 0 )
            {
                _rowset._xChildNotify->OnRowChange( *pcRowsObtained,
                                                    *rrghRows,
                                                    DBREASON_ROW_ACTIVATE,
                                                    DBEVENTPHASE_DIDEVENT,
                                                    TRUE);
            }
        }
        else if ( DB_E_BADSTARTPOSITION == sc )
        {
            // According to OLE DB 2.0 spec we should return the following
            sc = DB_S_ENDOFROWSET;
        }
    }
    CATCH( CException, e )
    {
        sc = e.GetErrorCode();
        vqDebugOut(( DEB_ERROR, "Exception 0x%x calling IRowset::GetRowsAt\n", sc ));

        //
        // If we already have some rows, then we can't 'unfetch' them, so we have
        // to mask this error.  Presumably it won't be transient and we'll get it
        // again later.
        //

        if ( *pcRowsObtained > 0 )
        {
             if ( FAILED(sc) )
                sc = DB_S_ROWLIMITEXCEEDED;
        }
        else if ( fAllocated )
            CoTaskMemFree( *rrghRows );

    }
    END_CATCH

    if (FAILED(sc))
        _DBErrorObj.PostHResult(sc, IID_IRowsetLocate);

    return( sc );
}

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::GetRowsByBookmark, public
//
//  Synopsis:   Fetch rows at specified location(s).
//
//  Arguments:  [hChapter]       -- Chapter.
//              [cRows]          -- Number of input bookmarks.
//              [rgcbBookmarks]  -- Count of element(s) in [ppBookmarks]
//              [ppBookmarks]    -- Bookmarks.
//              [pcRowsObtained] -- Count of rows in [rrghRows] returned here.
//              [rghRows]        -- HROWs returned here.
//              [rgRowStatus]    -- Row fetch statuses returned here
//
//  History:    03-Apr-95   KyleP       Created.
//
//  Notes: Need to have Ole DB error handling here because errors are being
//         translated.
//
//----------------------------------------------------------------------------

STDMETHODIMP CScrollableSorted::GetRowsByBookmark( HCHAPTER hChapter,
                                                   DBCOUNTITEM cRows,
                                                   const DBBKMARK rgcbBookmarks [],
                                                   const BYTE * ppBookmarks[],
                                                   HROW rghRows[],
                                                   DBROWSTATUS rgRowStatus[]
                                                  )
{
    _DBErrorObj.ClearErrorInfo();

    SCODE sc = S_OK;
    ULONG cRowsObtained = 0;

    Win4Assert( 0 == hChapter && "Chapter support not yet implemented" );

    if (0 == rgcbBookmarks || 0 == ppBookmarks || 0 == rghRows)
    {
      vqDebugOut((DEB_IERROR, "CScrollableSorted::GetRowsByBookmark: Invalid Argument(s)\n"));
      return _DBErrorObj.PostHResult(E_INVALIDARG, IID_IRowsetLocate);
    }

    SetupFetch( cRows, &rghRows );

    //
    // Note: This code could be optimized to fetch more bookmarks at
    //       once, but only by complicating the logic.  Until we find
    //       it's worth the pain, let's keep it simple!
    //

    unsigned cRowsProcessed;

    for ( cRowsProcessed = 0; cRowsProcessed < cRows; cRowsProcessed++ )
    {
        CDistributedBookmark bmk( rgcbBookmarks[cRowsProcessed],
                                  ppBookmarks[cRowsProcessed],
                                  _rowset._cbBookmark,
                                  _rowset._cChild );

        DBBKMARK cbBookmark = bmk.GetSize();
        BYTE const * pbBookmark = bmk.Get();
        sc = Get( bmk.Index() )->GetRowsByBookmark( hChapter,
                                                    1,
                                                    &cbBookmark,
                                                    (BYTE const **)&pbBookmark,
                                                    &rghRows[cRowsProcessed],
                                                    (0 == rgRowStatus) ? 0 : &rgRowStatus[cRowsProcessed]
                                                   );
        if ( FAILED(sc) )
        {
           continue;
        }
        else
        {
            rghRows[cRowsProcessed] = _rowset._RowManager.Add( bmk.Index(), rghRows[cRowsProcessed] );
            cRowsObtained++;
        }
    }

    if (cRowsProcessed == cRowsObtained)
        sc = S_OK;
    else if (cRowsObtained > 0) // and not all rows were successfully processed
        sc = DB_S_ERRORSOCCURRED;
    else    // no rows were successfully processed
        sc = DB_E_ERRORSOCCURRED;

    if ( SUCCEEDED( sc ) &&
         cRowsObtained > 0 &&
         !_rowset._xChildNotify.IsNull() )
    {
        _rowset._xChildNotify->OnRowChange( cRowsObtained,
                                            rghRows,
                                            DBREASON_ROW_ACTIVATE,
                                            DBEVENTPHASE_DIDEVENT,
                                            TRUE);
    }

    return ( S_OK == sc ?
             S_OK : _DBErrorObj.PostHResult(sc, IID_IRowsetLocate) );
}

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::Hash, public
//
//  Synopsis:   Hash bookmark
//
//  Arguments:  [hChapter]        -- Chapter.
//              [cBookmarks]      -- Number of bookmarks.
//              [rgcbBookmarks]   -- Size of bookmark(s)
//              [rgpBookmarks]    -- Bookmark(s) to hash.
//              [rgHashedValues]  -- Hash(s) returned here.
//              [rgRowStatus]    -- Row fetch statuses returned here
//
//  History:    03-Apr-95   KyleP       Created.
//
//  Notes: Need to have Ole DB error handling here because errors are being
//         translated.
//
//----------------------------------------------------------------------------

STDMETHODIMP CScrollableSorted::Hash( HCHAPTER hChapter,
                                      DBBKMARK cBookmarks,
                                      const DBBKMARK rgcbBookmarks[],
                                      const BYTE * rgpBookmarks[],
                                      DBHASHVALUE rgHashedValues[],
                                      DBROWSTATUS rgRowStatus[] )
{
    _DBErrorObj.ClearErrorInfo();

    SCODE sc = S_OK;
    ULONG cSuccessfulHashes = 0;

    Win4Assert( 0 == hChapter && "Chapter support not yet implemented" );
    Win4Assert( rgHashedValues != 0 );

    // We ignore error conditions returned on calls to individual bookmarks to be
    // able to process all the bookmarks. That means invalid arguments will never
    // be detected and reported without this explicit validation.

    if (0 == rgHashedValues || (cBookmarks && (0 == rgcbBookmarks || 0 == rgpBookmarks )))
    {
       vqDebugOut((DEB_IERROR, "CScrollableSorted::Hash: Invalid Argument(s)\n"));
       return _DBErrorObj.PostHResult(E_INVALIDARG, IID_IRowsetLocate);
    }

    TRY
    {
        ULONG partition = 0xFFFFFFFF / _rowset._cChild;

        for ( ULONG i = 0; i < cBookmarks; i++ )
        {
            //
            // Special bookmarks hash 'as-is'.
            //

            if ( rgcbBookmarks[i] == 1 )
            {
                rgHashedValues[i] = (ULONG)*rgpBookmarks[i];
                continue;
            }

            // This throws, so we need the try/catch around it
            CDistributedBookmark bmk( rgcbBookmarks[i],
                                      rgpBookmarks[i],
                                      _rowset._cbBookmark,
                                      _rowset._cChild );

            BYTE const * pBmk = bmk.Get();
            DBBKMARK cbBmk = bmk.GetSize();
            DBHASHVALUE hash;
            ULONG cErrs = 0;
            DBROWSTATUS * pErr = 0;

            sc = Get(bmk.Index())->Hash( 0, 1, &cbBmk, &pBmk, &hash,
                                         (rgRowStatus == 0) ? 0 : &rgRowStatus[i]
                                       );

            if ( FAILED(sc) )
            {
               continue;  // continue processing other bookmarks
            }

            rgHashedValues[i] = hash % partition + partition * bmk.Index();
            cSuccessfulHashes++;
        }
    }
    CATCH( CException, e )
    {
        sc = e.GetErrorCode();
        vqDebugOut(( DEB_ERROR, "CScrollableSorted::Hash caught exception 0x%x\n", sc ));
    }
    END_CATCH

    // if we see an error other than DB_E_ERRORSOCCURRED, pass it straight through
    if (FAILED(sc) && sc != DB_E_ERRORSOCCURRED)
        return _DBErrorObj.PostHResult(sc, IID_IRowsetLocate);

    if (cSuccessfulHashes == cBookmarks)
        return S_OK;

    if (cSuccessfulHashes > 0) // and not all bookmarks were successfully processed
        sc = DB_S_ERRORSOCCURRED;
    else    // no hashes were successfully processed
        sc = DB_E_ERRORSOCCURRED;

    return _DBErrorObj.PostHResult(sc, IID_IRowsetLocate);
}

//
// IRowsetScroll methods
//

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::GetApproximatePosition, public
//
//  Synopsis:   Determine approximate position of bookmark.
//
//  Arguments:  [hChapter]    -- Chapter.
//              [cbBookmark]  -- Size of [pBookmark]
//              [pBookmark]   -- Bookmark of starting fetch position.
//              [pulPosition] -- Approximate offset from beginning returned
//                               here.
//              [pulRows]     -- Approximate count of rows in table
//                               returned here.
//
//  History:    03-Apr-95   KyleP       Created.
//
//  Notes: Need to have Ole DB error handling here because an exception
//         could result in a local error.
//
//----------------------------------------------------------------------------

STDMETHODIMP CScrollableSorted::GetApproximatePosition( HCHAPTER hChapter,
                                                        DBBKMARK cbBookmark,
                                                        const BYTE * pBookmark,
                                                        DBCOUNTITEM * pulPosition,
                                                        DBCOUNTITEM * pulRows )
{
    _DBErrorObj.ClearErrorInfo();

    SCODE sc = S_OK;

    Win4Assert( 0 == hChapter && "Chapter support not yet implemented" );

    if (cbBookmark !=0 && 0 == pBookmark )
    {
       vqDebugOut((DEB_IERROR,
                   "CScrollableSorted::GetApproximatePosition: Invalid Argument(s)\n"));
       return _DBErrorObj.PostHResult(E_INVALIDARG, IID_IRowsetScroll);
    }

    TRY
    {
        CDistributedBookmark bmk( cbBookmark,
                                  (BYTE const *)pBookmark,
                                  _rowset._cbBookmark,
                                  _rowset._cChild );

        if ( 0 != pulPosition )
            *pulPosition = 0;

        if ( 0 != pulRows )
            *pulRows = 0;

        DBCOUNTITEM ulTotalRows = 0;
        DBCOUNTITEM ulValidRows = 0;
        unsigned cValidBmk = 0;

        for ( unsigned i = 0; i < _rowset._cChild; i++ )
        {
            DBCOUNTITEM ulPosition = 0;
            DBCOUNTITEM ulRows;

            BOOL fValid;
            DBBKMARK cb;

            if ( bmk.IsValid( i ) )
            {
                cValidBmk++;
                fValid = TRUE;
                cb = bmk.GetSize();
            }
            else
            {
                fValid = FALSE;
                cb = 0;
            }

            sc = Get(i)->GetApproximatePosition( hChapter,
                                                 cb,
                                                 bmk.Get(i),
                                                 (0 == pulPosition) ? 0 : &ulPosition,
                                                 &ulRows );

            if ( FAILED(sc) )
            {
                vqDebugOut(( DEB_ERROR,
                             "CScrollableSorted: GetApproximatePosition(%u) returned 0x%x\n",
                             i, sc ));
                break;
            }

            if ( 0 != pulPosition )
            {
                Win4Assert( ulPosition <= ulRows );
                *pulPosition += fValid ? ulPosition : ulRows; // If not valid bookmark,
                                                              // assume its at end
            }

            ulTotalRows += ulRows;

            if ( fValid )
                ulValidRows += ulRows;
        }

        if ( pulPosition && cValidBmk > 1 )
        {
            *pulPosition -= ( cValidBmk - 1 );
        }

        //
        // Special cases (speced in doc)
        //

        if ( cbBookmark == 1 && *(BYTE *)pBookmark == DBBMK_FIRST && 0 != pulPosition )
            *pulPosition = 1;
        else if ( cbBookmark == 1 && *(BYTE *)pBookmark == DBBMK_LAST && 0 != pulPosition )
            *pulPosition = ulTotalRows;
        //else if ( 0 != pulPosition && cValidBmk < _rowset._cChild )
        //{
        //    *pulPosition += *pulPosition * (ulTotalRows - ulValidRows) / ulValidRows;
        //}

        if ( 0 != pulRows )
            *pulRows = ulTotalRows;
    }
    CATCH( CException, e )
    {
        sc = e.GetErrorCode();
        vqDebugOut(( DEB_ERROR,
        "CScrollableSorted::GetApproximatePosition caught exception 0x%x\n", sc ));
    }
    END_CATCH

    if (FAILED(sc))
        _DBErrorObj.PostHResult(sc, IID_IRowsetScroll);

    return sc;
}


//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::GetExactPosition, public
//
//  Synopsis:   Returns the exact position of a bookmark
//
//  Arguments:  [hChapter]    -- chapter
//              [cbBookmark]  -- size of bookmark
//              [pBookmark]   -- bookmark
//              [pulPosition] -- return approx row number of bookmark
//              [pulRows]     -- returns approx # of rows in cursor or
//                               1 + approx rows if not at quiescence
//
//  Returns:    SCODE - the status of the operation.
//
//  Notes:      We don't distinguish between exact and approximate position.
//              IRowsetExactScroll is implemented only because ADO 1.5
//              started QI'ing for it.
//
//--------------------------------------------------------------------------

STDMETHODIMP CScrollableSorted::GetExactPosition(
    HCHAPTER      hChapter,
    DBBKMARK      cbBookmark,
    const BYTE *  pBookmark,
    DBCOUNTITEM * pulPosition,
    DBCOUNTITEM * pulRows) /*const*/
{
    return GetApproximatePosition( hChapter,
                                   cbBookmark,
                                   pBookmark,
                                   pulPosition,
                                   pulRows );
}


//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::GetRowsAtRatio, public
//
//  Synopsis:   Fetch rows from approximate position.
//
//  Arguments:  [hRegion]        -- Watch region
//  Arguments:  [hChapter]       -- Chapter.
//              [ulNumerator]    -- Numerator of position.
//              [ulDenominator]  -- Denominator of position.
//              [cRows]          -- Count of rows requested
//              [pcRowsObtained] -- Count of rows in [rrghRows] returned here.
//              [rrghRows]       -- HROWs returned here.
//
//  History:    03-Apr-95   KyleP       Created.
//
//  Notes: Need to have Ole DB error handling here because an exception
//         could result in a local error.
//
//----------------------------------------------------------------------------

STDMETHODIMP CScrollableSorted::GetRowsAtRatio( HWATCHREGION  hRegion,
                                                HCHAPTER      hChapter,
                                                DBCOUNTITEM   ulNumerator,
                                                DBCOUNTITEM   ulDenominator,
                                                DBROWCOUNT    cRows,
                                                DBCOUNTITEM * pcRowsObtained,
                                                HROW ** rrghRows )
{
    _DBErrorObj.ClearErrorInfo();

    SCODE sc = S_OK;
    BOOL  fAllocated = FALSE;

    Win4Assert( 0 == hChapter && "Chapter support not yet implemented" );

    if (0 == pcRowsObtained || 0 == rrghRows )
    {
       vqDebugOut((DEB_IERROR, "CScrollableSorted::GetRowsAtRatio: Invalid Argument(s)"));
       return _DBErrorObj.PostHResult(E_INVALIDARG, IID_IRowsetScroll);
    }

    TRY
    {
        *pcRowsObtained = 0;
        fAllocated = SetupFetch( cRows, rrghRows );

        //
        // Seek to position.  Special cases are beginning and end of rowset.
        //

        Seek( ulNumerator, ulDenominator );

        sc = StandardFetch( cRows, pcRowsObtained, *rrghRows );

        if ( SUCCEEDED( sc ) &&
             !_rowset._xChildNotify.IsNull() &&
             *pcRowsObtained != 0 )
        {
            _rowset._xChildNotify->OnRowChange( *pcRowsObtained,
                                                *rrghRows,
                                                DBREASON_ROW_ACTIVATE,
                                                DBEVENTPHASE_DIDEVENT,
                                                TRUE);
        }
    }
    CATCH( CException, e )
    {
        sc = e.GetErrorCode();

        vqDebugOut(( DEB_ERROR, "CScrollableSorted::GetRowsAtRatio: Exception 0x%x\n", sc ));

        //
        // If we already have some rows, then we can't 'unfetch' them, so we have
        // to mask this error.  Presumably it won't be transient and we'll get it
        // again later.
        //

        if ( *pcRowsObtained > 0 )
        {
            if (FAILED(sc))
                sc = DB_S_ROWLIMITEXCEEDED;
        }
        else if ( fAllocated )
        {
            CoTaskMemFree( *rrghRows );
            *rrghRows = 0;
        }
    }
    END_CATCH

    if (FAILED(sc))
        _DBErrorObj.PostHResult(sc, IID_IRowsetScroll);

    return( sc );
}

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::CScrollableSorted, public
//
//  Synopsis:   Initialize rowset.
//
//  Arguments:  [pUnkOuter] -- outer unknown
//              [ppMyUnk] -- OUT:  on return, filled with pointer to my
//                           non-delegating IUnknown
//              [aChild] -- Array of child cursors (rowsets).
//              [cChild] -- Count of elements in [aChild].
//              [Props]  -- Rowset properties.
//              [cCol]   -- Number of original columns.
//              [Sort]   -- Sort specification.
//
//  History:    03-Apr-95   KyleP       Created.
//
//----------------------------------------------------------------------------

CScrollableSorted::CScrollableSorted( IUnknown * pUnkOuter,
                                      IUnknown ** ppMyUnk,
                                      IRowsetScroll ** aChild,
                                      unsigned cChild,
                                      CMRowsetProps const & Props,
                                      unsigned cCol,
                                      CSort const & Sort,
                                      CAccessorBag & aAccessors ) :
          _rowset( 0, ppMyUnk, 
                   (IRowset **)aChild, cChild, Props, cCol + 1, // Add 1 col. for bookmark
                   Sort, aAccessors ),
          _apPosCursor(cChild),
          _heap( cChild ),
#pragma warning(disable : 4355) // 'this' in a constructor
          _impIUnknown(this),
          _DBErrorObj( * ((IUnknown *) (IRowset *) this), _mutex )
#pragma warning(default : 4355)    // 'this' in a constructor
{
    unsigned iChild = 0;

    TRY
    {
        _DBErrorObj.SetInterfaceArray(cRowsetErrorIFs, apRowsetErrorIFs);
        _rowset._RowManager.TrackSiblings( cChild );

        if (pUnkOuter) 
            _pControllingUnknown = pUnkOuter;
        else
            _pControllingUnknown = (IUnknown * )&_impIUnknown;


        //
        // Create accessors
        //

        for ( ; iChild < _rowset._cChild; iChild++ )
        {
            _apPosCursor[iChild] = new CMiniPositionableCache( iChild,
                                                               Get(iChild),
                                                               Sort.Count(),
                                                               _rowset._bindSort.GetPointer(),
                                                               _rowset._cbSort,
                                                               _rowset._iColumnBookmark,
                                                               _rowset._cbBookmark );
        }

        //
        // Initialize heap
        //

        _heap.Init( &_rowset._Comparator, GetCacheArray() );

        *ppMyUnk = ((IUnknown *)&_impIUnknown);
        (*ppMyUnk)->AddRef();

    }
    CATCH( CException, e )
    {
        long lChild = iChild;

        for ( lChild--; lChild >= 0; lChild-- )
            delete _apPosCursor[lChild];

        RETHROW();
    }
    END_CATCH

    END_CONSTRUCTION( CScrollableSorted );
}

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::~CScrollableSorted, private
//
//  Synopsis:   Destructor.
//
//  History:    03-Apr-95   KyleP       Created.
//
//----------------------------------------------------------------------------

CScrollableSorted::~CScrollableSorted()
{
    unsigned ii;

    for ( ii = _rowset._cChild ; ii > 0; ii-- )
    {
        delete _apPosCursor[ii-1];
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::_GetMaxPrevRowChild, private
//
//  Synopsis:   From all the child cursors, return the index which has the
//              max. prev. row. If none of the cursor has a prev. row (they are
//              all at the top), the index returned is the total no. of cursors.
//
//  Arguments:  none
//
//  History:    10-Sep-98   VikasMan       Created.
//
//----------------------------------------------------------------------------

unsigned CScrollableSorted::_GetMaxPrevRowChild()
{
    unsigned iMaxRowChild = _rowset._cChild;
    unsigned iChild;

    BYTE * pMaxPrevData;
    BYTE * pPrevData;

    for ( iChild = 0; iChild < _rowset._cChild; iChild++ )
    {
        pMaxPrevData = _apPosCursor[iChild]->GetPrevData();

        if ( pMaxPrevData )
        {
            iMaxRowChild = iChild;
            break;
        }
    }

    for ( iChild++; iChild < _rowset._cChild; iChild++ )
    {
        pPrevData = _apPosCursor[iChild]->GetPrevData();

        if ( pPrevData )
        {
            if ( _rowset._Comparator.IsLT( 
                                     pMaxPrevData,
                                     _apPosCursor[iMaxRowChild]->DataLength(),
                                     _apPosCursor[iMaxRowChild]->Index(),
                                     pPrevData,
                                     _apPosCursor[iChild]->DataLength(),
                                     _apPosCursor[iChild]->Index() ) )
            {
                iMaxRowChild = iChild;
                pMaxPrevData = pPrevData;
            }
        }
    }
    return iMaxRowChild;
}

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::Seek, private
//
//  Synopsis:   Position heap to specified bookmark + offset.
//
//  Arguments:  [cbBookmark] -- Size of [pbBookmark].
//              [pbBookmark] -- Bookmark.
//              [lOffset]    -- Offset from bookmark.
//
//  History:    03-Apr-95   KyleP       Created.
//
//----------------------------------------------------------------------------

SCODE CScrollableSorted::Seek( DBBKMARK cbBookmark, BYTE const * pbBookmark, DBROWOFFSET lOffset )
{
    unsigned cValid = _rowset._cChild;
    unsigned i;
    SCODE sc = S_OK;

    //
    // Special case: Beginning of table
    //

    if ( cbBookmark == 1 && *pbBookmark == DBBMK_FIRST )
    {
        //
        // Seek all cursors to beginning of table.
        //

        for ( i = 0; i < cValid; i++ )
            _apPosCursor[ i ]->Seek( cbBookmark, pbBookmark );
    }
    else if (cbBookmark == 1 && *pbBookmark ==  DBBMK_LAST)
    {
       //
       // First Seek all cursors to end of table.
       //

       for ( i = 0; i < cValid; i++ )
           _apPosCursor[ i ]->Seek( cbBookmark, pbBookmark );

       for ( i = 0; i < cValid; i++ )
       {
           if ( _apPosCursor[i]->IsAtEnd() )
           {
               _apPosCursor[i]->Seek(1);    // pushes it beyond the end
   
               //
               // Move unseekable cursor to end of array.
               //
   
               cValid--;
               SwapCursor(i, cValid);
               i--;
           }
       }
       // Find the cursor with the maximum value
       unsigned iCurWithMaxVal = 0;
       for (i = 1; i < cValid; i++)
       {
          if ( _rowset._Comparator.IsLT( _apPosCursor[iCurWithMaxVal]-> GetData(),
                                   _apPosCursor[iCurWithMaxVal]->DataLength(),
                                   _apPosCursor[iCurWithMaxVal]->Index(),
                                   _apPosCursor[i]->GetData(),
                                   _apPosCursor[i]->DataLength(),
                                   _apPosCursor[i]->Index() )
             )
             iCurWithMaxVal = i;
       }

       // Now set all cursors except _apPosCursor[iCurWithMaxVal] to end of table
       for (i = 0; i < cValid; i++)
           if (i != iCurWithMaxVal)
               _apPosCursor[i]->Seek(1);    // pushes it beyond the end

    }
    else
    {
        CDistributedBookmark bmk( cbBookmark,
                                  pbBookmark,
                                  _rowset._cbBookmark,
                                  _rowset._cChild );

        //
        // Seek target cursor.  We have to do this one first.
        //

        for ( unsigned iTarget = 0; iTarget < _rowset._cChild; iTarget++ )
        {
            if ( _apPosCursor[iTarget]->Index() == (int)bmk.Index() )
            {
                _apPosCursor[ iTarget ]->Seek( bmk.GetSize(), bmk.Get() );
                break;
            }
        }

        //
        // Seek child cursor(s), other than target.
        //

        for ( i = 0; i < cValid; i++ )
        {
            //
            // Ignore target cursor
            //

            if ( i == iTarget )
                continue;

            //
            // Seek to 'hint' position.
            //

            _apPosCursor[i]->FlushCache();
            _apPosCursor[i]->SetCacheSize( 1 );

            PMiniRowCache::ENext next;

            if ( bmk.IsValid( _apPosCursor[i]->Index() ) )
                next = _apPosCursor[i]->Seek( bmk.GetSize(), bmk.Get( _apPosCursor[i]->Index() ) );
            else
            {
                BYTE bStart = DBBMK_FIRST;
                next = _apPosCursor[i]->Seek( sizeof(bStart), &bStart );
            }

            //
            // And adjust so that the cursor is positioned just after the target cursor.
            //

            if ( next != PMiniRowCache::Ok ||
                AdjustPosition( i, iTarget ) != PMiniRowCache::Ok )

            {
                BYTE bEnd = DBBMK_LAST;
                _apPosCursor[i]->Seek( sizeof(bEnd), &bEnd );
                _apPosCursor[i]->Seek(1);    // pushes it beyond the end

                //
                // Move unseekable cursor to end of array.
                //

                cValid--;
                SwapCursor(i, cValid);
                if ( cValid == iTarget )
                {
                    iTarget = i;
                }

                i--;
            }
        }
    }

    for ( i = 0; i<cValid; i++ )
    {
        if ( _apPosCursor[i]->IsAtEnd() )
        {
            _apPosCursor[i]->Seek(1);    // pushes it beyond the end

            //
            // Move unseekable cursor to end of array.
            //

            cValid--;
            SwapCursor(i, cValid);
            i--;
        }
    }

    _heap.ReInit( cValid );

    if ( lOffset < 0 )
    {
        cValid = _rowset._cChild;

        // Load Previous rows from all valid
        for ( i = 0; i < cValid; i++ )
        {
            _apPosCursor[i]->LoadPrevRowData();
        }

        unsigned iMaxRowChild;
        BOOL fReInit = FALSE;
        for( ;; )
        {
            // Find out which rowset has the max. prev. row
            iMaxRowChild = _GetMaxPrevRowChild();

            if ( iMaxRowChild >= _rowset._cChild )
            {
                break;
                // no previous row
            }

            fReInit = TRUE;

            // Move the rowset with the max. prev. row back
            _apPosCursor[iMaxRowChild]->MovePrev();

            if ( 0 == ++lOffset )
            {
                break;
            }

            // Reload prev. row for the rowset which we moved back
            _apPosCursor[iMaxRowChild]->LoadPrevRowData();
        }

        if ( fReInit)
        {
            for ( i = 0; i<cValid; i++ )
            {
                if ( _apPosCursor[i]->IsAtEnd() )
                {
                    _apPosCursor[i]->Seek(1);    // pushes it beyond the end
        
                    //
                    // Move unseekable cursor to end of array.
                    //
        
                    cValid--;
                    SwapCursor(i, cValid);
                    i--;
                }
            }
            _heap.ReInit( cValid );
        }

        if ( lOffset < 0 )
        {
            // NTRAID#DB-NTBUG9-84055-2000/07/31-dlee Failed distribued query row fetches don't restore previous seek position
            // Do we need to reset the position of rowset back to
            // where it was before call to Seek ?

            sc = DB_E_BADSTARTPOSITION;
        }
    }
    else
    {
        for ( ; lOffset > 0; lOffset-- )
        {
            //
            // Release top HROW.
            //
    
            HROW hrow = _heap.Top()->GetHROW();
    
            SCODE sc2 = Get( _heap.Top()->Index() )->ReleaseRows( 1, &hrow, 0, 0, 0 );
    
            Win4Assert( SUCCEEDED(sc2) );
    
            PMiniRowCache::ENext next = _heap.Next();
    
            Win4Assert( PMiniRowCache::NotNow != next );
    
            if ( PMiniRowCache::EndOfRows == next )
                break;
        }
    }
    return sc;
}


//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::AdjustPosition, private
//
//  Synopsis:   Adjust position of iChild-th cursor to just after the
//              iTarget-th cursor.
//
//  Arguments:  [iChild]  -- Index of child in _apPosCursor.
//              [iTarget] -- Index of target in _apPosCursor.
//
//  Returns:    Seek result.
//
//  History:    03-Apr-95   KyleP       Created.
//
//----------------------------------------------------------------------------

PMiniRowCache::ENext CScrollableSorted::AdjustPosition( unsigned iChild, int iTarget )
{
    vqDebugOut(( DEB_ITRACE, "Child: %d data = 0x%x, size = %u\n",
                 _apPosCursor[iChild]->Index(),
                 _apPosCursor[iChild]-> GetData(),
                 _apPosCursor[iChild]->DataLength() ));

    vqDebugOut(( DEB_ITRACE, "Target: %d data = 0x%x, size = %u\n",
                 _apPosCursor[iTarget]->Index(),
                 _apPosCursor[iTarget]-> GetData(),
                 _apPosCursor[iTarget]->DataLength() ));

    PMiniRowCache::ENext next;  // Used to report error states.
    int iJump;                  // Seek offset (positive or negative) from starting point.
    int iNextInc;               // Next seek increment.
    int iDirection;             // Direction of seek from initial position to target.

    if ( _rowset._Comparator.IsLT( _apPosCursor[iTarget]-> GetData(),
                                   _apPosCursor[iTarget]->DataLength(),
                                   _apPosCursor[iTarget]->Index(),
                                   _apPosCursor[iChild]->GetData(),
                                   _apPosCursor[iChild]->DataLength(),
                                   _apPosCursor[iChild]->Index() ) )
    {
        next = InitialSeek( iChild, iTarget, -1, iJump, iNextInc, iDirection );

        //
        // Running into end (actually beginning) here is ok.
        //

        if ( next == PMiniRowCache::EndOfRows )
            return PMiniRowCache::Ok;
    }
    else
        next = InitialSeek( iChild, iTarget, 1, iJump, iNextInc, iDirection );

    if ( next != PMiniRowCache::Ok )
        return next;

    //
    // At this point, iChild is at least 1 row < iTarget (or 1 row > iTarget).
    //

    vqDebugOut(( DEB_ITRACE, "Final positioning:\n" ));

    while ( iNextInc > 0 )
    {
        iJump += (iNextInc * iDirection);

        vqDebugOut(( DEB_ITRACE, "Backward %d\n", -iJump ));

        PMiniRowCache::ENext next = _apPosCursor[iChild]->Seek( iJump );

        iNextInc /= 2;

        if ( _rowset._Comparator.IsLT( _apPosCursor[iTarget]-> GetData(),
                                       _apPosCursor[iTarget]->DataLength(),
                                       _apPosCursor[iTarget]->Index(),
                                       _apPosCursor[iChild]->GetData(),
                                       _apPosCursor[iChild]->DataLength(),
                                       _apPosCursor[iChild]->Index() ) )
            iDirection = -1;
        else
            iDirection = 1;
    }

    //
    // Either the row we are on is correct, or off by one.
    //

    if ( _rowset._Comparator.IsLT( _apPosCursor[iChild]-> GetData(),
                                   _apPosCursor[iChild]->DataLength(),
                                   _apPosCursor[iChild]->Index(),
                                   _apPosCursor[iTarget]->GetData(),
                                   _apPosCursor[iTarget]->DataLength(),
                                   _apPosCursor[iTarget]->Index() ) )
        return _apPosCursor[iChild]->Seek( iJump + 1 );
    else
        return PMiniRowCache::Ok;
}

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::InitialSeek, private
//
//  Synopsis:   Worker routine for AdjustPosition.  Binary searches until
//              iChild is at least one row on the opposite side of iTarget
//              from where it started.
//
//  Arguments:  [iChild]           -- Index of child in _apPosCursor.
//              [iTarget]          -- Index of target in _apPosCursor.
//              [InitialDirection] -- Direction to start moving.
//              [iJump]            -- Offset from starting point returned
//                                    here.
//              [iNextInc]         -- Size of last jump returned here.
//              [iDirection]       -- Direction of last jump returned here.
//
//  Returns:    Seek result.
//
//  History:    03-Apr-95   KyleP       Created.
//
//----------------------------------------------------------------------------


PMiniRowCache::ENext CScrollableSorted::InitialSeek( unsigned iChild,
                                                     int iTarget,
                                                     int InitialDirection,
                                                     int & iJump,
                                                     int & iNextInc,
                                                     int & iDirection )
{
    //
    // If the child starts out > target, then go backward until we pass
    // the target, and then forward until *just* after target, otherwise
    // do the opposite.  The goal is to go a known distance past the target
    // so we can seek back towards it logarithmically.
    //

    iJump = 1 * InitialDirection;
    iNextInc = 1 * InitialDirection;

    int LTa;
    int LTb;

    if ( InitialDirection == 1 )
    {
        LTa = iChild;
        LTb = iTarget;
    }
    else
    {
        LTa = iTarget;
        LTb = iChild;
    }

    vqDebugOut(( DEB_ITRACE, "Initial positioning:\n" ));

    PMiniRowCache::ENext next = PMiniRowCache::EndOfRows;

    while ( iNextInc != 0 )
    {
        do
        {
            vqDebugOut(( DEB_ITRACE, "%s %u\n", iJump < 0 ? "Backward" : "Forward",
                                                iJump < 0 ? -iJump : iJump ));


            next = _apPosCursor[iChild]->Seek( iJump );

            if ( 0 == iNextInc )
            {
                Win4Assert( next != PMiniRowCache::EndOfRows );
                next = PMiniRowCache::EndOfRows;
                break;
            }

            switch ( next )
            {
            case PMiniRowCache::Ok:
                iNextInc *= 2;
                break;

            case PMiniRowCache::EndOfRows:
                iJump -= iNextInc;
                iNextInc /= 2;
                iJump += iNextInc;
                break;

            default:
                return next;
            }
        } while ( next == PMiniRowCache::EndOfRows );

        Win4Assert( iJump * InitialDirection >= 0 );
        //if ( iJump * InitialDirection > 0 &&

        if ( iJump != 0 &&
             _rowset._Comparator.IsLT( _apPosCursor[LTa]-> GetData(),
                                   _apPosCursor[LTa]->DataLength(),
                                   _apPosCursor[LTa]->Index(),
                                   _apPosCursor[LTb]->GetData(),
                                   _apPosCursor[LTb]->DataLength(),
                                   _apPosCursor[LTb]->Index() ) )
            iJump += iNextInc;
        else
            break;
    }

    iNextInc = ((iJump*InitialDirection) / 2) + 1;
    iDirection = -InitialDirection;

    return next;
}

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::Seek, private
//
//  Synopsis:   Position heap to approximate position.
//
//  Arguments:  [ulNumerator]   -- Numerator.
//              [ulDenominator] -- Denominator.
//
//  History:    03-Apr-95   KyleP       Created.
//
//----------------------------------------------------------------------------

void CScrollableSorted::Seek( DBCOUNTITEM ulNumerator, DBCOUNTITEM ulDenominator )
{
    unsigned cValid = _rowset._cChild;

    //
    // Special case: Beginning of table
    //

    if ( 0 == ulNumerator )
    {
        //
        // Seek all cursors to beginning of table.
        //

        BYTE bmkStart = DBBMK_FIRST;

        for ( unsigned i = 0; i < _rowset._cChild; i++ )
            _apPosCursor[ i ]->Seek( sizeof(bmkStart), &bmkStart );

        _heap.ReInit( cValid );
    }

    //
    // Special case: End of table
    //

    else if ( ulNumerator == ulDenominator )
    {
        //
        // Seek all cursors to end of table.
        //

        BYTE bmkEnd = DBBMK_LAST;

        for ( unsigned i = 0; i < _rowset._cChild; i++ )
            _apPosCursor[ i ]->Seek( sizeof(bmkEnd), &bmkEnd );

        _heap.ReInit( cValid );
    
    }

    //
    // Normal case: Middle of table
    //

    else
    {
        //
        // Seek all cursors to ratio.
        //

        // Get the total # of rows

        DBCOUNTITEM ulRows = 0;
        SCODE sc = GetApproximatePosition( NULL,
                                           0,
                                           NULL,
                                           NULL,
                                           &ulRows );
        if ( SUCCEEDED (sc ) && ulRows > 0 )
        {
            DBROWOFFSET lSeekPos = (( ulNumerator * ulRows ) / ulDenominator );

            BYTE bmk;

            if ( (lSeekPos * 100 / ulRows) > 50 )
            {
                // seek from bottom
                bmk = DBBMK_LAST;
                lSeekPos = lSeekPos - (LONG) ulRows + 1;
            }
            else
            {
                // seek from top
                bmk = DBBMK_FIRST;
            }

            sc = Seek( sizeof(bmk), &bmk, lSeekPos );
        }


#if 0
        for ( unsigned i = 0; i < _rowset._cChild; i++ )
        {
            _apPosCursor[ i ]->FlushCache();
            _apPosCursor[ i ]->SetCacheSize( 1 );
            _apPosCursor[ i ]->Seek( ulNumerator, ulDenominator );
        }

        //
        // Heapify, then pick the cursor ulNumerator / ulDenominator from
        // top of the heap.
        //

        _heap.ReInit( _rowset._cChild );

        _heap.NthToTop( _rowset._cChild * ulNumerator / ulDenominator );

        unsigned iTarget = 0;

        //
        // Adjust position of all other cursors to follow target.
        //

        for ( i = 0; i < cValid; i++ )
        {
            //
            // Ignore target cursor
            //

            if ( i == iTarget )
                continue;

            if ( AdjustPosition( i, iTarget ) != PMiniRowCache::Ok )
            {
                //
                // Move unseekable cursor to end of array.
                //

                cValid--;
                SwapCursor(i, cValid);
                i--;
            }
        }
#endif
    }

}

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::SetupFetch, private
//
//  Synopsis:   Common operations before seek in Get* routines.
//
//  Arguments:  [cRows]    -- Number of rows requested.
//              [rrghRows] -- Rows returned here.  May have to allocate.
//
//  Returns:    TRUE if *rrghRows was allocated.
//
//  History:    03-Apr-95   KyleP       Created.
//
//----------------------------------------------------------------------------

BOOL CScrollableSorted::SetupFetch( DBROWCOUNT cRows, HROW * rrghRows[] )
{
    //
    // We may have reached some temporary condition such as
    // DB_S_ROWLIMITEXCEEDED on the last pass.  Iterate until we
    // have a valid heap.
    //

    PMiniRowCache::ENext next = _heap.Validate();

    if ( next == PMiniRowCache::NotNow )
    {
        THROW( CException( DB_E_ROWLIMITEXCEEDED ) );
    }

    //
    // We may have to allocate memory, if the caller didn't.
    //

    BOOL fAllocated = FALSE;

    if ( 0 == *rrghRows )
    {
        *rrghRows = (HROW *)CoTaskMemAlloc( (ULONG) ( abs(((LONG) cRows)) * sizeof(HROW) ) );
        fAllocated = TRUE;
    }

    if ( 0 == *rrghRows )
    {
        vqDebugOut(( DEB_ERROR, "CScrollableSorted::SetupFetch: Out of memory.\n" ));
        THROW( CException( E_OUTOFMEMORY ) );
    }

    return fAllocated;
}

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::SetupFetch, private
//
//  Synopsis:   Common operations before seek in Get* routines.
//
//  Arguments:  [cRows]          -- Number of rows requested.
//              [pcRowsObtained] -- Count actually fetched.
//              [rghRows]        -- Rows returned here.  Already allocated
//                                  if needed.
//
//  Returns:    Status code.
//
//  History:    03-Apr-95   KyleP       Created.
//
//----------------------------------------------------------------------------

SCODE CScrollableSorted::StandardFetch( DBROWCOUNT    cRows,
                                        DBCOUNTITEM * pcRowsObtained,
                                        HROW    rghRows[] )
{
    SCODE sc = S_OK;

    int iDir = 1;

    if ( cRows < 0 )
    {
        cRows = -cRows;
        iDir = -1;
    }

    unsigned ucRows = (unsigned) cRows;

    //
    // Adjust cache size if necessary.
    //

    _heap.AdjustCacheSize( ucRows );

    //
    // Fetch from top of heap.
    //

    while ( *pcRowsObtained < ucRows )
    {
        //
        // We may be entirely out of rows.
        //

        if ( _heap.IsHeapEmpty() )
        {
            sc = DB_S_ENDOFROWSET;
            break;
        }

        (rghRows)[*pcRowsObtained] =
            _rowset._RowManager.Add( _heap.Top()->Index(),
                                     _heap.TopHROWs() );

        (*pcRowsObtained)++;

        //
        // Fetch the next row.
        //

        PMiniRowCache::ENext next = _heap.Next( iDir );

        if ( CMiniRowCache::NotNow == next )
        {
            sc = DB_S_ROWLIMITEXCEEDED;
            break;
        }
    }

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::RatioFinished, public
//
//  Synopsis:   Ratio finished for asynchronously populated rowsets.
//
//  Arguments:  [pulDenominator] -- Denominator returned here.
//              [pulNumerator]   -- Numerator returned here.
//              [pcRows]         -- Count of rows returned here
//              [pfNewRows]      -- TRUE if rows added since last call.
//
//  History:    03-Apr-95   KyleP       Created.
//
//  Notes: Need to have Ole DB error handling here because an exception
//         could result in a local error.
//
//----------------------------------------------------------------------------

STDMETHODIMP CScrollableSorted::RatioFinished( DBCOUNTITEM * pulDenominator,
                                               DBCOUNTITEM * pulNumerator,
                                               DBCOUNTITEM * pcRows,
                                               BOOL * pfNewRows )
{
    _DBErrorObj.ClearErrorInfo();

    IRowsetAsynch * pra = 0;

    SCODE scResult = S_OK;

    *pulDenominator = 0;
    *pulNumerator = 0;
    *pcRows = 0;
    *pfNewRows = FALSE;

    for ( unsigned iChild = 0; iChild < _rowset._cChild; iChild++ )
    {
        DBCOUNTITEM ulDenom;
        DBCOUNTITEM ulNum;
        DBCOUNTITEM cRows;
        BOOL fNew;

        SCODE sc = Get(iChild)->QueryInterface( IID_IRowsetAsynch, (void **) &pra );
        if ( SUCCEEDED(sc) )
        {
            sc = pra->RatioFinished( &ulDenom, &ulNum, &cRows, &fNew );
            pra->Release();
        }

        if ( FAILED(sc) && E_NOTIMPL != sc && E_NOINTERFACE != sc )
        {
            vqDebugOut(( DEB_ERROR,
                         "IRowsetAsynch::RatioFinished(child %d) returned 0x%x\n",
                         iChild, sc ));
            scResult = sc;
            break;
        }

        if ( SUCCEEDED(sc) )
        {
            Win4Assert( *pulDenominator + ulDenom > *pulDenominator );

            *pulDenominator += ulDenom;
            *pulNumerator += ulNum;
            *pcRows += cRows;
            *pfNewRows = *pfNewRows || fNew;
        }
    }

    if ( 0 == *pulDenominator )
        scResult = E_NOTIMPL;

    if (FAILED(scResult))
        _DBErrorObj.PostHResult(scResult, IID_IRowsetAsynch);

    return( scResult );
}

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::Stop, public
//
//  Synopsis:   Stop population of asynchronously populated rowsets.
//
//  Arguments:  - None -
//
//  History:    16 Jun 95   Alanw       Created.
//
//  Notes: Need to have Ole DB error handling here because errors are
//         being translated.
//
//----------------------------------------------------------------------------

STDMETHODIMP CScrollableSorted::Stop( )
{
    _DBErrorObj.ClearErrorInfo();

    IRowsetAsynch * pra = 0;

    SCODE scResult = S_OK;

    for ( unsigned iChild = 0; iChild < _rowset._cChild; iChild++ )
    {
        SCODE sc = Get(iChild)->QueryInterface( IID_IRowsetAsynch,
                                                    (void **) &pra );
        if ( SUCCEEDED(sc) )
        {
            sc = pra->Stop( );
            pra->Release();
        }

        if ( FAILED(sc) && (S_OK == scResult ||
                            E_NOTIMPL != sc ||
                            E_NOINTERFACE != sc))
        {
            vqDebugOut(( DEB_ERROR,
                         "IRowsetAsynch::Stop (child %d) returned 0x%x\n",
                         iChild, sc ));
            scResult = sc;
        }
    }

    if (FAILED(scResult))
        _DBErrorObj.PostHResult(scResult, IID_IRowsetAsynch);

    return( scResult );
}

//
//  IDbAsynchStatus methods
//

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::Abort, public
//
//  Synopsis:   Cancels an asynchronously executing operation.
//
//  Arguments:  [hChapter]    -- chapter which should restart
//              [ulOperation] -- operation for which status is being requested
//
//  Returns:    SCODE error code
//
//  History:    03 Sep 1998    VikasMan    Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CScrollableSorted::Abort(
    HCHAPTER            hChapter,
    ULONG               ulOperation ) 
{
    _DBErrorObj.ClearErrorInfo();

    SCODE scResult = S_OK;
    XInterface<IDBAsynchStatus> xIDBAsynchStatus;

    for ( unsigned iChild = 0; iChild < _rowset._cChild; iChild++ )
    {
        SCODE sc = Get(iChild)->QueryInterface( IID_IDBAsynchStatus,
                                                xIDBAsynchStatus.GetQIPointer() );

        if ( SUCCEEDED( sc ) )
        {
            sc = xIDBAsynchStatus->Abort( hChapter, ulOperation );
            if ( S_OK == scResult )
            {
                scResult = sc;
            }
        }
        xIDBAsynchStatus.Free();
    }

    return scResult;
}

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::GetStatus, public
//
//  Synopsis:   Returns the status of an asynchronously executing operation.
//
//  Arguments:  [hChapter]    -- chapter which should restart
//              [ulOperation] -- operation for which status is being requested
//
//  Returns:    SCODE error code        
//
//  History:    03 Sep 1998    VikasMan    Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CScrollableSorted::GetStatus(
    HCHAPTER          hChapter,
    DBASYNCHOP             ulOperation,
    DBCOUNTITEM *           pulProgress,
    DBCOUNTITEM *           pulProgressMax,
    DBASYNCHPHASE *           pulAsynchPhase,
    LPOLESTR *        ppwszStatusText ) 
{
    _DBErrorObj.ClearErrorInfo();

    SCODE scResult = S_OK;
    XInterface<IDBAsynchStatus> xIDBAsynchStatus;

    if ( pulProgress )
        *pulProgress = 0;
    if ( pulProgressMax )
        *pulProgressMax = 0;
    if ( pulAsynchPhase )
        *pulAsynchPhase = DBASYNCHPHASE_COMPLETE;
    if ( ppwszStatusText )
        *ppwszStatusText = 0;

    XCoMem<OLECHAR> xStatusText;

    double dRatio = 0;

    for ( unsigned iChild = 0; iChild < _rowset._cChild; iChild++ )
    {
        SCODE sc = Get(iChild)->QueryInterface( IID_IDBAsynchStatus,
                                                xIDBAsynchStatus.GetQIPointer() );

        if ( SUCCEEDED( sc ) )
        {
            DBCOUNTITEM ulProgress, ulProgressMax; 
            DBASYNCHPHASE ulAsynchPhase;

            scResult = xIDBAsynchStatus->GetStatus ( hChapter, 
                                                     ulOperation,
                                                     &ulProgress,
                                                     &ulProgressMax,
                                                     &ulAsynchPhase,
                                                     0 == iChild ?
                                                     ppwszStatusText : 0 );
            if ( S_OK != scResult )
            {
                return scResult;
            }

            if ( 0 == iChild && ppwszStatusText )
            {
                xStatusText.Set( *ppwszStatusText );
            }

            if ( ulProgressMax )
            {
                dRatio += ( (double)ulProgress / (double)ulProgressMax );
            }

            if ( pulAsynchPhase && *pulAsynchPhase != DBASYNCHPHASE_POPULATION )
                *pulAsynchPhase = ulAsynchPhase;

        }
        xIDBAsynchStatus.Free();
    }

    DWORD dwNum = 0;
    DWORD dwDen = 0;

    if ( dRatio )
    {
        Win4Assert( _rowset._cChild );

        dRatio /= _rowset._cChild;
        dwDen = 1;

        while ( dRatio < 1.0 )
        {
            dRatio *= 10;
            dwDen *= 10;
        }
        dwNum = (DWORD)dRatio;
    }

    if ( pulProgress )
        *pulProgress = dwNum;

    if ( pulProgressMax )
        *pulProgressMax = dwDen;


    if ( SUCCEEDED( scResult ) )
    {
        // Let memory pass thru to the client
        xStatusText.Acquire();
    }

    return scResult;
}

//
// IRowsetWatchRegion methods
//

//+---------------------------------------------------------------------------
//
//  Member:     CScrollableSorted::Refresh, public
//
//  Synopsis:   Implementation of IRowsetWatchRegion::Refresh. Calls refresh on
//              all the child rowsets
//
//  Arguments:  pChangesObtained
//              prgChanges
//
//  Returns:    Always returns DB_S_TOOMAYCHANGES
//
//  History:    03 Sep 1998    VikasMan    Created
//
//----------------------------------------------------------------------------
STDMETHODIMP CScrollableSorted::Refresh(
        DBCOUNTITEM* pChangesObtained,
        DBROWWATCHCHANGE** prgChanges )
{
    _DBErrorObj.ClearErrorInfo();

    for ( unsigned iChild = 0; iChild < _rowset._cChild; iChild++ )
    {
        if ( _rowset._xArrChildRowsetWatchRegion[iChild].GetPointer() )
        {
            _rowset._xArrChildRowsetWatchRegion[iChild]->Refresh( pChangesObtained, prgChanges );
        }
    }

    *pChangesObtained = 0;

    return DB_S_TOOMANYCHANGES;
}