Files
mars-flaim/flaim/src/frsetblk.cpp
dsandersoremutah c55dab446f Renamed version4 to flaim and version5 to xflaim
git-svn-id: https://svn.code.sf.net/p/flaim/code/trunk@7 0109f412-320b-0410-ab79-c3e0c5ffbbe6
2006-01-27 21:06:39 +00:00

1340 lines
37 KiB
C++

//-------------------------------------------------------------------------
// Desc: Result set blocks
// Tabs: 3
//
// Copyright (c) 1996-2001,2003-2006 Novell, Inc. All Rights Reserved.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of version 2 of the GNU General Public
// License as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, contact Novell, Inc.
//
// To contact Novell about this file by physical or electronic mail,
// you may find current contact information at www.novell.com
//
// $Id: frsetblk.cpp 12320 2006-01-19 15:53:51 -0700 (Thu, 19 Jan 2006) dsanders $
//-------------------------------------------------------------------------
#include "flaimsys.h"
/****************************************************************************
Public: FResultSetBlk
Desc: Constructor
Notes:
****************************************************************************/
FResultSetBlk::FResultSetBlk()
{
m_pNext = m_pPrev = NULL;
reset();
}
/****************************************************************************
Public: reset
Desc: Reset a block so it can be reused.
****************************************************************************/
void FResultSetBlk::reset( void)
{
flmAssert( !m_pNext && !m_pPrev);
// Initialize all of the member variables
// between this constructor, SetBuffer() and Setup().
m_BlockHeader.ui64FilePos = RSBLK_UNSET_FILE_POS;
m_BlockHeader.uiEntryCount = 0;
// m_BlockHeader.uiBlockSize = RSBLK_BLOCK_SIZE; set in Setup()
//m_BlockHeader.FirstBlock = ?; set in Setup().
//m_BlockHeader.LastBlock = ?; set in Setup().
m_ppFileHdl64 = NULL;
m_uiBlkEntryPosition = RS_POSITION_NOT_SET;
m_iEntryPos = 0;
//m_uiEntrySize = set in Setup().
//m_bEntriesInOrder = set in Setup().
//m_bFixedEntrySize; set in Setup().
m_bDuplicateFound = FALSE;
m_bPositioned = FALSE;
m_bModifiedEntry = FALSE;
m_pBlockBuf = NULL;
}
/****************************************************************************
Public: Setup
Desc: Setup the result set block - passing in needed member variables
Notes: Must be called.
****************************************************************************/
void FResultSetBlk::Setup(
F_64BitFileHandle **
ppFileHdl64, // file handle to use for temp file.
RSET_COMPARE_FUNC_p // Zero or record compare function.
fnCompare, // Returns (FLMINT) -1, 0 or 1 values.
void * UserValue, // UserValue for callback.
FLMUINT uiEntrySize,
FLMBOOL bFirstInList, // Use RSBLK_IS_FIRST_IN_LIST for true.
FLMBOOL bDropDuplicates, // If TRUE drop duplicates
FLMBOOL bEntriesInOrder) // TRUE when entries are in order.
{
flmAssert( ppFileHdl64 != NULL);
m_ppFileHdl64 = ppFileHdl64;
m_fnCompare = fnCompare;
m_UserValue = UserValue;
m_uiEntrySize = uiEntrySize;
m_BlockHeader.bFirstBlock = bFirstInList;
m_BlockHeader.bLastBlock = FALSE; // Set for real in the flush call.
m_bFixedEntrySize = m_uiEntrySize ? TRUE : FALSE; // Friend member
if( !m_uiEntrySize )
{
m_uiEntrySize = BLKOFFSET_SIZE + LENGTH_SIZE;
}
m_bDropDuplicates = bDropDuplicates;
m_bEntriesInOrder = bEntriesInOrder;
// Other variables have been setup from the constructor call.
}
/****************************************************************************
Public: SetBuffer
Desc: The buffer is NOT allocated the by the result set block object.
Setup the pBuffer and associated variables. Read in the data
for this block if necessary. If NULL is passed in as pBuffer
then this block is not the active block anymore.
Notes: Must be called before other methods below are called.
****************************************************************************/
RCODE FResultSetBlk::SetBuffer(
FLMBYTE * pBuffer, // Working buffer or NULL
FLMUINT uiBufferLength) // Default value is RSBLK_BLOCK_SIZE.
{
RCODE rc = FERR_OK;
// If a buffer is defined then read in the data from disk.
f_yieldCPU();
if( pBuffer)
{
m_pBlockBuf = pBuffer;
// Is there data already in the block?
if( ! m_BlockHeader.uiEntryCount)
{
// uiBlockSize is the final block size after squeeze.
// uiLengthRemaining is working value of bytes available.
m_BlockHeader.uiBlockSize = uiBufferLength;
m_uiLengthRemaining = uiBufferLength;
m_pNextEntryPtr = m_pBlockBuf;
if( m_bFixedEntrySize)
{
m_pEndPoint = m_pBlockBuf;
}
else // variable length entries
{
m_pEndPoint = m_pBlockBuf + uiBufferLength;
}
}
else
{
// Read in the data if necessary.
if( RC_BAD( rc = Read())) // Sets bPositioned to TRUE on success.
goto Exit;
}
// GWBUG: 46817
// The block is now in focus
m_bPositioned = TRUE;
}
else // inactivating block so the buffer can be reused.
{
// Check if the block has been modified
if( m_bModifiedEntry )
{
// Is this a lone block?
if( !m_BlockHeader.bLastBlock || !m_BlockHeader.bFirstBlock )
{
if( RC_BAD( rc = Write()))
goto Exit;
}
m_bModifiedEntry = FALSE;
}
// GWBUG: 46817
// The block is now out of focus
m_bPositioned = FALSE;
m_pNextEntryPtr = m_pEndPoint = m_pBlockBuf = NULL;
}
Exit:
return( rc);
}
/****************************************************************************
Public: AddEntry
Desc: Add a variable length entry to the result set. If fixed length
entry then call AddEntry for fixed length entries.
Ret: RCODE - FERR_OK or FERR_EOF_HIT when full
Notes:
****************************************************************************/
RCODE FResultSetBlk::AddEntry(
FLMBYTE * pEntry,
FLMUINT uiEntryLength)
{
RCODE rc = FERR_OK;
BLKOFFSET uEntryOffset;
FLMUINT uiAlignLength; // Length taking into account alignment.
flmAssert( NULL != m_pBlockBuf );
// Was setup called for fixed length entries?
if( m_bFixedEntrySize )
{
rc = AddEntry( pEntry );
goto Exit;
}
uiAlignLength = (uiEntryLength + FLM_ALLOC_ALIGN) & (~FLM_ALLOC_ALIGN);
// Check to see if the current buffer will overflow.
if( m_uiLengthRemaining < uiAlignLength + BLKOFFSET_SIZE + LENGTH_SIZE )
{
// Caller should call Flush and setup correctly what to do next.
rc = RC_SET( FERR_EOF_HIT );
goto Exit;
}
// Copy entry and compute the offset value for pNextEntryPtr.
m_pEndPoint -= uiAlignLength;
f_memcpy( m_pEndPoint, pEntry, uiEntryLength );
uEntryOffset = (BLKOFFSET) (m_pEndPoint - m_pBlockBuf);
SetOffset( uEntryOffset, m_pNextEntryPtr );
SetLength( uiEntryLength, m_pNextEntryPtr );
m_pNextEntryPtr += (BLKOFFSET_SIZE + LENGTH_SIZE);
m_uiLengthRemaining -= (uiAlignLength + BLKOFFSET_SIZE + LENGTH_SIZE );
m_BlockHeader.uiEntryCount++;
Exit:
return( rc);
}
/****************************************************************************
Desc: Add an fixed length entry to the result set.
****************************************************************************/
RCODE FResultSetBlk::AddEntry(
FLMBYTE * pEntry)
{
RCODE rc = FERR_OK;
FLMUINT uiEntrySize = m_uiEntrySize; // Optimization
// Check that setup was called for fixed length entries.
flmAssert( m_bFixedEntrySize );
// Check to see if the current buffer is full.
if( m_uiLengthRemaining < uiEntrySize )
{
// Caller should call Flush and setup correctly what to do next.
rc = RC_SET( FERR_EOF_HIT );
goto Exit;
}
f_memcpy( m_pNextEntryPtr, pEntry, uiEntrySize );
m_BlockHeader.uiEntryCount++;
m_pNextEntryPtr += uiEntrySize;
m_pEndPoint += uiEntrySize;
m_uiLengthRemaining -= uiEntrySize;
Exit:
return( rc);
}
/****************************************************************************
Public: ModifyEntry
Desc: Modify the current entry being references.
Ret: RCODE - FERR_OK always. Will assert on entry size mismatch.
Notes: The size of each block cannot be modified. This is to allow
writing to the same location on disk and not waste disk memory.
****************************************************************************/
RCODE FResultSetBlk::ModifyEntry(
FLMBYTE * pEntry,
FLMUINT uiEntryLength)
{
RCODE rc = FERR_OK;
FLMBYTE * pCurEntry;
F_UNREFERENCED_PARM( uiEntryLength);
flmAssert( NULL != m_pBlockBuf );
// The incoming entry MUST be the same size.
if( m_bFixedEntrySize )
{
// Assert that the entry length must be zero.
// If not - still use m_uiEntrySize;
flmAssert( 0 == uiEntryLength );
// Position to the current item.
pCurEntry = &m_pBlockBuf[ m_iEntryPos * m_uiEntrySize ];
f_memcpy( pCurEntry, pEntry, m_uiEntrySize );
}
else
{
// Variable Length
FLMUINT uiCurEntryLength; // uiAlignLength;
pCurEntry = &m_pBlockBuf[ m_iEntryPos * m_uiEntrySize ];
uiCurEntryLength = GetLength( pCurEntry );
// We cannot support changing the entry size at this time.
flmAssert( uiEntryLength == uiCurEntryLength );
pCurEntry = m_pBlockBuf + GetOffset( pCurEntry );
f_memcpy( pCurEntry, pEntry, uiCurEntryLength );
}
m_bModifiedEntry = TRUE;
//Exit:
return( rc);
}
/****************************************************************************
Public: Flush
Desc: The block is full and need to flush the block to disk. If
bForceWrite is FALSE then will not write block to disk.
Ret: RCODE
Notes:
****************************************************************************/
RCODE FResultSetBlk::Flush(
FLMBOOL bLastBlockInList, // Last block in a block list.
FLMBOOL bForceWrite) // if TRUE write out to disk.
{
RCODE rc = FERR_OK;
flmAssert( NULL != m_pBlockBuf );// Insure SetBuffer was called.
// Set the Block header information, sort, kill dups and flush
SqueezeSpace(); // Squeeze out wasted space.
if( !m_bEntriesInOrder) // Are entries NOT in order?
{
rc = SortAndRemoveDups(); // Remove duplicate entries
if( RC_BAD(rc))
goto Exit;
}
m_bEntriesInOrder = TRUE; // Entries are now in order.
m_BlockHeader.bLastBlock = bLastBlockInList;
if( bForceWrite)
{
if( RC_BAD( rc = Write()))
goto Exit;
}
Exit:
return( rc);
}
/****************************************************************************
Desc: If there is length remaining, squeeze out additional space.
****************************************************************************/
void FResultSetBlk::SqueezeSpace()
{
// Fixed Entry Size?
if( m_bFixedEntrySize)
{
// Yes, no need to squeeze out any space.
goto Exit;
}
// Is there room to shift things down?
// Don't do if no entries or if less than 64 bytes or no entries.
if( (m_uiLengthRemaining >= 64 ) && m_BlockHeader.uiEntryCount )
{
FLMUINT uiBytesToMoveUp;
FLMBYTE * pEntry;
uiBytesToMoveUp = m_uiLengthRemaining;
m_uiLengthRemaining = 0;
// Overlapping memory move call.
flmAssert( (m_pBlockBuf + m_BlockHeader.uiBlockSize) > m_pEndPoint );
flmAssert( uiBytesToMoveUp < m_BlockHeader.uiBlockSize );
f_memmove( m_pEndPoint - uiBytesToMoveUp, m_pEndPoint,
(FLMUINT) ((m_pBlockBuf + m_BlockHeader.uiBlockSize ) - m_pEndPoint ));
m_BlockHeader.uiBlockSize -= uiBytesToMoveUp;
m_pEndPoint -= uiBytesToMoveUp;
// Change all of the offsets for every entry. This is expensive.
for( pEntry = m_pBlockBuf;
pEntry < m_pNextEntryPtr;
pEntry += BLKOFFSET_SIZE + LENGTH_SIZE )
{
BLKOFFSET uEntryOffset;
uEntryOffset = GetOffset( pEntry ) - ((BLKOFFSET) uiBytesToMoveUp);
SetOffset( uEntryOffset, pEntry );
}
}
Exit:
return;
}
/****************************************************************************
Desc: Sort the current block and remove all duplicates.
****************************************************************************/
RCODE FResultSetBlk::SortAndRemoveDups()
{
RCODE rc = FERR_OK;
// Nothing to do if one or zero entries in the block.
if( (m_BlockHeader.uiEntryCount <= 1 ) || (! m_fnCompare ))
{
goto Exit;
}
m_bDuplicateFound = FALSE;
if( RC_BAD( rc = QuickSort( 0, m_BlockHeader.uiEntryCount - 1 )))
goto Exit;
/*
Some users of result sets may not have any duplicates to remove
or may want the side effect of having duplicates to further
process the entries like for sorting tracker records. It is up
to the compare routine to never return RS_EQUALS in this case.
*/
/*
This algorithm is tuned for the case where there are zero or few
duplicate records. Removing duplicates is expensive in this design.
*/
if( m_bDropDuplicates && m_bDuplicateFound )
{
FLMUINT uiEntriesRemaining;
FLMBYTE * pBase;
FLMBYTE * pEntry;
FLMBYTE * pNextEntry;
FLMINT iCompare;
pBase = pEntry = m_pBlockBuf;
for( uiEntriesRemaining = m_BlockHeader.uiEntryCount - 1
; uiEntriesRemaining > 0
; uiEntriesRemaining-- )
{
pNextEntry = pEntry + m_uiEntrySize;
if( m_bFixedEntrySize)
{
rc = m_fnCompare( pEntry, m_uiEntrySize,
pNextEntry, m_uiEntrySize,
m_UserValue, &iCompare );
}
else
{
rc = m_fnCompare( pBase + GetOffset( pEntry ),
GetLength( pEntry ),
pBase + GetOffset( pNextEntry),
GetLength( pNextEntry ),
m_UserValue, &iCompare );
}
if( RC_BAD(rc))
goto Exit;
if( iCompare == RS_EQUALS )
{
RemoveEntry( pEntry );
// Leave pEntry alone - everyone will scoot down
}
else
{
pEntry += m_uiEntrySize;
}
}
}
Exit:
return( rc);
}
/****************************************************************************
Desc: Remove the current entry from the block.
****************************************************************************/
void FResultSetBlk::RemoveEntry(
FLMBYTE * pEntry)
{
if( m_bFixedEntrySize )
{
// Don't like moving zero bytes - check first.
if( pEntry + m_uiEntrySize < m_pEndPoint )
{
// This is really easy - just memmove everyone down.
f_memmove( pEntry, pEntry + m_uiEntrySize,
(FLMUINT) (m_pEndPoint - pEntry ) - m_uiEntrySize);
}
m_BlockHeader.uiEntryCount--;
m_BlockHeader.uiBlockSize -= m_uiEntrySize;
m_pEndPoint -= m_uiEntrySize;
}
else // Variable length - much harder
{
/*
Example - remove entry 3 below...
[entryOfs1:len][entryOfs2:len][entryOfs3:len][entryOfs4:len]
[entryData1][entryData2][entryData3][entryData4]
Need to reduce EntryOfs1 and entryOfs2 by m_uiEntrySize+entryLen3.
because these entries are stored AFTER entry 3 - entries are first
stored going from the back of the block to the front of the block.
Need to reduce Ofs4 by OFFSET_SIZE.
*/
BLKOFFSET uDeletedOffset = GetOffset( pEntry );
BLKOFFSET uTempOffset;
FLMUINT uiDeletedLength = GetLength( pEntry );
FLMBYTE * pCurEntry;
FLMUINT uiPos;
FLMUINT uiMoveBytes;
flmAssert( m_BlockHeader.uiBlockSize >=
(uDeletedOffset + uiDeletedLength ));
// Don't move zero bytes - don't know what will happen x-platform.
uiMoveBytes = (FLMUINT)
( m_BlockHeader.uiBlockSize - (uDeletedOffset + uiDeletedLength ));
if( uiMoveBytes)
{
// First move down the variable length entry data.
f_memmove( m_pBlockBuf + uDeletedOffset,
m_pBlockBuf + uDeletedOffset + uiDeletedLength,
uiMoveBytes );
}
flmAssert( m_BlockHeader.uiBlockSize >= (FLMUINT)
((pEntry + m_uiEntrySize) - m_pBlockBuf) );
uiMoveBytes = (FLMUINT) (m_BlockHeader.uiBlockSize -
((pEntry + m_uiEntrySize) - m_pBlockBuf));
if( uiMoveBytes )
{
f_memmove( pEntry, pEntry + m_uiEntrySize, uiMoveBytes );
}
m_BlockHeader.uiBlockSize -= uiDeletedLength + m_uiEntrySize;
// Adjust the offset values.
m_BlockHeader.uiEntryCount--;
for( uiPos = 0, pCurEntry = m_pBlockBuf
; uiPos < m_BlockHeader.uiEntryCount
; uiPos++, pCurEntry += m_uiEntrySize )
{
// Assume that the offsets are NOT in descending order.
// This will help in the future additional adding and deleting
// to an existing result set.
uTempOffset = GetOffset( pCurEntry );
if( uTempOffset > uDeletedOffset)
{
uTempOffset -= (BLKOFFSET) uiDeletedLength;
}
uTempOffset -= (BLKOFFSET)m_uiEntrySize;
SetOffset( uTempOffset, pCurEntry );
}
}
return;
}
/****************************************************************************
Desc: Quick sort an array of values.
Notes: Optimized the above quicksort algorithm. On page 559 the book
suggests that "The worst case can sometimes be avioded by choosing
more carefully the record for final placement at each state."
This algorithm picks a mid point for the compare value. Doing
this helps the worst case where the entries are in order. In Order
tests went from 101 seconds down to 6 seconds!
This helps the 'in order' sorts from worst case Order(N^^2)/2 with
the normal quickSort to Order(NLog2 N) for the worst case.
Also optimized the number of recursions to Log2 N from (N-2).
Will recurse the SMALLER side and will iterate to the top of
the routinefor the LARGER side. Follow comments below.
****************************************************************************/
RCODE FResultSetBlk::QuickSort(
FLMUINT uiLowerBounds,
FLMUINT uiUpperBounds)
{
FLMBYTE * pEntryTbl = m_pBlockBuf;
FLMBYTE * pCurEntry;
FLMUINT uiLBPos, uiUBPos, uiMIDPos;
FLMUINT uiLeftItems, uiRightItems;
FLMINT iCompare;
FLMUINT uiEntrySize = m_uiEntrySize;
RCODE rc = FERR_OK; // Set in case only one entry.
FLMBYTE ucaSwapBuffer[MAX_FIXED_ENTRY_SIZE];
#define RECURSIVE_CALL
// The problem with using the non-recursive code is that
// for formula to compute the maximum number of levels
// is so complex we cannot guess on a value.
#ifndef RECURSIVE_CALL
FLMUINT uiaLowerStack[64];
FLMUINT uiaUpperStack[64];
FLMUINT uiStackPos;
#endif
#define RS_SWAP(pTbl,pos1,pos2) { \
f_memcpy( ucaSwapBuffer, &pTbl[pos2*uiEntrySize], uiEntrySize); \
f_memcpy( &pTbl[ pos2 * uiEntrySize ], &pTbl[ pos1 * uiEntrySize ], uiEntrySize ); \
f_memcpy( &pTbl[ pos1 * uiEntrySize ], ucaSwapBuffer, uiEntrySize ); }
#ifndef RECURSIVE_CALL
// Setup the stack.
uiStackPos = 1;
uiaLowerStack[0] = uiLowerBounds;
uiaUpperStack[0] = uiUpperBounds;
while( uiStackPos )
{
uiStackPos--;
uiLowerBounds = uiaLowerStack[ uiStackPos ];
uiUpperBounds = uiaUpperStack[ uiStackPos ];
#endif
Iterate_Larger_Half:
uiUBPos = uiUpperBounds;
uiLBPos = uiLowerBounds;
uiMIDPos = (uiUpperBounds + uiLowerBounds + 1) / 2;
pCurEntry = &pEntryTbl[ uiMIDPos * uiEntrySize ];
for( ;;)
{
while( (uiLBPos == uiMIDPos) // Don't compare with target
|| (((rc = EntryCompare( &pEntryTbl[ uiLBPos * uiEntrySize ],
pCurEntry,
&iCompare )) == FERR_OK)
&& (iCompare == RS_LESS_THAN )))
{
if( uiLBPos >= uiUpperBounds) break;
uiLBPos++;
}
if(RC_BAD(rc)) // Check for error
goto Exit;
while( (uiUBPos == uiMIDPos) // Don't compare with target
|| (((rc = EntryCompare( pCurEntry,
&pEntryTbl[ uiUBPos * uiEntrySize ],
&iCompare )) == FERR_OK)
&& (iCompare == RS_LESS_THAN )))
{
if( uiUBPos == 0) // Check for underflow condition
{
break;
}
uiUBPos--;
}
if(RC_BAD(rc)) // Check for error
goto Exit;
if( uiLBPos < uiUBPos ) // Interchange and continue loop.
{
/* Interchange [uiLBPos] with [uiUBPos]. */
RS_SWAP( pEntryTbl, uiLBPos, uiUBPos );
uiLBPos++; // Scan from left to right.
uiUBPos--; // Scan from right to left.
}
else // Past each other - done
{
break;
}
}
/* 5 cases to check.
1) UB < MID < LB - Don't need to do anything.
2) MID < UB < LB - swap( UB, MID )
3) UB < LB < MID - swap( LB, MID )
4) UB = LB < MID - swap( LB, MID ) - At first position
5) MID < UB = LB - swap( UB, MID ) - At last position
*/
/* Check for swap( LB, MID ) - cases 3 and 4 */
if( uiLBPos < uiMIDPos )
{
/* Interchange [uiLBPos] with [uiMIDPos] */
RS_SWAP( pEntryTbl, uiMIDPos, uiLBPos );
uiMIDPos = uiLBPos;
}
else if( uiMIDPos < uiUBPos ) /* cases 2 and 5 */
{
/* Interchange [uUBPos] with [uiMIDPos] */
RS_SWAP( pEntryTbl, uiMIDPos, uiUBPos );
uiMIDPos = uiUBPos;
}
/* To save stack space - recurse the SMALLER Piece. For the larger
piece goto the top of the routine. Worst case will be
(Log2 N) levels of recursion.
Don't recurse in the following cases:
1) We are at an end. Just loop to the top.
2) There are two on one side. Compare and swap. Loop to the top.
Don't swap if the values are equal. There are many recursions
with one or two entries. This doesn't speed up any so it is
commented out.
*/
/* Check the left piece. */
uiLeftItems = (uiLowerBounds + 1 < uiMIDPos )
? uiMIDPos - uiLowerBounds // 2 or more
: 0;
uiRightItems = (uiMIDPos + 1 < uiUpperBounds )
? uiUpperBounds - uiMIDPos // 2 or more
: 0;
/*
A removed optimization was here.
If two left or right items then check and swap.
I didn't see any improvement in time using this code.
*/
if( uiLeftItems < uiRightItems )
{
/* Recurse on the LEFT side and goto the top on the RIGHT side. */
if( uiLeftItems )
{
#ifdef RECURSIVE_CALL
// Recursive call.
if( RC_BAD( rc = QuickSort( uiLowerBounds, uiMIDPos - 1 )))
goto Exit;
#else
uiaLowerStack[ uiStackPos ] = uiLowerBounds;
uiaUpperStack[ uiStackPos++ ] = uiMIDPos - 1;
#endif
}
uiLowerBounds = uiMIDPos + 1;
goto Iterate_Larger_Half;
}
else if( uiLeftItems ) // Compute a truth table to figure out this check.
{
/* Recurse on the RIGHT side and goto the top for the LEFT side. */
if( uiRightItems )
{
// Recursive call.
#ifdef RECURSIVE_CALL
if( RC_BAD( rc = QuickSort( uiMIDPos + 1, uiUpperBounds )))
goto Exit;
#else
uiaLowerStack[ uiStackPos ] = uiMIDPos + 1;
uiaUpperStack[ uiStackPos++ ] = uiUpperBounds;
#endif
}
uiUpperBounds = uiMIDPos - 1;
goto Iterate_Larger_Half;
}
#ifndef RECURSIVE_CALL
}
#endif
Exit:
return( rc);
}
/****************************************************************************
Public: FRSDefaultCompare
Desc: Default compare that compares the data like memcmp() from left
to right and then by length where smaller is less than bigger.
Ret: *piCompare = -1 RS_LESS_THAN; 0 = RS_EQUALS; 1 RS_GREATER_THAN
RCODE = any code from user defined compare routine.
non-zero rcode will terminate sort/compare.
Notes: Do NOT make this routine static. This routine needs to be public.
****************************************************************************/
RCODE FRSDefaultCompare(
void * pData1,
FLMUINT uiLength1,
void * pData2,
FLMUINT uiLength2,
void * UserValue,
FLMINT * piCompare)
{
FLMUINT uiMinLength = f_min( uiLength1, uiLength2 );
FLMINT iCompareValue;
int siMemcmpValue;
F_UNREFERENCED_PARM( UserValue);
if( (siMemcmpValue = f_memcmp( pData1, pData2, (FLMUINT) uiMinLength )) == 0)
{
// Both equal up to the minimum length. Is there additional data?
if( uiLength1 != uiLength2 )
{
iCompareValue = (uiLength1 < uiLength2)
? RS_LESS_THAN : RS_GREATER_THAN;
}
else
{
iCompareValue = RS_EQUALS;
}
}
else
{
iCompareValue = (siMemcmpValue < 0) ? RS_LESS_THAN : RS_GREATER_THAN;
}
*piCompare = iCompareValue;
return FERR_OK;
}
/****************************************************************************
Desc: Write this block to disk. Adjust variables.
****************************************************************************/
RCODE FResultSetBlk::Write()
{
FLMUINT uiTotalBytesWritten;
FLMUINT64 ui64FilePos;
FLMUINT uiBytesToWrite,
uiBytesWritten;
RCODE rc;
// By this time there better be something to write...
// The file should be opened by default.
if( m_BlockHeader.ui64FilePos == RSBLK_UNSET_FILE_POS )
{
if( RC_BAD(rc = (*m_ppFileHdl64)->Size( &m_BlockHeader.ui64FilePos )))
goto Exit;
}
// Write out the block header definition.
rc = (*m_ppFileHdl64)->Write(
m_BlockHeader.ui64FilePos,
sizeof( FBlockHeader), &m_BlockHeader,
&uiBytesWritten );
if( RC_BAD( rc))
goto Exit;
ui64FilePos = m_BlockHeader.ui64FilePos +
(FLMUINT64)sizeof( FBlockHeader);
for( uiTotalBytesWritten = 0
; uiTotalBytesWritten < m_BlockHeader.uiBlockSize
; uiTotalBytesWritten += uiBytesWritten )
{
if( uiTotalBytesWritten + (MAX_WRITE_BYTES) > m_BlockHeader.uiBlockSize)
{
uiBytesToWrite = m_BlockHeader.uiBlockSize - uiTotalBytesWritten;
}
else
{
uiBytesToWrite = MAX_WRITE_BYTES;
// Write at most 60K at a time.
}
rc = (*m_ppFileHdl64)->Write(
ui64FilePos,
uiBytesToWrite,
&m_pBlockBuf[ uiTotalBytesWritten ],
&uiBytesWritten );
if( RC_BAD( rc))
goto Exit;
ui64FilePos += uiBytesWritten;
}
Exit:
return( rc);
}
/****************************************************************************
Desc: Read in the specified block into memory.
Ret: FERR_OK, FERR_EOF_HIT or I/O error
****************************************************************************/
RCODE FResultSetBlk::Read()
{
RCODE rc = FERR_OK;
FLMUINT uiBytesRead, uiBytesToRead;
FLMUINT uiTotalBytesRead;
FLMUINT64 ui64FilePos = m_BlockHeader.ui64FilePos;
FBlockHeader BlockHeader;
// Nothing to do???
if( RSBLK_UNSET_FILE_POS == ui64FilePos)
goto Exit;
// First read the block header in.
rc = (*m_ppFileHdl64)->Read( ui64FilePos, sizeof( FBlockHeader ),
(void *) &BlockHeader, &uiBytesRead );
if( RC_BAD(rc))
goto Exit;
// Verify that the block header data is the same.
// This is the best we can do to verify that the file handle
// is not junky.
if( (BlockHeader.ui64FilePos != m_BlockHeader.ui64FilePos )
|| (BlockHeader.uiEntryCount != m_BlockHeader.uiEntryCount ))
{
// Returning data error because there is a big possibility
// that the file handles have become corrupt.
rc = RC_SET( FERR_FAILURE );
goto Exit;
}
ui64FilePos += sizeof( FBlockHeader );
for( uiTotalBytesRead = 0
; uiTotalBytesRead < m_BlockHeader.uiBlockSize
; uiTotalBytesRead += uiBytesRead )
{
if( uiTotalBytesRead + (MAX_WRITE_BYTES) > m_BlockHeader.uiBlockSize)
{
uiBytesToRead = m_BlockHeader.uiBlockSize - uiTotalBytesRead;
}
else
{
uiBytesToRead = MAX_WRITE_BYTES;
}
rc = (*m_ppFileHdl64)->Read(
ui64FilePos,
uiBytesToRead,
&m_pBlockBuf[ uiTotalBytesRead ],
&uiBytesRead );
if( RC_BAD(rc))
goto Exit;
ui64FilePos += (FLMUINT64)uiBytesRead;
}
Exit:
if( RC_OK(rc))
{
m_bPositioned = TRUE;
m_iEntryPos = -1;
}
return( rc);
}
/****************************************************************************
Desc: Copies the current entry into the user buffer. Checks for overflow.
****************************************************************************/
RCODE FResultSetBlk::CopyCurrentEntry(
FLMBYTE * pBuffer,
FLMUINT uiBufferLength,
FLMUINT * puiReturnLength)
{
RCODE rc = FERR_OK; // Must be initailized
FLMUINT uiEntrySize;
FLMBYTE * pEntry;
flmAssert( NULL != pBuffer );
// Copy the current entry. This is a shared routine
// because the code to copy an entry is a little complicated.
uiEntrySize = m_uiEntrySize;
pEntry = &m_pBlockBuf[ m_iEntryPos * uiEntrySize ];
if( !m_bFixedEntrySize )
{
uiEntrySize = GetLength( pEntry );
pEntry = m_pBlockBuf + GetOffset( pEntry );
}
if( uiBufferLength && (uiEntrySize > uiBufferLength))
{
uiEntrySize = uiBufferLength;
rc = RC_SET( FERR_CONV_DEST_OVERFLOW );
// Fall through into memcpy.
}
f_memcpy( pBuffer, pEntry, (FLMUINT) uiEntrySize );
if( puiReturnLength)
{
*puiReturnLength = uiEntrySize;
}
//Exit:
return( rc);
}
/****************************************************************************
Public: GetCurrent
Desc: Return the Current entry reference in the result set.
Ret: FERR_OK - Returned the current recRef[].
FERR_CONV_DEST_OVERFLOW - buffer is not big enough for data
FERR_NOT_FOUND - Not positioned anywhere in the result set.
FERR_EOF_HIT - Positioned past the last entry
FERR_BOF_HIT - Positioned before the first entry.
****************************************************************************/
RCODE FResultSetBlk::GetCurrent(
FLMBYTE * pBuffer,
FLMUINT uiBufferLength,
FLMUINT * puiReturnLength)
{
RCODE rc;
flmAssert( NULL != m_pBlockBuf );
if( !m_bPositioned )
{
rc = RC_SET( FERR_NOT_FOUND );
goto Exit;
}
// Check for EOF and BOF conditions - otherwise return current.
if( m_iEntryPos >= (FLMINT) m_BlockHeader.uiEntryCount )
{
rc = RC_SET( FERR_EOF_HIT );
goto Exit;
}
if( m_iEntryPos == -1)
{
rc = RC_SET( FERR_BOF_HIT );
goto Exit;
}
rc = CopyCurrentEntry( pBuffer, uiBufferLength, puiReturnLength );
Exit:
return( rc);
}
/****************************************************************************
Public: GetNextPtr
Desc: Return a pointer to the next reference in the result set.
If the result set is not positioned then the first entry will
be returned.
Ret: FERR_OK - Returned the current recRef[].
FERR_CONV_DEST_OVERFLOW - buffer is not big enough for data
FERR_NOT_FOUND - zero records in the result set.
FERR_EOF_HIT - Positioned past the last entry
****************************************************************************/
RCODE FResultSetBlk::GetNextPtr(
FLMBYTE ** ppBuffer,
FLMUINT * puiReturnLength)
{
RCODE rc = FERR_OK;
FLMUINT uiEntrySize;
FLMBYTE * pEntry;
flmAssert( ppBuffer != NULL );
flmAssert( puiReturnLength != NULL );
flmAssert( m_bPositioned );
// Are we on the last entry or past the last entry?
if( m_iEntryPos + 1 >= (FLMINT) m_BlockHeader.uiEntryCount )
{
m_iEntryPos = (FLMINT) m_BlockHeader.uiEntryCount;
rc = RC_SET( FERR_EOF_HIT );
goto Exit;
}
m_iEntryPos++; // Else position to next entry
uiEntrySize = m_uiEntrySize;
pEntry = &m_pBlockBuf[ m_iEntryPos * uiEntrySize ];
if( !m_bFixedEntrySize )
{
uiEntrySize = GetLength( pEntry );
pEntry = m_pBlockBuf + GetOffset( pEntry );
}
*ppBuffer = pEntry;
*puiReturnLength = uiEntrySize;
Exit:
return( rc);
}
/****************************************************************************
Public: GetPrev
Desc: Return the previous reference in the result set. If the result set
is not positioned then the last entry will be returned.
Ret: FERR_OK - Returned the current recRef[].
FERR_CONV_DEST_OVERFLOW - buffer is not big enough for data
FERR_BOF_HIT - Positioned past the first entry
****************************************************************************/
RCODE FResultSetBlk::GetPrev(
FLMBYTE * pBuffer,
FLMUINT uiBufferLength,
FLMUINT * puiReturnLength)
{
RCODE rc = FERR_OK;
flmAssert( m_bPositioned );
// If not positioned then position past last entry.
if( m_iEntryPos == -1)
{
m_iEntryPos = (FLMINT) m_BlockHeader.uiEntryCount;
}
// Are we on the first entry or before the first entry?
if( m_iEntryPos == 0)
{
m_iEntryPos = -1; // Just set it
rc = RC_SET( FERR_BOF_HIT );
goto Exit;
}
m_iEntryPos--; // position to previous entry.
rc = CopyCurrentEntry( pBuffer, uiBufferLength, puiReturnLength );
Exit:
return( rc);
}
/****************************************************************************
Public: SetPosition
Desc: Set the current entry position for this block.
In: uiPosition - Zero based position value for this block
or RS_POSITION_NOT_SET if invalid position (past the end).
Ret: FERR_OK - Returned OK
FERR_EOF_HIT - Positioned past the end of the block.
Note: The state must be that the data in the block has been
read in. Otherwise, will set position to first or last.
****************************************************************************/
RCODE FResultSetBlk::SetPosition(
FLMUINT uiPosition)
{
RCODE rc= FERR_OK;
// Buffer must be set or SetBuffer() will set iEntryPos back to -1.
flmAssert( m_bPositioned );
if( uiPosition == RS_POSITION_NOT_SET )
{
m_iEntryPos = -1;
goto Exit;
}
flmAssert( uiPosition >= m_uiBlkEntryPosition );
// Convert to a zero based number relative to this block.
if( uiPosition >= m_uiBlkEntryPosition )
uiPosition -= m_uiBlkEntryPosition;
else
uiPosition = 0; // Handle if assert condition is not in debug.
if( uiPosition >= m_BlockHeader.uiEntryCount)
{
rc = RC_SET( FERR_EOF_HIT );
m_iEntryPos = m_BlockHeader.uiEntryCount;
}
else
{
m_iEntryPos = (FLMINT) uiPosition;
}
Exit:
return( rc);
}
/****************************************************************************
Public: FindMatch
Desc: Find the matching entry within the block using the compare routine.
This does a binary search on entries. Watch the (out) variable.
Out: *piCompare = RS_EQUALS - match found OR entry would compare between
the low and high entries in the block.
= RS_GREATER_THAN - match entry is greater than
the highest entry in the block.
= RS_LESS_THAN - match entry is less than the
lowest entry in the block.
Ret: FERR_OK - Returned OK
FERR_NOT_FOUND - match not found
Notes: One side effect is that m_iEntryPos is set to the matched
entry or some random entry if not found is returned.
****************************************************************************/
RCODE FResultSetBlk::FindMatch( // Find and return an etnry that
// matches in this block.
FLMBYTE * pMatchEntry, // Entry to match
FLMUINT uiMatchEntryLength, // Variable length of above entry
FLMBYTE * pFoundEntry, // (out) Entry to return
FLMUINT * puiFoundEntryLength, // (out) Length of entry returned
RSET_COMPARE_FUNC_p // Record compare function.
fnCompare, // Returns (FLMINT) -1, 0 or 1 values.
void * UserValue, // UserValue for callback.
FLMINT * piCompare) // See comments above.
{
RCODE rc = FERR_OK;
FLMINT iCompare; // Return from CompareEntry
FLMUINT uiLow, uiHigh, uiMid, uiLimit;
uiLow = 0;
uiHigh = uiLimit = GetNumberOfEntries() - 1;
if( ! uiMatchEntryLength) // Set the match entry length.
uiMatchEntryLength = m_uiEntrySize;
// Check the first and last entries in the block.
// Copy the current entry if found.
if( RC_BAD( rc = CompareEntry( pMatchEntry, uiMatchEntryLength, uiLow,
fnCompare, UserValue, &iCompare )))
goto Exit;
if( iCompare != RS_GREATER_THAN )
{
if( iCompare == RS_LESS_THAN )
{
rc = RC_SET( FERR_NOT_FOUND );
}
*piCompare = iCompare;
goto Exit;
}
if( RC_BAD( rc = CompareEntry( pMatchEntry, uiMatchEntryLength, uiHigh,
fnCompare, UserValue, &iCompare )))
goto Exit;
if( iCompare != RS_LESS_THAN )
{
if( iCompare == RS_GREATER_THAN )
{
rc = RC_SET( FERR_NOT_FOUND );
}
*piCompare = iCompare;
goto Exit;
}
// Set the iCompare to equals because
// the match entry sorts within the block somewhere.
// Binary search the entries in the block. May still
// not find the matching entry.
*piCompare = RS_EQUALS;
for( ;; ) // Initialize low and high above.
{
uiMid = (uiLow + uiHigh) >> 1; // (uiLow + uiHigh) / 2
if( RC_BAD( rc = CompareEntry( pMatchEntry, uiMatchEntryLength, uiMid,
fnCompare, UserValue, &iCompare )))
goto Exit;
if( iCompare == RS_EQUALS )
{
// Found Match! All set up to return.
goto Exit;
}
// Check if we are done - where wLow equals uHigh.
if( uiLow >= uiHigh)
break; // Done - item not found.
if( iCompare == RS_LESS_THAN )
{
if( uiMid == 0) // Way too high?
break;
uiHigh = uiMid - 1; // Too high
}
else
{
if( uiMid == uiLimit) // Done - Hit the top
break;
uiLow = uiMid + 1; // Too low
}
}
// On break set we did not find the matching entry.
rc = RC_SET( FERR_NOT_FOUND );
Exit:
if( RC_OK(rc))
{
rc = CopyCurrentEntry( pFoundEntry, 0, puiFoundEntryLength );
}
return( rc);
}
/****************************************************************************
Desc: Compare the buffer entry with entry identifies by
uiEntryPos.
Out: *piCompare = RS_EQUALS - match found OR entry value would compare
between the low and high entries.
= RS_GREATER_THAN - match entry is greater than
the highest entry in the block.
= RS_LESS_THAN - match entry is less than the
lowest entry in the block.
****************************************************************************/
RCODE FResultSetBlk::CompareEntry( // Compares match entry with entry
// identified by uiEntryPos.
FLMBYTE * pMatchEntry, // Entry to match
FLMUINT uiMatchEntryLength, // Variable length of pMatchEntry.
FLMUINT uiEntryPos, // Position of entry in block.
RSET_COMPARE_FUNC_p // Record compare function.
fnCompare, // Returns (FLMINT) -1, 0 or 1 values.
void * UserValue, // UserValue for callback.
FLMINT * piCompare) // Return from compare.
{
RCODE rc;
FLMBYTE * pEntry;
FLMUINT uiEntrySize;
F_UNREFERENCED_PARM( fnCompare);
F_UNREFERENCED_PARM( UserValue);
// Position to the entry.
m_iEntryPos = (FLMINT) uiEntryPos;
uiEntrySize = m_uiEntrySize;
pEntry = &m_pBlockBuf[ m_iEntryPos * uiEntrySize ];
if( !m_bFixedEntrySize )
{
uiEntrySize = GetLength( pEntry );
pEntry = m_pBlockBuf + GetOffset( pEntry );
}
rc = m_fnCompare( pMatchEntry, uiMatchEntryLength,
pEntry, uiEntrySize,
m_UserValue, piCompare );
return( rc);
}