//+-------------------------------------------------------------------------
//
//  Copyright (C) 1994-1997, Microsoft Corporation.
//
//  File:       rowseek.cxx
//
//  Contents:   Classes which encapsulate a positioning operation
//              for a table.
//
//  Classes:    CRowSeekDescription
//              CRowSeekNext
//              CRowSeekAt
//              CRowSeekAtRatio
//              CRowSeekByBookmark
//
//  History:    06 Apr 1995     AlanW   Created
//
//--------------------------------------------------------------------------

#include <pch.cxx>
#pragma hdrstop

#include <query.hxx>
#include <sizeser.hxx>

#include "tabledbg.hxx"
#include "rowseek.hxx"

//+---------------------------------------------------------------------------
//
//  Member:     CRowSeekDescription::MarshalledSize, public
//
//  Synopsis:   Return Serialized size of a CRowSeekDescription structure
//
//  Arguments:  - none -
//
//  Returns:    unsigned - size in bytes of serialized structure.
//
//  Notes:      The returned size should be the maximum of the serialized
//              size on input and output.  None of the seek descriptions
//              grow on output, so the input size may be larger than the
//              output size.
//
//  History:    02 May 1995     AlanW   Created
//
//----------------------------------------------------------------------------

unsigned
CRowSeekDescription::MarshalledSize(
) const {
    //
    //  Determine the size of the serialized seek description
    //
    CSizeSerStream stmSize;
    Marshall(stmSize);

    return stmSize.Size();
}

//+---------------------------------------------------------------------------
//
//  Member:     CRowSeekDescription::MarshallBase, public
//
//  Synopsis:   Serialize the base CRowSeekDescription structure
//
//  Arguments:  [stm]  -- stream structure is serialized into
//              [eType] -- type descriminator for derived class
//
//  Returns:    nothing
//
//  History:    29 Jan 1995     AlanW   Created
//
//----------------------------------------------------------------------------

void
CRowSeekDescription::MarshallBase(
    PSerStream & stm,
    DWORD eType
) const {
    stm.PutULong(eType);
    stm.PutULong(GetChapter());
}

//+---------------------------------------------------------------------------
//
//  Member:     CRowSeekNext::Marshall, public
//
//  Synopsis:   Serialize a CRowSeekNext structure
//
//  Arguments:  [stm]  -- stream structure is serialized into
//
//  Returns:    nothing
//
//  History:    29 Jan 1995     AlanW   Created
//
//----------------------------------------------------------------------------

void
CRowSeekNext::Marshall(
    PSerStream & stm
) const {
    CRowSeekDescription::MarshallBase( stm, eRowSeekCurrent );
    stm.PutULong(GetSkip());
}


//+---------------------------------------------------------------------------
//
//  Member:     CRowSeekAt::Marshall, public
//
//  Synopsis:   Serialize a CRowSeekAt structure
//
//  Arguments:  [stm]  -- stream structure is serialized into
//
//  Returns:    nothing
//
//  History:    29 Jan 1995     AlanW   Created
//
//----------------------------------------------------------------------------

void
CRowSeekAt::Marshall(
    PSerStream & stm
) const {
    CRowSeekDescription::MarshallBase( stm, eRowSeekAt );

    stm.PutULong(Bmk());
    stm.PutLong(Offset());
    stm.PutULong(ULONG(_hRegion));
}


//+---------------------------------------------------------------------------
//
//  Member:     CRowSeekAtRatio::Marshall, public
//
//  Synopsis:   Serialize a CRowSeekAtRatio structure
//
//  Arguments:  [stm]  -- stream structure is serialized into
//
//  Returns:    nothing
//
//  History:    29 Jan 1995     AlanW   Created
//
//----------------------------------------------------------------------------

void CRowSeekAtRatio::Marshall( PSerStream & stm) const
{
    CRowSeekDescription::MarshallBase( stm, eRowSeekAtRatio );
    stm.PutULong(RatioNumerator());
    stm.PutULong(RatioDenominator());
    stm.PutULong(ULONG(_hRegion));
}


