IROWSET.CPP
//-------------------------------------------------------------------- 
// Microsoft OLE DB Sample Provider 
// (C) Copyright 1994 - 1996 Microsoft Corporation.  All Rights Reserved. 
// 
// @doc 
// 
// @module IROWSET.CPP | IRowset interface implementation 
// 
// 
 
// Includes ------------------------------------------------------------------ 
 
#include "headers.h" 
 
// Code ---------------------------------------------------------------------- 
 
//  IRowset specific interface methods 
 
// CImpIRowset::GetData -------------------------------------------------- 
// 
// @mfunc Retrieves data from the rowset's cache 
// 
// @rdesc HRESULT 
//      @flag S_OK                   | Method Succeeded 
//      @flag DB_S_ERRORSOCCURED     | Could not coerce a column value 
//      @flag DB_E_BADACCESSORHANDLE | Invalid Accessor given 
//      @flag DB_E_BADROWHANDLE      | Invalid row handle given 
//      @flag E_INVALIDARG           | pData was NULL 
//      @flag OTHER                  | Other HRESULTs returned by called functions 
// 
STDMETHODIMP CImpIRowset::GetData 
    ( 
    HROW        hRow,       //@parm IN | Row Handle 
    HACCESSOR   hAccessor,  //@parm IN | Accessor to use 
    void       *pData       //@parm OUT | Pointer to buffer where data should go. 
    ) 
{ 
    PACCESSOR   pAccessor; 
    ULONG       icol, ibind; 
    ROWBUFF     *pRowBuff; 
    COLUMNDATA  *pColumnData; 
    DBBINDING   *pBinding; 
    ULONG       cBindings; 
    ULONG       ulErrorCount; 
    DBTYPE      dwSrcType; 
    DBTYPE      dwDstType; 
    void        *pSrc; 
    void        *pDst; 
    ULONG       ulSrcLength; 
    ULONG       *pulDstLength; 
    ULONG       ulDstMaxLength; 
    DWORD       dwSrcStatus; 
    DWORD       *pdwDstStatus; 
    DWORD       dwPart; 
    HRESULT     hr; 
 
    // Coerce data for row 'hRow', according to hAccessor. 
    // Put in location 'pData'.  Offsets and types are in hAccessor's bindings. 
    // 
    // Return S_OK if all lossless conversions, 
    // return DB_S_ERRORSOCCURED if lossy conversion (truncation, rounding, etc.) 
    // Return E_FAIL, etc., if horrible errors. 
 
    // GetItemOfExtBuffer is basically operator[]. 
    // It takes an index (or handle) (referenced from 1...n), 
    // and a ptr for where to write the data. 
    // 
    // It holds ptrs to a variable-length ACCESSOR struct. 
    // So we get the ACCESSOR ptr for the client's accessor handle. 
 
    assert( m_pObj->m_pextbufferAccessor ); 
    hr = m_pObj->m_pextbufferAccessor->GetItemOfExtBuffer((ULONG) hAccessor, &pAccessor ); 
    if (FAILED( hr ) || pAccessor == NULL) 
        return ResultFromScode( DB_E_BADACCESSORHANDLE ); 
 
    assert( pAccessor ); 
    cBindings = pAccessor->cBindings; 
    pBinding  = pAccessor->rgBindings; 
 
    // IsSlotSet returns S_OK    if row is marked. 
    //                   S_FALSE if row is not marked. 
    // The "mark" means that there is data present in the row. 
    // Rows are [1...n], slot marks are [0...n-1]. 
    if (m_pObj->m_prowbitsIBuffer->IsSlotSet((ULONG) hRow ) != S_OK) 
        return ResultFromScode( DB_E_BADROWHANDLE ); 
 
    // Ensure a place to put data. 
    if (pData == NULL) 
        return ResultFromScode( E_INVALIDARG ); 
 
    pRowBuff = m_pObj->GetRowBuff((ULONG) hRow ); 
 
    // Internal error for a 0 reference count on this row, 
    // since we depend on the slot-set stuff. 
    assert( pRowBuff->ulRefCount ); 
 
 
    ulErrorCount = 0; 
    for (ibind = 0; ibind < cBindings; ibind++) 
        { 
        icol = pBinding[ibind].iOrdinal; 
        pColumnData = (COLUMNDATA *) ((BYTE *) pRowBuff + m_pObj->m_rgdwDataOffsets[icol]); 
 
        dwSrcType      = m_pObj->m_rgdbcolinfo[icol].wType; 
        pSrc           = &(pColumnData->bData); 
        ulSrcLength    = pColumnData->dwLength; 
        dwSrcStatus    = pColumnData->dwStatus; 
        ulDstMaxLength = pBinding[ibind].cbMaxLen; 
        dwDstType      = pBinding[ibind].wType; 
        dwPart         = pBinding[ibind].dwPart; 
 
        pDst           = dwPart & DBPART_VALUE ? ((BYTE*) pData + pBinding[ibind].obValue) : NULL; 
        pulDstLength   = dwPart & DBPART_LENGTH ? (ULONG *) ((BYTE*) pData + pBinding[ibind].obLength) : NULL; 
        pdwDstStatus   = dwPart & DBPART_STATUS ? (ULONG *) ((BYTE*) pData + pBinding[ibind].obStatus) : NULL; 
 
        hr = g_pIDataConvert->DataConvert( 
                dwSrcType, 
                dwDstType, 
                ulSrcLength, 
                pulDstLength, 
                pSrc, 
                pDst, 
                ulDstMaxLength, 
                dwSrcStatus, 
                pdwDstStatus, 
                0,// bPrecision for conversion to DBNUMERIC 
0,// bScale for conversion to DBNUMERIC 
DBDATACONVERT_DEFAULT); 
        if (FAILED( hr )) 
            return hr;  // fatal error 
        if (hr != ResultFromScode( S_OK )) 
            ulErrorCount++; // can't coerce 
        } 
 
    // We report any lossy conversions with a special status. 
    // Note that DB_S_ERRORSOCCURED is a success, rather than failure. 
    return ResultFromScode( ulErrorCount ? DB_S_ERRORSOCCURRED : S_OK ); 
} 
 
 
 
