Files
mars-flaim/xflaim/src/fqsort.cpp
dsandersoremutah 76cb67c695 Removed some unused parameters from the b-tree methods.
git-svn-id: https://svn.code.sf.net/p/flaim/code/trunk@466 0109f412-320b-0410-ab79-c3e0c5ffbbe6
2006-05-25 21:22:30 +00:00

2771 lines
61 KiB
C++

//------------------------------------------------------------------------------
// Desc: Contains the methods for doing sorting in the F_Query class.
//
// Tabs: 3
//
// Copyright (c) 2005-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: fqsort.cpp 3116 2006-01-19 13:31:53 -0700 (Thu, 19 Jan 2006) dsanders $
//------------------------------------------------------------------------------
#include "flaimsys.h"
#include "fquery.h"
FSTATIC RCODE fqGetDocId(
IXD * pIxd,
const FLMBYTE * pucKey,
FLMUINT uiKeyLen,
FLMUINT64 * pui64DocId);
/***************************************************************************
Desc: Add a sort key to the query.
***************************************************************************/
RCODE FLMAPI F_Query::addSortKey(
void * pvSortKeyContext,
FLMBOOL bChildToContext,
FLMBOOL bElement,
FLMUINT uiDictNum,
FLMUINT uiCompareRules,
FLMUINT uiLimit,
FLMUINT uiKeyComponent,
FLMBOOL bSortDescending,
FLMBOOL bSortMissingHigh,
void ** ppvContext)
{
RCODE rc = NE_XFLM_OK;
ICD * pSortIcd;
ICD * pSortIcdContext;
ICD * pTmpIcd;
// If an error has already occurred, cannot add more to query.
if (RC_BAD( rc = m_rc))
{
goto Exit;
}
if (m_bOptimized)
{
rc = RC_SET( NE_XFLM_Q_TOO_LATE_TO_ADD_SORT_KEYS);
goto Exit;
}
// Verify that the sort key component number is legal
if (uiKeyComponent > XFLM_MAX_SORT_KEYS)
{
rc = RC_SET( NE_XFLM_Q_INVALID_SORT_KEY_COMPONENT);
goto Exit;
}
// If we have not created an IXD, create one now.
if (!m_pSortIxd)
{
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( IXD), (void **)&m_pSortIxd)))
{
goto Exit;
}
// Assume single path - can be unset later on.
m_pSortIxd->uiFlags |= IXD_SINGLE_PATH;
m_pSortIxd->uiCollectionNum = m_uiCollection;
}
// Allocate an ICD structure.
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( ICD),
(void **)&pSortIcd)))
{
goto Exit;
}
pSortIcd->uiCdl = m_pSortIxd->uiNumIcds;
m_pSortIxd->uiNumIcds++;
pSortIcd->pIxd = m_pSortIxd;
pSortIcd->uiIndexNum = m_pSortIxd->uiIndexNum;
pSortIcd->uiDictNum = uiDictNum;
if (!bElement)
{
pSortIcd->uiFlags |= ICD_IS_ATTRIBUTE;
}
if ((pSortIcd->uiKeyComponent = uiKeyComponent) > 0)
{
pSortIcd->uiFlags |= (ICD_VALUE | ICD_REQUIRED_PIECE | ICD_REQUIRED_IN_SET);
pSortIcd->uiCompareRules = uiCompareRules;
if (bSortDescending)
{
pSortIcd->uiFlags |= ICD_DESCENDING;
}
if (bSortMissingHigh)
{
pSortIcd->uiFlags |= ICD_MISSING_HIGH;
}
if (!uiLimit)
{
pSortIcd->uiLimit = ICD_DEFAULT_LIMIT;
}
else
{
pSortIcd->uiLimit = uiLimit;
}
m_pSortIxd->uiNumKeyComponents++;
// Link into list of key components where it goes.
pTmpIcd = m_pSortIxd->pFirstKey;
while (pTmpIcd &&
pSortIcd->uiKeyComponent > pTmpIcd->uiKeyComponent)
{
pTmpIcd = pTmpIcd->pNextKeyComponent;
}
// Cannot have two components with the same value.
if (pTmpIcd && pSortIcd->uiKeyComponent == pTmpIcd->uiKeyComponent)
{
rc = RC_SET( NE_XFLM_Q_DUPLICATE_SORT_KEY_COMPONENT);
goto Exit;
}
if ((pSortIcd->pNextKeyComponent = pTmpIcd) == NULL)
{
// Link at end of list.
if ((pSortIcd->pPrevKeyComponent = m_pSortIxd->pLastKey) != NULL)
{
m_pSortIxd->pLastKey->pNextKeyComponent = pSortIcd;
}
else
{
m_pSortIxd->pFirstKey = pSortIcd;
}
m_pSortIxd->pLastKey = pSortIcd;
}
else
{
// Link in front of pTmpIcd
flmAssert( pSortIcd->uiKeyComponent < pTmpIcd->uiKeyComponent);
if ((pSortIcd->pPrevKeyComponent =
pTmpIcd->pPrevKeyComponent) == NULL)
{
m_pSortIxd->pFirstKey = pSortIcd;
}
else
{
pTmpIcd->pPrevKeyComponent->pNextKeyComponent = pSortIcd;
}
pTmpIcd->pPrevKeyComponent = pSortIcd;
}
}
else
{
m_pSortIxd->uiNumContextComponents++;
if ((pSortIcd->pPrevKeyComponent = m_pSortIxd->pLastContext) == NULL)
{
m_pSortIxd->pFirstContext = pSortIcd;
}
m_pSortIxd->pLastContext = pSortIcd;
}
if ((pSortIcdContext = (ICD *)(pvSortKeyContext)) != NULL)
{
flmAssert( m_pSortIxd->pIcdTree);
if (bChildToContext)
{
// Attributes cannot be parents to another sort ICD.
if (pSortIcdContext->uiFlags & ICD_IS_ATTRIBUTE)
{
rc = RC_SET( NE_XFLM_Q_SORT_KEY_CONTEXT_MUST_BE_ELEMENT);
goto Exit;
}
pSortIcd->pParent = pSortIcdContext;
if ((pSortIcd->pNextSibling = pSortIcdContext->pFirstChild) != NULL)
{
pSortIcd->pNextSibling->pPrevSibling = pSortIcd;
}
pSortIcdContext->pFirstChild = pSortIcd;
if (pSortIcdContext->pNextSibling || pSortIcd->pPrevSibling)
{
m_pSortIxd->uiFlags &= (~(IXD_SINGLE_PATH));
}
}
else
{
pSortIcd->pParent = pSortIcdContext->pParent;
pSortIcd->pPrevSibling = pSortIcdContext;
if ((pSortIcd->pNextSibling = pSortIcdContext->pNextSibling) != NULL)
{
pSortIcd->pNextSibling->pPrevSibling = pSortIcd;
}
pSortIcdContext->pNextSibling = pSortIcd;
if (pSortIcdContext->pFirstChild)
{
m_pSortIxd->uiFlags &= (~(IXD_SINGLE_PATH));
}
}
}
else if (m_pSortIxd->pIcdTree)
{
pSortIcdContext = m_pSortIxd->pIcdTree;
while (pSortIcdContext->pNextSibling)
{
if (pSortIcdContext->pFirstChild)
{
m_pSortIxd->uiFlags &= (~(IXD_SINGLE_PATH));
}
pSortIcdContext = pSortIcdContext->pNextSibling;
}
if (pSortIcdContext->pFirstChild)
{
m_pSortIxd->uiFlags &= (~(IXD_SINGLE_PATH));
}
pSortIcdContext->pNextSibling = pSortIcd;
pSortIcd->pPrevSibling = pSortIcdContext;
}
else
{
m_pSortIxd->pIcdTree = pSortIcd;
}
if (ppvContext)
{
*ppvContext = (void *)pSortIcd;
}
Exit:
m_rc = rc;
return( rc);
}
/***************************************************************************
Desc: Verify the sort keys, if any. This method is called during
optimization.
***************************************************************************/
RCODE F_Query::verifySortKeys( void)
{
RCODE rc = NE_XFLM_OK;
ICD * pSortIcd;
FLMUINT uiExpectedKeyComponent;
// This routine should not be called if there were no sort keys specified.
flmAssert( m_pSortIxd);
if ((pSortIcd = m_pSortIxd->pFirstKey) == NULL)
{
// No sort keys were really specified. The user called addSortKey
// without ever setting one of the sort key components.
m_pSortIxd = NULL;
rc = RC_SET( NE_XFLM_Q_NO_SORT_KEY_COMPONENTS_SPECIFIED);
goto Exit;
}
// Set the language for the sort keys
m_pSortIxd->uiLanguage = m_pDb->m_pDatabase->m_uiDefaultLanguage;
// Verify that we don't have any missing sort key components,
// and that we can get all of their data types.
uiExpectedKeyComponent = 1;
while (pSortIcd)
{
if (pSortIcd->uiKeyComponent != uiExpectedKeyComponent)
{
rc = RC_SET( NE_XFLM_Q_MISSING_SORT_KEY_COMPONENT);
goto Exit;
}
// Verify the dictionary number and get its data type.
if (pSortIcd->uiDictNum != ELM_ROOT_TAG)
{
F_AttrElmInfo info;
if (!(pSortIcd->uiFlags & ICD_IS_ATTRIBUTE))
{
if (RC_BAD( rc = m_pDb->m_pDict->getElement( m_pDb,
pSortIcd->uiDictNum, &info)))
{
if (rc == NE_XFLM_BAD_ELEMENT_NUM)
{
rc = RC_SET( NE_XFLM_Q_INVALID_ELEMENT_NUM_IN_SORT_KEYS);
}
goto Exit;
}
}
else
{
if (RC_BAD( rc = m_pDb->m_pDict->getAttribute( m_pDb,
pSortIcd->uiDictNum, &info)))
{
if (rc == NE_XFLM_BAD_ATTRIBUTE_NUM)
{
rc = RC_SET( NE_XFLM_Q_INVALID_ATTR_NUM_IN_SORT_KEYS);
}
goto Exit;
}
}
icdSetDataType( pSortIcd, info.m_uiDataType);
}
// Go to the next sort key component
pSortIcd = pSortIcd->pNextKeyComponent;
uiExpectedKeyComponent++;
}
Exit:
return( rc);
}
/***************************************************************************
Desc: Add the current document to the result set.
***************************************************************************/
RCODE F_Query::addToResultSet( void)
{
RCODE rc = NE_XFLM_OK;
FLMBYTE ucKey [XFLM_MAX_SORT_KEYS * 3 + 20];
FLMBYTE * pucTmp;
FLMUINT uiKeyLen;
FLMUINT64 ui64DocId;
FLMUINT uiIDLen;
if (m_pSortIxd)
{
// This routine should only be called if we have sort keys.
m_pSortResultSet->setIxInfo( m_pDb, m_pSortIxd);
// Call index document to generate the sort keys for the document.
if (RC_BAD( rc = m_pDb->indexDocument( m_pSortIxd, (F_DOMNode *)m_pCurrDoc)))
{
goto Exit;
}
// Take only the lowest sort key for the document.
if (m_pDb->m_uiKrefCount)
{
KREF_ENTRY * pKref;
pKref = m_pDb->m_pKrefTbl [0];
if (RC_BAD( rc = m_pSortResultSet->addEntry( (FLMBYTE *)&pKref [1],
(FLMUINT)pKref->ui16KeyLen, TRUE)))
{
goto Exit;
}
}
else
{
uiKeyLen = 2 * m_pSortIxd->uiNumKeyComponents;
// Generate an empty key and add to the result set.
f_memset( ucKey, 0, uiKeyLen);
// Put in document ID - so we can pull it out when traversing
// through the result set.
if (RC_BAD( rc = m_pCurrDoc->getDocumentId( (IF_Db *)m_pDb, &ui64DocId)))
{
goto Exit;
}
pucTmp = &ucKey [uiKeyLen];
uiIDLen = f_encodeSEN( ui64DocId, &pucTmp);
// Output a zero SEN (which is one byte) for all of the other node IDs.
f_memset( &ucKey [uiKeyLen + uiIDLen],
0, m_pSortIxd->uiNumKeyComponents);
uiIDLen += m_pSortIxd->uiNumKeyComponents;
uiKeyLen += uiIDLen;
if (RC_BAD( rc = m_pSortResultSet->addEntry( ucKey, uiKeyLen,
TRUE)))
{
goto Exit;
}
}
// Empty the table out for next document.
m_pDb->m_pKrefPool->poolReset( NULL, TRUE);
m_pDb->m_uiKrefCount = 0;
m_pDb->m_uiTotalKrefBytes = 0;
}
else
{
FLMUINT32 ui32Count = (FLMUINT32)m_pSortResultSet->getCount();
// If there is no sort key, the m_bPositioningEnabled flag better
// be set - meaning we are just keeping a result set with the objects
// in the order we found them.
flmAssert( m_bPositioningEnabled);
ui32Count++;
f_UINT32ToBigEndian( ui32Count, ucKey);
pucTmp = &ucKey [4];
if (RC_BAD( rc = m_pCurrDoc->getDocumentId( (IF_Db *)m_pDb, &ui64DocId)))
{
goto Exit;
}
uiIDLen = f_encodeSEN( ui64DocId, &pucTmp);
if (RC_BAD( rc = m_pSortResultSet->addEntry( ucKey, uiIDLen + 4,
TRUE)))
{
goto Exit;
}
}
m_ui64RSDocsPassed++;
Exit:
return( rc);
}
/***************************************************************************
Desc: Destructor for query result set.
***************************************************************************/
F_QueryResultSet::~F_QueryResultSet()
{
if (m_pBTree)
{
m_pBTree->btClose();
m_pBTree->Release();
}
if (m_pResultSetDb)
{
if (m_pResultSetDb->getTransType() != XFLM_NO_TRANS)
{
m_pResultSetDb->transAbort();
}
m_pResultSetDb->Release();
m_pResultSetDb = NULL;
gv_pXFlmDbSystem->dbRemove( m_szResultSetDibName, NULL, NULL, TRUE);
}
if (m_hMutex != F_MUTEX_NULL)
{
f_mutexDestroy( &m_hMutex);
}
}
/***************************************************************************
Desc: Initialize a query result set.
***************************************************************************/
RCODE F_QueryResultSet::initResultSet(
FLMBOOL bUseIxCompareObj,
FLMBOOL bEnableEncryption)
{
RCODE rc = NE_XFLM_OK;
XFLM_CREATE_OPTS createOpts;
FLMUINT uiNum = (FLMUINT)this;
flmAssert( !m_pResultSetDb);
// Create a mutex.
if (RC_BAD( f_mutexCreate( &m_hMutex)))
{
goto Exit;
}
f_memset( &createOpts, 0, sizeof( XFLM_CREATE_OPTS));
for (;;)
{
// Generate a random file name
f_sprintf( m_szResultSetDibName, "%x.db", (unsigned)uiNum);
if (RC_OK( rc = gv_pXFlmDbSystem->dbCreate( m_szResultSetDibName,
NULL, NULL, NULL, NULL, &createOpts,
TRUE, (IF_Db **)&m_pResultSetDb)))
{
break;
}
if (rc == NE_XFLM_FILE_EXISTS || rc == NE_FLM_IO_ACCESS_DENIED)
{
// Try again with a slightly altered number.
uiNum -= 10;
rc = NE_XFLM_OK;
}
else
{
goto Exit;
}
}
// Shouldn't have an RFL object - don't want anything
// logged by this database.
flmAssert( !m_pResultSetDb->getDatabase()->m_pRfl);
// Create a b-tree that will hold our result set data.
// Will always be #1.
// Should be one that keeps counts so we can do absolute positioning.
if (RC_BAD( rc = m_pResultSetDb->m_pDatabase->lFileCreate( m_pResultSetDb,
&m_LFile, NULL, 1, XFLM_LF_INDEX, TRUE, FALSE,
(FLMUINT)(bEnableEncryption ? 1 : 0))))
{
goto Exit;
}
// Open a b-tree object to read from and write to the btree.
if ((m_pBTree = f_new F_Btree) == NULL)
{
rc = RC_SET( NE_XFLM_MEM);
goto Exit;
}
// Open the btree and use the specified collection.
if (RC_BAD( rc = m_pBTree->btOpen( m_pResultSetDb,
&m_LFile, TRUE, FALSE,
bUseIxCompareObj
? &m_compareObj
: (IF_ResultSetCompare *)NULL)))
{
goto Exit;
}
Exit:
return( rc);
}
/***************************************************************************
Desc: Add an entry to the result set.
***************************************************************************/
RCODE F_QueryResultSet::addEntry(
FLMBYTE * pucKey,
FLMUINT uiKeyLen,
FLMBOOL bLockMutex)
{
RCODE rc = NE_XFLM_OK;
// May need to lock the mutex when adding an entry - this will hold off
// any other thread that may be trying to read the result set at the
// same time.
if (bLockMutex)
{
lockMutex();
}
m_pBTree->btResetBtree();
if (RC_BAD( rc = m_pBTree->btInsertEntry( pucKey, uiKeyLen, NULL,
0, TRUE, TRUE)))
{
goto Exit;
}
m_uiCount++;
m_bPositioned = FALSE;
Exit:
if (bLockMutex)
{
unlockMutex();
}
return( rc);
}
/***************************************************************************
Desc: Get the first entry in the result set.
***************************************************************************/
RCODE F_QueryResultSet::getFirst(
FLMBYTE * pucKey,
FLMUINT uiKeyBufSize,
FLMUINT * puiKeyLen,
FLMBOOL bLockMutex)
{
RCODE rc = NE_XFLM_OK;
if (bLockMutex)
{
lockMutex();
}
if (RC_BAD( rc = m_pBTree->btFirstEntry( pucKey, uiKeyBufSize, puiKeyLen)))
{
goto Exit;
}
// Remember our current position, so we can reposition there if necessary.
if (RC_BAD( rc = m_pBTree->btGetPosition( pucKey, *puiKeyLen, &m_uiCurrPos)))
{
goto Exit;
}
m_bPositioned = TRUE;
Exit:
if (bLockMutex)
{
unlockMutex();
}
return( rc);
}
/***************************************************************************
Desc: Get the last entry in the result set.
***************************************************************************/
RCODE F_QueryResultSet::getLast(
FLMBYTE * pucKey,
FLMUINT uiKeyBufSize,
FLMUINT * puiKeyLen,
FLMBOOL bLockMutex)
{
RCODE rc = NE_XFLM_OK;
if (bLockMutex)
{
lockMutex();
}
if (RC_BAD( rc = m_pBTree->btLastEntry( pucKey, uiKeyBufSize, puiKeyLen)))
{
goto Exit;
}
// Remember our current position, so we can reposition there if necessary.
if (RC_BAD( rc = m_pBTree->btGetPosition( pucKey, *puiKeyLen, &m_uiCurrPos)))
{
goto Exit;
}
m_bPositioned = TRUE;
Exit:
if (bLockMutex)
{
unlockMutex();
}
return( rc);
}
/***************************************************************************
Desc: Get the next entry from the current position in the result set.
***************************************************************************/
RCODE F_QueryResultSet::getNext(
FLMBYTE * pucKey,
FLMUINT uiKeyBufSize,
FLMUINT * puiKeyLen,
FLMBOOL bLockMutex)
{
RCODE rc = NE_XFLM_OK;
if (bLockMutex)
{
lockMutex();
}
if (m_uiCurrPos == FLM_MAX_UINT)
{
if (RC_BAD( rc = getFirst( pucKey, uiKeyBufSize, puiKeyLen, FALSE)))
{
goto Exit;
}
}
else
{
// m_bPositioned will be set to FALSE if addEntry is called - because
// that changes the positioning of the b-tree. If that happens, we need
// to reposition before calling btNextEntry.
if (!m_bPositioned)
{
if (RC_BAD( rc = m_pBTree->btPositionTo( m_uiCurrPos,
pucKey, uiKeyBufSize, puiKeyLen)))
{
goto Exit;
}
}
if (RC_BAD( rc = m_pBTree->btNextEntry( pucKey, uiKeyBufSize, puiKeyLen)))
{
goto Exit;
}
// Remember our current position, so we can reposition there if necessary.
if (RC_BAD( rc = m_pBTree->btGetPosition( pucKey, *puiKeyLen, &m_uiCurrPos)))
{
goto Exit;
}
}
m_bPositioned = TRUE;
Exit:
if (bLockMutex)
{
unlockMutex();
}
return( rc);
}
/***************************************************************************
Desc: Get the previous entry from the current position in the result set.
***************************************************************************/
RCODE F_QueryResultSet::getPrev(
FLMBYTE * pucKey,
FLMUINT uiKeyBufSize,
FLMUINT * puiKeyLen,
FLMBOOL bLockMutex)
{
RCODE rc = NE_XFLM_OK;
if (bLockMutex)
{
lockMutex();
}
if (m_uiCurrPos == FLM_MAX_UINT)
{
if (RC_BAD( rc = getLast( pucKey, uiKeyBufSize, puiKeyLen, FALSE)))
{
goto Exit;
}
}
else
{
// m_bPositioned will be set to FALSE if addEntry is called - because
// that changes the positioning of the b-tree. If that happens, we need
// to reposition before calling btPrevEntry.
if (!m_bPositioned)
{
if (RC_BAD( rc = m_pBTree->btPositionTo( m_uiCurrPos,
pucKey, uiKeyBufSize, puiKeyLen)))
{
goto Exit;
}
}
if (RC_BAD( rc = m_pBTree->btPrevEntry( pucKey, uiKeyBufSize, puiKeyLen)))
{
goto Exit;
}
// Remember our current position, so we can reposition there if necessary.
if (RC_BAD( rc = m_pBTree->btGetPosition( pucKey, *puiKeyLen, &m_uiCurrPos)))
{
goto Exit;
}
}
m_bPositioned = TRUE;
Exit:
if (bLockMutex)
{
unlockMutex();
}
return( rc);
}
/***************************************************************************
Desc: Get the entry from the current position in the result set.
***************************************************************************/
RCODE F_QueryResultSet::getCurrent(
FLMBYTE * pucKey,
FLMUINT uiKeyBufSize,
FLMUINT * puiKeyLen,
FLMBOOL bLockMutex)
{
RCODE rc = NE_XFLM_OK;
if (bLockMutex)
{
lockMutex();
}
if (m_uiCurrPos == FLM_MAX_UINT)
{
rc = RC_SET( NE_XFLM_BOF_HIT);
goto Exit;
}
else
{
if (RC_BAD( rc = m_pBTree->btPositionTo( m_uiCurrPos,
pucKey, uiKeyBufSize, puiKeyLen)))
{
goto Exit;
}
}
m_bPositioned = TRUE;
Exit:
if (bLockMutex)
{
unlockMutex();
}
return( rc);
}
/***************************************************************************
Desc: Position to a particular entry in the result set.
***************************************************************************/
RCODE F_QueryResultSet::positionToEntry(
FLMBYTE * pucKey,
FLMUINT uiKeyBufSize,
FLMUINT * puiKeyLen,
F_DataVector * pSearchKey,
FLMUINT uiFlags,
FLMBOOL bLockMutex)
{
RCODE rc = NE_XFLM_OK;
FLMBYTE ucSearchKey [XFLM_MAX_KEY_SIZE];
FLMUINT uiSearchKeyLen = 0;
FLMUINT uiOriginalFlags;
FLMUINT uiIdMatchFlags = uiFlags & (XFLM_MATCH_IDS | XFLM_MATCH_DOC_ID);
FLMBOOL bCompareDocId = FALSE;
FLMBOOL bCompareNodeIds = FALSE;
FLMUINT uiPos;
if (bLockMutex)
{
lockMutex();
}
if (uiFlags & XFLM_FIRST || (!pSearchKey &&
!(uiFlags & XFLM_FIRST) && !(uiFlags & XFLM_LAST)))
{
uiOriginalFlags = uiFlags = XFLM_FIRST;
}
else if (uiFlags & XFLM_LAST)
{
uiOriginalFlags = uiFlags = XFLM_LAST;
}
else
{
uiOriginalFlags = uiFlags;
if( !(uiIdMatchFlags & XFLM_MATCH_IDS))
{
flmAssert( !(uiFlags & XFLM_KEY_EXACT));
if (uiFlags & XFLM_EXCL)
{
uiFlags = XFLM_EXCL;
}
else if (uiFlags & XFLM_EXACT)
{
uiOriginalFlags = XFLM_EXACT | XFLM_KEY_EXACT;
uiFlags = XFLM_INCL;
}
else
{
uiFlags = XFLM_INCL;
}
}
else
{
if (uiFlags & XFLM_EXACT)
{
flmAssert( !(uiFlags & XFLM_KEY_EXACT));
uiFlags = XFLM_EXACT;
}
else if (uiFlags & XFLM_EXCL)
{
uiFlags = XFLM_EXCL;
}
else
{
uiFlags = XFLM_INCL;
}
}
if (RC_BAD( rc = pSearchKey->outputKey( m_pIxd, uiIdMatchFlags,
ucSearchKey, sizeof( ucSearchKey), &uiSearchKeyLen, SEARCH_KEY_FLAG)))
{
goto Exit;
}
// If we are not matching on the IDs and this is an XFLM_EXCL
// search, tack on a 0xFF for the IDs, which should get us past
// all keys that match. We need to turn on the match IDs flags
// in this case so that the comparison routine will match on the
// 0xFF.
if (!uiIdMatchFlags && (uiFlags & XFLM_EXCL))
{
ucSearchKey [uiSearchKeyLen++] = 0xFF;
bCompareDocId = TRUE;
bCompareNodeIds = TRUE;
}
else
{
if (uiIdMatchFlags & XFLM_MATCH_IDS)
{
bCompareNodeIds = TRUE;
bCompareDocId = TRUE;
}
else if (uiIdMatchFlags & XFLM_MATCH_DOC_ID)
{
bCompareDocId = TRUE;
}
}
}
m_compareObj.setCompareNodeIds( bCompareNodeIds);
m_compareObj.setCompareDocId( bCompareDocId);
m_compareObj.setSearchKey( pSearchKey);
// Search the for the key
if (uiSearchKeyLen)
{
f_memcpy( pucKey, ucSearchKey, uiSearchKeyLen);
}
*puiKeyLen = uiSearchKeyLen;
// NOTE: We don't pass m_uiCurrPos into btLocateEntry, because we may have to
// test below to see if we really got to something we can stay on.
// m_uiCurrPos should remain unchanged if we did not.
if (RC_BAD( rc = m_pBTree->btLocateEntry(
pucKey, uiKeyBufSize, puiKeyLen, uiFlags, &uiPos,
NULL, NULL, NULL)))
{
if (rc == NE_XFLM_EOF_HIT && uiOriginalFlags & XFLM_EXACT)
{
rc = RC_SET( NE_XFLM_NOT_FOUND);
}
goto Exit;
}
// See if we are in the same key
if (uiOriginalFlags & XFLM_KEY_EXACT)
{
FLMINT iTmpCmp;
if (RC_BAD( rc = ixKeyCompare( m_pSrcDb, m_pIxd,
pSearchKey, NULL, NULL,
(uiIdMatchFlags == XFLM_MATCH_DOC_ID) ? TRUE : FALSE,
FALSE, pucKey, *puiKeyLen,
ucSearchKey, uiSearchKeyLen, &iTmpCmp)))
{
goto Exit;
}
if (iTmpCmp != 0)
{
rc = (uiOriginalFlags & (XFLM_INCL | XFLM_EXCL))
? RC_SET( NE_XFLM_EOF_HIT)
: RC_SET( NE_XFLM_NOT_FOUND);
goto Exit;
}
}
m_bPositioned = TRUE;
m_uiCurrPos = uiPos;
Exit:
m_compareObj.setSearchKey( NULL);
m_compareObj.setCompareNodeIds( FALSE);
m_compareObj.setCompareDocId( FALSE);
if (bLockMutex)
{
unlockMutex();
}
return( rc);
}
/***************************************************************************
Desc: Position to a particular entry in the result set.
***************************************************************************/
RCODE F_QueryResultSet::positionToEntry(
FLMUINT uiPosition,
FLMBYTE * pucKey,
FLMUINT uiKeyBufSize,
FLMUINT * puiKeyLen,
FLMBOOL bLockMutex)
{
RCODE rc = NE_XFLM_OK;
if (bLockMutex)
{
lockMutex();
}
if (uiPosition >= m_uiCount)
{
rc = RC_SET( NE_XFLM_Q_INVALID_POSITION);
goto Exit;
}
if (RC_BAD( rc = m_pBTree->btPositionTo( uiPosition,
pucKey, uiKeyBufSize, puiKeyLen)))
{
goto Exit;
}
m_uiCurrPos = uiPosition;
m_bPositioned = TRUE;
Exit:
if (bLockMutex)
{
unlockMutex();
}
return( rc);
}
/***************************************************************************
Desc: Determine how much time is remaining on timer.
***************************************************************************/
FINLINE RCODE getRemainingTimeMilli(
FLMUINT uiStartTimeTU,
FLMUINT uiTimeLimitTU,
FLMUINT * puiRemainingTimeMilli)
{
RCODE rc = NE_XFLM_OK;
FLMUINT uiCurrTimeTU;
FLMUINT uiElapsedTimeTU;
FLMUINT uiRemainingTimeTU;
uiCurrTimeTU = FLM_GET_TIMER();
uiElapsedTimeTU = FLM_ELAPSED_TIME( uiCurrTimeTU, uiStartTimeTU);
if (uiElapsedTimeTU >= uiTimeLimitTU)
{
rc = RC_SET( NE_XFLM_TIMEOUT);
goto Exit;
}
else
{
uiRemainingTimeTU = uiTimeLimitTU - uiElapsedTimeTU;
*puiRemainingTimeMilli = FLM_TIMER_UNITS_TO_MILLI( uiRemainingTimeTU);
}
Exit:
return( rc);
}
/***************************************************************************
Desc: Create and initialize the query result set.
***************************************************************************/
RCODE F_Query::createResultSet( void)
{
RCODE rc = NE_XFLM_OK;
// Create a result set for the sort keys
if ((m_pSortResultSet = f_new F_QueryResultSet) == NULL)
{
rc = RC_SET( NE_XFLM_MEM);
goto Exit;
}
if (RC_BAD( rc = m_pSortResultSet->initResultSet(
m_pSortIxd ? TRUE : FALSE,
m_bEncryptResultSet)))
{
goto Exit;
}
m_ui64RSDocsRead = 0;
m_ui64RSDocsPassed = 0;
if (!m_pSortIxd)
{
m_bEntriesAlreadyInOrder = TRUE;
}
Exit:
return( rc);
}
/***************************************************************************
Desc: Build the query result set.
***************************************************************************/
RCODE F_Query::buildResultSet(
IF_Db * pDb,
FLMUINT uiTimeLimit,
FLMUINT uiNumToWaitFor)
{
RCODE rc = NE_XFLM_OK;
IF_DOMNode * pNode = NULL;
FLMUINT uiStartTimeTU = 0;
FLMUINT uiRemainingTimeMilli = 0;
FLMUINT uiTimeLimitTU = 0;
RS_WAITER * pWaiter;
FLMBOOL bMutexLocked = FALSE;
FLMBOOL bDoneBuilding = FALSE;
FLMBOOL bNotifyWaiters = FALSE;
// NOTE: uiTimeLimit will always be zero when building the result
// set in another thread.
if (uiTimeLimit)
{
uiTimeLimitTU = FLM_MILLI_TO_TIMER_UNITS( uiTimeLimit);
uiStartTimeTU = FLM_GET_TIMER();
uiRemainingTimeMilli = uiTimeLimit;
}
m_pSortResultSet->lockMutex();
bMutexLocked = TRUE;
// See if we have already built enough of the result set to get what
// we need from it.
if (m_pSortResultSet->getCount() >= uiNumToWaitFor || m_bResultSetPopulated)
{
goto Exit;
}
// Only one thread at a time can be actually be building the result set.
// This for loop waits for such a thread to build what it needs, or, if
// there is no thread running and the result set is not populated yet,
// it will build the result set out to the point it needs.
for (;;)
{
// If no other thread is building the result set, we can take over and
// do it. Otherwise, we will wait for that thread.
if (!m_uiBuildThreadId)
{
m_uiBuildThreadId = f_threadId();
bNotifyWaiters = TRUE;
break;
}
// Thread is currently building the result set. It cannot
// be our thread.
flmAssert( m_uiBuildThreadId != f_threadId());
// Just need to wait now.
if (RC_BAD( rc = waitResultSetBuild( pDb, uiTimeLimit, uiNumToWaitFor)))
{
goto Exit;
}
// See if the other thread built enough of the result set to get what
// we need from it.
if (m_pSortResultSet->getCount() >= uiNumToWaitFor || m_bResultSetPopulated)
{
goto Exit;
}
// At this point, we know that the result set is not built out far enough
// for us to get what we need, so we need to adjust our timeout.
if (uiTimeLimit)
{
if (RC_BAD( rc = getRemainingTimeMilli( uiStartTimeTU, uiTimeLimitTU,
&uiRemainingTimeMilli)))
{
goto Exit;
}
}
}
if (bMutexLocked)
{
m_pSortResultSet->unlockMutex();
bMutexLocked = FALSE;
}
for (;;)
{
if (m_bStopBuildingResultSet)
{
bDoneBuilding = TRUE;
rc = RC_SET( NE_XFLM_USER_ABORT);
break;
}
if (RC_BAD( rc = getNext( pDb, &pNode, uiRemainingTimeMilli, 0, NULL)))
{
if (rc == NE_XFLM_EOF_HIT)
{
bDoneBuilding = TRUE;
rc = NE_XFLM_OK;
break;
}
goto Exit;
}
// See how much time is left, if we are operating with a time limit.
if (uiTimeLimit)
{
if (RC_BAD( rc = getRemainingTimeMilli( uiStartTimeTU, uiTimeLimitTU,
&uiRemainingTimeMilli)))
{
goto Exit;
}
}
// See if any of the waiters timed out.
checkResultSetWaiters( NE_XFLM_OK);
if (m_pSortResultSet->getCount() >= uiNumToWaitFor)
{
break;
}
}
Exit:
if (pNode)
{
pNode->Release();
}
if (!bMutexLocked)
{
m_pSortResultSet->lockMutex();
bMutexLocked = TRUE;
}
if (!m_bResultSetPopulated && bDoneBuilding)
{
m_bResultSetPopulated = TRUE;
m_bPositioningEnabled = TRUE;
if (RC_OK( rc))
{
if (m_pQueryStatus)
{
flmAssert( m_ui64RSDocsPassed == (FLMUINT64)m_pSortResultSet->getCount());
rc = m_pQueryStatus->resultSetComplete(
m_ui64RSDocsRead, m_ui64RSDocsPassed);
}
}
}
if (bNotifyWaiters)
{
// Notify any waiters that are waiting.
pWaiter = m_pFirstWaiter;
m_pFirstWaiter = NULL;
while (pWaiter)
{
F_SEM hESem = pWaiter->hESem;
// Set waiter's return code to whatever we are returning.
*(pWaiter->pRc) = rc;
// Must get next waiter before signaling semaphore.
pWaiter = pWaiter->pNext;
f_semSignal( hESem);
}
}
// Must set m_uiBuildThreadId to 0 inside the
// mutex lock, because stopResultSetBuild() locks the mutex
// one last time to ensure that this method is no longer
// accessing anything inside the F_Query object. That is
// because it is fair game to destroy the F_Query object upon
// returning from this method.
// IMPORTANT NOTE: NOTHING SHOULD BE ACCESSED INSIDE THE F_Query
// OBJECT AFTER THE MUTEX IS UNLOCKED.
m_uiBuildThreadId = 0;
if (bMutexLocked)
{
m_pSortResultSet->unlockMutex();
}
return( rc);
}
/***************************************************************************
Desc: Stop building the result set.
***************************************************************************/
void FLMAPI F_Query::stopBuildingResultSet( void)
{
if (m_pSortResultSet)
{
m_pSortResultSet->lockMutex();
// If we have a thread currently building the
// result set, signal it to stop.
if (m_uiBuildThreadId)
{
m_bStopBuildingResultSet = TRUE;
(void)waitResultSetBuild( m_pDb, 0, FLM_MAX_UINT);
// waitResultSetBuild may temporarily unlock the
// mutex, but it will always be relocked upon
// returning. When we return this time,
// m_uiBuildThreadId should be zero.
flmAssert( !m_uiBuildThreadId);
}
else
{
m_bResultSetPopulated = TRUE;
}
m_pSortResultSet->unlockMutex();
}
}
/***************************************************************************
Desc: Build the query result set. This is the method that applications
can call. It implies enabling of positioning.
***************************************************************************/
RCODE FLMAPI F_Query::buildResultSet(
IF_Db * pDb,
FLMUINT uiTimeLimit)
{
RCODE rc = NE_XFLM_OK;
m_pDb = (F_Db *)pDb;
if (m_pDatabase && m_pDb->m_pDatabase != m_pDatabase)
{
// Make sure the passed in F_Db matches the one associated with
// the query.
rc = RC_SET( NE_XFLM_Q_MISMATCHED_DB);
goto Exit;
}
// See if the database is being forced to close
if (RC_BAD( rc = m_pDb->checkState( __FILE__, __LINE__)))
{
goto Exit;
}
// If we are not in a transaction, we cannot read.
if (m_pDb->m_eTransType == XFLM_NO_TRANS)
{
rc = RC_SET( NE_XFLM_NO_TRANS_ACTIVE);
goto Exit;
}
// See if we have a transaction going which should be aborted.
if (RC_BAD( m_pDb->m_AbortRc))
{
rc = RC_SET( NE_XFLM_ABORT_TRANS);
goto Exit;
}
if (!m_bOptimized)
{
m_bPositioningEnabled = TRUE;
if (RC_BAD( rc = optimize()))
{
goto Exit;
}
}
else if (!m_pSortResultSet)
{
// Cannot build a result set for a query that was not
// set up to use result sets.
rc = RC_SET( NE_XFLM_ILLEGAL_OP);
goto Exit;
}
// If the query can never evaluate to TRUE, return EOF without
// doing anything.
if (m_bEmpty)
{
m_eState = XFLM_QUERY_AT_BOF;
rc = RC_SET( NE_XFLM_BOF_HIT);
goto Exit;
}
// At this point, we should have a result set created.
flmAssert( m_pSortResultSet);
// Build the entire result set.
if (RC_BAD( rc = buildResultSet( pDb, uiTimeLimit, FLM_MAX_UINT)))
{
goto Exit;
}
Exit:
return( rc);
}
/***************************************************************************
Desc: Extract the document ID from the key.
***************************************************************************/
FSTATIC RCODE fqGetDocId(
IXD * pIxd,
const FLMBYTE * pucKey,
FLMUINT uiKeyLen,
FLMUINT64 * pui64DocId)
{
RCODE rc = NE_XFLM_OK;
const FLMBYTE * pucKeyEnd = pucKey + uiKeyLen;
if (pIxd)
{
ICD * pIcd = pIxd->pFirstKey;
FLMUINT uiComponentLen;
// Skip past all key components. Document ID will be right after them.
while (pIcd && pucKey < pucKeyEnd)
{
if (pucKey + 2 > pucKeyEnd)
{
rc = RC_SET( NE_XFLM_BAD_COLLATED_KEY);
goto Exit;
}
uiComponentLen = getKeyComponentLength( pucKey);
pucKey += (2 + uiComponentLen);
pIcd = pIcd->pNextKeyComponent;
}
if (pucKey >= pucKeyEnd)
{
rc = RC_SET( NE_XFLM_BAD_COLLATED_KEY);
goto Exit;
}
// Should be positioned on document ID
if (RC_BAD( rc = f_decodeSEN64( &pucKey, pucKeyEnd, pui64DocId)))
{
goto Exit;
}
}
else
{
// If no sort index was defined, the key is a 4 byte sequence number,
// followed by the document id stored as a SEN. This is done so that
// it can be sorted with a memcmp.
pucKey += 4;
if (pucKey >= pucKeyEnd)
{
rc = RC_SET( NE_XFLM_BAD_COLLATED_KEY);
goto Exit;
}
if (RC_BAD( rc = f_decodeSEN64( &pucKey, pucKeyEnd, pui64DocId)))
{
goto Exit;
}
}
Exit:
return( rc);
}
/***************************************************************************
Desc: Check result set waiters to see if any should be notified that they
are done waiting or that they timed out or that there was an error.
***************************************************************************/
void F_Query::checkResultSetWaiters(
RCODE rc)
{
RS_WAITER * pWaiter;
FLMUINT uiCurrTime;
F_SEM hESem;
if (m_pSortResultSet && !m_bResultSetPopulated && m_pFirstWaiter)
{
uiCurrTime = FLM_GET_TIMER();
m_pSortResultSet->lockMutex();
pWaiter = m_pFirstWaiter;
while (pWaiter)
{
// First see if their count has been satisfied.
if (RC_BAD( rc) ||
m_pSortResultSet->getCount() >= pWaiter->uiNumToWaitFor)
{
hESem = pWaiter->hESem;
*(pWaiter->pRc) = rc;
// Unlink the waiter from the list before signaling.
if (pWaiter->pNext)
{
pWaiter->pNext->pPrev = pWaiter->pPrev;
}
if (pWaiter->pPrev)
{
pWaiter->pPrev->pNext = pWaiter->pNext;
}
else
{
m_pFirstWaiter = pWaiter->pNext;
}
pWaiter = pWaiter->pNext;
f_semSignal( hESem);
}
else if (pWaiter->uiTimeLimit &&
FLM_ELAPSED_TIME( uiCurrTime, pWaiter->uiWaitStartTime) >
pWaiter->uiTimeLimit)
{
hESem = pWaiter->hESem;
*(pWaiter->pRc) = RC_SET( NE_XFLM_TIMEOUT);
// Unlink the waiter from the list before signaling.
if (pWaiter->pNext)
{
pWaiter->pNext->pPrev = pWaiter->pPrev;
}
if (pWaiter->pPrev)
{
pWaiter->pPrev->pNext = pWaiter->pNext;
}
else
{
m_pFirstWaiter = pWaiter->pNext;
}
pWaiter = pWaiter->pNext;
f_semSignal( hESem);
}
else
{
pWaiter = pWaiter->pNext;
}
}
m_pSortResultSet->unlockMutex();
}
}
/***************************************************************************
Desc: Wait for the result set to get completely built. This routine assumes
that the result set mutex has already been locked.
***************************************************************************/
RCODE F_Query::waitResultSetBuild(
IF_Db * pDb,
FLMUINT uiTimeLimit,
FLMUINT uiNumToWaitFor)
{
RCODE rc = NE_XFLM_OK;
RCODE TempRc;
RS_WAITER waiter;
FLMBOOL bMutexLocked = TRUE;
// See if we are still building the result set, or if our
// count has been reached.
if (m_pSortResultSet->getCount() >= uiNumToWaitFor || m_bResultSetPopulated)
{
goto Exit;
}
// This should only be called if there is actually a thread currently
// building the result set.
flmAssert( m_uiBuildThreadId && m_uiBuildThreadId != f_threadId());
waiter.uiThreadId = f_threadId();
waiter.hESem = ((F_Db *)pDb)->m_hWaitSem;
waiter.pRc = &rc;
if (uiTimeLimit)
{
waiter.uiWaitStartTime = FLM_GET_TIMER();
waiter.uiTimeLimit = FLM_MILLI_TO_TIMER_UNITS( uiTimeLimit);
}
else
{
waiter.uiWaitStartTime = 0;
waiter.uiTimeLimit = 0;
}
waiter.uiNumToWaitFor = uiNumToWaitFor;
waiter.pPrev = NULL;
if ((waiter.pNext = m_pFirstWaiter) != NULL)
{
m_pFirstWaiter->pPrev = &waiter;
}
m_pFirstWaiter = &waiter;
rc = NE_XFLM_FAILURE;
m_pSortResultSet->unlockMutex();
bMutexLocked = FALSE;
if (RC_BAD( TempRc = f_semWait( waiter.hESem, F_SEM_WAITFOREVER)))
{
flmAssert( 0);
rc = TempRc;
}
else
{
// Process that signaled us better set the rc to something
// besides NE_XFLM_FAILURE.
if( rc == NE_XFLM_FAILURE)
{
flmAssert( 0);
}
}
Exit:
if (!bMutexLocked)
{
m_pSortResultSet->lockMutex();
}
return( rc);
}
/***************************************************************************
Desc: Get the first item in the result set.
***************************************************************************/
RCODE F_Query::getFirstFromResultSet(
IF_Db * ifpDb,
IF_DOMNode ** ppNode,
FLMUINT uiTimeLimit)
{
RCODE rc = NE_XFLM_OK;
FLMBYTE ucKey [XFLM_MAX_KEY_SIZE];
FLMUINT uiKeyLen;
FLMUINT64 ui64DocId;
FLMUINT uiNumToWaitFor = 1;
FLMUINT uiStartTimeTU = 0;
FLMUINT uiRemainingTimeMilli = 0;
FLMUINT uiTimeLimitTU = 0;
// NOTE: uiTimeLimit will always be zero when building the result
// set in another thread.
if (uiTimeLimit)
{
uiTimeLimitTU = FLM_MILLI_TO_TIMER_UNITS( uiTimeLimit);
uiStartTimeTU = FLM_GET_TIMER();
uiRemainingTimeMilli = uiTimeLimit;
}
for (;;)
{
// If we are in the middle of populating the result set, we may need to wait
// for entries to come into the result set.
if (!m_bResultSetPopulated)
{
// If the entries are not in order, we must wait for the entire result
// set to be populated. NOTE: If we are not sorting, they are,
// by definition, in order - we order them with a sequence number.
if (!m_bEntriesAlreadyInOrder)
{
if (RC_BAD( rc = buildResultSet( ifpDb, uiRemainingTimeMilli,
FLM_MAX_UINT)))
{
goto Exit;
}
}
// If there are no entries in the result set yet, in order to get
// the first entry we must at least wait for the first one to
// appear.
else if (m_pSortResultSet->getCount() < uiNumToWaitFor)
{
if (RC_BAD( rc = buildResultSet( ifpDb, uiRemainingTimeMilli,
uiNumToWaitFor)))
{
goto Exit;
}
}
}
if (uiNumToWaitFor == 1)
{
// If the result set is not yet populated, we need to lock the mutex
// before accessing it - because another thread is trying to populate it.
if (RC_BAD( rc = m_pSortResultSet->getFirst( ucKey, sizeof( ucKey),
&uiKeyLen, m_bResultSetPopulated ? FALSE : TRUE)))
{
goto Exit;
}
}
else
{
if (RC_BAD( rc = m_pSortResultSet->getNext( ucKey, sizeof( ucKey),
&uiKeyLen,
m_bResultSetPopulated ? FALSE : TRUE)))
{
goto Exit;
}
}
if (RC_BAD( rc = fqGetDocId( m_pSortIxd, ucKey, uiKeyLen, &ui64DocId)))
{
goto Exit;
}
if (RC_OK( rc = ifpDb->getNode( m_uiCollection, ui64DocId, ppNode)))
{
break;
}
if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
{
goto Exit;
}
rc = NE_XFLM_OK;
// See how much time is left, if we are operating with a time limit.
if (uiTimeLimit)
{
if (RC_BAD( rc = getRemainingTimeMilli( uiStartTimeTU, uiTimeLimitTU,
&uiRemainingTimeMilli)))
{
goto Exit;
}
}
uiNumToWaitFor++;
}
Exit:
return( rc);
}
/***************************************************************************
Desc: Get the last item in the result set.
***************************************************************************/
RCODE F_Query::getLastFromResultSet(
IF_Db * ifpDb,
IF_DOMNode ** ppNode,
FLMUINT uiTimeLimit)
{
RCODE rc = NE_XFLM_OK;
FLMBYTE ucKey [XFLM_MAX_KEY_SIZE];
FLMUINT uiKeyLen;
FLMUINT64 ui64DocId;
FLMUINT uiStartTimeTU = 0;
FLMUINT uiRemainingTimeMilli = 0;
FLMUINT uiTimeLimitTU = 0;
FLMBOOL bGetLast = TRUE;
// NOTE: uiTimeLimit will always be zero when building the result
// set in another thread.
if (uiTimeLimit)
{
uiTimeLimitTU = FLM_MILLI_TO_TIMER_UNITS( uiTimeLimit);
uiStartTimeTU = FLM_GET_TIMER();
uiRemainingTimeMilli = uiTimeLimit;
}
for (;;)
{
// If we are in the middle of populating the result set, we may need to wait
// for entries to come into the result set.
if (!m_bResultSetPopulated)
{
// When getting the last entry, we must always wait for the result set
// to become fully populated.
if (RC_BAD( rc = buildResultSet( ifpDb, uiRemainingTimeMilli,
FLM_MAX_UINT)))
{
goto Exit;
}
}
// If the result set is not yet populated, we need to lock the mutex
// before accessing it - because another thread is trying to populate it.
if (bGetLast)
{
if (RC_BAD( rc = m_pSortResultSet->getLast( ucKey, sizeof( ucKey),
&uiKeyLen, m_bResultSetPopulated ? FALSE : TRUE)))
{
goto Exit;
}
}
else
{
if (RC_BAD( rc = m_pSortResultSet->getPrev( ucKey, sizeof( ucKey),
&uiKeyLen,
m_bResultSetPopulated ? FALSE : TRUE)))
{
goto Exit;
}
}
if (RC_BAD( rc = fqGetDocId( m_pSortIxd, ucKey, uiKeyLen, &ui64DocId)))
{
goto Exit;
}
if (RC_OK( rc = ifpDb->getNode( m_uiCollection, ui64DocId, ppNode)))
{
break;
}
if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
{
goto Exit;
}
rc = NE_XFLM_OK;
// See how much time is left, if we are operating with a time limit.
if (uiTimeLimit)
{
if (RC_BAD( rc = getRemainingTimeMilli( uiStartTimeTU, uiTimeLimitTU,
&uiRemainingTimeMilli)))
{
goto Exit;
}
}
bGetLast = FALSE;
}
Exit:
return( rc);
}
/***************************************************************************
Desc: Get the next item from the result set.
***************************************************************************/
RCODE F_Query::getNextFromResultSet(
IF_Db * ifpDb,
IF_DOMNode ** ppNode,
FLMUINT uiTimeLimit,
FLMUINT uiNumToSkip,
FLMUINT * puiNumSkipped)
{
RCODE rc = NE_XFLM_OK;
FLMBYTE ucKey [XFLM_MAX_KEY_SIZE];
FLMUINT uiKeyLen;
FLMUINT64 ui64DocId;
FLMUINT uiStartTimeTU = 0;
FLMUINT uiRemainingTimeMilli = 0;
FLMUINT uiTimeLimitTU = 0;
FLMUINT uiNumSkipped;
// NOTE: uiTimeLimit will always be zero when building the result
// set in another thread.
if (uiTimeLimit)
{
uiTimeLimitTU = FLM_MILLI_TO_TIMER_UNITS( uiTimeLimit);
uiStartTimeTU = FLM_GET_TIMER();
uiRemainingTimeMilli = uiTimeLimit;
}
if (!puiNumSkipped)
{
// puiNumSkipped has to be non-NULL so it can be incremented only
// if uiNumToSkip > 1
if (uiNumToSkip > 1)
{
uiNumSkipped = 0;
puiNumSkipped = &uiNumSkipped;
}
}
else
{
*puiNumSkipped = 0;
}
for (;;)
{
// If we are in the middle of populating the result set, we may need to wait
// for entries to come into the result set.
if (!m_bResultSetPopulated)
{
// If the entries are not in order, we must wait for the entire result
// set to be populated. NOTE: If we are not sorting, they are,
// by definition, in order - we order them with a sequence number.
if (!m_bEntriesAlreadyInOrder)
{
if (RC_BAD( rc = buildResultSet( ifpDb, uiRemainingTimeMilli,
FLM_MAX_UINT)))
{
goto Exit;
}
}
else
{
FLMUINT uiCurrPos = m_pSortResultSet->getCurrPos();
// See if we have enough entries in the result set to position
// to the next entry.
// If we are not yet positioned (uiCurrPos == FLM_MAX_UINT),
// we need to have at least one item in the result set.
// Otherwise, we need to have one more beyond uiCurrPos - which
// is (uiCurrPos + 1) + 1 --> uiCurrPos + 2.
if (RC_BAD( rc = buildResultSet( ifpDb, uiRemainingTimeMilli,
(uiCurrPos == FLM_MAX_UINT) ? 1 : uiCurrPos + 2)))
{
goto Exit;
}
}
}
// If the result set is not yet populated, we need to lock the mutex
// before accessing it - because another thread is trying to populate it.
if (RC_BAD( rc = m_pSortResultSet->getNext( ucKey, sizeof( ucKey),
&uiKeyLen, m_bResultSetPopulated ? FALSE : TRUE)))
{
goto Exit;
}
if (puiNumSkipped)
{
(*puiNumSkipped)++;
}
if (uiNumToSkip > 1)
{
// puiNumSkipped will always be non-NULL in the case
// where uiNumToSkip > 1
flmAssert( puiNumSkipped);
if (*puiNumSkipped < uiNumToSkip)
{
continue;
}
}
if (RC_BAD( rc = fqGetDocId( m_pSortIxd, ucKey, uiKeyLen, &ui64DocId)))
{
goto Exit;
}
if (RC_OK( rc = ifpDb->getNode( m_uiCollection, ui64DocId, ppNode)))
{
break;
}
if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
{
goto Exit;
}
rc = NE_XFLM_OK;
// See how much time is left, if we are operating with a time limit.
if (uiTimeLimit)
{
if (RC_BAD( rc = getRemainingTimeMilli( uiStartTimeTU, uiTimeLimitTU,
&uiRemainingTimeMilli)))
{
goto Exit;
}
}
}
Exit:
return( rc);
}
/***************************************************************************
Desc: Get the previous item in the result set.
***************************************************************************/
RCODE F_Query::getPrevFromResultSet(
IF_Db * ifpDb,
IF_DOMNode ** ppNode,
FLMUINT uiTimeLimit,
FLMUINT uiNumToSkip,
FLMUINT * puiNumSkipped)
{
RCODE rc = NE_XFLM_OK;
FLMBYTE ucKey [XFLM_MAX_KEY_SIZE];
FLMUINT uiKeyLen;
FLMUINT64 ui64DocId;
FLMUINT uiStartTimeTU = 0;
FLMUINT uiRemainingTimeMilli = 0;
FLMUINT uiTimeLimitTU = 0;
FLMUINT uiNumSkipped;
if (!puiNumSkipped)
{
// puiNumSkipped has to be non-NULL so it can be incremented only
// if uiNumToSkip > 1
if (uiNumToSkip > 1)
{
uiNumSkipped = 0;
puiNumSkipped = &uiNumSkipped;
}
}
else
{
*puiNumSkipped = 0;
}
// NOTE: uiTimeLimit will always be zero when building the result
// set in another thread.
if (uiTimeLimit)
{
uiTimeLimitTU = FLM_MILLI_TO_TIMER_UNITS( uiTimeLimit);
uiStartTimeTU = FLM_GET_TIMER();
uiRemainingTimeMilli = uiTimeLimit;
}
// If we are in the middle of populating the result set, we may need to wait
// for entries to come into the result set.
if (!m_bResultSetPopulated)
{
// If the entries are not in order, we must wait for the entire result
// set to be populated. NOTE: If we are not sorting, they are,
// by definition, in order - we order them with a sequence number.
if (!m_bEntriesAlreadyInOrder)
{
if (RC_BAD( rc = buildResultSet( ifpDb, uiRemainingTimeMilli,
FLM_MAX_UINT)))
{
goto Exit;
}
}
// If there are no entries in the result set yet, in order to get
// the first entry we must at least wait for the first one to
// appear.
else
{
FLMUINT uiCurrPos = m_pSortResultSet->getCurrPos();
// See if we have enough entries in the result set to position
// to the previous entry. We should never be positioned beyond
// the number that are currently in there. So, at most, we should
// only have to wait for one to appear.
if (uiCurrPos == FLM_MAX_UINT)
{
if (RC_BAD( rc = buildResultSet( ifpDb, uiRemainingTimeMilli, 1)))
{
goto Exit;
}
}
else
{
// Better never be beyond the current count.
flmAssert( uiCurrPos < m_pSortResultSet->getCount());
}
}
}
for (;;)
{
// If the result set is not yet populated, we need to lock the mutex
// before accessing it - because another thread is trying to populate it.
if (RC_BAD( rc = m_pSortResultSet->getPrev( ucKey, sizeof( ucKey),
&uiKeyLen, m_bResultSetPopulated ? FALSE : TRUE)))
{
goto Exit;
}
if (puiNumSkipped)
{
(*puiNumSkipped)++;
}
if (uiNumToSkip > 1)
{
// puiNumSkipped will always be non-NULL in the case
// where uiNumToSkip > 1
flmAssert( puiNumSkipped);
if (*puiNumSkipped < uiNumToSkip)
{
continue;
}
}
if (RC_BAD( rc = fqGetDocId( m_pSortIxd, ucKey, uiKeyLen, &ui64DocId)))
{
goto Exit;
}
if (RC_OK( rc = ifpDb->getNode( m_uiCollection, ui64DocId, ppNode)))
{
break;
}
if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
{
goto Exit;
}
rc = NE_XFLM_OK;
// See how much time is left, if we are operating with a time limit.
if (uiTimeLimit)
{
if (RC_BAD( rc = getRemainingTimeMilli( uiStartTimeTU, uiTimeLimitTU,
&uiRemainingTimeMilli)))
{
goto Exit;
}
}
}
Exit:
return( rc);
}
/***************************************************************************
Desc: Get the current item in the result set.
***************************************************************************/
RCODE F_Query::getCurrentFromResultSet(
IF_Db * ifpDb,
IF_DOMNode ** ppNode)
{
RCODE rc = NE_XFLM_OK;
FLMBYTE ucKey [XFLM_MAX_KEY_SIZE];
FLMUINT uiKeyLen;
FLMUINT64 ui64DocId;
// If we are in the middle of populating the result set, we may need to wait
// for entries to come into the result set.
if (!m_bResultSetPopulated)
{
// If the entries are not in order, we must wait for the entire result
// set to be populated. But that means that we have never positioned
// anywhere yet, so we should return an error.
if (!m_bEntriesAlreadyInOrder)
{
rc = RC_SET( NE_XFLM_Q_NOT_POSITIONED);
goto Exit;
}
// If there are no entries in the result set yet, in order to get
// the first entry we must at least wait for the first one to
// appear.
else
{
FLMUINT uiCurrPos = m_pSortResultSet->getCurrPos();
// It is an error to call this if we have not yet positioned
// anywhere in the result set.
if (uiCurrPos == FLM_MAX_UINT)
{
rc = RC_SET( NE_XFLM_Q_NOT_POSITIONED);
goto Exit;
}
else
{
// Better never be beyond the current count.
flmAssert( uiCurrPos < m_pSortResultSet->getCount());
}
}
}
// If the result set is not yet populated, we need to lock the mutex
// before accessing it - because another thread is trying to populate it.
if (RC_BAD( rc = m_pSortResultSet->getCurrent( ucKey, sizeof( ucKey),
&uiKeyLen, m_bResultSetPopulated ? FALSE : TRUE)))
{
goto Exit;
}
if (RC_BAD( rc = fqGetDocId( m_pSortIxd, ucKey, uiKeyLen, &ui64DocId)))
{
goto Exit;
}
if (RC_BAD( rc = ifpDb->getNode( m_uiCollection, ui64DocId, ppNode)))
{
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
{
rc = RC_SET( NE_XFLM_Q_NOT_POSITIONED);
}
goto Exit;
}
Exit:
return( rc);
}
/***************************************************************************
Desc: Get previous node/document that passes query expression.
***************************************************************************/
RCODE FLMAPI F_Query::positionTo(
IF_Db * ifpDb,
IF_DOMNode ** ppNode,
FLMUINT uiTimeLimit,
FLMUINT uiPosition
)
{
RCODE rc = NE_XFLM_OK;
FLMBYTE ucKey [XFLM_MAX_KEY_SIZE];
FLMUINT uiKeyLen;
FLMUINT64 ui64DocId;
FLMUINT uiStartTimeTU = 0;
FLMUINT uiRemainingTimeMilli = 0;
FLMUINT uiTimeLimitTU = 0;
// If we have a result set, or are in the middle of creating it,
// we don't want to change the member variables because a background
// thread may be using them.
if (!m_pSortResultSet)
{
// NOTE: uiTimeLimit will always be zero when building the result
// set in another thread.
if (uiTimeLimit)
{
uiTimeLimitTU = FLM_MILLI_TO_TIMER_UNITS( uiTimeLimit);
uiStartTimeTU = FLM_GET_TIMER();
uiRemainingTimeMilli = uiTimeLimit;
}
m_pDb = (F_Db *)ifpDb;
if (ppNode && *ppNode)
{
(*ppNode)->Release();
*ppNode = NULL;
}
if (m_pDatabase && m_pDb->m_pDatabase != m_pDatabase)
{
// Make sure the passed in F_Db matches the one associated with
// the query.
rc = RC_SET( NE_XFLM_Q_MISMATCHED_DB);
goto Exit;
}
// See if the database is being forced to close
if (RC_BAD( rc = m_pDb->checkState( __FILE__, __LINE__)))
{
goto Exit;
}
// If we are not in a transaction, we cannot read.
if (m_pDb->m_eTransType == XFLM_NO_TRANS)
{
rc = RC_SET( NE_XFLM_NO_TRANS_ACTIVE);
goto Exit;
}
// See if we have a transaction going which should be aborted.
if (RC_BAD( m_pDb->m_AbortRc))
{
rc = RC_SET( NE_XFLM_ABORT_TRANS);
goto Exit;
}
if (!m_bOptimized)
{
if (RC_BAD( rc = optimize()))
{
goto Exit;
}
}
// If we are not creating a result set, this is not a positionable
// query.
if (!m_pSortResultSet)
{
rc = RC_SET( NE_XFLM_Q_NON_POSITIONABLE_QUERY);
goto Exit;
}
}
// If we are in the middle of populating the result set, we may need to wait
// for entries to come into the result set.
if (!m_bResultSetPopulated)
{
// If the entries are not in order, we must wait for the entire result
// set to be populated before we can determine what will be
// the nth entry. NOTE: If we are not sorting, they are,
// by definition, in order - we order them with a sequence number.
if (!m_bEntriesAlreadyInOrder)
{
if (RC_BAD( rc = buildResultSet( ifpDb, uiRemainingTimeMilli,
FLM_MAX_UINT)))
{
goto Exit;
}
}
else
{
FLMUINT uiCurrPos = m_pSortResultSet->getCurrPos();
// See if we have the nth entry yet.
if (uiCurrPos == FLM_MAX_UINT ||
m_pSortResultSet->getCount() < uiPosition + 1)
{
// Must wait for uiPosition + 1, because uiPosition is zero based.
if (RC_BAD( rc = buildResultSet( ifpDb, uiRemainingTimeMilli,
uiPosition + 1)))
{
goto Exit;
}
}
}
}
if (m_pSortIxd)
{
m_pSortResultSet->setIxInfo( m_pDb, m_pSortIxd);
}
// If the result set is not yet populated, we need to lock the mutex
// before accessing it - because another thread is trying to populate it.
if (RC_BAD( rc = m_pSortResultSet->positionToEntry( uiPosition,
ucKey, sizeof( ucKey),
&uiKeyLen, m_bResultSetPopulated ? FALSE : TRUE)))
{
goto Exit;
}
if (RC_BAD( rc = fqGetDocId( m_pSortIxd, ucKey, uiKeyLen, &ui64DocId)))
{
goto Exit;
}
if (RC_BAD( rc = ifpDb->getNode( m_uiCollection, ui64DocId, ppNode)))
{
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
{
rc = RC_SET( NE_XFLM_Q_NOT_POSITIONED);
}
goto Exit;
}
Exit:
return( rc);
}
/***************************************************************************
Desc: Get previous node/document that passes query expression.
***************************************************************************/
RCODE FLMAPI F_Query::positionTo(
IF_Db * ifpDb,
IF_DOMNode ** ppNode,
FLMUINT uiTimeLimit,
IF_DataVector * pSearchKey,
FLMUINT uiFlags
)
{
RCODE rc = NE_XFLM_OK;
FLMBYTE ucKey [XFLM_MAX_KEY_SIZE];
FLMUINT uiKeyLen;
FLMUINT64 ui64DocId;
FLMUINT uiStartTimeTU = 0;
FLMUINT uiRemainingTimeMilli = 0;
FLMUINT uiTimeLimitTU = 0;
// If we have a result set, or are in the middle of creating it,
// we don't want to change the member variables because a background
// thread may be using them.
if (!m_pSortResultSet)
{
// NOTE: uiTimeLimit will always be zero when building the result
// set in another thread.
if (uiTimeLimit)
{
uiTimeLimitTU = FLM_MILLI_TO_TIMER_UNITS( uiTimeLimit);
uiStartTimeTU = FLM_GET_TIMER();
uiRemainingTimeMilli = uiTimeLimit;
}
m_pDb = (F_Db *)ifpDb;
if (ppNode && *ppNode)
{
(*ppNode)->Release();
*ppNode = NULL;
}
if (m_pDatabase && m_pDb->m_pDatabase != m_pDatabase)
{
// Make sure the passed in F_Db matches the one associated with
// the query.
rc = RC_SET( NE_XFLM_Q_MISMATCHED_DB);
goto Exit;
}
// See if the database is being forced to close
if (RC_BAD( rc = m_pDb->checkState( __FILE__, __LINE__)))
{
goto Exit;
}
// If we are not in a transaction, we cannot read.
if (m_pDb->m_eTransType == XFLM_NO_TRANS)
{
rc = RC_SET( NE_XFLM_NO_TRANS_ACTIVE);
goto Exit;
}
// See if we have a transaction going which should be aborted.
if (RC_BAD( m_pDb->m_AbortRc))
{
rc = RC_SET( NE_XFLM_ABORT_TRANS);
goto Exit;
}
if (!m_bOptimized)
{
if (RC_BAD( rc = optimize()))
{
goto Exit;
}
}
// If no sort keys were specified, we cannot do this kind of
// positioning.
if (!m_pSortIxd)
{
rc = RC_SET( NE_XFLM_Q_NON_POSITIONABLE_QUERY);
goto Exit;
}
if (!m_pSortResultSet)
{
//visit: Need to allow something here for indexes where we really don't
//care about a result set. Positioning to a particular key is still possible
//in that case.
rc = RC_SET( NE_XFLM_Q_NON_POSITIONABLE_QUERY);
goto Exit;
}
}
// If we are in the middle of populating the result set, and the entries
// are not in order, we need to wait for all entries to
// come into the result set.
//VISIT: Should we just wait for the entire result set even if the entries
//are in order?
if (!m_bResultSetPopulated && !m_bEntriesAlreadyInOrder)
{
if (RC_BAD( rc = buildResultSet( ifpDb, uiRemainingTimeMilli,
FLM_MAX_UINT)))
{
goto Exit;
}
}
// Need to set the result set's index and db.
m_pSortResultSet->setIxInfo( m_pDb, m_pSortIxd);
// If the result set is not yet populated, we need to lock the mutex
// before accessing it - because another thread is trying to populate it.
if (RC_BAD( rc = m_pSortResultSet->positionToEntry( ucKey, sizeof( ucKey),
&uiKeyLen, (F_DataVector *)pSearchKey, uiFlags,
m_bResultSetPopulated ? FALSE : TRUE)))
{
goto Exit;
}
if (RC_BAD( rc = fqGetDocId( m_pSortIxd, ucKey, uiKeyLen, &ui64DocId)))
{
goto Exit;
}
if (RC_BAD( rc = ifpDb->getNode( m_uiCollection, ui64DocId, ppNode)))
{
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
{
rc = RC_SET( NE_XFLM_Q_NOT_POSITIONED);
}
goto Exit;
}
Exit:
return( rc);
}
/***************************************************************************
Desc: Get current position.
***************************************************************************/
RCODE FLMAPI F_Query::getPosition(
IF_Db * ifpDb,
FLMUINT * puiPosition
)
{
RCODE rc = NE_XFLM_OK;
// If we do not have a result set, we will not be positioned anywhere.
if (!m_pSortResultSet)
{
m_pDb = (F_Db *)ifpDb;
if (m_pDatabase && m_pDb->m_pDatabase != m_pDatabase)
{
// Make sure the passed in F_Db matches the one associated with
// the query.
rc = RC_SET( NE_XFLM_Q_MISMATCHED_DB);
goto Exit;
}
// See if the database is being forced to close
if (RC_BAD( rc = m_pDb->checkState( __FILE__, __LINE__)))
{
goto Exit;
}
// If we are not in a transaction, we cannot read.
if (m_pDb->m_eTransType == XFLM_NO_TRANS)
{
rc = RC_SET( NE_XFLM_NO_TRANS_ACTIVE);
goto Exit;
}
// See if we have a transaction going which should be aborted.
if (RC_BAD( m_pDb->m_AbortRc))
{
rc = RC_SET( NE_XFLM_ABORT_TRANS);
goto Exit;
}
if (!m_bOptimized)
{
if (RC_BAD( rc = optimize()))
{
goto Exit;
}
}
if (!m_pSortResultSet)
{
rc = RC_SET( NE_XFLM_Q_NON_POSITIONABLE_QUERY);
goto Exit;
}
*puiPosition = 0;
rc = RC_SET( NE_XFLM_BOF_HIT);
goto Exit;
}
if ((*puiPosition = m_pSortResultSet->getCurrPos()) == FLM_MAX_UINT)
{
*puiPosition = 0;
rc = RC_SET( NE_XFLM_BOF_HIT);
goto Exit;
}
Exit:
return( rc);
}
/***************************************************************************
Desc: Get count. Only works for queries that are building result sets.
***************************************************************************/
RCODE FLMAPI F_Query::getCounts(
IF_Db * ifpDb,
FLMUINT uiTimeLimit,
FLMBOOL bPartialCountOk,
FLMUINT * puiReadCount,
FLMUINT * puiPassedCount,
FLMUINT * puiPositionableToCount,
FLMBOOL * pbDoneBuildingResultSet
)
{
RCODE rc = NE_XFLM_OK;
// If we do not have a result set, we will not have a count.
if (!m_pSortResultSet)
{
m_pDb = (F_Db *)ifpDb;
if (m_pDatabase && m_pDb->m_pDatabase != m_pDatabase)
{
// Make sure the passed in F_Db matches the one associated with
// the query.
rc = RC_SET( NE_XFLM_Q_MISMATCHED_DB);
goto Exit;
}
// See if the database is being forced to close
if (RC_BAD( rc = m_pDb->checkState( __FILE__, __LINE__)))
{
goto Exit;
}
// If we are not in a transaction, we cannot read.
if (m_pDb->m_eTransType == XFLM_NO_TRANS)
{
rc = RC_SET( NE_XFLM_NO_TRANS_ACTIVE);
goto Exit;
}
// See if we have a transaction going which should be aborted.
if (RC_BAD( m_pDb->m_AbortRc))
{
rc = RC_SET( NE_XFLM_ABORT_TRANS);
goto Exit;
}
if (!m_bOptimized)
{
if (RC_BAD( rc = optimize()))
{
goto Exit;
}
}
if (!m_pSortResultSet)
{
rc = RC_SET( NE_XFLM_Q_NON_POSITIONABLE_QUERY);
goto Exit;
}
}
// If the result set is not yet populated, we need to let it become
// completely populated before we return a count.
if (!m_bResultSetPopulated)
{
if (bPartialCountOk)
{
if (m_bEntriesAlreadyInOrder)
{
*puiPositionableToCount = *puiPassedCount = m_pSortResultSet->getCount();
}
else
{
*puiPassedCount = m_pSortResultSet->getCount();
*puiPositionableToCount = 0;
}
if (pbDoneBuildingResultSet)
{
*pbDoneBuildingResultSet = FALSE;
}
}
else
{
if (RC_BAD( rc = buildResultSet( ifpDb, uiTimeLimit, FLM_MAX_UINT)))
{
goto Exit;
}
*puiPositionableToCount = *puiPassedCount = m_pSortResultSet->getCount();
if (pbDoneBuildingResultSet)
{
*pbDoneBuildingResultSet = TRUE;
}
}
}
else
{
*puiPositionableToCount = *puiPassedCount = m_pSortResultSet->getCount();
if (pbDoneBuildingResultSet)
{
*pbDoneBuildingResultSet = TRUE;
}
}
// Always set *puiReadCount last, in case it is being incremented on
// another thread. It will be ok for it to advance a little after having
// set the other two counters. It would look weird, however, if it was
// set first, and the other two counters ended up being greater than
// this one.
*puiReadCount = (FLMUINT)m_ui64RSDocsRead;
Exit:
return( rc);
}