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 );
}