// CImpIRowset::GetNextRows -------------------------------------------------- 
// 
// @mfunc Fetches rows in a sequential style, remembering the previous position 
// 
// @rdesc HRESULT 
//      @flag S_OK                      | Method Succeeded 
//      @flag DB_S_ENDOFROWSET          | Reached end of rowset 
//      @flag DB_E_CANTFETCHBACKWARDS   | cRows was negative and we can't fetch backwards 
//      @flag DB_E_ROWSNOTRELEASED      | Must release all HROWs before calling GetNextRows 
//      @flag E_FAIL                    | Provider-specific error 
//      @flag E_INVALIDARG              | pcRowsObtained or prghRows was NULL 
//      @flag E_OUTOFMEMORY             | Out of Memory 
//      @flag OTHER                     | Other HRESULTs returned by called functions 
// 
STDMETHODIMP CImpIRowset::GetNextRows 
    ( 
    HCHAPTER   hReserved,       //@parm IN | Reserved for future use. Ingored. 
    LONG       cRowsToSkip,     //@parm IN | Rows to skip before reading 
    LONG       cRows,           //@parm IN | Number of rows to fetch 
    ULONG      *pcRowsObtained, //@parm OUT | Number of rows obtained 
    HROW       **prghRows       //@parm OUT | Array of Hrows obtained 
    ) 
{ 
    ULONG    cRowsTmp; 
    BOOL     fExtraRow; 
    ULONG    cSlotAlloc =0; 
    ULONG    di; 
    LONG     irow, ih; 
    ULONG    irowFirst, irowLast; 
    PROWBUFF prowbuff; 
    HRESULT  hr; 
    BYTE     *pbRowFirst; 
 
 
    // Check validity of arguments. 
    if (pcRowsObtained == NULL || prghRows == NULL) 
        return ResultFromScode( E_INVALIDARG ); 
 
    // init out-params 
    *pcRowsObtained = 0; 
 
    // No-op case always succeeds. 
    if (cRows == 0 && cRowsToSkip == 0) 
        return ResultFromScode( S_OK ); 
 
    // This implementation doesn't support scrolling backward. 
    if (cRows < 0 || cRowsToSkip < 0) 
        return ResultFromScode( DB_E_CANTFETCHBACKWARDS ); 
 
    // Are there any unreleased rows? 
    if ((m_pObj->m_prowbitsIBuffer)->ArrayEmpty() != S_OK) 
        return ResultFromScode( DB_E_ROWSNOTRELEASED ); 
    assert( m_pObj->m_ulRowRefCount == 0 ); // should be true since array was empty 
 
    // Is the cursor fully materialized (end-of-cursor condition)? 
    if (m_pObj->m_dwStatus & STAT_ENDOFCURSOR) 
        return ResultFromScode( DB_S_ENDOFROWSET ); 
 
    assert( m_pObj->m_rgbRowData ); 
    if (FAILED( m_pObj->Rebind((BYTE *) m_pObj->GetRowBuff( m_pObj->m_irowMin )))) 
        return ResultFromScode( E_FAIL ); 
 
    // Sometimes we need an extra row to fetch data and copy it to appropriate place. 
    fExtraRow = (cRows > 1); 
    cRowsTmp = cRows + (fExtraRow ? 1 : 0); 
 
 
    if (FAILED( hr = GetNextSlots( m_pObj->m_pIBuffer, cRowsTmp, &irowFirst ))) 
        return hr; 
    cSlotAlloc = cRowsTmp; 
 
    // 
    // Fetch Data 
    // 
    if (cRowsToSkip) 
        { 
        // Calculate the new position 
        m_pObj->m_irowFilePos += cRowsToSkip; 
 
        // Check if skip causes END_OF_ROWSET 
        if (m_pObj->m_irowFilePos > m_pObj->m_pFileio->GetRowCnt() || 
            m_pObj->m_irowFilePos <= 0) 
            { 
            m_pObj->m_dwStatus |= STAT_ENDOFCURSOR; 
            return ResultFromScode( DB_S_ENDOFROWSET ); 
            } 
        } 
 
    pbRowFirst = (BYTE *) m_pObj->GetRowBuff( irowFirst ); 
    for (irow =1; irow <= cRows; irow++) 
        { 
        // Get the Data from the File into the row buffer 
        if (S_FALSE == (hr =  m_pObj->m_pFileio->Fetch( m_pObj->m_irowFilePos + irow ))) 
            { 
            m_pObj->m_dwStatus |= STAT_ENDOFCURSOR; 
            break; 
            } 
        else 
            { 
            if (FAILED( hr )) 
                return ResultFromScode( E_FAIL ); 
            } 
 
        // Got a row, so copy it from bound row to the destination. 
        // Very first row is Fetch buffer, we give out rows [2...cRows+1]. 
        if (fExtraRow) 
            memcpy( pbRowFirst + (m_pObj->m_cbRowSize * irow), pbRowFirst, m_pObj->m_cbRowSize ); 
        } 
 
    cRowsTmp = irow - 1; //Irow will be +1 because of For Loop 
    m_pObj->m_irowLastFilePos = m_pObj->m_irowFilePos; 
    m_pObj->m_irowFilePos += cRowsTmp; 
 
 
    // 
    // Through fetching many rows of data 
    // 
    // Allocate row handles for client. 
    // Note that we need to use IMalloc for this. 
    // 
    // Should only malloc cRowsTmp, instead of cRows. 
    // 
    // Modified to use IMalloc. 
    // Should malloc cRows, since client will assume it's that big. 
    // 
 
    *pcRowsObtained = cRowsTmp; 
    if (*prghRows == NULL) 
        { 
        *prghRows = (HROW *) g_pIMalloc->Alloc( cRows*sizeof( HROW )); 
        } 
 
    if (NULL == *prghRows) 
        return ResultFromScode( E_OUTOFMEMORY ); 
 
    // 
    // Fill in the status information: Length, IsNull 
    // May be able to wait until first call to GetData, 
    // but have to do it sometime. 
    // 
    // Suggest keeping an array of structs of accessor info. 
    // One element is whether accessor requires any status info or length info. 
    // Then we could skip this whole section. 
    // 
    // Added check for cRowsTmp to MarkRows call. 
    // Don't want to call if cRowsTmp==0. 
    // (Range passed to MarkRows is inclusive, so can't specify marking 0 rows.) 
    // 
    // Note that SetSlots is a CBitArray member function -- not an IBuffer function. 
    // 
    // Bits are [0...n-1], row handles are [1...n]. 
    // 
    // Cleanup. Row handles, bits, indices are the same [m....(m+n)], where m is some # >0, 
    // 
    // Added row-wise reference counts and cursor-wise reference counts. 
    // 
 
    // Set row handles, fix data length field and compute data status field.// 
    di = fExtraRow ? 1 : 0; 
    m_pObj->m_cRows   = cRowsTmp; 
    irowLast = irowFirst + di + cRowsTmp - 1; 
 
    // Cleanup extra slots where no hRow actually was put.. 
    //  ** Because of less rows than asked for 
    //  ** Because of temporary for for data transfer. 
    if (fExtraRow) 
        if (FAILED( hr = ReleaseSlots( m_pObj->m_pIBuffer, irowFirst, 1 ))) 
            return hr; 
    if (cSlotAlloc > (cRowsTmp + di)) 
        if (FAILED( hr = ReleaseSlots( m_pObj->m_pIBuffer, irowFirst + cRowsTmp + di, (cSlotAlloc - cRowsTmp - di)))) 
            return hr; 
 
    for (irow = (LONG) (irowFirst + di), ih =0; irow <= (LONG) irowLast; irow++, ih++) 
        { 
        // Increment the rows-read count, 
        // then store it as the bookmark in the very first DWORD of the row. 
 
        prowbuff = m_pObj->GetRowBuff( irow ); 
 
        // Insert the bookmark and its row number (from 1...n) into a hash table. 
        // This allows us to quickly determine the presence of a row in mem, given the bookmark. 
        // The bookmark is contained in the row buffer, at the very beginning. 
        // Bookmark is the row number within the entire result set [1...num_rows_read]. 
 
        // This was a new Bookmark, not in memory, 
        // so return to user (in *prghRows) the hRow we stored. 
        prowbuff->ulRefCount++; 
        prowbuff->pbBmk = (BYTE*) m_pObj->m_irowLastFilePos + ih + 1; 
        m_pObj->m_ulRowRefCount++; 
 
        (*prghRows)[ih] = (HROW) (irow); 
        } 
 
    if (m_pObj->m_dwStatus & STAT_ENDOFCURSOR) 
        return ResultFromScode( DB_S_ENDOFROWSET ); 
    else 
        return ResultFromScode( S_OK ); 
} 
 
 
 