//+---------------------------------------------------------------------------
//
//  Member:     CRowSeekByBookmark::Marshall, public
//
//  Synopsis:   Serialize a CRowSeekByBookmark structure
//
//  Arguments:  [stm]  -- stream structure is serialized into
//
//  Returns:    nothing
//
//  Notes:      When serializing ByBookmarks, we only do bookmarks
//              or statuses, not both.  Only one must exist in the structure.
//
//  History:    29 Jan 1995     AlanW   Created
//
//----------------------------------------------------------------------------

void CRowSeekByBookmark::Marshall(PSerStream & stm) const
{
    Win4Assert(_cBookmarks == 0 || _cValidRet == 0);

    CRowSeekDescription::MarshallBase( stm, eRowSeekByBookmark );

    stm.PutULong(_cBookmarks);
    for (unsigned i = 0; i < _cBookmarks; i++)
        stm.PutULong(_aBookmarks[i]);

    stm.PutULong(_cValidRet);
    for (i = 0; i < _cValidRet; i++)
        stm.PutULong(_ascRet[i]);
}


//+---------------------------------------------------------------------------
//
//  Member:     CRowSeekNext::CRowSeekNext, public
//
//  Synopsis:   DeSerialize a CRowSeekNext structure
//
//  Arguments:  [stm]  -- input stream structure is read from
//              [iVersion]  -- input stream version
//
//  Returns:    nothing
//
//  History:    06 Apr 1995     AlanW   Created
//
//----------------------------------------------------------------------------

CRowSeekNext::CRowSeekNext( PDeSerStream & stm, int iVersion ) :
    CRowSeekDescription( eRowSeekCurrent, 0 )
{
    SetChapter( stm.GetULong() );
    SetSkip( stm.GetULong() );
}

//+---------------------------------------------------------------------------
//
//  Member:     CRowSeekAt::CRowSeekAt, public
//
//  Synopsis:   DeSerialize a CRowSeekAt structure
//
//  Arguments:  [stm]  -- input stream structure is read from
//
//  Returns:    nothing
//
//  History:    06 Apr 1995     AlanW   Created
//
//----------------------------------------------------------------------------

CRowSeekAt::CRowSeekAt( PDeSerStream & stm, int iVersion ) :
    CRowSeekDescription( eRowSeekAt, 0 )
{
    SetChapter( stm.GetULong() );
    _bmkOffset = stm.GetULong();
    _cRowsOffset = stm.GetLong();
    _hRegion = (HWATCHREGION) stm.GetULong();
}

//+---------------------------------------------------------------------------
//
//  Member:     CRowSeekAtRatio::CRowSeekAtRatio, public
//
//  Synopsis:   DeSerialize a CRowSeekAtRatio structure
//
//  Arguments:  [stm]  -- input stream structure is read from
//
//  Returns:    nothing
//
//  History:    06 Apr 1995     AlanW   Created
//
//----------------------------------------------------------------------------

CRowSeekAtRatio::CRowSeekAtRatio( PDeSerStream & stm, int iVersion ) :
    CRowSeekDescription( eRowSeekAtRatio, 0 )
{
    SetChapter( stm.GetULong() );
    _ulNumerator = stm.GetULong();
    _ulDenominator = stm.GetULong();
    _hRegion = (HWATCHREGION) stm.GetULong();
}

//+---------------------------------------------------------------------------
//
//  Member:     CRowSeekByBookmark::CRowSeekByBookmark, public
//
//  Synopsis:   DeSerialize a CRowSeekByBookmark structure
//
//  Arguments:  [stm]  -- input stream structure is read from
//
//  Returns:    nothing
//
//  History:    06 Apr 1995     AlanW   Created
//
//----------------------------------------------------------------------------

