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