// CImpIRowset::ReleaseRows --------------------------------------- 
// 
// @mfunc Releases row handles 
// 
// @rdesc HRESULT 
//      @flag S_OK              | Method Succeeded 
//      @flag E_INVALIDARG      | Invalid parameters were specified 
//      @flag DB_E_BADROWHANDLE | Row handle was invalid 
// 
STDMETHODIMP CImpIRowset::ReleaseRows 
    ( 
    ULONGcRow,           //@parm IN | Number of rows to release 
    const HROWrghRow[],       //@parm IN | Array of handles of rows to be released 
DBROWOPTIONSrgRowOptions[],//@parm IN | Additional Options 
    ULONG*pcRowReleased, //@parm OUT | Count of rows actually released 
    ULONGrgRefCount[]    //@parm OUT | Array of refcnts for the rows 
    ) 
{ 
    ULONG   ihRow; 
    BOOL    fEncounteredError; 
    ROWBUFF *pRowBuff; 
    ULONG   cRowReleased = 0; 
 
 
    // check params 
    if (cRow && !rghRow) 
        return ResultFromScode( E_INVALIDARG ); 
 
    // init out param 
    if (pcRowReleased) 
        *pcRowReleased = 0; 
 
    fEncounteredError = FALSE; 
    ihRow = 0; 
 
    while (ihRow < cRow) 
        { 
        if ((m_pObj->m_prowbitsIBuffer)->IsSlotSet((ULONG) rghRow[ihRow] ) == S_OK) 
            { 
            // Found valid row, so decrement reference counts. 
            // (Internal error for refcount to be 0 here, since slot set.) 
            pRowBuff = m_pObj->GetRowBuff((ULONG) rghRow[ihRow] ); 
            assert( pRowBuff->ulRefCount != 0 ); 
            assert( m_pObj->m_ulRowRefCount != 0 ); 
            --pRowBuff->ulRefCount; 
            --m_pObj->m_ulRowRefCount; 
 
            if (rgRefCount) 
                rgRefCount[ihRow] = pRowBuff->ulRefCount; 
 
            if (pRowBuff->ulRefCount == 0) 
                { 
                ReleaseSlots( m_pObj->m_pIBuffer, (ULONG) rghRow[ihRow], 1 ); 
                cRowReleased++; 
                } 
            ihRow++; 
            } 
        else 
            { 
            // It is an error for client to try to release a row 
            // for which "IsSetSlot" is false.  Client gave us an invalid handle. 
            // Ignore it (we can't release it...) and report error when done. 
            fEncounteredError = TRUE; 
            if (rgRefCount) 
                rgRefCount[ihRow] = 0; 
            // stop looping if we hit an error     
            break;     
            } 
        } 
 
    if (pcRowReleased) 
        *pcRowReleased = cRowReleased; 
 
    if (fEncounteredError) 
        return ResultFromScode( DB_E_BADROWHANDLE ); 
    else 
        return ResultFromScode( S_OK ); 
} 
 
 
 
 
 
 
// CImpIRowset::ResartPosition --------------------------------------- 
// 
// @mfunc Repositions the next fetch position to the start of the rowset 
// 
// - all rows must be released before calling this method 
// - it is not expensive to Restart us, because we are from a single table 
// 
// 
// @rdesc HRESULT 
//      @flag S_OK                  | Method Succeeded 
//      @flag DB_E_ROWSNOTRELEASED  | All HROWs must be released before calling 
// 
STDMETHODIMP CImpIRowset::RestartPosition 
    ( 
    HCHAPTER    hReserved        //@parm IN | Reserved for future use.  Ignored. 
    ) 
{ 
    // make sure all rows have been released 
    if (S_OK != (m_pObj->m_prowbitsIBuffer)->ArrayEmpty()) 
        return DB_E_ROWSNOTRELEASED; 
    assert( m_pObj->m_ulRowRefCount == 0 ); // should be true since array was empty 
 
    // set "next fetch" position to the start of the rowset 
    m_pObj->m_irowFilePos = 0; 
 
    // clear "end of cursor" flag 
    m_pObj->m_dwStatus &= ~STAT_ENDOFCURSOR; 
 
    return ResultFromScode( S_OK ); 
} 
 
 
// CImpIRowset::AddRefRows -------------------------------------------------- 
// 
// @mfunc Adds a reference count to an existing row handle 
// 
// @rdesc HResult 
//      @flag S_OK              | Method Succeeded 
//      @flag E_INVALIDARG      | rghRows was NULL and cRows was not 0 
//      @flag DB_E_BADROWHANDLE | An element of rghRows was invalid 
 
