Files
mars-flaim/flaim/src/fqsrch.cpp
2006-06-13 18:46:11 +00:00

1313 lines
30 KiB
C++

//-------------------------------------------------------------------------
// Desc: Query search
// Tabs: 3
//
// Copyright (c) 1996-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: fqsrch.cpp 12329 2006-01-20 17:49:30 -0700 (Fri, 20 Jan 2006) ahodgkinson $
//-------------------------------------------------------------------------
#include "flaimsys.h"
// Call progress callback once every second
#define STATUS_CB_INTERVAL 1
FSTATIC RCODE flmCurSetSubQuery(
CURSOR * pCursor,
SUBQUERY * pSubQuery);
FSTATIC int DRNCompareFunc(
void * pvData1,
void * pvData2,
void * pvUserValue);
FSTATIC RCODE flmCurRecValidate(
eFlmFuncs eFlmFuncId,
CURSOR * pCursor,
SUBQUERY * pSubQuery,
FLMUINT * puiSkipCount,
FLMUINT * puiCount,
FLMBOOL * pbReturnRecOK);
FSTATIC RCODE flmCurRetrieveRec(
FDB * pDb,
SUBQUERY * pSubQuery,
FLMUINT uiContainer);
FSTATIC RCODE flmCurSearchIndex(
eFlmFuncs eFlmFuncId,
CURSOR * pCursor,
FLMBOOL bFirstRead,
FLMBOOL bReadForward,
SUBQUERY * pSubQuery,
FLMUINT * puiCount,
FLMUINT * puiSkipCount,
FLMBOOL bGettingRecord);
FSTATIC RCODE flmCurSearchPredicate(
eFlmFuncs eFlmFuncId,
CURSOR * pCursor,
FLMBOOL bFirstRead,
FLMBOOL bReadForward,
SUBQUERY * pSubQuery,
FLMUINT * puiCount,
FLMUINT * puiSkipCount);
FSTATIC RCODE flmCurSearchContainer(
eFlmFuncs eFlmFuncId,
CURSOR * pCursor,
FLMBOOL bFirstRead,
FLMBOOL bReadForward,
SUBQUERY * pSubQuery,
FLMUINT * puiCount,
FLMUINT * puiSkipCount);
FSTATIC RCODE flmCurEvalSingleRec(
eFlmFuncs eFlmFuncId,
CURSOR * pCursor,
FLMBOOL bFirstRead,
FLMBOOL bReadForward,
SUBQUERY * pSubQuery,
FLMUINT * puiCount,
FLMUINT * puiSkipCount);
/****************************************************************************
Desc: This routine will do all the setup needed to establish a sub-query as
the current subquery for the query.
****************************************************************************/
FSTATIC RCODE flmCurSetSubQuery(
CURSOR * pCursor,
SUBQUERY * pSubQuery
)
{
RCODE rc = FERR_OK;
pCursor->pCurrSubQuery = pSubQuery;
// Do setup associated with a change of sub-queries.
f_memset( &pSubQuery->SQStatus, 0, sizeof( FCURSOR_SUBQUERY_STATUS));
pSubQuery->SQStatus.hDb = (HFDB)pCursor->pDb;
pSubQuery->SQStatus.uiContainerNum = pCursor->uiContainer;
if (pSubQuery->OptInfo.eOptType == QOPT_USING_INDEX)
{
pSubQuery->SQStatus.uiIndexNum = pSubQuery->OptInfo.uiIxNum;
// Make sure that all of the keys have been committed.
if (RC_BAD( rc = KYFlushKeys( pCursor->pDb)))
{
goto Exit;
}
}
else if (pSubQuery->OptInfo.eOptType == QOPT_SINGLE_RECORD_READ)
{
pSubQuery->bRecReturned = FALSE;
}
Exit:
return( rc);
}
/****************************************************************************
Desc: DRN comparison callback function for dynamic result set.
****************************************************************************/
FSTATIC int DRNCompareFunc(
void * pvData1,
void * pvData2,
void * // pvUserValue
)
{
if( *((FLMUINT *)pvData1) < *((FLMUINT *)pvData2))
{
return -1;
}
else if( *((FLMUINT *)pvData1) > *((FLMUINT *)pvData2))
{
return 1;
}
return 0;
}
/****************************************************************************
Desc: Validate a record that has passed the search criteria. This routine
checks the record against the validation function and also checks it
against the result set, if there is one.
****************************************************************************/
FSTATIC RCODE flmCurRecValidate(
eFlmFuncs eFlmFuncId,
CURSOR * pCursor,
SUBQUERY * pSubQuery,
FLMUINT * puiSkipCount,
FLMUINT * puiCount,
FLMBOOL * pbReturnRecOK)
{
RCODE rc = FERR_OK;
FLMBOOL bSavedInvisTrans;
// At this point, we have a record that has passed all the selection
// criteria in the query. If we have a record validator callback,
// call it.
if (pCursor->fnRecValidator)
{
CB_ENTER( pCursor->pDb, &bSavedInvisTrans);
*pbReturnRecOK = (pCursor->fnRecValidator)( eFlmFuncId,
(HFDB)pCursor->pDb, pCursor->uiContainer,
pSubQuery->pRec, NULL, pCursor->RecValData, &rc);
CB_EXIT( pCursor->pDb, bSavedInvisTrans);
if (!(*pbReturnRecOK))
{
pSubQuery->SQStatus.uiNumRejectedByCallback++;
rc = FERR_OK;
goto Exit;
}
else if (RC_BAD( rc))
{
goto Exit;
}
}
// Record passed all criteria -- check for dups if necessary.
if (pCursor->bEliminateDups)
{
// Setup the result set
if( !pCursor->pDRNSet)
{
char szTmpDir[ F_PATH_MAX_SIZE];
szTmpDir[ 0] = 0;
if ((pCursor->pDRNSet = f_new F_DynSearchSet) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
if( gv_FlmSysData.bTempDirSet && gv_FlmSysData.szTempDir[ 0])
{
if( RC_BAD( rc = flmGetTmpDir( szTmpDir)))
{
goto Exit;
}
}
if( !szTmpDir[ 0])
{
if( RC_BAD( rc = gv_FlmSysData.pFileSystem->pathReduce(
pCursor->pDb->pFile->pszDbPath, szTmpDir, NULL)))
{
goto Exit;
}
}
if (RC_BAD( rc = pCursor->pDRNSet->setup( szTmpDir, sizeof( FLMUINT))))
{
goto Exit;
}
pCursor->pDRNSet->setCompareFunc( DRNCompareFunc, NULL);
}
if (RC_BAD( rc = pCursor->pDRNSet->addEntry( &pSubQuery->uiDrn)))
{
if (rc == NE_FLM_EXISTS)
{
*pbReturnRecOK = FALSE;
rc = FERR_OK;
pSubQuery->SQStatus.uiDupsEliminated++;
}
goto Exit;
}
}
// If we get here, the record passed all possible criteria.
// However, it will not be returned if we are skipping
// or counting.
pSubQuery->SQStatus.uiMatchedCnt++;
// If this is a request to skip records, make sure that the
// correct number have been skipped.
if (puiSkipCount && (--(*puiSkipCount) > 0))
{
// If we are skipping, we need to continue past this
// record.
*pbReturnRecOK = FALSE;
goto Exit;
}
// If we are counting, we also want to continue past this
// record.
if (puiCount)
{
(*puiCount)++;
*pbReturnRecOK = FALSE;
goto Exit;
}
// If we get to here, we are going to keep the record and
// pass it back out to the caller.
*pbReturnRecOK = TRUE;
Exit:
return( rc);
}
/****************************************************************************
Desc: Does the actual reading of a record from cache or disk.
****************************************************************************/
FSTATIC RCODE flmCurRetrieveRec(
FDB * pDb,
SUBQUERY * pSubQuery,
FLMUINT uiContainer
)
{
RCODE rc = FERR_OK;
if (RC_BAD( rc = flmRcaRetrieveRec( pDb, NULL, uiContainer,
pSubQuery->uiDrn, TRUE, NULL, NULL, &pSubQuery->pRec)))
{
goto Exit;
}
pSubQuery->bRecIsAKey = FALSE;
Exit:
if (RC_BAD( rc) && pSubQuery->pRec)
{
pSubQuery->pRec->Release();
pSubQuery->pRec = NULL;
pSubQuery->uiDrn = 0;
}
return( rc);
}
/****************************************************************************
Desc: Searches an index for matching record.
****************************************************************************/
FSTATIC RCODE flmCurSearchIndex(
eFlmFuncs eFlmFuncId,
CURSOR * pCursor,
FLMBOOL bFirstRead,
FLMBOOL bReadForward,
SUBQUERY * pSubQuery,
FLMUINT * puiCount,
FLMUINT * puiSkipCount,
FLMBOOL bGettingRecord
)
{
RCODE rc = FERR_OK;
FDB * pDb = pCursor->pDb;
FSIndexCursor * pFSIndexCursor = pSubQuery->pFSIndexCursor;
FLMUINT uiCBTimer = 0;
FLMUINT uiCurrCBTime;
FLMBOOL bReturnRecOK;
FLMBOOL bDoKeyMatch;
FLMBOOL bDoRecMatch;
FLMBOOL bNeedToGetFullRec;
FLMBOOL bSaveDoRecMatch;
FLMBOOL bSaveNeedToGetFullRec;
FLMUINT uiCPUReleaseCnt = 0;
FLMBOOL bSavedInvisTrans;
FLMUINT uiStartTime = FLM_GET_TIMER();
FLMUINT uiCurrTime;
FLMUINT uiTimeLimit = pCursor->uiTimeLimit;
if (pCursor->fnStatus)
{
uiCBTimer = FLM_SECS_TO_TIMER_UNITS( STATUS_CB_INTERVAL);
}
// Set up initial search parameters.
bSaveDoRecMatch = pSubQuery->OptInfo.bDoRecMatch;
bDoKeyMatch = pSubQuery->OptInfo.bDoKeyMatch;
bSaveNeedToGetFullRec = !bSaveDoRecMatch && bGettingRecord;
pSubQuery->uiDrn = 0;
// Loop to evaluate keys and records.
for (;;)
{
bDoRecMatch = bSaveDoRecMatch;
bNeedToGetFullRec = bSaveNeedToGetFullRec;
// Release the CPU periodically to prevent CPU hog
if (((++uiCPUReleaseCnt) & 0x1F) == 0)
{
f_yieldCPU();
}
// See if we have timed out
if (uiTimeLimit)
{
uiCurrTime = FLM_GET_TIMER();
// Use greater than to compare because if the timeout was one
// second, we want to be sure and give it at least one
// full second. We would rather give it an extra second
// than shortchange it.
if (FLM_ELAPSED_TIME( uiCurrTime, uiStartTime) > uiTimeLimit)
{
rc = RC_SET( FERR_TIMEOUT);
goto Exit;
}
}
// Do the progress callback if enough time has elapsed.
pSubQuery->SQStatus.uiProcessedCnt++;
if (pCursor->fnStatus)
{
uiCurrCBTime = FLM_GET_TIMER();
if (FLM_ELAPSED_TIME( uiCurrCBTime,
pCursor->uiLastCBTime) > uiCBTimer)
{
CB_ENTER( pDb, &bSavedInvisTrans);
rc = (pCursor->fnStatus)( FLM_SUBQUERY_STATUS,
(void *)&pSubQuery->SQStatus,
(void *)FALSE,
pCursor->StatusData);
CB_EXIT( pDb, bSavedInvisTrans);
if (RC_BAD( rc))
{
goto Exit;
}
pCursor->uiLastCBTime = FLM_GET_TIMER();
}
}
// Get the next or previous key or reference
if (bFirstRead)
{
// A value in uiCurrKeyMatch means we have not evaluated the
// current key.
pSubQuery->uiCurrKeyMatch = 0;
rc = (RCODE)((bReadForward)
? pFSIndexCursor->firstKey( pDb,
&pSubQuery->pRec, &pSubQuery->uiDrn)
: pFSIndexCursor->lastKey( pDb,
&pSubQuery->pRec, &pSubQuery->uiDrn));
if (RC_OK( rc))
{
bFirstRead = FALSE;
}
pSubQuery->bFirstReference = TRUE;
}
else if (!pSubQuery->bFirstReference)
{
rc = (RCODE)((bReadForward)
? pFSIndexCursor->nextRef( pDb,
&pSubQuery->uiDrn)
: pFSIndexCursor->prevRef( pDb,
&pSubQuery->uiDrn));
if ((bReadForward && rc == FERR_EOF_HIT) ||
(!bReadForward && rc == FERR_BOF_HIT))
{
rc = FERR_OK;
pSubQuery->bFirstReference = TRUE;
goto Get_Key;
}
// If we don't have a current key, need to get it.
// pSubQuery->pRec could be NULL if we returned the last
// record that passed the criteria back to the caller. In
// that case we just need to get it from the index cursor.
if (!pSubQuery->pRec || !pSubQuery->bRecIsAKey)
{
if (RC_BAD( rc = pFSIndexCursor->currentKey( pDb,
&pSubQuery->pRec, &pSubQuery->uiDrn)))
{
goto Exit;
}
pSubQuery->bRecIsAKey = TRUE;
// These should have been set by currentKey
flmAssert( pSubQuery->pRec->getContainerID() ==
pCursor->uiContainer);
flmAssert( pSubQuery->pRec->getID() == pSubQuery->uiDrn);
}
else
{
// Container should have already been set.
flmAssert( pSubQuery->pRec->getContainerID() ==
pCursor->uiContainer);
// Need to modify DRN to the newly retrieved DRN.
pSubQuery->pRec->setID( pSubQuery->uiDrn);
}
}
else
{
Get_Key:
// A value in uiCurrKeyMatch means we have not evaluated the
// current key.
pSubQuery->uiCurrKeyMatch = 0;
rc = (RCODE)((bReadForward)
? pFSIndexCursor->nextKey( pDb,
&pSubQuery->pRec, &pSubQuery->uiDrn)
: pFSIndexCursor->prevKey( pDb,
&pSubQuery->pRec, &pSubQuery->uiDrn));
}
if (RC_BAD( rc))
{
goto Exit;
}
if (pSubQuery->bFirstReference)
{
pSubQuery->SQStatus.uiKeysTraversed++;
pSubQuery->bRecIsAKey = TRUE;
}
pSubQuery->SQStatus.uiRefsTraversed++;
// Make sure the container of the key matches the container
// of the query. NOTE: flmCurEvalCriteria also does this, but
// we need to do the test here in case it is not called for
// the key below. In that case, we want to avoid retrieving
// the record. Furthermore, even if bHaveDrnFlds is TRUE,
// there is no need to check each DRN of this key, because
// it is guaranteed that none of them will match, so in this
// case we simply want to skip the key entirely.
if (pSubQuery->pRec->getContainerID() != pCursor->uiContainer)
{
pSubQuery->uiCurrKeyMatch = FLM_FALSE;
// If bFirstReference is TRUE at this point, this is the
// first reference of this key, so we need to increment
// the uiNumKeysRejected counter as well as the
// uiNumRefsRejected counter.
if (pSubQuery->bFirstReference)
{
pSubQuery->SQStatus.uiKeysRejected++;
}
pSubQuery->SQStatus.uiRefsRejected++;
// Set bFirstReference to TRUE so it will skip to the next
// key.
pSubQuery->bFirstReference = TRUE;
continue;
}
// See if we need to evaluate the key against the criteria.
if (bDoKeyMatch &&
(pSubQuery->bFirstReference || pSubQuery->bHaveDrnFlds))
{
if (RC_BAD( rc = flmCurEvalCriteria( pCursor, pSubQuery,
pSubQuery->pRec, TRUE,
&pSubQuery->uiCurrKeyMatch)))
{
if (rc == FERR_TRUNCATED_KEY)
{
rc = FERR_OK;
pSubQuery->uiCurrKeyMatch = FLM_UNK;
}
else
{
goto Exit;
}
}
flmAssert( pSubQuery->uiCurrKeyMatch != 0);
if (pSubQuery->uiCurrKeyMatch == FLM_FALSE)
{
// If bFirstReference is TRUE at this point, this is the
// first reference of this key, so we need to increment
// the uiNumKeysRejected counter as well as the
// uiNumRefsRejected counter.
if (pSubQuery->bFirstReference)
{
pSubQuery->SQStatus.uiKeysRejected++;
}
pSubQuery->SQStatus.uiRefsRejected++;
// If we must evaluate DRN fields, we need to go to the
// next reference, so we set bFirstReference to FALSE.
// Otherwise, we set it to TRUE so it will skip to the
// next key.
pSubQuery->bFirstReference = !pSubQuery->bHaveDrnFlds;
// Continue loop to process next key or reference
continue;
}
}
// If we did a key match, see if it passed or was unknown.
// If it passed, no need to do the record match. If it
// was unknown, must do record match.
if (pSubQuery->uiCurrKeyMatch == FLM_TRUE)
{
bDoRecMatch = FALSE;
}
else if (pSubQuery->uiCurrKeyMatch == FLM_UNK)
{
bDoRecMatch = TRUE;
}
else
{
// NOTE: If uiCurrKeyMatch is FLM_FALSE, we should never get
// to this point - it will continue up above. Thus, at this
// point, uiCurrKeyMatch should be zero, meaning that no
// evaluation was done on the current key - which also means
// that bDoKeyMatch better be FALSE at this point.
// Even though bDoKeyMatch is FALSE, bDoRecMatch needs to be left
// unchanged. If bDoRecMatch is FALSE, we know from the query
// criteria that all keys we read through will automatically
// match. If bDoRecMatch is TRUE, we have previously decided
// that we MUST NOT do a key match, but force a record match
// instead. This would be the case where the comparison involves
// a substring that is not the first substring - you can't do a
// key match in that case because it will come out FALSE.
flmAssert( pSubQuery->uiCurrKeyMatch == 0 && !bDoKeyMatch);
}
// Set up to get next reference the next time around.
pSubQuery->bFirstReference = FALSE;
// Retrieve and evalute the data record if necessary.
if (bDoRecMatch)
{
FLMUINT uiRecMatch;
// Because we are fetching the full record here, no need to below.
bNeedToGetFullRec = FALSE;
// Retrieve the record
if (RC_OK( rc = flmCurRetrieveRec( pDb, pSubQuery,
pCursor->uiContainer)))
{
pSubQuery->SQStatus.uiRecsFetchedForEval++;
}
else
{
if (rc == FERR_NOT_FOUND)
{
pSubQuery->SQStatus.uiRecsNotFound++;
rc = RC_SET( FERR_NO_REC_FOR_KEY);
}
goto Exit;
}
// Evaluate the record against the query criteria
if (RC_BAD( rc = flmCurEvalCriteria( pCursor, pSubQuery,
pSubQuery->pRec, FALSE, &uiRecMatch)))
{
goto Exit;
}
if (uiRecMatch != FLM_TRUE)
{
pSubQuery->SQStatus.uiRecsRejected++;
// Continue loop to process next key or reference
continue;
}
}
// If we must return full records, and we have not yet fetched
// the record, retrieve the record now.
if (bNeedToGetFullRec)
{
if (RC_OK( rc = flmCurRetrieveRec( pDb, pSubQuery,
pCursor->uiContainer)))
{
pSubQuery->SQStatus.uiRecsFetchedForView++;
}
else
{
if (rc == FERR_NOT_FOUND)
{
pSubQuery->SQStatus.uiRecsNotFound++;
rc = RC_SET( FERR_NO_REC_FOR_KEY);
}
goto Exit;
}
}
// Record has passed all of the sub-query criteria. See if
// it passes our validation tests.
if (RC_BAD( rc = flmCurRecValidate( eFlmFuncId, pCursor,
pSubQuery, puiSkipCount, puiCount,
&bReturnRecOK)))
{
goto Exit;
}
if (bReturnRecOK)
{
break;
}
}
Exit:
return( rc);
}
/****************************************************************************
Desc: Searches a user predicate for matching record.
****************************************************************************/
FSTATIC RCODE flmCurSearchPredicate(
eFlmFuncs eFlmFuncId,
CURSOR * pCursor,
FLMBOOL bFirstRead,
FLMBOOL bReadForward,
SUBQUERY * pSubQuery,
FLMUINT * puiCount,
FLMUINT * puiSkipCount
)
{
RCODE rc = FERR_OK;
FDB * pDb = pCursor->pDb;
FlmUserPredicate * pPredicate = pSubQuery->pPredicate;
FLMBOOL bSavedInvisTrans;
FLMUINT uiCBTimer = 0;
FLMUINT uiCurrCBTime;
FLMBOOL bReturnRecOK;
FLMUINT uiCPUReleaseCnt = 0;
FLMUINT uiStartTime = FLM_GET_TIMER();
FLMUINT uiCurrTime;
FLMUINT uiTimeLimit = pCursor->uiTimeLimit;
FLMUINT uiRecMatch;
if (pCursor->fnStatus)
{
uiCBTimer = FLM_SECS_TO_TIMER_UNITS( STATUS_CB_INTERVAL);
}
// Set up initial search parameters.
pSubQuery->uiDrn = 0;
// Loop to evaluate keys and records.
for (;;)
{
// Release the CPU periodically to prevent CPU hog
if (((++uiCPUReleaseCnt) & 0x1F) == 0)
{
f_yieldCPU();
}
// See if we have timed out
if (uiTimeLimit)
{
uiCurrTime = FLM_GET_TIMER();
// Use greater than to compare because if the timeout was one
// second, we want to be sure and give it at least one
// full second. We would rather give it an extra second
// than shortchange it.
if (FLM_ELAPSED_TIME( uiCurrTime, uiStartTime) > uiTimeLimit)
{
rc = RC_SET( FERR_TIMEOUT);
goto Exit;
}
}
// Do the progress callback if enough time has elapsed.
pSubQuery->SQStatus.uiProcessedCnt++;
if (pCursor->fnStatus)
{
uiCurrCBTime = FLM_GET_TIMER();
if (FLM_ELAPSED_TIME( uiCurrCBTime,
pCursor->uiLastCBTime) > uiCBTimer)
{
CB_ENTER( pDb, &bSavedInvisTrans);
rc = (pCursor->fnStatus)( FLM_SUBQUERY_STATUS,
(void *)&pSubQuery->SQStatus,
(void *)FALSE,
pCursor->StatusData);
CB_EXIT( pDb, bSavedInvisTrans);
if (RC_BAD( rc))
{
goto Exit;
}
pCursor->uiLastCBTime = FLM_GET_TIMER();
}
}
// Can't go backwards with embedded predicates
CB_ENTER( pDb, &bSavedInvisTrans);
if (bFirstRead)
{
if (bReadForward)
{
rc = pPredicate->firstRecord( (HFDB)pDb,
&pSubQuery->uiDrn, &pSubQuery->pRec);
}
else
{
rc = pPredicate->lastRecord( (HFDB)pDb,
&pSubQuery->uiDrn, &pSubQuery->pRec);
}
if (RC_OK( rc))
{
bFirstRead = FALSE;
}
}
else
{
if (bReadForward)
{
rc = pPredicate->nextRecord( (HFDB)pDb,
&pSubQuery->uiDrn, &pSubQuery->pRec);
}
else
{
rc = pPredicate->prevRecord( (HFDB)pDb,
&pSubQuery->uiDrn, &pSubQuery->pRec);
}
}
CB_EXIT( pDb, bSavedInvisTrans);
if (RC_BAD( rc))
{
goto Exit;
}
pSubQuery->SQStatus.uiRecsFetchedForEval++;
pSubQuery->bRecIsAKey = FALSE;
// Evaluate the record against the query criteria
if (RC_BAD( rc = flmCurEvalCriteria( pCursor, pSubQuery,
pSubQuery->pRec, FALSE, &uiRecMatch)))
{
goto Exit;
}
if (uiRecMatch != FLM_TRUE)
{
pSubQuery->SQStatus.uiRecsRejected++;
// Continue loop to process next key or reference
continue;
}
// Record has passed all of the sub-query criteria. See if
// it passes our validation tests.
if (RC_BAD( rc = flmCurRecValidate( eFlmFuncId, pCursor,
pSubQuery, puiSkipCount, puiCount,
&bReturnRecOK)))
{
goto Exit;
}
if (bReturnRecOK)
{
break;
}
}
Exit:
return( rc);
}
/****************************************************************************
Desc: Searches a container for matching record.
****************************************************************************/
FSTATIC RCODE flmCurSearchContainer(
eFlmFuncs eFlmFuncId,
CURSOR * pCursor,
FLMBOOL bFirstRead,
FLMBOOL bReadForward,
SUBQUERY * pSubQuery,
FLMUINT * puiCount,
FLMUINT * puiSkipCount
)
{
RCODE rc = FERR_OK;
FDB * pDb = pCursor->pDb;
FSDataCursor * pFSDataCursor = pSubQuery->pFSDataCursor;
FLMUINT uiCBTimer = 0;
FLMUINT uiCurrCBTime;
FLMBOOL bReturnRecOK;
FLMUINT uiCPUReleaseCnt = 0;
FLMBOOL bSavedInvisTrans;
FLMUINT uiStartTime = FLM_GET_TIMER();
FLMUINT uiCurrTime;
FLMUINT uiTimeLimit = pCursor->uiTimeLimit;
FLMUINT uiRecMatch;
if (pCursor->fnStatus)
{
uiCBTimer = FLM_SECS_TO_TIMER_UNITS( STATUS_CB_INTERVAL);
}
// Set up initial search parameters.
pSubQuery->uiDrn = 0;
// Loop to evaluate keys and records.
for (;;)
{
// Release the CPU periodically to prevent CPU hog
if (((++uiCPUReleaseCnt) & 0x1F) == 0)
{
f_yieldCPU();
}
// See if we have timed out
if (uiTimeLimit)
{
uiCurrTime = FLM_GET_TIMER();
// Use greater than to compare because if the timeout was one
// second, we want to be sure and give it at least one
// full second. We would rather give it an extra second
// than shortchange it.
if (FLM_ELAPSED_TIME( uiCurrTime, uiStartTime) > uiTimeLimit)
{
rc = RC_SET( FERR_TIMEOUT);
goto Exit;
}
}
// Do the progress callback if enough time has elapsed.
pSubQuery->SQStatus.uiProcessedCnt++;
if (pCursor->fnStatus)
{
uiCurrCBTime = FLM_GET_TIMER();
if (FLM_ELAPSED_TIME( uiCurrCBTime,
pCursor->uiLastCBTime) > uiCBTimer)
{
CB_ENTER( pDb, &bSavedInvisTrans);
rc = (pCursor->fnStatus)( FLM_SUBQUERY_STATUS,
(void *)&pSubQuery->SQStatus,
(void *)FALSE,
pCursor->StatusData);
CB_EXIT( pDb, bSavedInvisTrans);
if (RC_BAD( rc))
{
goto Exit;
}
pCursor->uiLastCBTime = FLM_GET_TIMER();
}
}
// Get the next or previous key or reference
if (bFirstRead)
{
rc = (RCODE)((bReadForward)
? pFSDataCursor->firstRec( pDb,
&pSubQuery->pRec, &pSubQuery->uiDrn)
: pFSDataCursor->lastRec( pDb,
&pSubQuery->pRec, &pSubQuery->uiDrn));
if (RC_OK( rc))
{
bFirstRead = FALSE;
}
}
else
{
rc = (RCODE)((bReadForward)
? pFSDataCursor->nextRec( pDb,
&pSubQuery->pRec, &pSubQuery->uiDrn)
: pFSDataCursor->prevRec( pDb,
&pSubQuery->pRec, &pSubQuery->uiDrn));
}
if (RC_BAD( rc))
{
goto Exit;
}
pSubQuery->SQStatus.uiRecsFetchedForEval++;
pSubQuery->bRecIsAKey = FALSE;
// Evaluate the record against the query criteria
if (RC_BAD( rc = flmCurEvalCriteria( pCursor, pSubQuery,
pSubQuery->pRec, FALSE, &uiRecMatch)))
{
goto Exit;
}
if (uiRecMatch != FLM_TRUE)
{
pSubQuery->SQStatus.uiRecsRejected++;
// Continue loop to process next record
continue;
}
// Record has passed all of the sub-query criteria. See if
// it passes our validation tests.
if (RC_BAD( rc = flmCurRecValidate( eFlmFuncId, pCursor,
pSubQuery, puiSkipCount, puiCount,
&bReturnRecOK)))
{
goto Exit;
}
if (bReturnRecOK)
{
break;
}
}
Exit:
return( rc);
}
/****************************************************************************
Desc: Retrieves a single record and evaluates it.
****************************************************************************/
FSTATIC RCODE flmCurEvalSingleRec(
eFlmFuncs eFlmFuncId,
CURSOR * pCursor,
FLMBOOL bFirstRead,
FLMBOOL bReadForward,
SUBQUERY * pSubQuery,
FLMUINT * puiCount,
FLMUINT * puiSkipCount
)
{
RCODE rc = FERR_OK;
FDB * pDb = pCursor->pDb;
FLMBOOL bReturnRecOK;
FLMUINT uiRecMatch;
// Return EOF or BOF if we have already returned the
// record and it is not a first() or last() call.
if (!bFirstRead && pSubQuery->bRecReturned)
{
rc = (RCODE)((bReadForward)
? RC_SET( FERR_EOF_HIT)
: RC_SET( FERR_BOF_HIT));
goto Exit;
}
// Set up initial search parameters.
pSubQuery->uiDrn = pSubQuery->OptInfo.uiDrn;
if (RC_BAD( rc = flmCurRetrieveRec( pDb, pSubQuery, pCursor->uiContainer)))
{
goto Exit;
}
pSubQuery->SQStatus.uiRecsFetchedForEval++;
// Evaluate the record against the query criteria
if (RC_BAD( rc = flmCurEvalCriteria( pCursor, pSubQuery,
pSubQuery->pRec, FALSE, &uiRecMatch)))
{
goto Exit;
}
if (uiRecMatch != FLM_TRUE)
{
pSubQuery->SQStatus.uiRecsRejected++;
rc = (RCODE)((bReadForward)
? (RCODE)FERR_EOF_HIT
: (RCODE)FERR_BOF_HIT);
goto Exit;
}
if (RC_BAD( rc = flmCurRecValidate( eFlmFuncId, pCursor,
pSubQuery, puiSkipCount, puiCount,
&bReturnRecOK)))
{
goto Exit;
}
if (!bReturnRecOK)
{
rc = (RCODE)((bReadForward)
? (RCODE)FERR_EOF_HIT
: (RCODE)FERR_BOF_HIT);
goto Exit;
}
// Found a record, set bRecReturned to TRUE so that the
// next time we come in on a next() or prev() call, we will
// return EOF or BOF.
pSubQuery->bRecReturned = TRUE;
Exit:
return( rc);
}
/****************************************************************************
Desc: Performs the search operation.
****************************************************************************/
RCODE flmCurSearch(
eFlmFuncs eFlmFuncId,
CURSOR * pCursor,
FLMBOOL bFirstRead,
FLMBOOL bReadForward,
FLMUINT * puiCount,
FLMUINT * puiSkipCount,
FlmRecord ** ppUserRecord,
FLMUINT * puiDrn
)
{
RCODE rc = FERR_OK;
FDB * pDb;
DB_STATS * pDbStats;
RCODE TmpRc;
SUBQUERY * pSubQuery = pCursor->pCurrSubQuery;
FLMBOOL bSavedInvisTrans;
pDb = pCursor->pDb;
if( RC_BAD( rc = flmCurDbInit( pCursor)))
{
goto Exit;
}
if ((pDbStats = pDb->pDbStats) != NULL)
{
pDbStats->bHaveStats = TRUE;
pDbStats->ui64NumCursorReads++;
}
// If this is a first or last call, we need to reset the
// current sub-query.
if (bFirstRead)
{
pSubQuery = pCursor->pSubQueryList;
// If reading backwards, position to the last subquery
// in the list.
if (!bReadForward)
{
while (pSubQuery->pNext)
{
pSubQuery = pSubQuery->pNext;
}
}
if (RC_BAD( rc = flmCurSetSubQuery( pCursor, pSubQuery)))
{
goto Exit;
}
}
// If counting, initialize the count to zero.
if (puiCount)
{
*puiCount = 0;
}
// Loop through sub-queries
for (;;)
{
switch (pSubQuery->OptInfo.eOptType)
{
case QOPT_USING_INDEX:
rc = flmCurSearchIndex( eFlmFuncId,
pCursor, bFirstRead, bReadForward,
pSubQuery, puiCount, puiSkipCount,
(FLMBOOL)((ppUserRecord && !pCursor->bOkToReturnKeys)
? (FLMBOOL)TRUE
: (FLMBOOL)FALSE));
break;
case QOPT_USING_PREDICATE:
rc = flmCurSearchPredicate( eFlmFuncId,
pCursor, bFirstRead, bReadForward, pSubQuery,
puiCount, puiSkipCount);
break;
case QOPT_SINGLE_RECORD_READ:
rc = flmCurEvalSingleRec( eFlmFuncId,
pCursor, bFirstRead, bReadForward, pSubQuery,
puiCount, puiSkipCount);
break;
case QOPT_PARTIAL_CONTAINER_SCAN:
case QOPT_FULL_CONTAINER_SCAN:
rc = flmCurSearchContainer( eFlmFuncId,
pCursor, bFirstRead, bReadForward,
pSubQuery, puiCount, puiSkipCount);
break;
default:
// Should never happen
flmAssert( 0);
rc = RC_SET( FERR_NOT_IMPLEMENTED);
goto Exit;
}
// If rc is FERR_OK, it means we got something that passed.
if (RC_OK( rc))
{
// ppUserRecord will be NULL if this is a DRN only function or
// the record count function.
if (ppUserRecord)
{
flmAssert( pSubQuery->pRec != NULL);
*ppUserRecord = pSubQuery->pRec;
(*ppUserRecord)->AddRef();
// We must release the record here since we are giving it
// back to the caller.
pSubQuery->pRec->Release();
pSubQuery->pRec = NULL;
}
if (puiDrn)
{
*puiDrn = pSubQuery->uiDrn;
}
break;
}
// This is the place where we handle EOF and BOF. We attempt to go to
// the next or previous sub-query, if there is one. If the error is not
// EOF or BOF, we simply go to Exit.
if (rc != (RCODE)((bReadForward)
? (RCODE)FERR_EOF_HIT
: (RCODE)FERR_BOF_HIT))
{
goto Exit;
}
// If there is a callback, call it to give the final status of
// this sub-query.
if (pCursor->fnStatus)
{
CB_ENTER( pDb, &bSavedInvisTrans);
TmpRc = (pCursor->fnStatus)( FLM_SUBQUERY_STATUS,
(void *)&pSubQuery->SQStatus,
(void *)TRUE,
pCursor->StatusData);
CB_EXIT( pDb, bSavedInvisTrans);
if (RC_BAD( TmpRc))
{
rc = TmpRc;
goto Exit;
}
pCursor->uiLastCBTime = FLM_GET_TIMER();
}
// Reached BOF/EOF in the current subquery. Move on to the next one.
pSubQuery = (SUBQUERY *)((bReadForward)
? pSubQuery->pNext
: pSubQuery->pPrev);
if (!pSubQuery)
{
// No more sub-queries. If we were counting and have a non-zero
// count, we can return FERR_OK.
if (puiCount && *puiCount)
{
rc = FERR_OK;
}
//else rc should already be EOF or BOF.
// If we are at the real EOF or BOF, and doing a move
// relative, we must decrement the skip counter by one
// more. That way, if the skip count is set to one and
// we hit EOF, the value returned to the user will be
// one.
if (puiSkipCount)
{
(*puiSkipCount)--;
}
goto Exit;
}
// Set up up to use the next or previous sub-query.
if (RC_BAD( rc = flmCurSetSubQuery( pCursor, pSubQuery)))
{
goto Exit;
}
// For the next (or previous) sub-query, need to set the
// bFirstRead flag to TRUE the first time in.
bFirstRead = TRUE;
}
Exit:
if (pDb)
{
flmExit( eFlmFuncId, pDb, rc);
}
return( rc);
}