CRowSeekByBookmark::CRowSeekByBookmark( PDeSerStream & stm, int iVersion ) :
    CRowSeekDescription( eRowSeekByBookmark, 0 ),
    _aBookmarks( 0 ),
    _ascRet( 0 )
{
    SetChapter( stm.GetULong() );

    _cBookmarks = stm.GetULong();
    if (_cBookmarks)
    {
        // Protect agains unreasonable requests, which probably are attacks

        if ( _cBookmarks >= 65536 )
            THROW( CException( E_INVALIDARG ) );

        _aBookmarks = new CI_TBL_BMK [ _cBookmarks ];

        for (unsigned i = 0; i < _cBookmarks; i++)
            _aBookmarks[i] = stm.GetULong();
    }

    _maxRet = _cValidRet = stm.GetULong();

    if (_cValidRet)
    {
        // Protect against unreasonable requests, which probably are attacks

        if ( _cValidRet >= 65536 )
            THROW( CException( E_INVALIDARG ) );

        _ascRet = new SCODE [ _cValidRet ];

        for (unsigned i = 0; i < _cValidRet; i++)
            _ascRet[i] = stm.GetULong();
    }

    //
    // We don't expect both bookmarks and statuses.
    //
    Win4Assert(_cBookmarks == 0 || _cValidRet == 0);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRowSeekByBookmark::~CRowSeekByBookmark, public
//
//  Synopsis:   Destroy a CRowSeekByBookmark structure
//
//  Returns:    nothing
//
//  History:    29 Jan 1995     AlanW   Created
//
//----------------------------------------------------------------------------

CRowSeekByBookmark::~CRowSeekByBookmark( )
{
    delete _aBookmarks;
    delete _ascRet;
}


//+-------------------------------------------------------------------------
//
//  Member:     CRowSeekNext::GetRows, public
//
//  Synopsis:   Retrieve row data for a table cursor
//
//  Arguments:  [rCursor] - the cursor to fetch data for
//              [rTable] - the table from which data is fetched
//              [rFetchParams] - row fetch parameters and buffer pointers
//              [pSeekDescOut] - pointer to seek description for restart
//
//  Returns:    SCODE - the status of the operation.
//
//  Notes:
//
//  History:    07 Apr 1995     Alanw   Created
//
//--------------------------------------------------------------------------

SCODE
CRowSeekNext::GetRows(
    CTableCursor& rCursor,
    CTableSource& rTable,
    CGetRowsParams& rFetchParams,
    XPtr<CRowSeekDescription>& pSeekDescOut) const
{
    LONG cRowsToSkip = GetSkip();

    if (cRowsToSkip)
    {
        tbDebugOut(( DEB_IWARN, "CRowSeekNext::GetRows - non-zero skip count %d\n",
                            cRowsToSkip ));
    }

    WORKID widStart;
    if ( rTable.IsFirstGetNextRows() )
    {
        //
        // For the first GetNextRows call, the start position is
        // beginning of table if cRowsToSkip is positive, and end
        // of table if its negative. For subsequent calls, the
        // current position is the start position.
        //

        if ( cRowsToSkip >= 0 )
            widStart = WORKID_TBLBEFOREFIRST;
        else
            widStart = WORKID_TBLAFTERLAST;
    }
    else
        widStart = rTable.GetCurrentPosition( GetChapter() );

    WORKID widEnd = widStart;

    //
    // OffsetSameDirFetch implements the skip of one row that
    // Oledb::GetNextRows requires on the first fetch
    // when scrolling and fetching are in the same direction,
    // and for subsequent fetches when the fetch is in the
    // same direction as previous fetch. When the direction is
    // reversed the first wid fetched is same as the last wid
    // returned from the previous call, and offsetSameDirFetch
    // is 0 in this case.
    //
    LONG offsetSameDirFetch = 0;
    if ( rTable.IsFirstGetNextRows() )
    {
        if ( cRowsToSkip >= 0 && rFetchParams.GetFwdFetch() )
            offsetSameDirFetch = 1;
        else if ( cRowsToSkip < 0 && !rFetchParams.GetFwdFetch() )
            offsetSameDirFetch = -1;
    }
    else
    {
        if ( rFetchParams.GetFwdFetch() == rTable.GetFwdFetchPrev() )
        {
            if ( rFetchParams.GetFwdFetch() )
                offsetSameDirFetch = 1;
            else
                offsetSameDirFetch = -1;
        }
    }

    SCODE scRet = rTable.GetRowsAt( 0,  // no watch region
                                    widStart,
                                    GetChapter(),
                                    cRowsToSkip + offsetSameDirFetch,
                                    rCursor.GetBindings(),
                                    rFetchParams,
                                    widEnd );

    //
    //  Don't attempt to save the widEnd if the positioning
    //  operation got us past the end of the table.  Storing
    //  widEnd in rCursor will cause us to be stuck at the
    //  end, with no way to get any more rows.  In this situation,
    //  we don't expect to have successfully transferred any rows.
    //
    // NOTE: don't throw, the error may need to be seen by caller
    //      in CRowset::_FetchRows
    //
    if ( WORKID_TBLAFTERLAST == widEnd ||
         WORKID_TBLBEFOREFIRST == widEnd ||
         ( scRet == DB_E_BADSTARTPOSITION && cRowsToSkip == 0 ) )
    {
        Win4Assert(rFetchParams.RowsTransferred() == 0);
        return DB_S_ENDOFROWSET;
    }

    if (SUCCEEDED(scRet))
    {
        rTable.SetCurrentPosition( GetChapter(), widEnd );
        rTable.SetFwdFetchPrev( rFetchParams.GetFwdFetch() );
        rTable.ResetFirstGetNextRows();
    }
    else
    {
        tbDebugOut(( DEB_WARN, "CRowSeekNext::GetRows failed, sc=%x\n",
                               scRet ));
    }


    if (DB_S_BLOCKLIMITEDROWS == scRet)
    {
        Win4Assert(rFetchParams.RowsTransferred() > 0);
        pSeekDescOut.Set(new CRowSeekNext(GetChapter(), 0) );
    }

    return scRet;
}


//+-------------------------------------------------------------------------
//
//  Member:     CRowSeekAt::GetRows, public
//
//  Synopsis:   Retrieve row data for a table cursor
//
//  Arguments:  [rCursor] - the cursor to fetch data for
//              [rTable] - the table from which data is fetched
//              [rFetchParams] - row fetch parameters and buffer pointers
//              [pSeekDescOut] - pointer to seek description for restart
//
//  Returns:    SCODE - the status of the operation.
//
//  Notes:
//
//  History:    07 Apr 1995     Alanw   Created
//
//--------------------------------------------------------------------------

SCODE
CRowSeekAt::GetRows(
    CTableCursor& rCursor,
    CTableSource& rTable,
    CGetRowsParams& rFetchParams,
    XPtr<CRowSeekDescription>& pSeekDescOut) const
{
    WORKID widStart = Bmk();
    LONG iRowOffset = Offset();

    SCODE scRet = rTable.GetRowsAt( _hRegion,
                                    widStart,
                                    GetChapter(),
                                    iRowOffset,
                                    rCursor.GetBindings(),
                                    rFetchParams,
                                    widStart );

    // The first fetch took care of the hRegion manipulation
    // set the new seek descriptor's hRegion to 0
    if (DB_S_BLOCKLIMITEDROWS == scRet)
    {
        Win4Assert(rFetchParams.RowsTransferred() > 0);
        pSeekDescOut.Set(new CRowSeekAt(0,
                                        GetChapter(),
                                        rFetchParams.GetFwdFetch() ? 1 : -1,
                                        widStart) );
    }

    return scRet;
}


//+-------------------------------------------------------------------------
//
//  Member:     CRowSeekAtRatio::GetRows, public
//
//  Synopsis:   Retrieve row data for a table cursor
//
//  Arguments:  [rCursor] - the cursor to fetch data for
//              [rTable] - the table from which data is fetched
//              [rFetchParams] - row fetch parameters and buffer pointers
//              [pSeekDescOut] - pointer to seek description for restart
//
//  Returns:    SCODE - the status of the operation.
//
//  Notes:
//
//  History:    07 Apr 1995     Alanw   Created
//
//--------------------------------------------------------------------------

SCODE
CRowSeekAtRatio::GetRows(
    CTableCursor& rCursor,
    CTableSource& rTable,
    CGetRowsParams& rFetchParams,
    XPtr<CRowSeekDescription>& pSeekDescOut) const
{
    WORKID widRestart = widInvalid;
    SCODE scRet = rTable.GetRowsAtRatio( _hRegion,
                                         RatioNumerator(),
                                         RatioDenominator(),
                                         GetChapter(),
                                         rCursor.GetBindings(),
                                         rFetchParams,
                                         widRestart );

    // The first fetch took care of the hRegion manipulation
    // set the new seek descriptor's hRegion to 0
    if (DB_S_BLOCKLIMITEDROWS == scRet)
    {
        Win4Assert(rFetchParams.RowsTransferred() > 0);
        pSeekDescOut.Set(new CRowSeekAt(0, GetChapter(), 1, widRestart) );
    }

    return scRet;
}


//+-------------------------------------------------------------------------
//
//  Member:     CRowSeekByBookmark::GetRows, public
//
//  Synopsis:   Retrieve row data for a table cursor
//
//  Arguments:  [rCursor] - the cursor to fetch data for
//              [rTable] - the table from which data is fetched
//              [rFetchParams] - row fetch parameters and buffer pointers
//              [pSeekDescOut] - pointer to seek description for restart
//
//  Returns:    SCODE - the status of the operation.
//
//
//  History:    07 Apr 1995     Alanw   Created
//
//--------------------------------------------------------------------------

SCODE
CRowSeekByBookmark::GetRows(
    CTableCursor& rCursor,
    CTableSource& rTable,
    CGetRowsParams& rFetchParams,
    XPtr<CRowSeekDescription>& pSeekDescOut) const
{
    unsigned cFailed = 0;
    ULONG cSavedRowsReq = rFetchParams.RowsToTransfer();

    Win4Assert(_cBookmarks > 0);
    Win4Assert(cSavedRowsReq <= _cBookmarks);
    Win4Assert(0 == rFetchParams.RowsTransferred() && cSavedRowsReq > 0);

    rFetchParams.SetRowsRequested(0);

    SCODE scRet = S_OK;

    TRY
    {
        XPtr<CRowSeekByBookmark> pSeekOut(
                    new CRowSeekByBookmark(GetChapter(), _cBookmarks) );

        BOOL fFailed = FALSE;
        // Iterate over bookmarks, calling rTable.GetRowsAt for each
        for (unsigned i = 0; i < cSavedRowsReq; i++)
        {
            if (! fFailed)
                rFetchParams.IncrementRowsRequested( );

            WORKID widNext = _aBookmarks[i];
            if (widNext == widInvalid)
            {
                scRet = DB_E_BADBOOKMARK;
            }
            else
            {
                TRY
                {
                    scRet = rTable.GetRowsAt( 0,   // no watch region
                                              widNext,
                                              GetChapter(),
                                              0,
                                              rCursor.GetBindings(),
                                              rFetchParams,
                                              widNext );
                }
                CATCH( CException, e )
                {
                    scRet = e.GetErrorCode();
                    Win4Assert( scRet != STATUS_ACCESS_VIOLATION &&
                                scRet != STATUS_NO_MEMORY );
                }
                END_CATCH;

                if (! SUCCEEDED(scRet) && scRet != STATUS_BUFFER_TOO_SMALL )
                    scRet = DB_E_BOOKMARKSKIPPED;
            }

            if (scRet == DB_S_ENDOFROWSET)
                scRet = S_OK;

            if ( STATUS_BUFFER_TOO_SMALL == scRet ||
                 DB_S_BLOCKLIMITEDROWS == scRet )
            {
                scRet = DB_S_BLOCKLIMITEDROWS;
                break;
            }

            //
            // set per-row error status
            //
            pSeekOut->_SetStatus(i, scRet);
            if (FAILED(scRet))
            {
                cFailed++;
                fFailed = TRUE;
            }
            else
                fFailed = FALSE;
        }
        pSeekDescOut.Set( pSeekOut.Acquire() );
    }
    CATCH( CException, e )
    {
        scRet = e.GetErrorCode();
    }
    END_CATCH;

    if (cFailed)
        return DB_S_ERRORSOCCURRED;
    else if (0 == rFetchParams.RowsTransferred())
        return STATUS_BUFFER_TOO_SMALL;
    else
        return S_OK;
}


//+-------------------------------------------------------------------------
//
//  Member:     CRowSeekByBookmark::_SetStatus, private
//
//  Synopsis:   Set row status for a bookmark lookup
//
//  Arguments:  [iBmk] - index of bookmark to set status for
//              [scRet] - the status to be saved
//
//  Returns:    Nothing
//
//  History:    12 Apr 1995     Alanw   Created
//
//--------------------------------------------------------------------------

void
CRowSeekByBookmark::_SetStatus(
    unsigned    iBmk,
    SCODE       scRet
) {
    Win4Assert( iBmk < _maxRet );

    if (_ascRet == 0)
        _ascRet = new SCODE[_maxRet];

    _ascRet[iBmk] = scRet;
    if (iBmk >= _cValidRet)
    {
        Win4Assert( iBmk == _cValidRet );
        _cValidRet = iBmk + 1;
    }
}

//+-------------------------------------------------------------------------
//
//  Member:     CRowSeekDescription::MergeResults, public
//
//  Synopsis:   Update seek description state after a transfer
//
//  Arguments:  [pRowSeek] - row seek description after transfer
//
//  Returns:    Nothing
//
//  Notes:      Used only in user mode.  Does nothing for CRowSeekNext,
//              CRowSeekAt and CRowSeekAtRatio.
//
//  History:    02 May 1995     Alanw   Created
//
//--------------------------------------------------------------------------

void
CRowSeekDescription::MergeResults(
    CRowSeekDescription * pRowSeek )
{
    return;
}

//+-------------------------------------------------------------------------
//
//  Member:     CRowSeekByBookmark::MergeResults, public
//
//  Synopsis:   Update seek description state after a transfer
//
//  Arguments:  [pRowSeek] - row seek description after transfer
//
//  Returns:    Nothing
//
//  Notes:      Used only in user mode.  Transfers statuses into
//              original rowseek and bookmarks from original to
//              result rowseek.
//
//  History:    02 May 1995     Alanw   Created
//
//--------------------------------------------------------------------------

void
CRowSeekByBookmark::MergeResults(
    CRowSeekDescription * pRowSeekDesc)
{
    Win4Assert(pRowSeekDesc->IsByBmkRowSeek());

    CRowSeekByBookmark* pRowSeek = (CRowSeekByBookmark*) pRowSeekDesc;

    Win4Assert(_cBookmarks > 0 &&
               pRowSeek->_cValidRet > 0 && pRowSeek->_cBookmarks == 0);

    //
    // Transfer return statuses to this object from the other
    //
    unsigned iBaseRet = _cValidRet;

    for (unsigned i=0; i < pRowSeek->_cValidRet; i++)
    {
        _SetStatus( i+iBaseRet, pRowSeek->_ascRet[i] );
    }

    delete [] pRowSeek->_ascRet;
    pRowSeek->_ascRet = 0;
    pRowSeek->_cValidRet = pRowSeek->_maxRet = 0;

    //
    //  Transfer bookmarks from this object to the other.
    //
    if (_cBookmarks - _cValidRet > 0)
    {
        pRowSeek->_cBookmarks = _cBookmarks - _cValidRet;
        pRowSeek->_aBookmarks = new CI_TBL_BMK[ pRowSeek->_cBookmarks ];

        for (unsigned i=0; i<pRowSeek->_cBookmarks; i++)
        {
            pRowSeek->_aBookmarks[i] = _aBookmarks[ i+_cValidRet ];
        }
    }
}