STDMETHODIMP  CImpIRowset::AddRefRows 
    ( 
    ULONG           cRows,          // @parm IN     | Number of rows to refcount 
    const HROW      rghRows[],      // @parm IN     | Array of row handles to refcount 
    ULONG*          pcRefCounted,   // @parm OUT    | Number successfully refcounted 
    ULONG           rgRefCounts[]   // @parm OUT    | Array of refcounts 
    ) 
{ 
    ULONG   ihRow = 0; 
    ROWBUFF *pRowBuff; 
    ULONG   cAddRef; 
 
    // init out param 
    if (pcRefCounted) 
        *pcRefCounted = 0; 
 
    // check params 
    if (cRows && !rghRows) 
        return ResultFromScode( E_INVALIDARG ); 
 
    cAddRef = 0; 
 
    // for each of the HROWs the caller provided... 
    for (ihRow = 0; ihRow < cRows; ihRow++) 
        { 
        // check the row handle 
        if (S_OK != (m_pObj->m_prowbitsIBuffer)->IsSlotSet((ULONG) rghRows[ihRow] )) 
            return ResultFromScode( DB_E_BADROWHANDLE ); 
 
        // bump refcount 
        pRowBuff = m_pObj->GetRowBuff((ULONG) rghRows[ihRow] ); 
        assert( pRowBuff->ulRefCount != 0 ); 
        assert( m_pObj->m_ulRowRefCount != 0 ); 
        ++pRowBuff->ulRefCount; 
        ++m_pObj->m_ulRowRefCount; 
 
        // bump total refcounted 
        cAddRef++; 
 
        // stuff new refcount into caller's array 
        if (rgRefCounts) 
            rgRefCounts[ihRow] = pRowBuff->ulRefCount; 
        } 
 
    // fill out-param 
    if (pcRefCounted) 
        (*pcRefCounted) = cAddRef; 
 
    return ResultFromScode( S_OK ); 
}