Files
mars-flaim/flaim/src/frset.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

1518 lines
39 KiB
C++
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//-------------------------------------------------------------------------
// Desc: Result sets
// 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: frset.cpp 12329 2006-01-20 17:49:30 -0700 (Fri, 20 Jan 2006) ahodgkinson $
//-------------------------------------------------------------------------
#include "flaimsys.h"
/*
** Sorting Result Sets:
**
New algorithm 7/2/97. This is a good one!
Below are refinements to the existing result set code.
1) We now have two files that are used in the merge-sort process.
The first file is used to hold all of the blocks created when
adding entries into the result set. The second file is used for
the first and thereafter odd merge steps. The first file is then
truncated and used for each even merge step. At the end of the
merge one of the files will be deleted. Three buffers are used
during merge and only one will remain after the merge is done.
This is safer than the previous method and uses a little less
disk space. There are many small improvements that can be made.
2) The result set code now takes a buffer and a length and has no
knowledge of the data in the result set. In fact, the data may
be fixed length or variable length. We removed the record cache
from the result set and made a record cache manager.
Future enhancements to consider:
1) Do a 3, 4 or N-Way merge.
This will greatly increase memory allocations but save a lot of time
reading and writing when the number of entries is large.
2) Use 3 buffers on the initial load. This is really doing the first
phase of the merge when adding entries. The algorithm would add
entries to two buffers, and when full merge to the third buffer and
write out two sorted buffer.
3) Don't write out the last block - use it as the first block of the
merge when not complete. This will save a write and read on each
pass. In addition, the I/O cache may be helped out.
In addition, the last block of each phase should be used first
on the next phase.
Old Notes:
Duplicate Entries:
Duplicate entries are very difficult for a general purpose sorter
to find. In some cases the user would want to compare only these
fields and not these to determine a duplicate. This result set
code lets the user pass in a callback routine to determine if
two entries are the same and if one should be dropped. The user
could pass in NULL to cause all duplicates to be retained.
Quick Sort Algorithm:
This algorithm, in FRSETBLK.CPP is a great algorithm that Scott
came up with. It will recurse only Log(base2)N times (number of
bits needed to represent N). This is a breakthrough because
all sorting algorithms I have seen will recurse N-1 times if
the data is in order or in reverse order. This will crash the
stack for a production quality sort.
Variable Length
This sorting engine (result set) supports variable length and
fixed length data. There is very low overhead for variable
length support. This sorting engine can be used for a variety
of tasks.
Example:
All numbers are logical block numbers.
Adding Pass 1 Pass2 Pass3 Pass4
Phase File 2 File 1 File 2 File 1
File 1 (created) (truncated) (truncated) (truncated)
========= ========= ========= =========== ====================
1 10 (1+2) 14 (10+11) 16 (14+15) 17 Final file (16+9)
2
3 11 (3+4) 15 (12+13) 9
4
5 12 (5+6) 9
6
7 13 (7+8)
8
9 9
*/
/*****************************************************************************
*****
** Setup/Shutdown Routines
*****
*****************************************************************************/
FResultSet::FResultSet()
{
// Let's just initialize all member variables.
m_fnCompare = NULL; // Setup
m_UserValue = (void *) 0; // Setup
m_fnCallback = NULL;
m_CallbackInfo.UserValue = (void *)0;
m_CallbackInfo.ui64EstTotalUnits = 0;
m_CallbackInfo.ui64UnitsDone = 0;
m_uiEntrySize = 0;
m_ui64TotalEntries = 0;
m_pCurRSBlk = m_pFirstRSBlk = m_pLastRSBlk = NULL;
f_memset( m_szDefaultPath, 0, F_PATH_MAX_SIZE);
m_pBlockBuf1 = m_pBlockBuf2 = m_pBlockBuf3 = NULL;
m_uiBlockBuf1Len = 0;
m_bFile1Opened = m_bFile2Opened = FALSE;
m_pFileHdl641 = m_pFileHdl642 = NULL;
m_bOutput2ndFile = FALSE;
m_bInitialAdding = TRUE;
m_bFinalizeCalled = FALSE;
m_bSetupCalled = FALSE;
}
FResultSet::~FResultSet()
{
FResultSetBlk *pCurRSBlk;
FResultSetBlk *pNextRSBlk;
// Free up the result set block chain.
for( pCurRSBlk = m_pFirstRSBlk; pCurRSBlk; pCurRSBlk = pNextRSBlk )
{
FLMUINT uiCount;
pNextRSBlk = pCurRSBlk->GetNext();
uiCount = pCurRSBlk->Release();
flmAssert( uiCount == 0);
}
// Set list to NULL for debugging in memory.
m_pFirstRSBlk = m_pLastRSBlk = m_pCurRSBlk = NULL;
// Free up all of the block buffers in the list.
f_free( &m_pBlockBuf1);
f_free( &m_pBlockBuf2);
f_free( &m_pBlockBuf3);
// Close all opened files
CloseFile( &m_pFileHdl641 );
CloseFile( &m_pFileHdl642 );
}
/****************************************************************************
Public: reset
Desc: Reset the result set so it can be reused.
****************************************************************************/
RCODE FResultSet::reset( void)
{
RCODE rc = FERR_OK;
FResultSetBlk * pCurRSBlk;
FResultSetBlk * pNextRSBlk;
// Free up the result set block chain - except for the first one.
for( pCurRSBlk = m_pFirstRSBlk; pCurRSBlk; pCurRSBlk = pNextRSBlk )
{
FLMUINT uiCount;
pNextRSBlk = pCurRSBlk->GetNext();
if (pCurRSBlk != m_pFirstRSBlk)
{
uiCount = pCurRSBlk->Release();
flmAssert( uiCount == 0);
}
}
// Free up all of the block buffers in the list, except for the first one.
f_free( &m_pBlockBuf2);
f_free( &m_pBlockBuf3);
// We want a buffer that is at least RSBLK_BLOCK_SIZE.
if (!m_pBlockBuf1 || m_uiBlockBuf1Len < RSBLK_BLOCK_SIZE)
{
if (m_pBlockBuf1)
{
f_free( &m_pBlockBuf1);
}
if( RC_BAD( rc = f_calloc( RSBLK_BLOCK_SIZE, &m_pBlockBuf1)))
{
goto Exit;
}
m_uiBlockBuf1Len = RSBLK_BLOCK_SIZE;
}
// Close all opened files
CloseFile( &m_pFileHdl641 );
CloseFile( &m_pFileHdl642 );
m_bFile1Opened = m_bFile2Opened = FALSE;
m_pFileHdl641 = m_pFileHdl642 = NULL;
// Reset some other variables
m_fnCallback = NULL;
m_CallbackInfo.UserValue = (void *)0;
m_CallbackInfo.ui64EstTotalUnits = 0;
m_CallbackInfo.ui64UnitsDone = 0;
m_ui64TotalEntries = 0;
m_bOutput2ndFile = FALSE;
m_bInitialAdding = TRUE;
m_bEntriesInOrder = m_bAppAddsInOrder;
m_bFinalizeCalled = FALSE;
// If we don't have a block, allocate it. Otherwise
// reset the one we have left.
if (!m_pFirstRSBlk)
{
if ((m_pFirstRSBlk = f_new FResultSetBlk) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
}
else
{
m_pFirstRSBlk->reset();
}
m_pLastRSBlk = m_pCurRSBlk = m_pFirstRSBlk;
(void)m_pFirstRSBlk->Setup( &m_pFileHdl641, m_fnCompare, m_UserValue,
m_uiEntrySize, RSBLK_IS_FIRST_IN_LIST, m_bDropDuplicates,
m_bEntriesInOrder);
(void) m_pFirstRSBlk->SetBuffer( m_pBlockBuf1, m_uiBlockBuf1Len);
Exit:
return( rc);
}
/****************************************************************************
Public: Setup
Desc: Setup the result set with all of the needed input values.
This method must only be called once.
Ret: FERR_OK - Created FERR_OKfully
WERR_MEM - Allocation error
Notes: Handles all error conditions.
****************************************************************************/
RCODE FResultSet::Setup(
const char * pszIoPath,
RSET_COMPARE_FUNC_p fnCompare,
void * UserValue,
FLMUINT uiEntrySize,
FLMBOOL bDropDuplicates,
FLMBOOL bEntriesInOrder)
{
RCODE rc = FERR_OK;
FLMBOOL bNewBlock = FALSE;
FLMBOOL bNewBuffer = FALSE;
flmAssert( !m_bSetupCalled );
flmAssert( uiEntrySize <= MAX_FIXED_ENTRY_SIZE );
// Perform all of the allocations first.
m_pFirstRSBlk = m_pLastRSBlk = m_pCurRSBlk = f_new FResultSetBlk;
// Allocation Error?
if( ! m_pCurRSBlk )
{
rc = RC_SET( FERR_MEM );
goto Exit;
}
bNewBlock = TRUE;
m_pCurRSBlk->Setup( &m_pFileHdl641, fnCompare, UserValue,
uiEntrySize, RSBLK_IS_FIRST_IN_LIST, bDropDuplicates, bEntriesInOrder );
// Allocate only the first buffer - other buffers only used in merge.
if( RC_BAD( rc = f_calloc( RSBLK_BLOCK_SIZE, &m_pBlockBuf1)))
goto Exit;
m_uiBlockBuf1Len = RSBLK_BLOCK_SIZE;
bNewBuffer = TRUE;
(void) m_pCurRSBlk->SetBuffer( m_pBlockBuf1);
// Set the input variables.
if( pszIoPath)
{
f_strcpy( m_szDefaultPath, pszIoPath);
}
m_fnCompare = fnCompare;
m_UserValue = UserValue;
m_uiEntrySize = uiEntrySize;
m_bDropDuplicates = bDropDuplicates;
m_bEntriesInOrder = m_bAppAddsInOrder = bEntriesInOrder;
Exit:
// Free allocations on any error
if( RC_BAD(rc))
{
if( bNewBlock)
{
m_pCurRSBlk->Release();
m_pFirstRSBlk = m_pLastRSBlk = m_pCurRSBlk = NULL;
}
if( bNewBuffer)
{
f_free( &m_pBlockBuf1);
m_uiBlockBuf1Len = 0;
}
}
else
{
m_bSetupCalled = TRUE;
}
return( rc);
}
/*****************************************************************************
*****
** Methods for Building a Result Set
*****
*****************************************************************************/
/****************************************************************************
Public: AddEntry
Desc: Interface to add a variable length entry to the result set.
Ret: RCODE - I/O error
Notes: Public method used by application and by the internal sort
and merge steps during finalize. The user must never add an
entry that is larger than the block size.
****************************************************************************/
RCODE FResultSet::AddEntry(
void * pEntry,
FLMUINT uiEntryLength) // If zero then entry is fixed length
{
RCODE rc;
flmAssert( m_bSetupCalled );
flmAssert( !m_bFinalizeCalled );
rc = m_pCurRSBlk->AddEntry( (FLMBYTE *) pEntry, uiEntryLength );
if( rc == FERR_EOF_HIT ) // Current block is full.
{
FResultSetBlk * pNextRSBlk;
F_64BitFileHandle ** ppFileHdl64;
if( m_bInitialAdding && !m_bFile1Opened )
{
// Need to create and open the output file?
// In a merge we may be working on the 2nd file and NOT the 1st.
// There just isn't a better place to open the 1st file.
if( RC_BAD(rc = OpenFile( &m_pFileHdl641 )))
goto Exit;
}
ppFileHdl64 = ( m_bOutput2ndFile ) ? &m_pFileHdl642 : &m_pFileHdl641;
// Always flush to disk (TRUE) from here.
if( RC_BAD( rc = m_pCurRSBlk->Flush( m_bInitialAdding, TRUE )))
goto Exit;
(void) m_pCurRSBlk->SetBuffer( NULL );
// Adding the current block is complete so allocate a new
// block object and link it into the list.
// We must continue to use this same block buffer.
// Allocate a new RSBlk and link into the result block list.
pNextRSBlk = f_new FResultSetBlk;
if( ! pNextRSBlk )
{
rc = RC_SET( FERR_MEM );
goto Exit;
}
m_pCurRSBlk->SetNext( pNextRSBlk );
pNextRSBlk->SetPrev( m_pCurRSBlk );
m_pLastRSBlk = m_pCurRSBlk = pNextRSBlk;
m_pCurRSBlk->Setup( ppFileHdl64, m_fnCompare,
m_UserValue, m_uiEntrySize, m_bInitialAdding, m_bDropDuplicates,
!m_bInitialAdding );
// Reset all of the buffer pointers and values.
(void) m_pCurRSBlk->SetBuffer( m_pBlockBuf1 );
// Make the callback only during the merge phase.
if( !m_bInitialAdding && m_fnCallback )
{
if( m_CallbackInfo.ui64EstTotalUnits <=
++m_CallbackInfo.ui64UnitsDone )
{
m_CallbackInfo.ui64EstTotalUnits =
m_CallbackInfo.ui64UnitsDone;
}
(void) m_fnCallback( &m_CallbackInfo );
}
// Add the entry again. This call should never fail because of space.
// If it does fail then the entry is larger than the buffer size.
if( RC_BAD( rc = m_pCurRSBlk->AddEntry( (FLMBYTE *) pEntry, uiEntryLength )))
{
if( rc == FERR_EOF_HIT )
{
flmAssert( FALSE ); // Force assert for testing
rc = RC_SET( FERR_FAILURE );
}
goto Exit;
}
}
Exit:
return( rc);
}
/****************************************************************************
Public: Finalize
Desc: Done adding entries. Sort all of the entries and perform a merge.
Ret: WERR_OK
Notes: This algorithm is tricky and there are many variations that we
could make to it. We have tried a lot of variations. In the
future this method may be replaced by CreateIterator().
Caution: On any error the result set is in a bad state and should be dumped.
****************************************************************************/
RCODE FResultSet::Finalize(
FLMUINT64 * pui64TotalEntries) // (OUT) Returns total number of entries.
{
RCODE rc = FERR_OK;
FLMBOOL bMergeSort;
// Avoid being called more than once.
flmAssert( !m_bFinalizeCalled);
flmAssert( m_bSetupCalled );
// Not a bug - but for future possibilities just check
// if there is more than one block and if so then
// the while() loop merge sort needs to be called.
bMergeSort = (m_pFirstRSBlk != m_pLastRSBlk) ? TRUE : FALSE;
// Force the write to disk if bMergeSort is TRUE.
if( RC_BAD(rc = m_pCurRSBlk->Finalize( bMergeSort )))
goto Exit;
m_bInitialAdding = FALSE;
// If the entries are in order fixup the block chain and we are done.
if( m_bEntriesInOrder )
{
FResultSetBlk * pBlk;
if( NumberOfBlockChains() > (FLMUINT64)1 )
{
// Entries already in order - need to fixup the blocks.
for( pBlk = m_pFirstRSBlk; pBlk; pBlk = pBlk->GetNext() )
{
pBlk->SetFirstInChain( FALSE);
pBlk->SetLastInChain( FALSE );
}
m_pFirstRSBlk->SetFirstInChain( TRUE );
m_pLastRSBlk->SetLastInChain( TRUE );
m_pCurRSBlk = NULL;
}
goto Exit;
}
// Compute total number of blocks.
if( m_fnCallback)
{
// Estimate total number of unit blocks to be written.
FLMUINT64 ui64Units = NumberOfBlockChains();
FLMUINT64 ui64Loops;
m_CallbackInfo.ui64EstTotalUnits = 0;
for( ui64Loops = ui64Units; ui64Loops > (FLMUINT64)1;
ui64Loops = (ui64Loops + (FLMUINT64)1) / (FLMUINT64)2 )
{
m_CallbackInfo.ui64EstTotalUnits += ui64Units;
}
}
// Do the merge sort.
// Keep looping until we have only one block in the result set list.
while( NumberOfBlockChains() > (FLMUINT64)1)
{
// Allocate two more buffers. Merge will open the 2nd file.
// Exit will free these allocations and close one of the files.
// Are the 2nd and 3rd buffers allocated?
if( !m_pBlockBuf2)
{
if( RC_BAD( rc = f_calloc( RSBLK_BLOCK_SIZE, &m_pBlockBuf2)))
goto Exit;
}
if( !m_pBlockBuf3)
{
if( RC_BAD( rc = f_calloc( RSBLK_BLOCK_SIZE, &m_pBlockBuf3)))
goto Exit;
}
// Swap which file is selected as the output file.
m_bOutput2ndFile = m_bOutput2ndFile ? FALSE : TRUE;
// Here is the magical call that does all of the work!
if( RC_BAD( rc = MergeSort()))
goto Exit;
}
Exit:
// If we did a merge sort of multiple blocks then
// free the first and second buffers and close one of the files.
if( RC_BAD(rc))
{
f_free( &m_pBlockBuf1);
m_uiBlockBuf1Len = 0;
}
f_free( &m_pBlockBuf2);
f_free( &m_pBlockBuf3);
// Close the non-output opened file. Close both on error.
// If m_bFile2Opened then we did a merge - close one file
if( m_bFile2Opened || RC_BAD(rc))
{
if( m_bOutput2ndFile || RC_BAD(rc) )
{
if( m_bFile1Opened )
{
m_pFileHdl641->Close( TRUE);
m_bFile1Opened = FALSE;
}
if (m_pFileHdl641)
{
m_pFileHdl641->Release();
m_pFileHdl641 = NULL;
}
}
if( !m_bOutput2ndFile || RC_BAD(rc) )
{
if( m_bFile2Opened )
{
m_pFileHdl642->Close( TRUE);
m_bFile2Opened = FALSE;
}
if (m_pFileHdl642)
{
m_pFileHdl642->Release();
m_pFileHdl642 = NULL;
}
}
}
if( RC_OK(rc))
{
FLMUINT uiPos;
FResultSetBlk * pRSBlk;
m_bFinalizeCalled = TRUE; // Used for asserts.
m_bEntriesInOrder = TRUE;
m_ui64TotalEntries = GetTotalEntries();
// Set the return value for total entries.
if( pui64TotalEntries)
{
*pui64TotalEntries = m_ui64TotalEntries;
}
if( !m_ui64TotalEntries)
{
if( m_pCurRSBlk )
m_pCurRSBlk->Release();
m_pCurRSBlk = m_pFirstRSBlk = m_pLastRSBlk = NULL;
f_free( &m_pBlockBuf1);
m_uiBlockBuf1Len = 0;
}
// Set the uiBlkEntryPosition values in each block.
for( uiPos = 0, pRSBlk = m_pFirstRSBlk;
pRSBlk;
pRSBlk = pRSBlk->GetNext() )
{
pRSBlk->SetInitialPosition( uiPos );
uiPos += pRSBlk->GetNumberOfEntries();
}
// Resize the buffer to save space if only one block & in memory.
if( (m_pFirstRSBlk == m_pLastRSBlk) && m_pCurRSBlk )
{
FLMBYTE * pNewBlk;
FLMUINT uiLen = m_pCurRSBlk->BytesUsedInBuffer();
if (uiLen != m_uiBlockBuf1Len)
{
rc = f_alloc( uiLen, &pNewBlk);
if( RC_OK(rc))
{
f_memcpy( pNewBlk, m_pBlockBuf1, uiLen);
f_free( &m_pBlockBuf1);
m_pBlockBuf1 = pNewBlk;
m_uiBlockBuf1Len = uiLen;
}
}
// Need to always do the SetBuffer, because it causes the
// result set to get positioned.
if (RC_OK( rc))
{
rc = m_pCurRSBlk->SetBuffer( m_pBlockBuf1, uiLen);
}
}
}
// else on error finalize leaves the block list in an awful state.
return( rc);
}
/****************************************************************************
Desc: Perform a Merge Sort on a list of result set blocks. This new
algorithm uses two files for the sort. The end result may
be one of the two files. At the end of the sort all old result set
block objects will be freed and only one result set block object
will be left. This RSBlk object will be used for reading the
entries. At this point there are at least 'N' result set block
objects that will be merged into ('N'/2) block objects.
****************************************************************************/
RCODE FResultSet::MergeSort()
{
RCODE rc = FERR_OK;
FResultSetBlk * pBlkList = NULL,
* pTempBlk,
* pLeftBlk,
* pRightBlk;
F_64BitFileHandle ** ppFileHdl64;
// Set output file and truncate it.
// OpenFilex() Closes and creats a new file.
// This is prefered over truncating the file because
// if a database gets truncated we will be blamed.
rc = (m_bOutput2ndFile)
? OpenFile( &m_pFileHdl642)
: OpenFile( &m_pFileHdl641);
if( RC_BAD( rc))
{
flmAssert( 0);
goto Exit;
}
ppFileHdl64 = ( m_bOutput2ndFile ) ? &m_pFileHdl642 : &m_pFileHdl641;
// Get the list to the RS blocks
pBlkList = m_pFirstRSBlk;
// Form an empty list to build.
m_pFirstRSBlk = m_pLastRSBlk = m_pCurRSBlk = NULL;
// Read and UNION one or two blocks at a time getting rid of duplicates.
// Reading the entries when performing a union of only one block
// is a lot of work for nothing - but it simplifies the code.
pTempBlk = pBlkList;
while( pTempBlk )
{
pLeftBlk = pTempBlk;
pRightBlk = pTempBlk->GetNext(); // May be NULL so watch out!
while( pRightBlk && (!pRightBlk->IsFirstInChain()) )
{
pRightBlk = pRightBlk->GetNext();
}
// Allocate a new result set block list and link into the new list.
m_pCurRSBlk = f_new FResultSetBlk;
if( ! m_pCurRSBlk )
{
rc = RC_SET( FERR_MEM );
goto Exit;
}
if( ! m_pLastRSBlk ) // First Time?
{
m_pFirstRSBlk = m_pLastRSBlk = m_pCurRSBlk;
}
else
{
m_pLastRSBlk->SetNext( m_pCurRSBlk );
m_pCurRSBlk->SetPrev( m_pLastRSBlk);
m_pLastRSBlk = m_pCurRSBlk;
}
m_pCurRSBlk->Setup( ppFileHdl64, m_fnCompare, m_UserValue,
m_uiEntrySize, RSBLK_IS_FIRST_IN_LIST, m_bDropDuplicates,
RSBLK_ENTRIES_IN_ORDER );
// Output to block buffer 1
(void) m_pCurRSBlk->SetBuffer( m_pBlockBuf1 );
if( RC_BAD( rc = pLeftBlk->SetBuffer( m_pBlockBuf2 )))
goto Exit;
if( pRightBlk)
{
if( RC_BAD( rc = pRightBlk->SetBuffer( m_pBlockBuf3 )))
goto Exit;
}
// pRightBlk may be NULL - will move left block to output.
// Output leftBlk and rightBlk to the output block (m_pCurRSBlk)
if( RC_BAD(rc = UnionBlkLists( pLeftBlk, pRightBlk )))
goto Exit;
// Setup for the next loop.
pTempBlk = pRightBlk ? pRightBlk->GetNext() : NULL;
while( pTempBlk && (!pTempBlk->IsFirstInChain()) )
{
pTempBlk = pTempBlk->GetNext();
}
}
Exit:
// Free the working block list.
pTempBlk = pBlkList;
while( pTempBlk )
{
FLMUINT uiTemp;
pRightBlk = pTempBlk->GetNext();
uiTemp = pTempBlk->Release();
flmAssert( uiTemp == 0);
pTempBlk = pRightBlk;
}
return( rc);
}
/*****************************************************************************
*****
** Reading Result Set Entries
*****
*****************************************************************************/
/****************************************************************************
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 FResultSet::GetCurrent(
void * vpBuffer,
FLMUINT uiBufferLength,
FLMUINT * puiReturnLength)
{
RCODE rc;
flmAssert( m_bFinalizeCalled );
if( !m_pCurRSBlk )
{
rc = RC_SET( FERR_NOT_FOUND );
}
else
{
rc = m_pCurRSBlk->GetCurrent( (FLMBYTE *) vpBuffer, uiBufferLength,
puiReturnLength );
}
return( rc);
}
/****************************************************************************
Public: GetNext
Desc: Return 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_EOF_HIT - Positioned past the last entry
****************************************************************************/
RCODE FResultSet::GetNext(
void * vpBuffer,
FLMUINT uiBufferLength,
FLMUINT * puiReturnLength)
{
RCODE rc;
flmAssert( m_bFinalizeCalled );
// Make sure we are positioned to a block.
if( ! m_pCurRSBlk )
{
m_pCurRSBlk = m_pFirstRSBlk;
if( ! m_pCurRSBlk )
{
rc = RC_SET( FERR_EOF_HIT );
goto Exit;
}
if( RC_BAD( rc = m_pCurRSBlk->SetBuffer( m_pBlockBuf1 )))
goto Exit;
}
rc = m_pCurRSBlk->GetNext( (FLMBYTE *) vpBuffer, uiBufferLength,
puiReturnLength );
// Position to the next block?
if( rc == FERR_EOF_HIT )
{
if( m_pCurRSBlk->GetNext() != NULL )
{
m_pCurRSBlk->SetBuffer( NULL );
m_pCurRSBlk = m_pCurRSBlk->GetNext();
if( RC_BAD( rc= m_pCurRSBlk->SetBuffer( m_pBlockBuf1 )))
goto Exit;
if( RC_BAD( rc = m_pCurRSBlk->GetNext( (FLMBYTE *) vpBuffer, uiBufferLength,
puiReturnLength )))
goto Exit;
}
}
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 before the first entry
****************************************************************************/
RCODE FResultSet::GetPrev(
void * vpBuffer,
FLMUINT uiBufferLength,
FLMUINT * puiReturnLength)
{
RCODE rc;
flmAssert( m_bFinalizeCalled );
// Make sure we are positioned to a block.
if( !m_pCurRSBlk )
{
m_pCurRSBlk = m_pLastRSBlk;
if( ! m_pCurRSBlk )
{
rc = RC_SET( FERR_BOF_HIT );
goto Exit;
}
if( RC_BAD( rc = m_pCurRSBlk->SetBuffer( m_pBlockBuf1 )))
goto Exit;
}
rc = m_pCurRSBlk->GetPrev( (FLMBYTE *) vpBuffer, uiBufferLength,
puiReturnLength );
// Position to the previous block?
if( rc == FERR_BOF_HIT )
{
if( m_pCurRSBlk->GetPrev() != NULL )
{
m_pCurRSBlk->SetBuffer( NULL );
m_pCurRSBlk = m_pCurRSBlk->GetPrev();
if( RC_BAD( rc = m_pCurRSBlk->SetBuffer( m_pBlockBuf1 )))
goto Exit;
rc = m_pCurRSBlk->GetPrev( (FLMBYTE *) vpBuffer, uiBufferLength,
puiReturnLength );
if( RC_BAD(rc))
goto Exit;
}
}
Exit:
return( rc);
}
/****************************************************************************
Public: GetFirst
Desc: Return the first 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 - zero records in the result set.
****************************************************************************/
RCODE FResultSet::GetFirst(
void * vpBuffer,
FLMUINT uiBufferLength,
FLMUINT * puiReturnLength)
{
RCODE rc;
flmAssert( m_bFinalizeCalled );
if( m_pCurRSBlk != m_pFirstRSBlk )
{
if( m_pCurRSBlk)
m_pCurRSBlk->SetBuffer( NULL );
m_pCurRSBlk = m_pFirstRSBlk;
if( RC_BAD( rc = m_pCurRSBlk->SetBuffer( m_pBlockBuf1 )))
goto Exit;
}
else if( ! m_pCurRSBlk )
{
rc = RC_SET( FERR_NOT_FOUND );
goto Exit;
}
rc = m_pCurRSBlk->GetNext( (FLMBYTE *) vpBuffer, uiBufferLength,
puiReturnLength );
Exit:
return( rc);
}
/****************************************************************************
Public: GetLast
Desc: Return the last 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 - zero records in the result set.
****************************************************************************/
RCODE FResultSet::GetLast(
void * vpBuffer,
FLMUINT uiBufferLength,
FLMUINT * puiReturnLength)
{
RCODE rc;
flmAssert( m_bFinalizeCalled );
if( m_pCurRSBlk != m_pLastRSBlk )
{
if( m_pCurRSBlk)
m_pCurRSBlk->SetBuffer( NULL );
m_pCurRSBlk = m_pLastRSBlk;
if( RC_BAD(rc = m_pCurRSBlk->SetBuffer( m_pBlockBuf1 )))
goto Exit;
}
else if( ! m_pCurRSBlk )
{
rc = RC_SET( FERR_EOF_HIT );
goto Exit;
}
rc = m_pCurRSBlk->GetPrev( (FLMBYTE *) vpBuffer, uiBufferLength,
puiReturnLength );
Exit:
return( rc);
}
/****************************************************************************
Public: FindMatch
Desc: Find the matching entry in the result set using the compare routine.
This does a binary search on the list of blocks.
Ret: FERR_OK - Returned OK
FERR_NOT_FOUND - match not found
****************************************************************************/
RCODE FResultSet::FindMatch( // Find and return an etnry that
// matches in the result set (variable).
void * vpMatchEntry, // Entry to match
FLMUINT uiMatchEntryLength, // Variable length of above entry
void * vpFoundEntry, // (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.
{
RCODE rc;
FLMINT iBlkCompare; // RS_EQUALS if key is/would be in block.
FResultSetBlk *pLowBlk; // Used for locating block.
FResultSetBlk *pHighBlk; // Low and High are exclusive.
flmAssert( m_bFinalizeCalled );
// If not positioned anywhere, position to the midpoint.
// Otherwise, start on the current block we are on.
if( ! m_pCurRSBlk )
{
if( ! m_pFirstRSBlk ) // Will be null if no entries.
{
rc = RC_SET( FERR_NOT_FOUND );
goto Exit;
}
if( m_pFirstRSBlk == m_pLastRSBlk )
m_pCurRSBlk = m_pFirstRSBlk;
else
{
m_pCurRSBlk = SelectMidpoint( m_pFirstRSBlk, m_pLastRSBlk, FALSE );
}
if( RC_BAD( rc = m_pCurRSBlk->SetBuffer( m_pBlockBuf1 )))
goto Exit;
}
// Set the exclusive low block and high block.
pLowBlk = m_pFirstRSBlk;
pHighBlk = m_pLastRSBlk;
// Loop until the correct block is found.
for(;;)
{
// Two return value returned: rc and iBlkCompare.
// blk->FindMatch returns FERR_OK if the entry if found in the block.
// returns FERR_NOT_FOUND if not found in the block.
// uiCompare returns RS_EQUALS if entry would be within the block.
// otherwise RS_LESS_THAN if previous blocks should be checked
// and RS_GREATER_THAN if next blocks should be checked.
rc = m_pCurRSBlk->FindMatch(
(FLMBYTE *) vpMatchEntry, uiMatchEntryLength,
(FLMBYTE *) vpFoundEntry, puiFoundEntryLength,
fnCompare, UserValue,
&iBlkCompare );
// Found match or should key be within the block.
if( RC_OK(rc) || (RS_EQUALS == iBlkCompare ))
{
goto Exit;
}
if( RS_LESS_THAN == iBlkCompare )
{
if( m_pCurRSBlk == pLowBlk ) // done if the low block
goto Exit; // keep FERR_NOT_FOUND value
pHighBlk = m_pCurRSBlk->GetPrev();// Set the new high block
}
else // RS_GREATER_THAN == iBlkCompare
{
if( m_pCurRSBlk == pHighBlk ) // done if we are at the high block
goto Exit; // keep FERR_NOT_FOUND value
pLowBlk = m_pCurRSBlk->GetNext();
}
if( RC_BAD( rc = m_pCurRSBlk->SetBuffer( NULL )))
goto Exit;
m_pCurRSBlk = SelectMidpoint( pLowBlk, pHighBlk, FALSE );
// GWBUG 46817 - need to set the working buffer.
if( RC_BAD( rc = m_pCurRSBlk->SetBuffer( m_pBlockBuf1 )))
goto Exit;
}
Exit:
return( rc);
}
/****************************************************************************
Desc: Select the midpoint between two different blocks in a list.
Entries should not be the same value.
****************************************************************************/
FResultSetBlk * FResultSet::SelectMidpoint(
FResultSetBlk *pLowBlk,
FResultSetBlk *pHighBlk,
FLMBOOL bPickHighIfNeighbors)
{
int siCount;
FResultSetBlk *pTempBlk;
// If the same then return.
if( pLowBlk == pHighBlk )
{
pTempBlk = pLowBlk;
goto Exit;
}
// Check if neighbors and use the boolean flag.
if( pLowBlk->GetNext() == pHighBlk)
{
if( bPickHighIfNeighbors )
pTempBlk = pHighBlk;
else
pTempBlk = pLowBlk;
goto Exit;
}
// Count the total blocks exclusive between low and high and add one.
// Check pTempBlk against null to not crash.
for( pTempBlk = pLowBlk, siCount = 1;
pTempBlk && (pTempBlk != pHighBlk );
siCount++ )
{
pTempBlk = pTempBlk->GetNext();
}
// Check for implementation error - pTempBlk is NULL and handle.
flmAssert( NULL != pTempBlk );
if( ! pTempBlk ) // on error
{
pTempBlk = pLowBlk; // Just position to low block to be safe.
goto Exit;
}
// Loop to the middle item.
siCount = siCount >> 1; // Divide by two.
for( pTempBlk = pLowBlk; siCount > 0; siCount-- )
{
pTempBlk = pTempBlk->GetNext();
}
Exit:
return pTempBlk;
}
/****************************************************************************
Public: SetPosition
Desc: Set the current entry position.
In: uiPosition - Zero based position value or RS_POSITION_NOT_SET
to set back to the beginning or end.
Ret: FERR_OK - Returned OK
FERR_EOF_HIT - Positioned to the end of the result set.
****************************************************************************/
RCODE FResultSet::SetPosition(
FLMUINT uiPosition)
{
RCODE rc = FERR_OK;
FResultSetBlk * pInitialBlk = m_pCurRSBlk;
flmAssert( m_bFinalizeCalled );
if( uiPosition == RS_POSITION_NOT_SET )
{
// GWBUG 46817 - set out of focus.
if( m_pCurRSBlk )
{
if( RC_BAD( rc = m_pCurRSBlk->SetBuffer( NULL )))
goto Exit;
}
m_pCurRSBlk = NULL;
goto Exit;
}
// else position to the correct block that holds uiPosition.
if( !m_pCurRSBlk )
{
m_pCurRSBlk = m_pFirstRSBlk;
}
// Check for empty result set.
if( !m_pCurRSBlk )
{
rc = RC_SET( FERR_EOF_HIT );
goto Exit;
}
if( uiPosition < m_pCurRSBlk->GetInitialPosition() )
{
// Go backwards looking for the correct block.
do
{
m_pCurRSBlk = m_pCurRSBlk->GetPrev();
flmAssert( NULL != m_pCurRSBlk );
}
while( uiPosition < m_pCurRSBlk->GetInitialPosition() );
}
else if( uiPosition >=
m_pCurRSBlk->GetInitialPosition() + m_pCurRSBlk->GetNumberOfEntries() )
{
// Go forward looking for the correct block.
do
{
if( ! m_pCurRSBlk->GetNext() )
{
// Will set rc to EOF in SetPosition below.
break;
}
m_pCurRSBlk = m_pCurRSBlk->GetNext();
}
while( uiPosition >=
m_pCurRSBlk->GetInitialPosition() + m_pCurRSBlk->GetNumberOfEntries() );
}
// GWBUG 46817 - need working buffer out of focus.
if( pInitialBlk != m_pCurRSBlk )
{
if( pInitialBlk)
{
if( RC_BAD( rc = pInitialBlk->SetBuffer( NULL )))
goto Exit;
}
// GWBUG 46817 - need working buffer into focus.
if( RC_BAD( rc = m_pCurRSBlk->SetBuffer( m_pBlockBuf1 )))
goto Exit;
}
// Now we are positioned to the correct block.
rc = m_pCurRSBlk->SetPosition( uiPosition );
Exit:
return( rc);
}
/****************************************************************************
Desc: Return a pointer to the next entry in the list.
****************************************************************************/
RCODE FResultSet::GetNextPtr(
FResultSetBlk ** ppCurBlk,
FLMBYTE ** ppBuffer,
FLMUINT * puiReturnLength)
{
RCODE rc;
FResultSetBlk *pCurBlk = *ppCurBlk;
FResultSetBlk *pNextBlk;
FLMBYTE * pBuffer;
flmAssert( NULL != pCurBlk );
while( RC_BAD( rc = pCurBlk->GetNextPtr( ppBuffer, puiReturnLength)))
{
if( rc == FERR_EOF_HIT )
{
if( pCurBlk->GetNext())
{
pNextBlk = pCurBlk->GetNext();
if( !pNextBlk->IsFirstInChain() )
{
pBuffer = pCurBlk->GetBuffer();
pCurBlk->SetBuffer( NULL );
pCurBlk = pNextBlk;
if( RC_BAD( rc = pCurBlk->SetBuffer( pBuffer )))
goto Exit;
*ppCurBlk = pCurBlk;
continue;
}
}
}
goto Exit;
}
Exit:
return( rc);
}
/****************************************************************************
Desc: Union two block lists into a result output list. This may be
called to union two result sets or to perform the initial merge-sort
on a create result set.
Performaing an N-way merge would be fast when we have over 10K
of entries. However, the code is more complex.
Notes: Intersection and cross product routines are very similar
to this routine.
****************************************************************************/
RCODE FResultSet::UnionBlkLists(
FResultSetBlk *pLeftBlk, // (IN) Left block
FResultSetBlk *pRightBlk) // (IN) Right block - may be NULL
{
RCODE rc = FERR_OK;
FLMBYTE * pLeftEntry,
* pRightEntry;
FLMUINT uiLeftLength, uiRightLength;
// If no right block then copy all of the items from the left block
// to the output block. We could optimize this in the future.
if( ! pRightBlk )
{
rc = CopyRemainingItems( pLeftBlk );
goto Exit;
}
// Now the fun begins. Read entries from both lists and union
// while checking the order of the entries.
if( RC_BAD(rc = GetNextPtr( &pLeftBlk, &pLeftEntry, &uiLeftLength )))
{
if( rc == FERR_EOF_HIT )
{
rc = CopyRemainingItems( pRightBlk );
}
goto Exit;
}
if( RC_BAD(rc = GetNextPtr( &pRightBlk, &pRightEntry, &uiRightLength )))
{
if( rc == FERR_EOF_HIT )
{
rc = CopyRemainingItems( pLeftBlk );
}
goto Exit;
}
for(;;)
{
FLMINT iCompare;
if( RC_BAD( rc = m_fnCompare( pLeftEntry, uiLeftLength,
pRightEntry, uiRightLength,
m_UserValue, &iCompare)))
{
goto Exit;
}
if( iCompare == RS_LESS_THAN )
{
// Take the left item.
if( RC_BAD(rc = AddEntry( pLeftEntry, uiLeftLength )))
goto Exit;
if( RC_BAD(rc = GetNextPtr( &pLeftBlk, &pLeftEntry, &uiLeftLength )))
{
if( rc != FERR_EOF_HIT )
goto Exit;
if( RC_BAD(rc = AddEntry( pRightEntry, uiRightLength )))
goto Exit;
// Left entries are done - read all of the right entries.
rc = CopyRemainingItems( pRightBlk );
goto Exit;
}
}
else
{
// If equals then drop the right item and continue comparing left.
// WARNING: Don't try to optimize for equals because when one
// list runs out the remaining duplicate entries must be dropped.
// Continuing to compare the duplicate item is the correct way.
if( (iCompare == RS_GREATER_THAN) || !m_bDropDuplicates )
{
// Take the right item.
if( RC_BAD(rc = AddEntry( pRightEntry, uiRightLength )))
goto Exit;
}
if( RC_BAD(rc = GetNextPtr( &pRightBlk, &pRightEntry, &uiRightLength)))
{
if( rc != FERR_EOF_HIT )
goto Exit;
if( RC_BAD(rc = AddEntry( pLeftEntry, uiLeftLength )))
goto Exit;
// Right entries are done - read all of the left entries.
rc = CopyRemainingItems( pLeftBlk );
goto Exit;
}
}
}
Exit:
if( RC_OK(rc))
{
// Flush out the output entries.
rc = m_pCurRSBlk->Finalize( TRUE );
m_pCurRSBlk->SetBuffer( NULL );
m_pCurRSBlk = NULL;
if( m_fnCallback )
{
++m_CallbackInfo.ui64UnitsDone;
(void) m_fnCallback( &m_CallbackInfo );
}
}
return( rc);
}
/****************************************************************************
Desc: Copy the remaining items from a block list to the output.
****************************************************************************/
RCODE FResultSet::CopyRemainingItems(
FResultSetBlk * pCurBlk)
{
RCODE rc;
FLMBYTE * pEntry;
FLMUINT uiLength;
while( RC_OK(rc = GetNextPtr( &pCurBlk, &pEntry, &uiLength )))
{
if( RC_BAD(rc = AddEntry( pEntry, uiLength )))
goto Exit;
}
if( rc == FERR_EOF_HIT )
{
rc = FERR_OK;
}
Exit:
return( rc);
}
/****************************************************************************
Desc: Closes and deletes one of two files.
****************************************************************************/
void FResultSet::CloseFile(
F_64BitFileHandle ** ppFileHdl64)
{
if( ppFileHdl64 == &m_pFileHdl641)
{
if( m_bFile1Opened)
{
m_pFileHdl641->Close( TRUE);
m_bFile1Opened = FALSE;
}
if (m_pFileHdl641)
{
m_pFileHdl641->Release();
m_pFileHdl641 = NULL;
}
}
else
{
if( m_bFile2Opened)
{
m_pFileHdl642->Close( TRUE);
m_bFile2Opened = FALSE;
}
if (m_pFileHdl642)
{
m_pFileHdl642->Release();
m_pFileHdl642 = NULL;
}
}
return;
}
/****************************************************************************
Desc: Close the file if previously opened and creates the file.
****************************************************************************/
RCODE FResultSet::OpenFile(
F_64BitFileHandle ** ppFileHdl64)
{
RCODE rc = FERR_OK;
FLMBOOL * pbFileOpened;
char * pszIoPath;
// Will close and delete if opened, else will do nothing.
CloseFile( ppFileHdl64 );
if( ppFileHdl64 == &m_pFileHdl641 )
{
pbFileOpened = &m_bFile1Opened;
pszIoPath = &m_szFilePath1[ 0];
}
else
{
pbFileOpened = &m_bFile2Opened;
pszIoPath = &m_szFilePath2[ 0];
}
f_strcpy( pszIoPath, m_szDefaultPath);
if( (*ppFileHdl64 = f_new F_64BitFileHandle) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
if( RC_BAD( rc = (*ppFileHdl64)->CreateUnique( pszIoPath, "frs")))
{
(*ppFileHdl64)->Release();
*ppFileHdl64 = NULL;
goto Exit;
}
*pbFileOpened = TRUE;
Exit:
return( rc);
}