git-svn-id: https://svn.code.sf.net/p/flaim/code/trunk@409 0109f412-320b-0410-ab79-c3e0c5ffbbe6
14336 lines
317 KiB
C++
14336 lines
317 KiB
C++
//------------------------------------------------------------------------------
|
|
// Desc: Contains the methods for F_Query class.
|
|
//
|
|
// Tabs: 3
|
|
//
|
|
// Copyright (c) 2003-2006 Novell, Inc. All Rights Reserved.
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of version 2 of the GNU General Public
|
|
// License as published by the Free Software Foundation.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, contact Novell, Inc.
|
|
//
|
|
// To contact Novell about this file by physical or electronic mail,
|
|
// you may find current contact information at www.novell.com
|
|
//
|
|
// $Id: fquery.cpp 3114 2006-01-19 13:22:45 -0700 (Thu, 19 Jan 2006) dsanders $
|
|
//------------------------------------------------------------------------------
|
|
|
|
#include "flaimsys.h"
|
|
#include "fquery.h"
|
|
#include "fscursor.h"
|
|
#include "fdynsset.h"
|
|
|
|
#define MIN_OPT_COST 8
|
|
|
|
static FLMUINT uiPrecedenceTable[ XFLM_RPAREN_OP - XFLM_AND_OP + 1] =
|
|
{
|
|
2, // XFLM_AND_OP
|
|
1, // XFLM_OR_OP
|
|
10, // XFLM_NOT_OP
|
|
6, // XFLM_EQ_OP
|
|
6, // XFLM_NE_OP
|
|
6, // XFLM_APPROX_EQ_OP
|
|
7, // XFLM_LT_OP
|
|
7, // XFLM_LE_OP
|
|
7, // XFLM_GT_OP
|
|
7, // XFLM_GE_OP
|
|
5, // XFLM_BITAND_OP
|
|
3, // XFLM_BITOR_OP
|
|
4, // XFLM_BITXOR_OP
|
|
9, // XFLM_MULT_OP
|
|
9, // XFLM_DIV_OP
|
|
9, // XFLM_MOD_OP
|
|
8, // XFLM_PLUS_OP
|
|
8, // XFLM_MINUS_OP
|
|
10, // XFLM_NEG_OP
|
|
0, // XFLM_LPAREN_OP
|
|
0 // XFLM_RPAREN_OP
|
|
};
|
|
|
|
FINLINE FLMUINT getPrecedence(
|
|
eQueryOperators eOperator)
|
|
{
|
|
return( uiPrecedenceTable [eOperator - XFLM_AND_OP]);
|
|
}
|
|
|
|
FSTATIC void fqUnlinkFromParent(
|
|
FQNODE * pQNode);
|
|
|
|
FSTATIC void fqLinkFirstChild(
|
|
FQNODE * pParent,
|
|
FQNODE * pChild);
|
|
|
|
FSTATIC void fqLinkLastChild(
|
|
FQNODE * pParent,
|
|
FQNODE * pChild);
|
|
|
|
FSTATIC void fqReplaceNode(
|
|
FQNODE * pNodeToReplace,
|
|
FQNODE * pReplacementNode);
|
|
|
|
FSTATIC RCODE fqGetPosition(
|
|
FQVALUE * pQValue,
|
|
FLMUINT * puiPos);
|
|
|
|
FSTATIC RCODE fqCompareValues(
|
|
FQVALUE * pValue1,
|
|
FLMBOOL bInclusive1,
|
|
FLMBOOL bNullIsLow1,
|
|
FQVALUE * pValue2,
|
|
FLMBOOL bInclusive2,
|
|
FLMBOOL bNullIsLow2,
|
|
FLMUINT uiCompareRules,
|
|
FLMUINT uiLanguage,
|
|
FLMINT * piCmp);
|
|
|
|
FSTATIC RCODE fqCheckUnionPredicates(
|
|
CONTEXT_PATH * pContextPath,
|
|
FLMUINT uiLanguage,
|
|
PATH_PRED * pPred);
|
|
|
|
FSTATIC void fqClipContext(
|
|
OP_CONTEXT * pContext);
|
|
|
|
FSTATIC void fqImportChildContexts(
|
|
OP_CONTEXT * pDestContext,
|
|
OP_CONTEXT * pSrcContext);
|
|
|
|
FSTATIC void fqImportContextPaths(
|
|
OP_CONTEXT * pDestContext,
|
|
OP_CONTEXT * pSrcContext);
|
|
|
|
FSTATIC void fqImportContext(
|
|
OP_CONTEXT * pDestContext,
|
|
OP_CONTEXT * pSrcContext);
|
|
|
|
FSTATIC void fqMergeContexts(
|
|
FQNODE * pQNode,
|
|
OP_CONTEXT * pDestContext);
|
|
|
|
FSTATIC void fqCheckPathMatch(
|
|
XPATH_COMPONENT * pXPathContextComponent,
|
|
XPATH_COMPONENT * pXPathComponent);
|
|
|
|
FSTATIC FQNODE * fqEvalLogicalOperands(
|
|
FQNODE * pQNode);
|
|
|
|
FSTATIC FQNODE * fqClipNotNode(
|
|
FQNODE * pQNode,
|
|
FQNODE ** ppExpr);
|
|
|
|
FSTATIC RCODE fqGetNodeIdValue(
|
|
FQVALUE * pQValue);
|
|
|
|
FSTATIC FLMBOOL haveChildKeyComponents(
|
|
ICD * pParentIcd);
|
|
|
|
FSTATIC RCODE fqEvalOperator(
|
|
FLMUINT uiLanguage,
|
|
FQNODE * pQNode);
|
|
|
|
FSTATIC void fqResetIterator(
|
|
FQNODE * pQNode,
|
|
FLMBOOL bFullRelease,
|
|
FLMBOOL bUseKeyNodes);
|
|
|
|
FSTATIC RCODE fqGetValueFromNode(
|
|
F_Db * pDb,
|
|
IF_DOMNode * pNode,
|
|
FQVALUE * pQValue,
|
|
FLMUINT uiMetaDataType);
|
|
|
|
FSTATIC void fqResetQueryTree(
|
|
FQNODE * pQueryTree,
|
|
FLMBOOL bUseKeyNodes,
|
|
FLMBOOL bResetAllXPaths);
|
|
|
|
FSTATIC RCODE fqTryEvalOperator(
|
|
FLMUINT uiLanguage,
|
|
FQNODE ** ppCurrNode);
|
|
|
|
FSTATIC FQNODE * fqBackupTree(
|
|
FQNODE * pCurrNode,
|
|
FLMBOOL * pbGetNodeValue);
|
|
|
|
FSTATIC void fqReleaseQueryExpr(
|
|
FQNODE * pQNode);
|
|
|
|
FSTATIC FLMBOOL fqTestValue(
|
|
FQNODE * pQueryExpr);
|
|
|
|
FSTATIC RCODE fqGetValueFromKey(
|
|
FLMUINT uiDataType,
|
|
F_DataVector * pKey,
|
|
FQVALUE * pQValue,
|
|
FLMBYTE ** ppucValue,
|
|
FLMUINT uiValueBufSize);
|
|
|
|
FSTATIC RCODE fqPredCompare(
|
|
FLMUINT uiLanguage,
|
|
PATH_PRED * pPred,
|
|
FQVALUE * pQValue,
|
|
FLMBOOL * pbPasses);
|
|
|
|
FSTATIC void fqMarkXPathNodeListPassed(
|
|
PATH_PRED * pPred);
|
|
|
|
FSTATIC int nodeIdCompareFunc(
|
|
void * pvData1,
|
|
void * pvData2,
|
|
void * pvUserData);
|
|
|
|
/***************************************************************************
|
|
Desc: Constructor
|
|
***************************************************************************/
|
|
F_Query::F_Query()
|
|
{
|
|
m_pool.poolInit( 1024);
|
|
m_uiLanguage = FLM_US_LANG;
|
|
m_uiCollection = XFLM_DATA_COLLECTION;
|
|
initVars();
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Destructor
|
|
***************************************************************************/
|
|
F_Query::~F_Query()
|
|
{
|
|
clearQuery();
|
|
m_pool.poolFree();
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Reset function
|
|
***************************************************************************/
|
|
void F_Query::clearQuery( void)
|
|
{
|
|
stopBuildingResultSet();
|
|
resetQuery();
|
|
|
|
if (m_pDatabase)
|
|
{
|
|
m_pDatabase->lockMutex();
|
|
|
|
// Unlink the query from the list off of the F_Database object.
|
|
|
|
if (m_pPrev)
|
|
{
|
|
m_pPrev->m_pNext = m_pNext;
|
|
}
|
|
else
|
|
{
|
|
m_pDatabase->m_pFirstQuery = m_pNext;
|
|
}
|
|
if (m_pNext)
|
|
{
|
|
m_pNext->m_pPrev = m_pPrev;
|
|
}
|
|
else
|
|
{
|
|
m_pDatabase->m_pLastQuery = m_pPrev;
|
|
}
|
|
m_pDatabase->unlockMutex();
|
|
}
|
|
|
|
if (m_pCurrDoc)
|
|
{
|
|
m_pCurrDoc->Release();
|
|
m_pCurrDoc = NULL;
|
|
}
|
|
|
|
if (m_pCurrNode)
|
|
{
|
|
m_pCurrNode->Release();
|
|
m_pCurrNode = NULL;
|
|
}
|
|
|
|
if (m_ppObjectList)
|
|
{
|
|
while (m_uiObjectCount)
|
|
{
|
|
m_uiObjectCount--;
|
|
m_ppObjectList [m_uiObjectCount]->Release();
|
|
m_ppObjectList [m_uiObjectCount] = NULL;
|
|
}
|
|
f_free( &m_ppObjectList);
|
|
}
|
|
|
|
if (m_pDocIdSet)
|
|
{
|
|
m_pDocIdSet->Release();
|
|
m_pDocIdSet = NULL;
|
|
}
|
|
|
|
if (m_pFSIndexCursor)
|
|
{
|
|
m_pFSIndexCursor->Release();
|
|
m_pFSIndexCursor = NULL;
|
|
}
|
|
|
|
// Clear all of the predicate cursors that may still be
|
|
// laying around.
|
|
|
|
if (m_pQuery && m_pQuery->pContext)
|
|
{
|
|
OP_CONTEXT * pContext = m_pQuery->pContext;
|
|
CONTEXT_PATH * pContextPath;
|
|
PATH_PRED * pPred;
|
|
|
|
for (;;)
|
|
{
|
|
|
|
// Clear predicates of the context we are in.
|
|
|
|
pContextPath = pContext->pFirstPath;
|
|
while (pContextPath)
|
|
{
|
|
pPred = pContextPath->pFirstPred;
|
|
while (pPred)
|
|
{
|
|
|
|
// Predicate should only have either an index cursor
|
|
// or a collection cursor or an app. predicate.
|
|
|
|
if (pPred->pFSIndexCursor)
|
|
{
|
|
flmAssert( !pPred->pFSCollectionCursor);
|
|
flmAssert( !pPred->pNodeSource);
|
|
pPred->pFSIndexCursor->Release();
|
|
}
|
|
else if (pPred->pFSCollectionCursor)
|
|
{
|
|
flmAssert( !pPred->pFSIndexCursor);
|
|
flmAssert( !pPred->pNodeSource);
|
|
pPred->pFSCollectionCursor->Release();
|
|
}
|
|
else if (pPred->pNodeSource)
|
|
{
|
|
flmAssert( !pPred->pFSIndexCursor);
|
|
flmAssert( !pPred->pFSCollectionCursor);
|
|
pPred->pNodeSource->releaseResources();
|
|
}
|
|
pPred = pPred->pNext;
|
|
}
|
|
pContextPath = pContextPath->pNext;
|
|
}
|
|
|
|
if (pContext->pFirstChild)
|
|
{
|
|
pContext = pContext->pFirstChild;
|
|
continue;
|
|
}
|
|
|
|
// Go to sibling context, if any
|
|
|
|
while (!pContext->pNextSib)
|
|
{
|
|
if ((pContext = pContext->pParent) == NULL)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If pContext is NULL at this point, there are no
|
|
// more contexts.
|
|
|
|
if (!pContext)
|
|
{
|
|
break;
|
|
}
|
|
pContext = pContext->pNextSib;
|
|
|
|
// There has to have been a sibling context at this point.
|
|
|
|
flmAssert( pContext);
|
|
}
|
|
}
|
|
if (m_pSortResultSet)
|
|
{
|
|
m_pSortResultSet->Release();
|
|
m_pSortResultSet = NULL;
|
|
}
|
|
if (m_pQueryStatus)
|
|
{
|
|
m_pQueryStatus->Release();
|
|
m_pQueryStatus = NULL;
|
|
}
|
|
if (m_pQueryValidator)
|
|
{
|
|
m_pQueryValidator->Release();
|
|
m_pQueryValidator = NULL;
|
|
}
|
|
initVars();
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Initialize all the member variables.
|
|
***************************************************************************/
|
|
void F_Query::initVars( void)
|
|
{
|
|
m_rc = NE_XFLM_OK;
|
|
m_bScan = FALSE;
|
|
m_bScanIndex = FALSE;
|
|
m_bResetAllXPaths = FALSE;
|
|
m_pFSIndexCursor = NULL;
|
|
m_bEmpty = FALSE;
|
|
m_pSortIxd = NULL;
|
|
m_pSortResultSet = NULL;
|
|
m_pFirstWaiter = NULL;
|
|
m_bStopBuildingResultSet = FALSE;
|
|
m_uiBuildThreadId = 0;
|
|
m_bPositioningEnabled = FALSE;
|
|
m_bResultSetPopulated = FALSE;
|
|
m_bEntriesAlreadyInOrder = FALSE;
|
|
m_bEncryptResultSet = FALSE;
|
|
m_eState = XFLM_QUERY_NOT_POSITIONED;
|
|
m_pQueryStatus = NULL;
|
|
m_pQueryValidator = NULL;
|
|
m_pDatabase = NULL;
|
|
m_pPrev = NULL;
|
|
m_pNext = NULL;
|
|
m_bOptimized = FALSE;
|
|
m_pCurrContext = NULL;
|
|
m_pCurrContextPath = NULL;
|
|
m_pCurrPred = NULL;
|
|
m_pExprXPathSource = NULL;
|
|
m_pQuery = NULL;
|
|
m_pCurrOpt = NULL;
|
|
m_pDb = NULL;
|
|
m_pCurExprState = NULL;
|
|
m_pCurrDoc = NULL;
|
|
m_pCurrNode = NULL;
|
|
m_ppObjectList = NULL;
|
|
m_uiObjectListSize = 0;
|
|
m_uiObjectCount = 0;
|
|
m_bRemoveDups = FALSE;
|
|
m_pDocIdSet = NULL;
|
|
m_uiIndex = 0;
|
|
m_bIndexSet = FALSE;
|
|
m_uiTimeLimit = 0;
|
|
m_uiStartTime = 0;
|
|
m_pool.poolReset( NULL);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Allocate a new expression evaluation state structure
|
|
***************************************************************************/
|
|
RCODE F_Query::allocExprState( void)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
EXPR_STATE * pExprState;
|
|
|
|
if (!m_pCurExprState || !m_pCurExprState->pNext)
|
|
{
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( EXPR_STATE),
|
|
(void **)&pExprState)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if ((pExprState->pPrev = m_pCurExprState) != NULL)
|
|
{
|
|
m_pCurExprState->pNext = pExprState;
|
|
}
|
|
m_pCurExprState = pExprState;
|
|
}
|
|
else
|
|
{
|
|
EXPR_STATE * pSaveNext;
|
|
EXPR_STATE * pSavePrev;
|
|
|
|
m_pCurExprState = m_pCurExprState->pNext;
|
|
|
|
// Zero out everything except for the prev and next pointers
|
|
|
|
pSaveNext = m_pCurExprState->pNext;
|
|
pSavePrev = m_pCurExprState->pPrev;
|
|
f_memset( m_pCurExprState, 0, sizeof( EXPR_STATE));
|
|
m_pCurExprState->pNext = pSaveNext;
|
|
m_pCurExprState->pPrev = pSavePrev;
|
|
}
|
|
m_pCurExprState->uiNumExprNeeded = 1;
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Unlinks a node from its parent and siblings. This routine assumes
|
|
that the test has already been made that the node has a parent.
|
|
***************************************************************************/
|
|
FSTATIC void fqUnlinkFromParent(
|
|
FQNODE * pQNode)
|
|
{
|
|
flmAssert( pQNode->pParent);
|
|
if (pQNode->pPrevSib)
|
|
{
|
|
pQNode->pPrevSib->pNextSib = pQNode->pNextSib;
|
|
}
|
|
else
|
|
{
|
|
pQNode->pParent->pFirstChild = pQNode->pNextSib;
|
|
}
|
|
if (pQNode->pNextSib)
|
|
{
|
|
pQNode->pNextSib->pPrevSib = pQNode->pPrevSib;
|
|
}
|
|
else
|
|
{
|
|
pQNode->pParent->pLastChild = pQNode->pPrevSib;
|
|
}
|
|
|
|
pQNode->pParent = NULL;
|
|
pQNode->pPrevSib = NULL;
|
|
pQNode->pNextSib = NULL;
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Links one FQNODE as the first child of another. Will unlink the
|
|
child node from any parent it may be linked to.
|
|
***************************************************************************/
|
|
FSTATIC void fqLinkFirstChild(
|
|
FQNODE * pParent,
|
|
FQNODE * pChild
|
|
)
|
|
{
|
|
|
|
// If necessary, unlink the child from parent and siblings
|
|
|
|
if (pChild->pParent)
|
|
{
|
|
fqUnlinkFromParent( pChild);
|
|
}
|
|
|
|
// Link child as the first child to parent
|
|
|
|
pChild->pParent = pParent;
|
|
pChild->pPrevSib = NULL;
|
|
if ((pChild->pNextSib = pParent->pFirstChild) != NULL)
|
|
{
|
|
pChild->pNextSib->pPrevSib = pChild;
|
|
}
|
|
else
|
|
{
|
|
pParent->pLastChild = pChild;
|
|
}
|
|
pParent->pFirstChild = pChild;
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Links one FQNODE as the last child of another. Will unlink the
|
|
child node from any parent it may be linked to.
|
|
***************************************************************************/
|
|
FSTATIC void fqLinkLastChild(
|
|
FQNODE * pParent,
|
|
FQNODE * pChild
|
|
)
|
|
{
|
|
|
|
// If necessary, unlink the child from parent and siblings
|
|
|
|
if (pChild->pParent)
|
|
{
|
|
fqUnlinkFromParent( pChild);
|
|
}
|
|
|
|
// Link child as the last child to parent
|
|
|
|
pChild->pParent = pParent;
|
|
pChild->pNextSib = NULL;
|
|
if ((pChild->pPrevSib = pParent->pLastChild) != NULL)
|
|
{
|
|
pChild->pPrevSib->pNextSib = pChild;
|
|
}
|
|
else
|
|
{
|
|
pParent->pFirstChild = pChild;
|
|
}
|
|
pParent->pLastChild = pChild;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Replace one node with another node in the tree.
|
|
****************************************************************************/
|
|
FSTATIC void fqReplaceNode(
|
|
FQNODE * pNodeToReplace,
|
|
FQNODE * pReplacementNode
|
|
)
|
|
{
|
|
FQNODE_p pParentNode;
|
|
FLMBOOL bLinkAsFirst = (pNodeToReplace->pNextSib) ? TRUE : FALSE;
|
|
|
|
if (pReplacementNode->pParent)
|
|
{
|
|
fqUnlinkFromParent( pReplacementNode);
|
|
}
|
|
if ((pParentNode = pNodeToReplace->pParent) != NULL)
|
|
{
|
|
fqUnlinkFromParent( pNodeToReplace);
|
|
if (bLinkAsFirst)
|
|
{
|
|
fqLinkFirstChild( pParentNode, pReplacementNode);
|
|
}
|
|
else
|
|
{
|
|
fqLinkLastChild( pParentNode, pReplacementNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Allocate a value node
|
|
***************************************************************************/
|
|
RCODE F_Query::allocValueNode(
|
|
FLMUINT uiValLen,
|
|
eValTypes eValType,
|
|
FQNODE ** ppQNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pQNode;
|
|
|
|
// If an error has already occurred, cannot add more to query.
|
|
|
|
if (RC_BAD( rc = m_rc))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (!m_pCurExprState)
|
|
{
|
|
if (RC_BAD( rc = allocExprState()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if (m_pCurExprState->bExpectingLParen)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_EXPECTING_LPAREN);
|
|
goto Exit;
|
|
}
|
|
|
|
if (!expectingOperand())
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_UNEXPECTED_VALUE);
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( FQNODE),
|
|
(void **)ppQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQNode = *ppQNode;
|
|
pQNode->eNodeType = FLM_VALUE_NODE;
|
|
pQNode->currVal.eValType = eValType;
|
|
pQNode->currVal.uiDataLen = uiValLen;
|
|
pQNode->currVal.uiFlags = VAL_IS_CONSTANT;
|
|
|
|
// For string and binary data, allocate a buffer.
|
|
|
|
if (uiValLen &&
|
|
(eValType == XFLM_UTF8_VAL || eValType == XFLM_BINARY_VAL))
|
|
{
|
|
if (RC_BAD( rc = m_pool.poolAlloc( uiValLen,
|
|
(void **)&pQNode->currVal.val.pucBuf)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if (m_pCurExprState->pCurOperatorNode)
|
|
{
|
|
fqLinkLastChild( m_pCurExprState->pCurOperatorNode, pQNode);
|
|
}
|
|
else
|
|
{
|
|
flmAssert( !m_pCurExprState->pExpr);
|
|
m_pCurExprState->pExpr = pQNode;
|
|
}
|
|
m_pCurExprState->bExpectingOperator = TRUE;
|
|
m_pCurExprState->pLastNode = pQNode;
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Adds a unicode value to the query criteria.
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::addUnicodeValue(
|
|
const FLMUNICODE * puzVal)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pQNode;
|
|
FLMUINT uiValLen;
|
|
FLMUINT uiCharCount;
|
|
FLMUINT uiSenLen;
|
|
|
|
// If an error has already occurred, cannot add more to query.
|
|
|
|
if (RC_BAD( rc = m_rc))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = flmUnicode2Storage( puzVal, 0, NULL,
|
|
&uiValLen, &uiCharCount)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = allocValueNode( uiValLen, XFLM_UTF8_VAL, &pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (uiValLen)
|
|
{
|
|
FLMBOOL bHaveWildCards;
|
|
const FLMUNICODE * puzTmp;
|
|
|
|
// See if there are wildcards
|
|
|
|
puzTmp = puzVal;
|
|
bHaveWildCards = FALSE;
|
|
while (*puzTmp)
|
|
{
|
|
if (*puzTmp == ASCII_BACKSLASH)
|
|
{
|
|
|
|
// Skip over the next character no matter what
|
|
// because it is escaped.
|
|
|
|
puzTmp++;
|
|
if (*puzTmp == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if (*puzTmp == ASCII_WILDCARD)
|
|
{
|
|
bHaveWildCards = TRUE;
|
|
break;
|
|
}
|
|
puzTmp++;
|
|
}
|
|
|
|
if (RC_BAD( rc = flmUnicode2Storage( puzVal, uiCharCount,
|
|
pQNode->currVal.val.pucBuf,
|
|
&pQNode->currVal.uiDataLen, &uiCharCount)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Skip past the SEN
|
|
|
|
if (RC_BAD( rc = flmGetCharCountFromStorageBuf(
|
|
(const FLMBYTE **)&pQNode->currVal.val.pucBuf,
|
|
pQNode->currVal.uiDataLen, NULL, &uiSenLen)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
pQNode->currVal.uiDataLen -= uiSenLen;
|
|
if (bHaveWildCards)
|
|
{
|
|
pQNode->currVal.uiFlags |= VAL_HAS_WILDCARDS;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
m_rc = rc;
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Adds a UTF8 value to the query criteria.
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::addUTF8Value(
|
|
const char * pszVal,
|
|
FLMUINT uiUTF8Len)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pQNode;
|
|
FLMUINT uiValLen;
|
|
FLMUINT uiSenLen;
|
|
FLMBYTE * pucEnd = NULL;
|
|
|
|
// If an error has already occurred, cannot add more to query.
|
|
|
|
if (RC_BAD( rc = m_rc))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = flmUTF8ToStorage(
|
|
(FLMBYTE *)pszVal, uiUTF8Len, NULL, &uiValLen)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = allocValueNode( uiValLen, XFLM_UTF8_VAL, &pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (uiValLen)
|
|
{
|
|
FLMBOOL bHaveWildCards;
|
|
const FLMBYTE * pszTmp;
|
|
FLMUNICODE uzChar;
|
|
|
|
// See if there are wildcards
|
|
|
|
pszTmp = (FLMBYTE *)pszVal;
|
|
if (uiUTF8Len)
|
|
{
|
|
pucEnd = (FLMBYTE *)pszVal + uiUTF8Len;
|
|
}
|
|
bHaveWildCards = FALSE;
|
|
for (;;)
|
|
{
|
|
if (RC_BAD( rc = f_getCharFromUTF8Buf( &pszTmp, pucEnd, &uzChar)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (uzChar == ASCII_BACKSLASH)
|
|
{
|
|
|
|
// Skip over the next character no matter what
|
|
// because it is escaped.
|
|
|
|
if (RC_BAD( rc = f_getCharFromUTF8Buf( &pszTmp, pucEnd, &uzChar)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!uzChar)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if (uzChar == ASCII_WILDCARD)
|
|
{
|
|
bHaveWildCards = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (!uzChar)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (RC_BAD( rc = flmUTF8ToStorage( (FLMBYTE *)pszVal,
|
|
uiUTF8Len, (FLMBYTE *)pQNode->currVal.val.pucBuf,
|
|
&pQNode->currVal.uiDataLen)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Skip past the SEN
|
|
|
|
if (RC_BAD( rc = flmGetCharCountFromStorageBuf(
|
|
(const FLMBYTE **)&pQNode->currVal.val.pucBuf,
|
|
pQNode->currVal.uiDataLen, NULL, &uiSenLen)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
pQNode->currVal.uiDataLen -= uiSenLen;
|
|
if (bHaveWildCards)
|
|
{
|
|
pQNode->currVal.uiFlags |= VAL_HAS_WILDCARDS;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
m_rc = rc;
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Adds a binary value to the query criteria.
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::addBinaryValue(
|
|
const void * pvVal,
|
|
FLMUINT uiValLen)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pQNode;
|
|
|
|
if (RC_BAD( rc = allocValueNode( uiValLen, XFLM_BINARY_VAL, &pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (uiValLen)
|
|
{
|
|
f_memcpy( pQNode->currVal.val.pucBuf, pvVal, uiValLen);
|
|
}
|
|
|
|
Exit:
|
|
|
|
m_rc = rc;
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Adds a UINT value to the query criteria.
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::addUINTValue(
|
|
FLMUINT uiVal
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pQNode;
|
|
|
|
if (RC_BAD( rc = allocValueNode( 0, XFLM_UINT_VAL, &pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQNode->currVal.val.uiVal = uiVal;
|
|
|
|
Exit:
|
|
|
|
m_rc = rc;
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Adds an INT value to the query criteria.
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::addINTValue(
|
|
FLMINT iVal
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pQNode;
|
|
|
|
if (RC_BAD( rc = allocValueNode( 0, XFLM_INT_VAL, &pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQNode->currVal.val.iVal = iVal;
|
|
|
|
Exit:
|
|
|
|
m_rc = rc;
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Adds a UINT64 value to the query criteria.
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::addUINT64Value(
|
|
FLMUINT64 ui64Val
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pQNode;
|
|
|
|
if (RC_BAD( rc = allocValueNode( 0, XFLM_UINT64_VAL, &pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQNode->currVal.val.ui64Val = ui64Val;
|
|
|
|
Exit:
|
|
|
|
m_rc = rc;
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Adds an INT64 value to the query criteria.
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::addINT64Value(
|
|
FLMINT64 i64Val
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pQNode;
|
|
|
|
if (RC_BAD( rc = allocValueNode( 0, XFLM_INT64_VAL, &pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQNode->currVal.val.i64Val = i64Val;
|
|
|
|
Exit:
|
|
|
|
m_rc = rc;
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Adds a BOOL value to the query criteria.
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::addBoolean(
|
|
FLMBOOL bVal,
|
|
FLMBOOL bUnknown
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pQNode;
|
|
|
|
if (RC_BAD( rc = allocValueNode( 0, XFLM_BOOL_VAL, &pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQNode->currVal.val.eBool = (XFlmBoolType)(bUnknown
|
|
? XFLM_UNKNOWN
|
|
: (XFlmBoolType)(bVal
|
|
? XFLM_TRUE
|
|
: XFLM_FALSE));
|
|
|
|
Exit:
|
|
|
|
m_rc = rc;
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Add an XPATH component
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::addXPathComponent(
|
|
eXPathAxisTypes eXPathAxis,
|
|
eDomNodeType eNodeType,
|
|
FLMUINT uiDictNum,
|
|
IF_QueryNodeSource * pNodeSource
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
XPATH_COMPONENT * pXPathComponent;
|
|
FXPATH * pXPath;
|
|
FQNODE * pQNode;
|
|
|
|
// If an error has already occurred, cannot add more to query.
|
|
|
|
if (RC_BAD( rc = m_rc))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (!m_pCurExprState)
|
|
{
|
|
if (RC_BAD( rc = allocExprState()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// Must be expecting an operand, or the last node
|
|
// must be an XPATH
|
|
|
|
if (!expectingOperand() &&
|
|
m_pCurExprState->pLastNode->eNodeType != FLM_XPATH_NODE)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_UNEXPECTED_XPATH_COMPONENT);
|
|
goto Exit;
|
|
}
|
|
|
|
// If axis is META_AXIS, verify that the specific type of meta data
|
|
// requested is valid.
|
|
|
|
if (eXPathAxis == META_AXIS)
|
|
{
|
|
switch (uiDictNum)
|
|
{
|
|
case XFLM_META_NODE_ID:
|
|
case XFLM_META_DOCUMENT_ID:
|
|
case XFLM_META_PARENT_ID:
|
|
case XFLM_META_FIRST_CHILD_ID:
|
|
case XFLM_META_LAST_CHILD_ID:
|
|
case XFLM_META_NEXT_SIBLING_ID:
|
|
case XFLM_META_PREV_SIBLING_ID:
|
|
case XFLM_META_VALUE:
|
|
break;
|
|
default:
|
|
rc = RC_SET( NE_XFLM_Q_INVALID_META_DATA_TYPE);
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// Allocate an XPATH component
|
|
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( XPATH_COMPONENT),
|
|
(void **)&pXPathComponent)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pXPathComponent->eNodeType = eNodeType;
|
|
pXPathComponent->eXPathAxis = eXPathAxis;
|
|
pXPathComponent->uiDictNum = uiDictNum;
|
|
pXPathComponent->pNodeSource = pNodeSource;
|
|
if (m_pCurExprState->pPrev &&
|
|
m_pCurExprState->pXPathComponent)
|
|
{
|
|
|
|
// pXPathContext is the XPATH component context for this
|
|
// XPATH component. This may be used in optimization.
|
|
|
|
pXPathComponent->pXPathContext = m_pCurExprState->pXPathComponent;
|
|
}
|
|
|
|
// If we are not expecting an operand, then the last component
|
|
// has to be an XPATH node.
|
|
|
|
if (!expectingOperand())
|
|
{
|
|
pXPath = m_pCurExprState->pLastNode->nd.pXPath;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Need to allocate a node and an XPATH
|
|
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( FQNODE),
|
|
(void **)&pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( FXPATH),
|
|
(void **)&pXPath)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQNode->eNodeType = FLM_XPATH_NODE;
|
|
pQNode->nd.pXPath = pXPath;
|
|
|
|
// Link this node into the expression
|
|
|
|
if (m_pCurExprState->pCurOperatorNode)
|
|
{
|
|
fqLinkLastChild( m_pCurExprState->pCurOperatorNode, pQNode);
|
|
}
|
|
else
|
|
{
|
|
flmAssert( !m_pCurExprState->pExpr);
|
|
m_pCurExprState->pExpr = pQNode;
|
|
}
|
|
m_pCurExprState->bExpectingOperator = TRUE;
|
|
m_pCurExprState->pLastNode = pQNode;
|
|
}
|
|
pXPathComponent->pXPathNode = m_pCurExprState->pLastNode;
|
|
|
|
// Link the new XPATH component into the XPATH as the last
|
|
// component.
|
|
|
|
if ((pXPathComponent->pPrev = pXPath->pLastComponent) != NULL)
|
|
{
|
|
pXPath->pLastComponent->pNext = pXPathComponent;
|
|
}
|
|
else
|
|
{
|
|
pXPath->pFirstComponent = pXPathComponent;
|
|
}
|
|
pXPath->pLastComponent = pXPathComponent;
|
|
|
|
if (pNodeSource)
|
|
{
|
|
if (RC_BAD( rc = objectAddRef( pNodeSource)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
m_rc = rc;
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Keep track of objects supplied by the application that we use
|
|
for callbacks, etc.
|
|
***************************************************************************/
|
|
RCODE F_Query::objectAddRef(
|
|
F_Object * pObject)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
// If object list is full, make room for 20 more
|
|
|
|
if (m_uiObjectCount == m_uiObjectListSize)
|
|
{
|
|
if (RC_BAD( rc = f_realloc( sizeof( F_Object *) *
|
|
(m_uiObjectListSize + 20),
|
|
&m_ppObjectList)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
m_uiObjectListSize += 20;
|
|
}
|
|
|
|
m_ppObjectList [m_uiObjectCount++] = pObject;
|
|
pObject->AddRef();
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get a context position from a value. It must be a positive
|
|
integer. Anything else will cause an error to be returned.
|
|
***************************************************************************/
|
|
FSTATIC RCODE fqGetPosition(
|
|
FQVALUE * pQValue,
|
|
FLMUINT * puiPos
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
switch (pQValue->eValType)
|
|
{
|
|
case XFLM_UINT_VAL:
|
|
if (!pQValue->val.uiVal)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_INVALID_CONTEXT_POS);
|
|
goto Exit;
|
|
}
|
|
*puiPos = pQValue->val.uiVal;
|
|
break;
|
|
case XFLM_UINT64_VAL:
|
|
if (!pQValue->val.ui64Val ||
|
|
pQValue->val.ui64Val > (FLMUINT64)(~((FLMUINT)0)))
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_INVALID_CONTEXT_POS);
|
|
goto Exit;
|
|
}
|
|
*puiPos = (FLMUINT)pQValue->val.ui64Val;
|
|
break;
|
|
case XFLM_INT_VAL:
|
|
if (pQValue->val.iVal <= 0)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_INVALID_CONTEXT_POS);
|
|
goto Exit;
|
|
}
|
|
*puiPos = (FLMUINT)pQValue->val.iVal;
|
|
break;
|
|
case XFLM_INT64_VAL:
|
|
if (pQValue->val.i64Val <= 0 ||
|
|
pQValue->val.i64Val > (FLMINT64)(~((FLMUINT)0)))
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_INVALID_CONTEXT_POS);
|
|
goto Exit;
|
|
}
|
|
*puiPos = (FLMUINT)pQValue->val.i64Val;
|
|
break;
|
|
default:
|
|
rc = RC_SET( NE_XFLM_Q_INVALID_CONTEXT_POS);
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Determine if an XPATH component has a test for context position.
|
|
***************************************************************************/
|
|
FINLINE FLMBOOL hasContextPosTest(
|
|
XPATH_COMPONENT * pXPathComponent
|
|
)
|
|
{
|
|
return( pXPathComponent->pContextPosExpr ||
|
|
pXPathComponent->uiContextPosNeeded
|
|
? TRUE
|
|
: FALSE);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Add an operator to the query expression
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::addOperator(
|
|
eQueryOperators eOperator,
|
|
FLMUINT uiCompareRules,
|
|
IF_OperandComparer * pOpComparer)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pQNode;
|
|
FQEXPR * pQExpr;
|
|
FQNODE * pParentNode;
|
|
|
|
// If an error has already occurred, cannot add more to query.
|
|
|
|
if (RC_BAD( rc = m_rc))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (!m_pCurExprState)
|
|
{
|
|
if (RC_BAD( rc = allocExprState()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// If we are expecting a left paren (for a function), that is
|
|
// the only thing that is acceptable at this point.
|
|
|
|
if (m_pCurExprState->bExpectingLParen && eOperator != XFLM_LPAREN_OP)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_EXPECTING_LPAREN);
|
|
goto Exit;
|
|
}
|
|
|
|
switch (eOperator)
|
|
{
|
|
case XFLM_LPAREN_OP:
|
|
|
|
// If the operator is a left paren, increment the nesting level
|
|
|
|
if (expectingOperator())
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_UNEXPECTED_LPAREN);
|
|
goto Exit;
|
|
}
|
|
m_pCurExprState->uiNestLevel++;
|
|
m_pCurExprState->bExpectingLParen = FALSE;
|
|
goto Exit;
|
|
|
|
case XFLM_RPAREN_OP:
|
|
if (expectingOperand())
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_UNEXPECTED_RPAREN);
|
|
goto Exit;
|
|
}
|
|
if (!m_pCurExprState->uiNestLevel)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_UNMATCHED_RPAREN);
|
|
goto Exit;
|
|
}
|
|
m_pCurExprState->uiNestLevel--;
|
|
|
|
// See if this is the right paren to a function call
|
|
|
|
if (!m_pCurExprState->uiNestLevel && parsingFunction())
|
|
{
|
|
|
|
// If we have a valid expression, link it to the
|
|
// function node as its last parameter.
|
|
|
|
if (m_pCurExprState->pExpr)
|
|
{
|
|
m_pCurExprState->uiNumExpressions++;
|
|
|
|
// uiNumExprNeeded might be zero.
|
|
|
|
if (m_pCurExprState->uiNumExpressions >
|
|
m_pCurExprState->uiNumExprNeeded)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_INVALID_NUM_FUNC_ARGS);
|
|
goto Exit;
|
|
}
|
|
|
|
// Allocate an expression node and link it to the
|
|
// function.
|
|
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( FQEXPR),
|
|
(void **)&pQExpr)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if ((pQExpr->pPrev =
|
|
m_pCurExprState->pQFunction->pLastArg) != NULL)
|
|
{
|
|
pQExpr->pPrev->pNext = pQExpr;
|
|
}
|
|
else
|
|
{
|
|
m_pCurExprState->pQFunction->pFirstArg = pQExpr;
|
|
}
|
|
m_pCurExprState->pQFunction->pLastArg = pQExpr;
|
|
pQExpr->pExpr = m_pCurExprState->pExpr;
|
|
if (RC_BAD( rc = getPredicates( &pQExpr->pExpr, NULL,
|
|
m_pCurExprState->pXPathComponent)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If this is a user-defined function, make sure the
|
|
// expression parameter (there should only be one) is
|
|
// an XPATH expression, and that the getPredicates call
|
|
// didn't eliminate the expression.
|
|
|
|
if (m_pCurExprState->pQFunction->pFuncObj)
|
|
{
|
|
if (!pQExpr->pExpr ||
|
|
pQExpr->pExpr->eNodeType != FLM_XPATH_NODE)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_INVALID_FUNC_ARG);
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// See if we got the required number of arguments for
|
|
// the function
|
|
|
|
if (m_pCurExprState->uiNumExpressions <
|
|
m_pCurExprState->uiNumExprNeeded)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_INVALID_NUM_FUNC_ARGS);
|
|
goto Exit;
|
|
}
|
|
|
|
// Return to the former context.
|
|
|
|
m_pCurExprState = m_pCurExprState->pPrev;
|
|
}
|
|
goto Exit;
|
|
|
|
case XFLM_NEG_OP:
|
|
case XFLM_NOT_OP:
|
|
if (expectingOperator())
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_EXPECTING_OPERATOR);
|
|
goto Exit;
|
|
}
|
|
break;
|
|
|
|
case XFLM_COMMA_OP:
|
|
|
|
// In order for a comma to be legal, the following conditions
|
|
// must be met:
|
|
// 1) Must be inside a function
|
|
// 2) Must need at least two arguments for the function
|
|
// 3) Must not already have enough arguments
|
|
// 4) Must be at nesting level 1
|
|
// 5) Must be expecting an operator
|
|
// 6) Must have a non-empty expression we can link to
|
|
// function node.
|
|
|
|
if (!parsingFunction() ||
|
|
m_pCurExprState->uiNumExprNeeded < 2 ||
|
|
m_pCurExprState->uiNumExpressions <
|
|
m_pCurExprState->uiNumExprNeeded - 1 ||
|
|
m_pCurExprState->uiNestLevel > 1 ||
|
|
expectingOperand() ||
|
|
!m_pCurExprState->pExpr)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_UNEXPECTED_COMMA);
|
|
goto Exit;
|
|
}
|
|
m_pCurExprState->uiNumExpressions++;
|
|
|
|
// Allocate an expression node and link it to the
|
|
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( FQEXPR),
|
|
(void **)&pQExpr)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if ((pQExpr->pPrev =
|
|
m_pCurExprState->pQFunction->pLastArg) != NULL)
|
|
{
|
|
pQExpr->pPrev->pNext = pQExpr;
|
|
}
|
|
else
|
|
{
|
|
m_pCurExprState->pQFunction->pFirstArg = pQExpr;
|
|
}
|
|
m_pCurExprState->pQFunction->pLastArg = pQExpr;
|
|
if (RC_BAD( rc = getPredicates( &pQExpr->pExpr, NULL,
|
|
m_pCurExprState->pXPathComponent)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Reset the expression
|
|
|
|
m_pCurExprState->pExpr = NULL;
|
|
m_pCurExprState->pCurOperatorNode = NULL;
|
|
|
|
// The following conditions better already be set
|
|
|
|
flmAssert( expectingOperand());
|
|
flmAssert( !m_pCurExprState->bExpectingLParen);
|
|
flmAssert( m_pCurExprState->uiNestLevel == 1);
|
|
goto Exit;
|
|
|
|
case XFLM_LBRACKET_OP:
|
|
|
|
// Last node has to be an XPATH node
|
|
|
|
if (m_pCurExprState->pLastNode->eNodeType != FLM_XPATH_NODE)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_ILLEGAL_LBRACKET);
|
|
goto Exit;
|
|
}
|
|
|
|
// Save the last node into pQNode, because when we call allocExprState
|
|
// we will no longer be able to get at it.
|
|
|
|
pQNode = m_pCurExprState->pLastNode;
|
|
|
|
// Cannot add expressions if the last component already has
|
|
// an expression to test context position.
|
|
|
|
if (hasContextPosTest( pQNode->nd.pXPath->pLastComponent))
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_NEW_EXPR_NOT_ALLOWED);
|
|
goto Exit;
|
|
}
|
|
|
|
// Always allocate a new expression state for an xpath expression
|
|
|
|
if (RC_BAD( rc = allocExprState()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
m_pCurExprState->pXPathComponent = pQNode->nd.pXPath->pLastComponent;
|
|
|
|
goto Exit;
|
|
|
|
case XFLM_RBRACKET_OP:
|
|
|
|
// Right bracket is only allowed if we are parsing an
|
|
// xpath expression and we are at nesting level zero and
|
|
// we are not expecting an operand
|
|
|
|
if (!parsingXPathExpr() ||
|
|
m_pCurExprState->uiNestLevel ||
|
|
(expectingOperand() && m_pCurExprState->pExpr))
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_ILLEGAL_RBRACKET);
|
|
goto Exit;
|
|
}
|
|
|
|
// If we have a non-empty expression, link it to the
|
|
// list of xpath component expressions off of the xpath
|
|
// component.
|
|
|
|
if (m_pCurExprState->pExpr)
|
|
{
|
|
|
|
// If the XPATH component does not have any expression yet,
|
|
// make this expression its expression. Otherwise, AND
|
|
// this expression to the existing expression.
|
|
|
|
if (m_pCurExprState->pExpr->eNodeType == FLM_VALUE_NODE)
|
|
{
|
|
if (RC_BAD( rc = fqGetPosition( &m_pCurExprState->pExpr->currVal,
|
|
&m_pCurExprState->pXPathComponent->uiContextPosNeeded)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else if (m_pCurExprState->pExpr->eNodeType == FLM_OPERATOR_NODE &&
|
|
isArithOp( m_pCurExprState->pExpr->nd.op.eOperator))
|
|
{
|
|
m_pCurExprState->pXPathComponent->pContextPosExpr =
|
|
m_pCurExprState->pExpr;
|
|
if (RC_BAD( rc = getPredicates(
|
|
&m_pCurExprState->pXPathComponent->pContextPosExpr,
|
|
NULL,
|
|
m_pCurExprState->pXPathComponent)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If, after the optimization, we are left with a constant,
|
|
// NULL out pContextPosExpr and put it into uiContextPosNeeded.
|
|
|
|
if (m_pCurExprState->pXPathComponent->pContextPosExpr->eNodeType ==
|
|
FLM_VALUE_NODE)
|
|
{
|
|
if (RC_BAD( rc = fqGetPosition(
|
|
&m_pCurExprState->pXPathComponent->pContextPosExpr->currVal,
|
|
&m_pCurExprState->pXPathComponent->uiContextPosNeeded)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
m_pCurExprState->pXPathComponent->pContextPosExpr = NULL;
|
|
}
|
|
}
|
|
else if (!m_pCurExprState->pXPathComponent->pExpr)
|
|
{
|
|
m_pCurExprState->pXPathComponent->pExpr =
|
|
m_pCurExprState->pExpr;
|
|
if (RC_BAD( rc = getPredicates(
|
|
&m_pCurExprState->pXPathComponent->pExpr,
|
|
NULL,
|
|
m_pCurExprState->pXPathComponent)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// Create an AND node and link the existing expression with
|
|
// this new expression as children of this new AND node.
|
|
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( FQNODE),
|
|
(void **)&pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQNode->eNodeType = FLM_OPERATOR_NODE;
|
|
pQNode->nd.op.eOperator = XFLM_AND_OP;
|
|
|
|
fqLinkFirstChild( pQNode,
|
|
m_pCurExprState->pXPathComponent->pExpr);
|
|
fqLinkLastChild( pQNode, m_pCurExprState->pExpr);
|
|
m_pCurExprState->pXPathComponent->pExpr = pQNode;
|
|
|
|
// Set up a context node for the new AND node.
|
|
// If the left operand's context (which would have been
|
|
// set up previously by a call to getPredicates) is
|
|
// an intersect context, we can point this node
|
|
// right at it, and make the context's root node this
|
|
// new node. Otherwise, we have to create a new context
|
|
// and link the left operand's context to it.
|
|
|
|
if (pQNode->pFirstChild->pContext->bIntersect)
|
|
{
|
|
pQNode->pContext = pQNode->pFirstChild->pContext;
|
|
pQNode->pContext->pQRootNode = pQNode;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = createOpContext( NULL, TRUE, pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Put first child's context as child of this node's
|
|
// context.
|
|
|
|
pQNode->pContext->pFirstChild = pQNode->pFirstChild->pContext;
|
|
pQNode->pContext->pLastChild = pQNode->pFirstChild->pContext;
|
|
pQNode->pFirstChild->pContext->pParent = pQNode->pContext;
|
|
}
|
|
|
|
// Get the predicates of ONLY the right-hand side of the
|
|
// tree - because we haven't gotten its predicates yet, but
|
|
// the left hand side has already been done.
|
|
|
|
if (RC_BAD( rc = getPredicates(
|
|
&m_pCurExprState->pXPathComponent->pExpr,
|
|
pQNode->pLastChild,
|
|
m_pCurExprState->pXPathComponent)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return to the former context.
|
|
|
|
m_pCurExprState = m_pCurExprState->pPrev;
|
|
|
|
goto Exit;
|
|
|
|
default:
|
|
|
|
if (expectingOperand())
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_EXPECTING_OPERAND);
|
|
goto Exit;
|
|
}
|
|
if (!isLegalOperator( eOperator))
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_ILLEGAL_OPERATOR);
|
|
goto Exit;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// Cannot set both XFLM_COMP_COMPRESS_WHITESPACE and XFLM_COMP_NO_WHITESPACE
|
|
// in comparison rules. Also, cannot set XFLM_COMP_IGNORE_LEADING_SPACE or
|
|
// XFLM_COMP_IGNORE_TRAILING_SPACE with XFLM_COMP_NO_WHITESPACE.
|
|
|
|
if ((uiCompareRules & XFLM_COMP_NO_WHITESPACE) &&
|
|
(uiCompareRules & (XFLM_COMP_COMPRESS_WHITESPACE |
|
|
XFLM_COMP_IGNORE_LEADING_SPACE |
|
|
XFLM_COMP_IGNORE_TRAILING_SPACE)))
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_Q_ILLEGAL_COMPARE_RULES);
|
|
goto Exit;
|
|
}
|
|
|
|
// Make a QNODE and find a place for it in the query tree
|
|
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( FQNODE),
|
|
(void **)&pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQNode->eNodeType = FLM_OPERATOR_NODE;
|
|
pQNode->nd.op.eOperator = eOperator;
|
|
pQNode->nd.op.uiCompareRules = uiCompareRules;
|
|
pQNode->nd.op.pOpComparer = pOpComparer;
|
|
pQNode->uiNestLevel = m_pCurExprState->uiNestLevel;
|
|
|
|
// If this is the first operator in the query, set the current operator
|
|
// to it and graft in the current operand as its child.
|
|
|
|
if (!m_pCurExprState->pExpr)
|
|
{
|
|
m_pCurExprState->pExpr = pQNode;
|
|
m_pCurExprState->pCurOperatorNode = pQNode;
|
|
goto Exit;
|
|
}
|
|
|
|
// Go up the stack until an operator whose nest level or precedence is <
|
|
// this one's is encountered, then link this one in as the last child
|
|
|
|
pParentNode = m_pCurExprState->pCurOperatorNode;
|
|
while (pParentNode &&
|
|
(pParentNode->uiNestLevel > pQNode->uiNestLevel ||
|
|
(pParentNode->uiNestLevel == pQNode->uiNestLevel &&
|
|
getPrecedence( pParentNode->nd.op.eOperator) >=
|
|
getPrecedence( eOperator))))
|
|
{
|
|
pParentNode = pParentNode->pParent;
|
|
}
|
|
if (!pParentNode)
|
|
{
|
|
if (m_pCurExprState->pExpr)
|
|
{
|
|
fqLinkLastChild( pQNode, m_pCurExprState->pExpr);
|
|
}
|
|
m_pCurExprState->pExpr = pQNode;
|
|
}
|
|
else if (eOperator == XFLM_NOT_OP || eOperator == XFLM_NEG_OP)
|
|
{
|
|
|
|
// Need to treat NOT and NEG as if they were operands.
|
|
|
|
// Parent better be an operator.
|
|
|
|
flmAssert( pParentNode->eNodeType == FLM_OPERATOR_NODE);
|
|
|
|
#ifdef FLM_DEBUG
|
|
if (pParentNode->nd.op.eOperator == XFLM_NEG_OP ||
|
|
pParentNode->nd.op.eOperator == XFLM_NOT_OP)
|
|
{
|
|
|
|
// Must have no children.
|
|
|
|
flmAssert( pParentNode->pFirstChild == NULL);
|
|
}
|
|
else
|
|
{
|
|
|
|
// Must only have one or zero children.
|
|
|
|
flmAssert( pParentNode->pFirstChild == pParentNode->pLastChild);
|
|
}
|
|
#endif
|
|
|
|
fqLinkLastChild( pParentNode, pQNode);
|
|
flmAssert( !m_pCurExprState->bExpectingOperator);
|
|
}
|
|
else
|
|
{
|
|
|
|
// Parent better be an operator.
|
|
|
|
flmAssert( pParentNode->eNodeType == FLM_OPERATOR_NODE);
|
|
|
|
// Unlink last child of parent node and replace with this
|
|
// new node. The parent node better already have the correct
|
|
// number of children, or we are not parsing correctly.
|
|
|
|
flmAssert( pParentNode->pFirstChild);
|
|
if (pParentNode->nd.op.eOperator == XFLM_NEG_OP ||
|
|
pParentNode->nd.op.eOperator == XFLM_NOT_OP)
|
|
{
|
|
|
|
// Better only be one child.
|
|
|
|
flmAssert( !pParentNode->pFirstChild->pNextSib);
|
|
|
|
fqLinkLastChild( pQNode, pParentNode->pFirstChild);
|
|
}
|
|
else
|
|
{
|
|
|
|
// Better only be two child nodes
|
|
|
|
flmAssert( pParentNode->pFirstChild->pNextSib ==
|
|
pParentNode->pLastChild);
|
|
fqLinkLastChild( pQNode, pParentNode->pLastChild);
|
|
}
|
|
fqLinkLastChild( pParentNode, pQNode);
|
|
}
|
|
|
|
m_pCurExprState->pCurOperatorNode = pQNode;
|
|
m_pCurExprState->bExpectingOperator = FALSE;
|
|
m_pCurExprState->pLastNode = pQNode;
|
|
|
|
if (pOpComparer)
|
|
{
|
|
if (RC_BAD( rc = objectAddRef( pOpComparer)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
m_rc = rc;
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Add a function to the query expression
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::addFunction(
|
|
eQueryFunctions eFunction,
|
|
IF_QueryValFunc * pFuncObj,
|
|
FLMBOOL bHaveXPathExpr)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pQNode;
|
|
FQFUNCTION * pQFunction;
|
|
FQNODE * pParentNode;
|
|
XPATH_COMPONENT * pSaveXPathComponent;
|
|
|
|
// If an error has already occurred, cannot add more to query.
|
|
|
|
if (RC_BAD( rc = m_rc))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (!m_pCurExprState)
|
|
{
|
|
if (RC_BAD( rc = allocExprState()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// Must be expecting an operand
|
|
|
|
if (expectingOperator())
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_EXPECTING_OPERATOR);
|
|
goto Exit;
|
|
}
|
|
|
|
// Allocate a function node
|
|
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( FQNODE),
|
|
(void **)&pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( FQFUNCTION),
|
|
(void **)&pQFunction)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQNode->nd.pQFunction = pQFunction;
|
|
pQNode->eNodeType = FLM_FUNCTION_NODE;
|
|
pQFunction->eFunction = eFunction;
|
|
pQFunction->pFuncObj = pFuncObj;
|
|
|
|
// See if this is the first node in the expression.
|
|
|
|
if (!m_pCurExprState->pExpr)
|
|
{
|
|
m_pCurExprState->pExpr = pQNode;
|
|
}
|
|
else
|
|
{
|
|
pParentNode = m_pCurExprState->pCurOperatorNode;
|
|
flmAssert( pParentNode);
|
|
|
|
// Parent better be an operator, and better have room for
|
|
// function to be linked as one of its operands.
|
|
|
|
flmAssert( pParentNode->eNodeType == FLM_OPERATOR_NODE);
|
|
|
|
if (pParentNode->nd.op.eOperator == XFLM_NEG_OP ||
|
|
pParentNode->nd.op.eOperator == XFLM_NOT_OP)
|
|
{
|
|
|
|
// Better not have any children yet.
|
|
|
|
flmAssert( !pParentNode->pFirstChild);
|
|
}
|
|
else
|
|
{
|
|
|
|
// Better only have one child node.
|
|
|
|
flmAssert( pParentNode->pFirstChild == pParentNode->pLastChild);
|
|
}
|
|
fqLinkLastChild( pParentNode, pQNode);
|
|
}
|
|
m_pCurExprState->pLastNode = pQNode;
|
|
pSaveXPathComponent = m_pCurExprState->pXPathComponent;
|
|
|
|
// Always allocate a new expression state for a function
|
|
|
|
if (RC_BAD( rc = allocExprState()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// First thing we expect in this state is a left paren
|
|
|
|
m_pCurExprState->bExpectingLParen = TRUE;
|
|
m_pCurExprState->pQFunction = pQFunction;
|
|
m_pCurExprState->pXPathComponent = pSaveXPathComponent;
|
|
if (pFuncObj)
|
|
{
|
|
if (RC_BAD( rc = objectAddRef( pFuncObj)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// In this case, the expression must return a node.
|
|
|
|
m_pCurExprState->uiNumExprNeeded = bHaveXPathExpr ? (FLMUINT)1 : (FLMUINT)0;
|
|
}
|
|
else
|
|
{
|
|
|
|
//visit
|
|
// m_pCurExprState->uiNumExprNeeded = ??? - number specified by the function.
|
|
}
|
|
|
|
// Parent state needs to be expecting an operator when we come out from
|
|
// parsing the function.
|
|
|
|
m_pCurExprState->pPrev->bExpectingOperator = TRUE;
|
|
|
|
Exit:
|
|
|
|
m_rc = rc;
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Compare two values
|
|
***************************************************************************/
|
|
FSTATIC RCODE fqCompareValues(
|
|
FQVALUE * pValue1,
|
|
FLMBOOL bInclusive1,
|
|
FLMBOOL bNullIsLow1,
|
|
FQVALUE * pValue2,
|
|
FLMBOOL bInclusive2,
|
|
FLMBOOL bNullIsLow2,
|
|
FLMUINT uiCompareRules,
|
|
FLMUINT uiLanguage,
|
|
FLMINT * piCmp)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
// We have already called fqCanCompare, so no need to do it here
|
|
|
|
if (!pValue1)
|
|
{
|
|
if (!pValue2)
|
|
{
|
|
if (bNullIsLow2)
|
|
{
|
|
*piCmp = (FLMINT)(bNullIsLow1 ? 0 : 1);
|
|
}
|
|
else
|
|
{
|
|
*piCmp = (FLMINT)(bNullIsLow1 ? -1 : 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*piCmp = (FLMINT)(bNullIsLow1 ? -1 : 1);
|
|
}
|
|
goto Exit;
|
|
}
|
|
else if (!pValue2)
|
|
{
|
|
*piCmp = (FLMINT)(bNullIsLow2 ? 1 : -1);
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = fqCompare( pValue1, pValue2,
|
|
uiCompareRules, NULL, uiLanguage, piCmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If everything else is equal, the last distinguisher
|
|
// is the inclusive flags and which side of the
|
|
// value we are on if we are exclusive which is indicated
|
|
// by the bNullIsLow flags
|
|
|
|
if (*piCmp == 0)
|
|
{
|
|
if (bInclusive1 != bInclusive2)
|
|
{
|
|
if (bNullIsLow1)
|
|
{
|
|
if (bNullIsLow2)
|
|
{
|
|
// *--> v1
|
|
// o--> v2 v1 < v2
|
|
|
|
// o--> v1
|
|
// *--> v2 v1 > v2
|
|
|
|
*piCmp = bInclusive1 ? -1 : 1;
|
|
}
|
|
else
|
|
{
|
|
// *--> v1
|
|
// v2 <--o v1 > v2
|
|
|
|
// o--> v1
|
|
// v2 <--* v1 > v2
|
|
|
|
*piCmp = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bNullIsLow2)
|
|
{
|
|
// v1 <--*
|
|
// o--> v2 v1 < v2
|
|
|
|
// v1 <--o
|
|
// *--> v2 v1 < v2
|
|
|
|
*piCmp = -1;
|
|
}
|
|
else
|
|
{
|
|
// v1 <--*
|
|
// v2 <--o v1 > v2
|
|
|
|
// v1 <--o
|
|
// v2 <--* v1 < v2
|
|
|
|
*piCmp = bInclusive1 ? 1 : -1;
|
|
}
|
|
}
|
|
}
|
|
else if (!bInclusive1)
|
|
{
|
|
|
|
// bInclusive2 is also FALSE
|
|
|
|
if (bNullIsLow1)
|
|
{
|
|
if (!bNullIsLow2)
|
|
{
|
|
// o--> v1
|
|
// v2 <--o v1 > v2
|
|
*piCmp = 1;
|
|
}
|
|
// else
|
|
// {
|
|
// o--> v1
|
|
// o--> v2 v1 == v2
|
|
// *piCmp = 0;
|
|
// }
|
|
}
|
|
else
|
|
{
|
|
if (bNullIsLow2)
|
|
{
|
|
|
|
// v1 <--o
|
|
// o--> v2 v1 < v2
|
|
|
|
*piCmp = -1;
|
|
}
|
|
// else
|
|
// {
|
|
// v1 <--o
|
|
// v2 <--o v1 == v2
|
|
// *piCmp = 0;
|
|
// }
|
|
}
|
|
}
|
|
// else
|
|
// {
|
|
// bInclusive1 == TRUE && bInclusive2 == TRUE
|
|
// else case covers the cases where
|
|
// both are inclusive, in which case it
|
|
// doesn't matter which is low and which
|
|
// is high
|
|
|
|
// v1 <--*
|
|
// *--> v2 v1 == v2
|
|
|
|
// v1 <--*
|
|
// v2 <--* v1 == v2
|
|
|
|
// *--> v1
|
|
// v2 <--* v1 == v2
|
|
|
|
// *--> v1
|
|
// *--> v2 v1 == v2
|
|
|
|
// }
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Intersect a predicate into a context path.
|
|
***************************************************************************/
|
|
RCODE F_Query::intersectPredicates(
|
|
CONTEXT_PATH * pContextPath,
|
|
FQNODE * pXPathNode,
|
|
eQueryOperators eOperator,
|
|
FLMUINT uiCompareRules,
|
|
IF_OperandComparer * pOpComparer,
|
|
FQNODE * pContextNode,
|
|
FLMBOOL bNotted,
|
|
FQVALUE * pQValue,
|
|
FLMBOOL * pbClipContext
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
PATH_PRED * pPred;
|
|
FLMINT iCmp;
|
|
FLMBOOL bDoMatch;
|
|
|
|
if (!pQValue || pQValue->eValType != XFLM_UTF8_VAL)
|
|
{
|
|
bDoMatch = FALSE;
|
|
|
|
// Comparison rules don't matter for anything that is
|
|
// not text, so we normalize them to zero, so the test
|
|
// below to see if the comparison rule is the same as
|
|
// the comparison rule of the operator will work.
|
|
|
|
uiCompareRules = 0;
|
|
}
|
|
else
|
|
{
|
|
bDoMatch = (eOperator == XFLM_EQ_OP &&
|
|
(pQValue->uiFlags & VAL_IS_CONSTANT) &&
|
|
(pQValue->uiFlags & VAL_HAS_WILDCARDS))
|
|
? TRUE
|
|
: FALSE;
|
|
}
|
|
|
|
if ((pPred = pContextPath->pFirstPred) != NULL)
|
|
{
|
|
if (eOperator == XFLM_EXISTS_OP)
|
|
{
|
|
|
|
// An exists operator will either
|
|
// merge with an existing predicate or
|
|
// cancel the whole thing out as an
|
|
// empty result.
|
|
|
|
// If this predicate is not-exists, another
|
|
// predicate ANDed with this one can never
|
|
// return a result that will match, unless
|
|
// that predicate is also a not-exists, in
|
|
// which case, we simply combine this one
|
|
// with that one.
|
|
|
|
if (bNotted)
|
|
{
|
|
if (pPred->eOperator != XFLM_EXISTS_OP ||
|
|
!pPred->bNotted)
|
|
{
|
|
*pbClipContext = TRUE;
|
|
}
|
|
}
|
|
goto Exit;
|
|
}
|
|
else if (pPred->eOperator == XFLM_EXISTS_OP)
|
|
{
|
|
|
|
// If the first predicate is an exists operator
|
|
// it will be the only one, because otherwise
|
|
// it will have been merged with another operator
|
|
// in the code just above.
|
|
|
|
flmAssert( !pPred->pNext);
|
|
|
|
// If the predicate is notted, another predicate
|
|
// ANDed with this one can never return a result.
|
|
|
|
if (pPred->bNotted)
|
|
{
|
|
*pbClipContext = TRUE;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Change the predicate to the current
|
|
// operator.
|
|
|
|
pPred->eOperator = eOperator;
|
|
pPred->pFromValue = pQValue;
|
|
pPred->bNotted = bNotted;
|
|
}
|
|
goto Exit;
|
|
}
|
|
else if ((eOperator == XFLM_EQ_OP && !bDoMatch) ||
|
|
eOperator == XFLM_LE_OP ||
|
|
eOperator == XFLM_LT_OP ||
|
|
eOperator == XFLM_GE_OP ||
|
|
eOperator == XFLM_GT_OP)
|
|
{
|
|
|
|
// If there is range operator, there
|
|
// should only be one of them with the
|
|
// same compare rules, because they
|
|
// will all always be merged with these operators
|
|
// or they will cancel to yield an empty result
|
|
// when doing intersections.
|
|
|
|
while (pPred)
|
|
{
|
|
if (pPred->eOperator == XFLM_RANGE_OP &&
|
|
pPred->uiCompareRules == uiCompareRules)
|
|
{
|
|
FQVALUE * pFromValue;
|
|
FQVALUE * pUntilValue;
|
|
FLMBOOL bInclFrom;
|
|
FLMBOOL bInclUntil;
|
|
|
|
pFromValue = (eOperator == XFLM_EQ_OP ||
|
|
eOperator == XFLM_GE_OP ||
|
|
eOperator == XFLM_GT_OP)
|
|
? pQValue
|
|
: NULL;
|
|
pUntilValue = (eOperator == XFLM_EQ_OP ||
|
|
eOperator == XFLM_LE_OP ||
|
|
eOperator == XFLM_LT_OP)
|
|
? pQValue
|
|
: NULL;
|
|
bInclFrom = (FLMBOOL)(eOperator == XFLM_EQ_OP ||
|
|
eOperator == XFLM_GE_OP
|
|
? TRUE
|
|
: FALSE);
|
|
bInclUntil = (FLMBOOL)(eOperator == XFLM_EQ_OP ||
|
|
eOperator == XFLM_LE_OP
|
|
? TRUE
|
|
: FALSE);
|
|
|
|
// If the value type is not compatible with the predicate's
|
|
// value type, we cannot do the comparison, and there is
|
|
// no intersection.
|
|
|
|
if (!fqCanCompare( pQValue, pPred->pFromValue) ||
|
|
!fqCanCompare( pQValue, pPred->pUntilValue))
|
|
{
|
|
*pbClipContext = TRUE;
|
|
}
|
|
else if (RC_BAD( rc = fqCompareValues( pFromValue,
|
|
bInclFrom, TRUE,
|
|
pPred->pFromValue, pPred->bInclFrom, TRUE,
|
|
uiCompareRules, m_uiLanguage, &iCmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
else if (iCmp > 0)
|
|
{
|
|
|
|
// From value is greater than predicate's from value.
|
|
// If the from value is also greater than the predicate's
|
|
// until value, we have no intersection.
|
|
|
|
if (RC_BAD( rc = fqCompareValues( pFromValue,
|
|
bInclFrom, TRUE,
|
|
pPred->pUntilValue, pPred->bInclUntil, FALSE,
|
|
uiCompareRules, m_uiLanguage, &iCmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (iCmp > 0)
|
|
{
|
|
*pbClipContext = TRUE;
|
|
}
|
|
else
|
|
{
|
|
pPred->pFromValue = pFromValue;
|
|
pPred->bInclFrom = bInclFrom;
|
|
}
|
|
}
|
|
else if (RC_BAD( rc = fqCompareValues( pUntilValue,
|
|
bInclUntil, FALSE,
|
|
pPred->pUntilValue, pPred->bInclUntil, FALSE,
|
|
uiCompareRules, m_uiLanguage, &iCmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
else if (iCmp < 0)
|
|
{
|
|
|
|
// Until value is less than predicate's until value. If the
|
|
// until value is also less than predicate's from value, we
|
|
// have no intersection.
|
|
|
|
if (RC_BAD( rc = fqCompareValues( pUntilValue,
|
|
bInclUntil, FALSE,
|
|
pPred->pFromValue, pPred->bInclFrom, TRUE,
|
|
uiCompareRules, m_uiLanguage, &iCmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (iCmp < 0)
|
|
{
|
|
*pbClipContext = TRUE;
|
|
}
|
|
else
|
|
{
|
|
pPred->pUntilValue = pUntilValue;
|
|
pPred->bInclUntil = bInclUntil;
|
|
}
|
|
}
|
|
goto Exit;
|
|
}
|
|
pPred = pPred->pNext;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add a new predicate to the context path
|
|
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( PATH_PRED),
|
|
(void **)&pPred)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pPred->uiCompareRules = uiCompareRules;
|
|
pPred->pOpComparer = pOpComparer;
|
|
|
|
// Link the predicate as the last predicate for the path
|
|
|
|
if ((pPred->pPrev = pContextPath->pLastPred) != NULL)
|
|
{
|
|
pPred->pPrev->pNext = pPred;
|
|
}
|
|
else
|
|
{
|
|
pContextPath->pFirstPred = pPred;
|
|
}
|
|
pContextPath->pLastPred = pPred;
|
|
|
|
// Set other items in the predicate.
|
|
|
|
pPred->pContextNode = pContextNode;
|
|
pPred->bNotted = bNotted;
|
|
switch (eOperator)
|
|
{
|
|
case XFLM_EXISTS_OP:
|
|
case XFLM_NE_OP:
|
|
pPred->eOperator = eOperator;
|
|
pPred->pFromValue = pQValue;
|
|
break;
|
|
case XFLM_APPROX_EQ_OP:
|
|
pPred->eOperator = eOperator;
|
|
pPred->pFromValue = pQValue;
|
|
pPred->bInclFrom = TRUE;
|
|
pPred->bInclUntil = TRUE;
|
|
break;
|
|
case XFLM_EQ_OP:
|
|
if (bDoMatch)
|
|
{
|
|
pPred->eOperator = XFLM_MATCH_OP;
|
|
pPred->pFromValue = pQValue;
|
|
}
|
|
else
|
|
{
|
|
pPred->eOperator = XFLM_RANGE_OP;
|
|
pPred->pFromValue = pQValue;
|
|
pPred->pUntilValue = pQValue;
|
|
pPred->bInclFrom = TRUE;
|
|
pPred->bInclUntil = TRUE;
|
|
}
|
|
break;
|
|
case XFLM_LE_OP:
|
|
pPred->eOperator = XFLM_RANGE_OP;
|
|
pPred->pFromValue = NULL;
|
|
pPred->pUntilValue = pQValue;
|
|
pPred->bInclUntil = TRUE;
|
|
break;
|
|
case XFLM_LT_OP:
|
|
pPred->eOperator = XFLM_RANGE_OP;
|
|
pPred->pFromValue = NULL;
|
|
pPred->pUntilValue = pQValue;
|
|
pPred->bInclUntil = FALSE;
|
|
break;
|
|
case XFLM_GE_OP:
|
|
pPred->eOperator = XFLM_RANGE_OP;
|
|
pPred->pFromValue = pQValue;
|
|
pPred->pUntilValue = NULL;
|
|
pPred->bInclFrom = TRUE;
|
|
break;
|
|
case XFLM_GT_OP:
|
|
pPred->eOperator = XFLM_RANGE_OP;
|
|
pPred->pFromValue = pQValue;
|
|
pPred->pUntilValue = NULL;
|
|
pPred->bInclFrom = FALSE;
|
|
break;
|
|
default:
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_NOT_IMPLEMENTED);
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (RC_OK( rc) && !(*pbClipContext))
|
|
{
|
|
PATH_PRED_NODE * pPathPredNode;
|
|
|
|
if (RC_OK( rc = m_pool.poolCalloc( sizeof( PATH_PRED_NODE),
|
|
(void **)&pPathPredNode)))
|
|
{
|
|
pPathPredNode->pXPathNode = pXPathNode;
|
|
pPathPredNode->pNext = pPred->pXPathNodeList;
|
|
pPred->pXPathNodeList = pPathPredNode;
|
|
}
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Check to see if any predicates need to be unioned with the passed
|
|
in predicate. If so, perform the union.
|
|
***************************************************************************/
|
|
FSTATIC RCODE fqCheckUnionPredicates(
|
|
CONTEXT_PATH * pContextPath,
|
|
FLMUINT uiLanguage,
|
|
PATH_PRED * pPred
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
PATH_PRED * pMergePred;
|
|
FLMINT iCmp;
|
|
FLMBOOL bDidOverlap;
|
|
|
|
pMergePred = pContextPath->pFirstPred;
|
|
|
|
// This should only be done on predicates that have a range
|
|
// operator.
|
|
|
|
flmAssert( pPred->eOperator == XFLM_RANGE_OP);
|
|
while (pMergePred)
|
|
{
|
|
bDidOverlap = FALSE;
|
|
if (pMergePred != pPred &&
|
|
pMergePred->eOperator == XFLM_RANGE_OP &&
|
|
pMergePred->uiCompareRules == pPred->uiCompareRules)
|
|
{
|
|
|
|
// If the value type is not compatible with the predicate's
|
|
// value type, we cannot do the comparison, and there is
|
|
// no overlap.
|
|
|
|
if (!fqCanCompare( pMergePred->pFromValue, pPred->pFromValue) ||
|
|
!fqCanCompare( pMergePred->pFromValue, pPred->pUntilValue) ||
|
|
!fqCanCompare( pMergePred->pUntilValue, pPred->pFromValue) ||
|
|
!fqCanCompare( pMergePred->pUntilValue, pPred->pUntilValue))
|
|
{
|
|
// Nothing to do here
|
|
}
|
|
else if (RC_BAD( rc = fqCompareValues( pMergePred->pFromValue,
|
|
pMergePred->bInclFrom, TRUE,
|
|
pPred->pFromValue, pPred->bInclFrom, TRUE,
|
|
pPred->uiCompareRules, uiLanguage, &iCmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
else if (iCmp >= 0)
|
|
{
|
|
|
|
// From value is greater than or equal to the predicate's
|
|
// from value.
|
|
// If the from value is also less than or equal to the
|
|
// predicate's until value, we have an overlap.
|
|
|
|
if (RC_BAD( rc = fqCompareValues( pMergePred->pFromValue,
|
|
pMergePred->bInclFrom, TRUE,
|
|
pPred->pUntilValue, pPred->bInclUntil, FALSE,
|
|
pPred->uiCompareRules, uiLanguage, &iCmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (iCmp <= 0)
|
|
{
|
|
|
|
// If the until value is greater than the predicate's
|
|
// until value, change the predicate's until value.
|
|
|
|
if (RC_BAD( rc = fqCompareValues( pMergePred->pUntilValue,
|
|
pMergePred->bInclUntil, FALSE,
|
|
pPred->pUntilValue, pPred->bInclUntil, FALSE,
|
|
pPred->uiCompareRules, uiLanguage, &iCmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (iCmp > 0)
|
|
{
|
|
pPred->pUntilValue = pMergePred->pUntilValue;
|
|
pPred->bInclUntil = pMergePred->bInclUntil;
|
|
}
|
|
bDidOverlap = TRUE;
|
|
}
|
|
}
|
|
|
|
// At this point we already know that the from value is
|
|
// less than the predicate's from value.
|
|
// See if the until value is greater than or equal
|
|
// to the from value. If it is we have an overlap.
|
|
|
|
else if (RC_BAD( rc = fqCompareValues( pMergePred->pUntilValue,
|
|
pMergePred->bInclUntil, FALSE,
|
|
pPred->pFromValue, pPred->bInclFrom, TRUE,
|
|
pPred->uiCompareRules, uiLanguage, &iCmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
else if (iCmp >= 0)
|
|
{
|
|
|
|
// Until value is greater than or equal to the predicate's
|
|
// from value, so we definitely have an overlap. We
|
|
// already know that the from value is less than the
|
|
// predicate's from value, so we will change that for sure.
|
|
|
|
pPred->pFromValue = pMergePred->pFromValue;
|
|
pPred->bInclFrom = pMergePred->bInclFrom;
|
|
|
|
// See if the until value is greater than the
|
|
// predicate's until value, in which case we need to
|
|
// change the predicate's until value.
|
|
|
|
if (RC_BAD( rc = fqCompareValues( pMergePred->pUntilValue,
|
|
pMergePred->bInclUntil, FALSE,
|
|
pPred->pUntilValue, pPred->bInclUntil, FALSE,
|
|
pPred->uiCompareRules, uiLanguage, &iCmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (iCmp > 0)
|
|
{
|
|
pPred->pUntilValue = pMergePred->pUntilValue;
|
|
pPred->bInclUntil = pMergePred->bInclUntil;
|
|
}
|
|
bDidOverlap = TRUE;
|
|
}
|
|
}
|
|
|
|
// If the predicates overlapped, remove pMergePred from the list
|
|
// of predicates. But move its list of predicate nodes into the
|
|
// list off of pPred.
|
|
|
|
if (bDidOverlap)
|
|
{
|
|
PATH_PRED_NODE * pPathPredNode;
|
|
|
|
// Merge the predicate node lists, if any - into pPred's list.
|
|
|
|
if ((pPathPredNode = pPred->pXPathNodeList) != NULL)
|
|
{
|
|
while (pPathPredNode->pNext)
|
|
{
|
|
pPathPredNode = pPathPredNode->pNext;
|
|
}
|
|
pPathPredNode->pNext = pMergePred->pXPathNodeList;
|
|
}
|
|
else
|
|
{
|
|
pPred->pXPathNodeList = pMergePred->pXPathNodeList;
|
|
}
|
|
|
|
// Remove pMergePred from the list
|
|
|
|
if (pMergePred->pPrev)
|
|
{
|
|
pMergePred->pPrev->pNext = pMergePred->pNext;
|
|
}
|
|
else
|
|
{
|
|
pContextPath->pFirstPred = pMergePred->pNext;
|
|
}
|
|
if (pMergePred->pNext)
|
|
{
|
|
pMergePred->pNext->pPrev = pMergePred->pPrev;
|
|
|
|
// Set up so we are on the next node
|
|
|
|
pMergePred = pMergePred->pNext;
|
|
}
|
|
else
|
|
{
|
|
pContextPath->pLastPred = pMergePred->pPrev;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pMergePred = pMergePred->pNext;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Union a predicate into a context path.
|
|
***************************************************************************/
|
|
RCODE F_Query::unionPredicates(
|
|
CONTEXT_PATH * pContextPath,
|
|
FQNODE * pXPathNode,
|
|
eQueryOperators eOperator,
|
|
FLMUINT uiCompareRules,
|
|
IF_OperandComparer * pOpComparer,
|
|
FQNODE * pContextNode,
|
|
FLMBOOL bNotted,
|
|
FQVALUE * pQValue
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
PATH_PRED * pPred;
|
|
FLMINT iCmp;
|
|
FLMBOOL bDoMatch;
|
|
FLMBOOL bDidOverlap = FALSE;
|
|
|
|
if (!pQValue || pQValue->eValType != XFLM_UTF8_VAL)
|
|
{
|
|
bDoMatch = FALSE;
|
|
|
|
// Comparison rules don't matter for anything that is
|
|
// not text, so we normalize them to zero, so the test
|
|
// below to see if the comparison rule is the same as
|
|
// the comparison rule of the operator will work.
|
|
|
|
uiCompareRules = 0;
|
|
}
|
|
else
|
|
{
|
|
bDoMatch = (eOperator == XFLM_EQ_OP &&
|
|
(pQValue->uiFlags & VAL_IS_CONSTANT) &&
|
|
(pQValue->uiFlags & VAL_HAS_WILDCARDS))
|
|
? TRUE
|
|
: FALSE;
|
|
}
|
|
|
|
if ((pPred = pContextPath->pFirstPred) != NULL)
|
|
{
|
|
if (eOperator == XFLM_EXISTS_OP || eOperator == XFLM_NE_OP)
|
|
{
|
|
|
|
// See if there is another operator that is an exact
|
|
// match of this one.
|
|
|
|
while (pPred)
|
|
{
|
|
if (pPred->eOperator == eOperator)
|
|
{
|
|
if ((bNotted && pPred->bNotted) ||
|
|
(!bNotted && !pPred->bNotted))
|
|
{
|
|
|
|
// Perfect match - no need to do any more.
|
|
|
|
goto Exit;
|
|
}
|
|
}
|
|
pPred = pPred->pNext;
|
|
}
|
|
}
|
|
else if ((eOperator == XFLM_EQ_OP && !bDoMatch) ||
|
|
eOperator == XFLM_LE_OP ||
|
|
eOperator == XFLM_LT_OP ||
|
|
eOperator == XFLM_GE_OP ||
|
|
eOperator == XFLM_GT_OP)
|
|
{
|
|
|
|
// See if the operator overlaps with another range operator
|
|
|
|
while (pPred)
|
|
{
|
|
if (pPred->eOperator == XFLM_RANGE_OP &&
|
|
pPred->uiCompareRules == uiCompareRules)
|
|
{
|
|
FQVALUE * pFromValue;
|
|
FQVALUE * pUntilValue;
|
|
FLMBOOL bInclFrom;
|
|
FLMBOOL bInclUntil;
|
|
|
|
pFromValue = (eOperator == XFLM_EQ_OP ||
|
|
eOperator == XFLM_GE_OP ||
|
|
eOperator == XFLM_GT_OP)
|
|
? pQValue
|
|
: NULL;
|
|
pUntilValue = (eOperator == XFLM_EQ_OP ||
|
|
eOperator == XFLM_LE_OP ||
|
|
eOperator == XFLM_LT_OP)
|
|
? pQValue
|
|
: NULL;
|
|
bInclFrom = (FLMBOOL)(eOperator == XFLM_EQ_OP ||
|
|
eOperator == XFLM_GE_OP
|
|
? TRUE
|
|
: FALSE);
|
|
bInclUntil = (FLMBOOL)(eOperator == XFLM_EQ_OP ||
|
|
eOperator == XFLM_LE_OP
|
|
? TRUE
|
|
: FALSE);
|
|
|
|
// If the value type is not compatible with the predicate's
|
|
// value type, we cannot do the comparison, and there is
|
|
// no overlap.
|
|
|
|
if (!fqCanCompare( pQValue, pPred->pFromValue) ||
|
|
!fqCanCompare( pQValue, pPred->pUntilValue))
|
|
{
|
|
// Nothing to do here
|
|
}
|
|
else if (RC_BAD( rc = fqCompareValues( pFromValue,
|
|
bInclFrom, TRUE,
|
|
pPred->pFromValue, pPred->bInclFrom, TRUE,
|
|
uiCompareRules, m_uiLanguage, &iCmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
else if (iCmp >= 0)
|
|
{
|
|
|
|
// From value is greater than or equal to the predicate's
|
|
// from value.
|
|
// If the from value is also less than or equal to the
|
|
// predicate's until value, we have an overlap.
|
|
|
|
if (RC_BAD( rc = fqCompareValues( pFromValue,
|
|
bInclFrom, TRUE,
|
|
pPred->pUntilValue, pPred->bInclUntil, FALSE,
|
|
uiCompareRules, m_uiLanguage, &iCmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (iCmp <= 0)
|
|
{
|
|
|
|
// If the until value is greater than the predicate's
|
|
// until value, change the predicate's until value.
|
|
|
|
if (RC_BAD( rc = fqCompareValues( pUntilValue,
|
|
bInclUntil, FALSE,
|
|
pPred->pUntilValue, pPred->bInclUntil, FALSE,
|
|
uiCompareRules, m_uiLanguage, &iCmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (iCmp > 0)
|
|
{
|
|
pPred->pUntilValue = pUntilValue;
|
|
pPred->bInclUntil = bInclUntil;
|
|
}
|
|
bDidOverlap = TRUE;
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// At this point we already know that the from value is
|
|
// less than the predicate's from value.
|
|
// See if the until value is greater than or equal
|
|
// to the from value. If it is we have an overlap.
|
|
|
|
else if (RC_BAD( rc = fqCompareValues( pUntilValue,
|
|
bInclUntil, FALSE,
|
|
pPred->pFromValue, pPred->bInclFrom, TRUE,
|
|
uiCompareRules, m_uiLanguage, &iCmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
else if (iCmp >= 0)
|
|
{
|
|
|
|
// Until value is greater than or equal to the predicate's
|
|
// from value, so we definitely have an overlap. We
|
|
// already know that the from value is less than the
|
|
// predicate's from value, so we will change that for sure.
|
|
|
|
pPred->pFromValue = pFromValue;
|
|
pPred->bInclFrom = bInclFrom;
|
|
|
|
// See if the until value is greater than the
|
|
// predicate's until value, in which case we need to
|
|
// change the predicate's until value.
|
|
|
|
if (RC_BAD( rc = fqCompareValues( pUntilValue,
|
|
bInclUntil, FALSE,
|
|
pPred->pUntilValue, pPred->bInclUntil, FALSE,
|
|
uiCompareRules, m_uiLanguage, &iCmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (iCmp > 0)
|
|
{
|
|
pPred->pUntilValue = pUntilValue;
|
|
pPred->bInclUntil = bInclUntil;
|
|
}
|
|
bDidOverlap = TRUE;
|
|
goto Exit;
|
|
}
|
|
}
|
|
pPred = pPred->pNext;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add a new predicate to the context path
|
|
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( PATH_PRED),
|
|
(void **)&pPred)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pPred->uiCompareRules = uiCompareRules;
|
|
pPred->pOpComparer = pOpComparer;
|
|
|
|
// Link the predicate as the last predicate for the path
|
|
|
|
if ((pPred->pPrev = pContextPath->pLastPred) != NULL)
|
|
{
|
|
pPred->pPrev->pNext = pPred;
|
|
}
|
|
else
|
|
{
|
|
pContextPath->pFirstPred = pPred;
|
|
}
|
|
pContextPath->pLastPred = pPred;
|
|
|
|
// Set other items in the predicate.
|
|
|
|
pPred->pContextNode = pContextNode;
|
|
pPred->bNotted = bNotted;
|
|
switch (eOperator)
|
|
{
|
|
case XFLM_EXISTS_OP:
|
|
case XFLM_NE_OP:
|
|
pPred->eOperator = eOperator;
|
|
pPred->pFromValue = pQValue;
|
|
break;
|
|
case XFLM_APPROX_EQ_OP:
|
|
pPred->eOperator = eOperator;
|
|
pPred->pFromValue = pQValue;
|
|
pPred->bInclFrom = TRUE;
|
|
pPred->bInclUntil = TRUE;
|
|
break;
|
|
case XFLM_EQ_OP:
|
|
if (bDoMatch)
|
|
{
|
|
pPred->eOperator = XFLM_MATCH_OP;
|
|
pPred->pFromValue = pQValue;
|
|
}
|
|
else
|
|
{
|
|
pPred->eOperator = XFLM_RANGE_OP;
|
|
pPred->pFromValue = pQValue;
|
|
pPred->pUntilValue = pQValue;
|
|
pPred->bInclFrom = TRUE;
|
|
pPred->bInclUntil = TRUE;
|
|
}
|
|
break;
|
|
case XFLM_LE_OP:
|
|
pPred->eOperator = XFLM_RANGE_OP;
|
|
pPred->pFromValue = NULL;
|
|
pPred->pUntilValue = pQValue;
|
|
pPred->bInclUntil = TRUE;
|
|
break;
|
|
case XFLM_LT_OP:
|
|
pPred->eOperator = XFLM_RANGE_OP;
|
|
pPred->pFromValue = NULL;
|
|
pPred->pUntilValue = pQValue;
|
|
pPred->bInclUntil = FALSE;
|
|
break;
|
|
case XFLM_GE_OP:
|
|
pPred->eOperator = XFLM_RANGE_OP;
|
|
pPred->pFromValue = pQValue;
|
|
pPred->pUntilValue = NULL;
|
|
pPred->bInclFrom = TRUE;
|
|
break;
|
|
case XFLM_GT_OP:
|
|
pPred->eOperator = XFLM_RANGE_OP;
|
|
pPred->pFromValue = pQValue;
|
|
pPred->pUntilValue = NULL;
|
|
pPred->bInclFrom = FALSE;
|
|
break;
|
|
default:
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_NOT_IMPLEMENTED);
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (RC_OK( rc))
|
|
{
|
|
PATH_PRED_NODE * pPathPredNode;
|
|
|
|
if (RC_OK( rc = m_pool.poolCalloc( sizeof( PATH_PRED_NODE),
|
|
(void **)&pPathPredNode)))
|
|
{
|
|
pPathPredNode->pXPathNode = pXPathNode;
|
|
pPathPredNode->pNext = pPred->pXPathNodeList;
|
|
pPred->pXPathNodeList = pPathPredNode;
|
|
|
|
if (bDidOverlap)
|
|
{
|
|
rc = fqCheckUnionPredicates( pContextPath, m_uiLanguage, pPred);
|
|
}
|
|
}
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Prune a context out of the context tree.
|
|
***************************************************************************/
|
|
FSTATIC void fqClipContext(
|
|
OP_CONTEXT * pContext
|
|
)
|
|
{
|
|
|
|
// If this context had a parent, we can unlink it from
|
|
// its parent.
|
|
|
|
if (pContext->pParent)
|
|
{
|
|
if (pContext->pPrevSib)
|
|
{
|
|
pContext->pPrevSib->pNextSib = pContext->pNextSib;
|
|
}
|
|
else
|
|
{
|
|
pContext->pParent->pFirstChild = pContext->pNextSib;
|
|
}
|
|
if (pContext->pNextSib)
|
|
{
|
|
pContext->pNextSib->pPrevSib = pContext->pPrevSib;
|
|
}
|
|
else
|
|
{
|
|
pContext->pParent->pLastChild = pContext->pPrevSib;
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Add a predicate to a context.
|
|
***************************************************************************/
|
|
RCODE F_Query::addPredicateToContext(
|
|
OP_CONTEXT * pContext,
|
|
XPATH_COMPONENT * pXPathContext,
|
|
XPATH_COMPONENT * pXPathComp,
|
|
eQueryOperators eOperator,
|
|
FLMUINT uiCompareRules,
|
|
IF_OperandComparer * pOpComparer,
|
|
FQNODE * pContextNode,
|
|
FLMBOOL bNotted,
|
|
FQVALUE * pQValue,
|
|
FLMBOOL * pbClipContext,
|
|
FQNODE ** ppQNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
CONTEXT_PATH * pContextPath = NULL;
|
|
|
|
*pbClipContext = FALSE;
|
|
|
|
// Better be the leaf component of the XPATH
|
|
|
|
flmAssert( !pXPathComp->pNext);
|
|
|
|
// Convert the constant value in a node id predicate to
|
|
// a 64 bit unsigned value.
|
|
|
|
if (eOperator != XFLM_EXISTS_OP && pXPathComp->eXPathAxis == META_AXIS)
|
|
{
|
|
if (RC_BAD( rc = fqGetNodeIdValue( pQValue)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// See if we can find a matching XPATH component. If not,
|
|
// create a new one. NOTE: We can only match if the axis
|
|
// is the SELF_AXIS, or the XPATH component is an attribute.
|
|
// If the XPATH component is an attribute, it is guaranteed
|
|
// there there will only be one instance of the attribute
|
|
// Note also that the matching is only done if we are in the
|
|
// context of another XPATH component.
|
|
|
|
if (pXPathContext && !pXPathComp->pPrev && !pXPathComp->pNodeSource)
|
|
{
|
|
if (pXPathComp->eXPathAxis == META_AXIS)
|
|
{
|
|
pContextPath = pContext->pFirstPath;
|
|
while (pContextPath)
|
|
{
|
|
if (!pContextPath->pXPathComponent->pPrev &&
|
|
pContextPath->pXPathComponent->uiDictNum ==
|
|
pXPathComp->uiDictNum &&
|
|
pContextPath->pXPathComponent->eXPathAxis == META_AXIS)
|
|
{
|
|
break;
|
|
}
|
|
pContextPath = pContextPath->pNext;
|
|
}
|
|
}
|
|
else if (pXPathComp->eNodeType == ELEMENT_NODE ||
|
|
pXPathComp->eNodeType == DATA_NODE)
|
|
{
|
|
if (pXPathComp->eXPathAxis == SELF_AXIS)
|
|
{
|
|
pContextPath = pContext->pFirstPath;
|
|
while (pContextPath)
|
|
{
|
|
if (!pContextPath->pXPathComponent->pPrev &&
|
|
pContextPath->pXPathComponent->eXPathAxis == SELF_AXIS &&
|
|
pContextPath->pXPathComponent->uiDictNum ==
|
|
pXPathComp->uiDictNum &&
|
|
pContextPath->pXPathComponent->eNodeType ==
|
|
pXPathComp->eNodeType)
|
|
{
|
|
break;
|
|
}
|
|
pContextPath = pContextPath->pNext;
|
|
}
|
|
}
|
|
}
|
|
else if (pXPathComp->eNodeType == ATTRIBUTE_NODE)
|
|
{
|
|
pContextPath = pContext->pFirstPath;
|
|
while (pContextPath)
|
|
{
|
|
if (!pContextPath->pXPathComponent->pPrev &&
|
|
pContextPath->pXPathComponent->uiDictNum ==
|
|
pXPathComp->uiDictNum &&
|
|
pContextPath->pXPathComponent->eNodeType == ATTRIBUTE_NODE)
|
|
{
|
|
break;
|
|
}
|
|
pContextPath = pContextPath->pNext;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we did not find one, allocate it.
|
|
|
|
if (!pContextPath)
|
|
{
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( CONTEXT_PATH),
|
|
(void **)&pContextPath)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Link the new context path into the context as its last path
|
|
|
|
if ((pContextPath->pPrev = pContext->pLastPath) != NULL)
|
|
{
|
|
pContextPath->pPrev->pNext = pContextPath;
|
|
}
|
|
else
|
|
{
|
|
pContext->pFirstPath = pContextPath;
|
|
}
|
|
pContext->pLastPath = pContextPath;
|
|
pContextPath->pXPathComponent = pXPathComp;
|
|
}
|
|
|
|
// See if this operator can be merged with another one.
|
|
|
|
if (pContext->bIntersect)
|
|
{
|
|
if (RC_BAD( rc = intersectPredicates( pContextPath, *ppQNode,
|
|
eOperator,
|
|
uiCompareRules, pOpComparer,
|
|
pContextNode, bNotted, pQValue, pbClipContext)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If we get a false result, then we know that the
|
|
// intersection of predicates is creating a situation where
|
|
// it can never be true, so we will turn the root node of
|
|
// the context into a XFLM_BOOL_VAL node with a FALSE value.
|
|
// The branch of the query tree represented underneath this
|
|
// node will have been cut off. The caller must account for this.
|
|
|
|
if (*pbClipContext)
|
|
{
|
|
FQNODE * pRootNode = pContext->pQRootNode;
|
|
|
|
pRootNode->eNodeType = FLM_VALUE_NODE;
|
|
pRootNode->pFirstChild = NULL;
|
|
pRootNode->pLastChild = NULL;
|
|
pRootNode->pContext = pContext->pParent;
|
|
pRootNode->currVal.eValType = XFLM_BOOL_VAL;
|
|
pRootNode->currVal.uiFlags = VAL_IS_CONSTANT;
|
|
pRootNode->currVal.val.eBool = XFLM_FALSE;
|
|
*ppQNode = pRootNode;
|
|
|
|
// Clip this context out of the context tree.
|
|
// Don't want to travel down this path when optimizing.
|
|
|
|
fqClipContext( pContext);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = unionPredicates( pContextPath, *ppQNode,
|
|
eOperator,
|
|
uiCompareRules, pOpComparer,
|
|
pContextNode, bNotted, pQValue)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Import child context from pSrcContext into pDestContext.
|
|
***************************************************************************/
|
|
FSTATIC void fqImportChildContexts(
|
|
OP_CONTEXT * pDestContext,
|
|
OP_CONTEXT * pSrcContext)
|
|
{
|
|
OP_CONTEXT * pTmpContext;
|
|
|
|
if ((pTmpContext = pSrcContext->pFirstChild) != NULL)
|
|
{
|
|
|
|
// Change all of the parent pointers of the child contexts to
|
|
// point to pDestContext;
|
|
|
|
while (pTmpContext)
|
|
{
|
|
flmAssert( (!pTmpContext->bIntersect && pDestContext->bIntersect) ||
|
|
(pTmpContext->bIntersect && !pDestContext->bIntersect));
|
|
pTmpContext->pParent = pDestContext;
|
|
pTmpContext = pTmpContext->pNextSib;
|
|
}
|
|
|
|
// Link all of pSrcContext's children as children of pDestContext.
|
|
|
|
if ((pSrcContext->pFirstChild->pPrevSib =
|
|
pDestContext->pLastChild) != NULL)
|
|
{
|
|
pDestContext->pLastChild->pNextSib = pSrcContext->pFirstChild;
|
|
}
|
|
else
|
|
{
|
|
pDestContext->pFirstChild = pSrcContext->pFirstChild;
|
|
}
|
|
pDestContext->pLastChild = pSrcContext->pLastChild;
|
|
pSrcContext->pFirstChild = NULL;
|
|
pSrcContext->pLastChild = NULL;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Import context paths from pSrcContext into pDestContext.
|
|
***************************************************************************/
|
|
FSTATIC void fqImportContextPaths(
|
|
OP_CONTEXT * pDestContext,
|
|
OP_CONTEXT * pSrcContext)
|
|
{
|
|
if (pSrcContext->pFirstPath)
|
|
{
|
|
if ((pSrcContext->pFirstPath->pPrev =
|
|
pDestContext->pLastPath) != NULL)
|
|
{
|
|
pDestContext->pLastPath->pNext = pSrcContext->pFirstPath;
|
|
}
|
|
else
|
|
{
|
|
pDestContext->pFirstPath = pSrcContext->pFirstPath;
|
|
}
|
|
pDestContext->pLastPath = pSrcContext->pLastPath;
|
|
pSrcContext->pFirstPath = NULL;
|
|
pSrcContext->pLastPath = NULL;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Import pSrcContext into pDestContext.
|
|
***************************************************************************/
|
|
FSTATIC void fqImportContext(
|
|
OP_CONTEXT * pDestContext,
|
|
OP_CONTEXT * pSrcContext)
|
|
{
|
|
|
|
// Merge all of the child contexts from pSrcContext into pDestContext
|
|
|
|
fqImportChildContexts( pDestContext, pSrcContext);
|
|
|
|
// Merge all of the paths from pSrcContext into pDestContext
|
|
|
|
fqImportContextPaths( pDestContext, pSrcContext);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Merge the context of pQNode, if any, into pDestContext
|
|
***************************************************************************/
|
|
FSTATIC void fqMergeContexts(
|
|
FQNODE * pQNode,
|
|
OP_CONTEXT * pDestContext
|
|
)
|
|
{
|
|
OP_CONTEXT * pSrcContext = pQNode->pContext;
|
|
|
|
// At this point, pQNode MUST have a context to merge in, and
|
|
// pDestContext MUST be an intersect context.
|
|
|
|
flmAssert( pSrcContext && pDestContext->bIntersect);
|
|
|
|
// The context we are merging should not have any parent context.
|
|
// It should be a root context.
|
|
|
|
flmAssert( !pSrcContext->pNextSib && !pSrcContext->pPrevSib &&
|
|
!pSrcContext->pParent);
|
|
|
|
// Root node of context should be same as pQNode.
|
|
|
|
flmAssert( pSrcContext->pQRootNode == pQNode);
|
|
|
|
if (pSrcContext->bIntersect)
|
|
{
|
|
|
|
// If the context to be merged is an intersect context (AND),
|
|
// we take its child OP_CONTEXTs (which should all be non-intersect)
|
|
// and make them children of the destination context. We also
|
|
// move its predicates into the predicate list of the destination
|
|
// context.
|
|
|
|
fqImportContext( pDestContext, pSrcContext);
|
|
}
|
|
else
|
|
{
|
|
|
|
// If the context to be merged is a non-intersect context (OR)
|
|
// we just put the whole thing in as a child of the destination
|
|
// context.
|
|
|
|
if ((pSrcContext->pPrevSib = pDestContext->pLastChild) != NULL)
|
|
{
|
|
pSrcContext->pPrevSib->pNextSib = pSrcContext;
|
|
}
|
|
else
|
|
{
|
|
pDestContext->pFirstChild = pSrcContext;
|
|
}
|
|
pDestContext->pLastChild = pSrcContext;
|
|
pSrcContext->pParent = pDestContext;
|
|
}
|
|
pQNode->pContext = pDestContext;
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Create a new context for predicates
|
|
***************************************************************************/
|
|
RCODE F_Query::createOpContext(
|
|
OP_CONTEXT * pParentContext,
|
|
FLMBOOL bIntersect,
|
|
FQNODE * pQRootNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
OP_CONTEXT * pContext;
|
|
|
|
// Allocate a new context and link it in as a child
|
|
// to the current context.
|
|
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( OP_CONTEXT),
|
|
(void **)&pContext)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQRootNode->pContext = pContext;
|
|
pContext->pQRootNode = pQRootNode;
|
|
pContext->bIntersect = bIntersect;
|
|
pContext->bMustScan = FALSE;
|
|
pContext->uiCost = (FLMUINT)(bIntersect
|
|
? ~((FLMUINT)0)
|
|
: 0);
|
|
if ((pContext->pParent = pParentContext) != NULL)
|
|
{
|
|
if ((pContext->pPrevSib =
|
|
pParentContext->pLastChild) != NULL)
|
|
{
|
|
pParentContext->pLastChild->pNextSib = pContext;
|
|
}
|
|
else
|
|
{
|
|
pParentContext->pFirstChild = pContext;
|
|
}
|
|
pParentContext->pLastChild = pContext;
|
|
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Check to see if an XPATH component matches the XPATH context
|
|
component we are inside of.
|
|
***************************************************************************/
|
|
FSTATIC void fqCheckPathMatch(
|
|
XPATH_COMPONENT * pXPathContextComponent,
|
|
XPATH_COMPONENT * pXPathComponent
|
|
)
|
|
{
|
|
|
|
// If this is a self:: axis, and we are in the context of another
|
|
// xpath component, see if we match the context xpath component
|
|
|
|
if (pXPathContextComponent &&
|
|
pXPathContextComponent->uiDictNum &&
|
|
(pXPathContextComponent->eNodeType == ELEMENT_NODE ||
|
|
pXPathContextComponent->eNodeType == DATA_NODE ||
|
|
pXPathContextComponent->eNodeType == ATTRIBUTE_NODE ||
|
|
pXPathContextComponent->eXPathAxis == META_AXIS) &&
|
|
pXPathComponent->eXPathAxis == SELF_AXIS &&
|
|
!pXPathComponent->pNext &&
|
|
((pXPathComponent->eNodeType == pXPathContextComponent->eNodeType &&
|
|
!pXPathComponent->uiDictNum) ||
|
|
pXPathComponent->eNodeType == ANY_NODE_TYPE))
|
|
{
|
|
pXPathComponent->uiDictNum = pXPathContextComponent->uiDictNum;
|
|
pXPathComponent->eNodeType = pXPathContextComponent->eNodeType;
|
|
if( pXPathContextComponent->eXPathAxis == META_AXIS)
|
|
{
|
|
pXPathComponent->eXPathAxis = META_AXIS;
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get predicates for the XPATH node (pQNode) that was passed in.
|
|
***************************************************************************/
|
|
RCODE F_Query::getPathPredicates(
|
|
FQNODE * pParentNode,
|
|
FQNODE ** ppQNode,
|
|
XPATH_COMPONENT * pXPathContext
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pQNode = *ppQNode;
|
|
FXPATH * pXPath = pQNode->nd.pXPath;
|
|
XPATH_COMPONENT * pXPathComp = pXPath->pFirstComponent;
|
|
OP_CONTEXT * pContext;
|
|
FQNODE * pContextNode;
|
|
FLMBOOL bHadComponentExpressions = FALSE;
|
|
FLMBOOL bClippedContext;
|
|
|
|
// pXPathContext is the XPATH component context for this
|
|
// XPATH component. This may be used in optimization.
|
|
// It should have already been set up.
|
|
|
|
flmAssert( pXPathComp->pXPathContext == pXPathContext);
|
|
|
|
fqCheckPathMatch( pXPathContext, pXPathComp);
|
|
|
|
// See if any of the XPATH components have expressions ([]). If they
|
|
// do, we want to AND this XPATH expression with them into
|
|
// an intersect context. If we are not currently in an intersect
|
|
// context, we need to create one.
|
|
|
|
pContext = NULL;
|
|
pContextNode = pXPathContext ? pXPathContext->pXPathNode : NULL;
|
|
while (pXPathComp)
|
|
{
|
|
if (pXPathComp->pExpr && !pContext)
|
|
{
|
|
bHadComponentExpressions = TRUE;
|
|
|
|
// If we have not yet determined the context for the
|
|
// XPATH components to be merged into, do it now.
|
|
|
|
if (pParentNode)
|
|
{
|
|
pContext = pQNode->pContext = pParentNode->pContext;
|
|
|
|
// If the context is not an intersect context, we need to
|
|
// create a new one that is, and link it as the last child
|
|
// of the current context.
|
|
|
|
if (!pContext->bIntersect)
|
|
{
|
|
if (RC_BAD( rc = createOpContext( pParentNode->pContext,
|
|
TRUE, pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pContext = pQNode->pContext;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// We set the bIntersect flag to TRUE for this context,
|
|
// even though it is not an AND operator. This is done
|
|
// because we know there will only ever be one predicate
|
|
// in this context, and therefore, if it is ever merged
|
|
// into another "intersect" context, we want the predicate
|
|
// merged in by itself instead of the entire context.
|
|
// See fqMergeContexts.
|
|
|
|
if (RC_BAD( rc = createOpContext( NULL, TRUE, pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pContext = pQNode->pContext;
|
|
}
|
|
break;
|
|
}
|
|
pXPathComp = pXPathComp->pNext;
|
|
}
|
|
|
|
// Create a context if one was not created above.
|
|
|
|
if (!pContext)
|
|
{
|
|
if (pParentNode)
|
|
{
|
|
pContext = pQNode->pContext = pParentNode->pContext;
|
|
}
|
|
else
|
|
{
|
|
|
|
// We set the bIntersect flag to TRUE for this context,
|
|
// even though it is not an AND operator. This is done
|
|
// because we know there will only ever be one predicate
|
|
// in this context, and therefore, if it is ever merged
|
|
// into another "intersect" context, we want the predicate
|
|
// merged in by itself instead of the entire context.
|
|
// See fqMergeContexts.
|
|
|
|
if (RC_BAD( rc = createOpContext( NULL, TRUE, pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pContext = pQNode->pContext;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flmAssert( pQNode->pContext == pContext);
|
|
}
|
|
|
|
// Create predicate, if possible
|
|
|
|
// Find the terminating XPATH component.
|
|
|
|
pXPathComp = pXPath->pLastComponent;
|
|
|
|
// See if we can create a predicate from this component.
|
|
|
|
if (pXPathComp->pNodeSource)
|
|
{
|
|
|
|
// Create an exists predicate
|
|
|
|
if (RC_BAD( rc = addPredicateToContext( pContext, pXPathContext,
|
|
pXPathComp,
|
|
XFLM_EXISTS_OP, 0, NULL,
|
|
pContextNode, pQNode->bNotted, NULL,
|
|
&bClippedContext, &pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Context should never be clipped because it shouldn't ever
|
|
// merge with another predicate.
|
|
|
|
flmAssert( !bClippedContext);
|
|
}
|
|
else if (pXPathComp->uiDictNum &&
|
|
(pXPathComp->eXPathAxis == META_AXIS ||
|
|
pXPathComp->eNodeType == ELEMENT_NODE ||
|
|
pXPathComp->eNodeType == DATA_NODE ||
|
|
pXPathComp->eNodeType == ATTRIBUTE_NODE))
|
|
{
|
|
if (pParentNode)
|
|
{
|
|
if (isLogicalOp( pParentNode->nd.op.eOperator))
|
|
{
|
|
|
|
// Create an exists predicate
|
|
|
|
if (RC_BAD( rc = addPredicateToContext( pContext, pXPathContext,
|
|
pXPathComp,
|
|
XFLM_EXISTS_OP, 0, NULL,
|
|
pContextNode, pQNode->bNotted, NULL,
|
|
&bClippedContext, &pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If adding this predicate would cause a false result,
|
|
// the root node of the context will have been turned into
|
|
// a boolean value with a FALSE value. There is no need to
|
|
// process any more of this branch, because this branch will
|
|
// have been cut off. pQNode will have been altered.
|
|
|
|
if (bClippedContext)
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else if (isCompareOp( pParentNode->nd.op.eOperator))
|
|
{
|
|
|
|
// Sibling must be a value node
|
|
|
|
if (pQNode->pNextSib)
|
|
{
|
|
flmAssert( !pQNode->pPrevSib);
|
|
if (pQNode->pNextSib->eNodeType == FLM_VALUE_NODE)
|
|
{
|
|
|
|
// Create a compare predicate.
|
|
|
|
if (RC_BAD( rc = addPredicateToContext( pContext,
|
|
pXPathContext,
|
|
pXPathComp,
|
|
pParentNode->nd.op.eOperator,
|
|
pParentNode->nd.op.uiCompareRules,
|
|
pParentNode->nd.op.pOpComparer,
|
|
pContextNode, pQNode->bNotted,
|
|
&pQNode->pNextSib->currVal,
|
|
&bClippedContext, &pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If adding this predicate would cause a false
|
|
// result, the root node of the context will
|
|
// have been turned into a boolean value with a
|
|
// FALSE value. There is no need to process any
|
|
// more of this branch, because this branch will
|
|
// have been cut off. pQNode will have been altered.
|
|
|
|
if (bClippedContext)
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// We have a comparison operation that cannot be
|
|
// optimized. If this is a non-intersect context,
|
|
// there is no point in doing optimizations.
|
|
// If it is an intersect context, other predicates
|
|
// may come along that can be used to optimize.
|
|
// We won't know until we have processed all of
|
|
// them.
|
|
|
|
if (!pContext->bIntersect)
|
|
{
|
|
pContext->bForceOptToScan = TRUE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flmAssert( pQNode->pPrevSib);
|
|
if (pQNode->pPrevSib->eNodeType == FLM_VALUE_NODE)
|
|
{
|
|
FQNODE * pSaveSib;
|
|
|
|
// Switch the two operators around
|
|
|
|
switch (pParentNode->nd.op.eOperator)
|
|
{
|
|
case XFLM_EQ_OP:
|
|
case XFLM_NE_OP:
|
|
pSaveSib = pQNode->pPrevSib;
|
|
break;
|
|
case XFLM_LT_OP:
|
|
pSaveSib = pQNode->pPrevSib;
|
|
pParentNode->nd.op.eOperator = XFLM_GE_OP;
|
|
break;
|
|
case XFLM_LE_OP:
|
|
pSaveSib = pQNode->pPrevSib;
|
|
pParentNode->nd.op.eOperator = XFLM_GT_OP;
|
|
break;
|
|
case XFLM_GT_OP:
|
|
pSaveSib = pQNode->pPrevSib;
|
|
pParentNode->nd.op.eOperator = XFLM_LE_OP;
|
|
break;
|
|
case XFLM_GE_OP:
|
|
pSaveSib = pQNode->pPrevSib;
|
|
pParentNode->nd.op.eOperator = XFLM_LT_OP;
|
|
break;
|
|
default:
|
|
pSaveSib = NULL;
|
|
break;
|
|
}
|
|
|
|
// If we can switch the operator, we can create a
|
|
// predicate for optimization. Otherwise, we must
|
|
// leave it alone - or assert?
|
|
|
|
if (pSaveSib)
|
|
{
|
|
pQNode->pNextSib = pSaveSib;
|
|
pQNode->pPrevSib = NULL;
|
|
pSaveSib->pPrevSib = pQNode;
|
|
pSaveSib->pNextSib = NULL;
|
|
pParentNode->pFirstChild = pQNode;
|
|
pParentNode->pLastChild = pSaveSib;
|
|
|
|
// Create a compare predicate, but need to switch
|
|
// the operators around
|
|
|
|
if (RC_BAD( rc = addPredicateToContext( pContext,
|
|
pXPathContext,
|
|
pXPathComp,
|
|
pParentNode->nd.op.eOperator,
|
|
pParentNode->nd.op.uiCompareRules,
|
|
pParentNode->nd.op.pOpComparer,
|
|
pContextNode, pQNode->bNotted,
|
|
&pQNode->pNextSib->currVal,
|
|
&bClippedContext, &pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If adding this predicate would cause a false
|
|
// result, the root node of the context will
|
|
// have been turned into a boolean value with a
|
|
// FALSE value. There is no need to process any
|
|
// more of this branch, because this branch will
|
|
// have been cut off. pQNode will have been
|
|
// altered.
|
|
|
|
if (bClippedContext)
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// We have a comparison operation that cannot be
|
|
// optimized.
|
|
|
|
if (!pContext->bIntersect)
|
|
{
|
|
pContext->bForceOptToScan = TRUE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// We have a comparison operation that cannot be
|
|
// optimized.
|
|
|
|
if (!pContext->bIntersect)
|
|
{
|
|
pContext->bForceOptToScan = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// Create an exists predicate
|
|
|
|
if (RC_BAD( rc = addPredicateToContext( pContext, pXPathContext,
|
|
pXPathComp, XFLM_EXISTS_OP, 0, NULL,
|
|
pContextNode, pQNode->bNotted, NULL, &bClippedContext,
|
|
&pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If adding this predicate would cause a false
|
|
// result, the root node of the context will
|
|
// have been turned into a boolean value with a
|
|
// FALSE value. There is no need to process any
|
|
// more of this branch, because this branch will
|
|
// have been cut off. pQNode will have been
|
|
// altered.
|
|
|
|
if (bClippedContext)
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// We have something in the expression that cannot be
|
|
// optimized.
|
|
|
|
if (!pContext->bIntersect)
|
|
{
|
|
pContext->bForceOptToScan = TRUE;
|
|
}
|
|
}
|
|
|
|
if (bHadComponentExpressions)
|
|
{
|
|
|
|
// Now, merge in the expressions for each component of the
|
|
// XPATH. We wait to do this until AFTER the predicates
|
|
// have been added above, because this "merge" does not
|
|
// actually merge RANGE operators, etc., but just adds
|
|
// the predicates to the list - which is all we want to
|
|
// have happen at this point.
|
|
|
|
pXPathComp = pXPath->pFirstComponent;
|
|
while (pXPathComp)
|
|
{
|
|
if (pXPathComp->pExpr)
|
|
{
|
|
|
|
// Merge each expression's context into pContext, which
|
|
// should be an intersection context - should have
|
|
// been set up above.
|
|
|
|
flmAssert( pContext && pContext->bIntersect);
|
|
|
|
fqMergeContexts( pXPathComp->pExpr, pContext);
|
|
}
|
|
pXPathComp = pXPathComp->pNext;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
*ppQNode = pQNode;
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Evaluate operands of an AND or OR operator to see if we can
|
|
replace one.
|
|
TRUE && P1 will be replaced with P1
|
|
FALSE && P1 will be replaced with FALSE
|
|
TRUE || P1 will be replaced with TRUE
|
|
FALSE || P1 will be replaced with P1
|
|
***************************************************************************/
|
|
FSTATIC FQNODE * fqEvalLogicalOperands(
|
|
FQNODE * pQNode
|
|
)
|
|
{
|
|
FLMBOOL bLeftIsBool = FALSE;
|
|
FLMBOOL bRightIsBool = FALSE;
|
|
XFlmBoolType eLeftBoolVal = XFLM_UNKNOWN;
|
|
XFlmBoolType eRightBoolVal = XFLM_UNKNOWN;
|
|
FQNODE * pLeftNode = pQNode->pFirstChild;
|
|
FQNODE * pRightNode = pLeftNode->pNextSib;
|
|
FQNODE * pReplacementNode = NULL;
|
|
OP_CONTEXT * pContext;
|
|
OP_CONTEXT * pParentContext;
|
|
|
|
if (isBoolNode( pLeftNode))
|
|
{
|
|
bLeftIsBool = TRUE;
|
|
eLeftBoolVal = pLeftNode->currVal.val.eBool;
|
|
}
|
|
if (isBoolNode( pRightNode))
|
|
{
|
|
bRightIsBool = TRUE;
|
|
eRightBoolVal = pRightNode->currVal.val.eBool;
|
|
}
|
|
|
|
// If neither operand is a boolean value, there is no replacement
|
|
// that can be done, but we still need to go up one level.
|
|
|
|
if (!bLeftIsBool && !bRightIsBool)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Handle the case where both operands are boolean values.
|
|
|
|
if (bLeftIsBool && bRightIsBool)
|
|
{
|
|
XFlmBoolType eNewBoolVal;
|
|
|
|
if (pQNode->nd.op.eOperator == XFLM_AND_OP)
|
|
{
|
|
if (eLeftBoolVal == XFLM_FALSE ||
|
|
eRightBoolVal == XFLM_FALSE)
|
|
{
|
|
eNewBoolVal = XFLM_FALSE;
|
|
}
|
|
else if (eLeftBoolVal == XFLM_TRUE &&
|
|
eRightBoolVal == XFLM_TRUE)
|
|
{
|
|
eNewBoolVal = XFLM_TRUE;
|
|
}
|
|
else
|
|
{
|
|
eNewBoolVal = XFLM_UNKNOWN;
|
|
}
|
|
}
|
|
else // XFLM_OR_OP
|
|
{
|
|
if (eLeftBoolVal == XFLM_TRUE ||
|
|
eRightBoolVal == XFLM_TRUE)
|
|
{
|
|
eNewBoolVal = XFLM_TRUE;
|
|
}
|
|
else if (eLeftBoolVal == XFLM_FALSE &&
|
|
eRightBoolVal == XFLM_FALSE)
|
|
{
|
|
eNewBoolVal = XFLM_FALSE;
|
|
}
|
|
else
|
|
{
|
|
eNewBoolVal = XFLM_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
// Doesn't really matter which one we use to
|
|
// replace the AND or OR node - we will use
|
|
// the left one.
|
|
|
|
pLeftNode->currVal.val.eBool = eNewBoolVal;
|
|
pReplacementNode = pLeftNode;
|
|
}
|
|
else if (pQNode->nd.op.eOperator == XFLM_OR_OP)
|
|
{
|
|
|
|
// Operator is an OR, and only one of the operands
|
|
// is a boolean value.
|
|
|
|
if (bLeftIsBool)
|
|
{
|
|
pReplacementNode = (eLeftBoolVal == XFLM_TRUE)
|
|
? pLeftNode
|
|
: pRightNode;
|
|
}
|
|
else
|
|
{
|
|
pReplacementNode = (eRightBoolVal == XFLM_TRUE)
|
|
? pRightNode
|
|
: pLeftNode;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// Operator is an AND, and only one of the operands is
|
|
// a boolean value.
|
|
|
|
if (bLeftIsBool)
|
|
{
|
|
pReplacementNode = (eLeftBoolVal != XFLM_TRUE)
|
|
? pLeftNode
|
|
: pRightNode;
|
|
}
|
|
else
|
|
{
|
|
pReplacementNode = (eRightBoolVal != XFLM_TRUE)
|
|
? pRightNode
|
|
: pLeftNode;
|
|
}
|
|
}
|
|
|
|
// If the node we are going to replace (pQNode) is the root
|
|
// of the context, change the context to point to the replacement
|
|
// node as the new root of the context. Also, if the replacement
|
|
// node is a boolean, or a non-logical operator, set the context
|
|
// to be an intersect context, so that if it is merged into another
|
|
// context, it will merge correctly.
|
|
|
|
pContext = pQNode->pContext;
|
|
if (pContext->pQRootNode == pQNode)
|
|
{
|
|
pParentContext = pContext->pParent;
|
|
if (pReplacementNode->eNodeType == FLM_VALUE_NODE)
|
|
{
|
|
|
|
// If this context node is a child to another
|
|
// context, we can excise it completely
|
|
|
|
// At this point, if pQNode was an OR, the
|
|
// replacement node is guaranteed to be TRUE.
|
|
// If the replacement node was an AND, the
|
|
// replacement node is guaranteed to be FALSE.
|
|
// We are at the top of a context, so the entire
|
|
// context can be removed.
|
|
|
|
// Strip off any child contexts and predicates of this context -
|
|
// Don't want to travel down this path when optimizing.
|
|
|
|
pReplacementNode->pContext = pParentContext;
|
|
fqClipContext( pContext);
|
|
}
|
|
else if (pReplacementNode->eNodeType == FLM_FUNCTION_NODE)
|
|
{
|
|
|
|
// Better not be anything in the context that needs to
|
|
// be imported into a parent context
|
|
|
|
flmAssert( !pContext->pFirstPath);
|
|
flmAssert( !pContext->pFirstChild);
|
|
if (pParentContext)
|
|
{
|
|
pReplacementNode->pContext = pParentContext;
|
|
fqClipContext( pContext);
|
|
}
|
|
else
|
|
{
|
|
pReplacementNode->pContext = pContext;
|
|
pContext->bIntersect = TRUE;
|
|
pContext->pQRootNode = pReplacementNode;
|
|
}
|
|
}
|
|
else if (pReplacementNode->eNodeType != FLM_OPERATOR_NODE ||
|
|
!isLogicalOp( pReplacementNode->nd.op.eOperator))
|
|
{
|
|
if (pParentContext)
|
|
{
|
|
// Context has a parent context.
|
|
// If there is only one path and no child contexts,
|
|
// import that path into the parent context.
|
|
// Otherwise, change the root node of the context
|
|
// to point to the replacement node.
|
|
|
|
if( pContext->pFirstPath == pContext->pLastPath &&
|
|
!pContext->pFirstChild)
|
|
{
|
|
fqImportContextPaths( pParentContext, pContext);
|
|
pReplacementNode->pContext = pParentContext;
|
|
fqClipContext( pContext);
|
|
}
|
|
else
|
|
{
|
|
pContext->pQRootNode = pReplacementNode;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// No parent context, so set the bIntersect flag to
|
|
// TRUE so that if it ever is merged to a higher
|
|
// context, it will merge properly - because we
|
|
// know that there is only one predicate in this
|
|
// context.
|
|
|
|
pContext->bIntersect = TRUE;
|
|
pReplacementNode->pContext = pContext;
|
|
pContext->pQRootNode = pReplacementNode;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// pReplacement node is either an AND or OR operator.
|
|
|
|
// At this point, we know that we are moving an AND or OR up the tree
|
|
// to replace an AND or an OR. If they are the same operator, they
|
|
// would have already been in the same context. If not, they would have
|
|
// been in different contexts, and the replacement node will be the
|
|
// same operator as the parent of the node being replaced. Hence,
|
|
// the replacement node's context needs to be merged with the parent
|
|
// node's context.
|
|
|
|
if (pReplacementNode->pContext != pContext)
|
|
{
|
|
fqClipContext( pContext);
|
|
|
|
// Replace node's operator is different than the node it is
|
|
// replacing. AND --> OR, or OR --> AND. This means that
|
|
// the parent context should be the same as the replacement
|
|
// node's context, and hence, they should be merged.
|
|
|
|
if (pParentContext)
|
|
{
|
|
flmAssert( (pParentContext->bIntersect &&
|
|
pReplacementNode->pContext->bIntersect) ||
|
|
(!pParentContext->bIntersect &&
|
|
!pReplacementNode->pContext->bIntersect));
|
|
fqImportContext( pParentContext, pReplacementNode->pContext);
|
|
fqClipContext( pReplacementNode->pContext);
|
|
pReplacementNode->pContext = pParentContext;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fqReplaceNode( pQNode, pReplacementNode);
|
|
pQNode = pReplacementNode;
|
|
|
|
Exit:
|
|
|
|
return( pQNode);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Clip a NOT node out of the tree.
|
|
***************************************************************************/
|
|
FSTATIC FQNODE * fqClipNotNode(
|
|
FQNODE * pQNode,
|
|
FQNODE ** ppExpr
|
|
)
|
|
{
|
|
FQNODE * pKeepNode;
|
|
|
|
// If this NOT node has no parent, the root
|
|
// of the tree needs to be set to its child.
|
|
|
|
pKeepNode = pQNode->pFirstChild;
|
|
|
|
// Child better not have any siblings - NOT nodes only have
|
|
// one operand.
|
|
|
|
flmAssert( !pKeepNode->pNextSib && !pKeepNode->pPrevSib);
|
|
|
|
// Set child to point to the NOT node's parent.
|
|
|
|
if ((pKeepNode->pParent = pQNode->pParent) == NULL)
|
|
{
|
|
*ppExpr = pKeepNode;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Link child in where the NOT node used to be.
|
|
|
|
if ((pKeepNode->pPrevSib = pQNode->pPrevSib) != NULL)
|
|
{
|
|
pKeepNode->pPrevSib->pNextSib = pKeepNode;
|
|
}
|
|
else
|
|
{
|
|
pKeepNode->pParent->pFirstChild = pKeepNode;
|
|
}
|
|
if ((pKeepNode->pNextSib = pQNode->pNextSib) != NULL)
|
|
{
|
|
pKeepNode->pNextSib->pPrevSib = pKeepNode;
|
|
}
|
|
else
|
|
{
|
|
pKeepNode->pParent->pLastChild = pKeepNode;
|
|
}
|
|
}
|
|
return( pKeepNode);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get predicates for a query expression. Also strips out NOT nodes.
|
|
***************************************************************************/
|
|
RCODE F_Query::getPredicates(
|
|
FQNODE ** ppExpr,
|
|
FQNODE * pStartNode,
|
|
XPATH_COMPONENT * pXPathContextComponent
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pQNode = pStartNode ? pStartNode : *ppExpr;
|
|
FQNODE * pParentNode = NULL;
|
|
eNodeTypes eNodeType;
|
|
eQueryOperators eOperator;
|
|
FLMBOOL bNotted = FALSE;
|
|
FLMBOOL bGetPredicates;
|
|
|
|
// Don't get predicates if this is a constant or arithmetic expression.
|
|
// But if it is an arithmetic expression, still need to reduce it to
|
|
// a single constant if possible.
|
|
|
|
if (pQNode->eNodeType == FLM_VALUE_NODE ||
|
|
pQNode->eNodeType == FLM_OPERATOR_NODE &&
|
|
isArithOp( pQNode->nd.op.eOperator))
|
|
{
|
|
bGetPredicates = FALSE;
|
|
}
|
|
else
|
|
{
|
|
bGetPredicates = TRUE;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
eNodeType = pQNode->eNodeType;
|
|
|
|
// Need to save bNotted on each node so that when we traverse
|
|
// back up the tree it can be reset properly. If bNotted is
|
|
// TRUE and pQNode is an operator, we may change the operator in
|
|
// some cases. Even if we change the operator, we still want to
|
|
// set the bNotted flag because it also implies "for every" when set
|
|
// to TRUE, and we need to remember that as well.
|
|
|
|
pQNode->bNotted = bNotted;
|
|
if (eNodeType == FLM_OPERATOR_NODE)
|
|
{
|
|
eOperator = pQNode->nd.op.eOperator;
|
|
if (eOperator == XFLM_AND_OP || eOperator == XFLM_OR_OP)
|
|
{
|
|
|
|
// Logical sub-expressions can only be operands of
|
|
// AND, OR, or NOT operators.
|
|
|
|
if (pParentNode)
|
|
{
|
|
if (!isLogicalOp( pParentNode->nd.op.eOperator))
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_ILLEGAL_OPERAND);
|
|
goto Exit;
|
|
}
|
|
}
|
|
if (bNotted)
|
|
{
|
|
eOperator = (eOperator == XFLM_AND_OP
|
|
? XFLM_OR_OP
|
|
: XFLM_AND_OP);
|
|
pQNode->nd.op.eOperator = eOperator;
|
|
}
|
|
if (pParentNode)
|
|
{
|
|
flmAssert( pParentNode->pContext);
|
|
if (pParentNode->nd.op.eOperator == eOperator)
|
|
{
|
|
pQNode->pContext = pParentNode->pContext;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = createOpContext( pParentNode->pContext,
|
|
(FLMBOOL)(eOperator == XFLM_AND_OP
|
|
? TRUE
|
|
: FALSE), pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = createOpContext( NULL,
|
|
(FLMBOOL)(eOperator == XFLM_AND_OP
|
|
? TRUE
|
|
: FALSE), pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
else if (eOperator == XFLM_NOT_OP)
|
|
{
|
|
|
|
// Logical sub-expressions can only be operands of
|
|
// AND, OR, or NOT operators.
|
|
|
|
if (pParentNode)
|
|
{
|
|
if (!isLogicalOp( pParentNode->nd.op.eOperator))
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_ILLEGAL_OPERAND);
|
|
goto Exit;
|
|
}
|
|
}
|
|
bNotted = !bNotted;
|
|
|
|
// Clip NOT nodes out of the tree.
|
|
|
|
pQNode = fqClipNotNode( pQNode, ppExpr);
|
|
pParentNode = pQNode->pParent;
|
|
continue;
|
|
}
|
|
else if (isCompareOp( eOperator))
|
|
{
|
|
|
|
// Comparison sub-expressions can only be operands of
|
|
// AND, OR, or NOT operators.
|
|
|
|
if (pParentNode)
|
|
{
|
|
if (!isLogicalOp( pParentNode->nd.op.eOperator))
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_ILLEGAL_OPERAND);
|
|
goto Exit;
|
|
}
|
|
|
|
// Associate context with parent node
|
|
|
|
pQNode->pContext = pParentNode->pContext;
|
|
}
|
|
else
|
|
{
|
|
|
|
// We set the bIntersect flag to TRUE for this context,
|
|
// even though it is not an AND operator. This is done
|
|
// because we know there will only ever be one predicate
|
|
// in this context, and therefore, if it is ever merged
|
|
// into another "intersect" context, we want the predicate
|
|
// merged in by itself instead of the entire context.
|
|
// See fqMergeContexts.
|
|
|
|
if (RC_BAD( rc = createOpContext( NULL, TRUE, pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
if (bNotted)
|
|
{
|
|
switch (eOperator)
|
|
{
|
|
case XFLM_EQ_OP:
|
|
eOperator = XFLM_NE_OP;
|
|
break;
|
|
case XFLM_NE_OP:
|
|
eOperator = XFLM_EQ_OP;
|
|
break;
|
|
case XFLM_LT_OP:
|
|
eOperator = XFLM_GE_OP;
|
|
break;
|
|
case XFLM_LE_OP:
|
|
eOperator = XFLM_GT_OP;
|
|
break;
|
|
case XFLM_GT_OP:
|
|
eOperator = XFLM_LE_OP;
|
|
break;
|
|
case XFLM_GE_OP:
|
|
eOperator = XFLM_LT_OP;
|
|
break;
|
|
default:
|
|
|
|
// Don't change the other operators.
|
|
// Will just use the bNotted flag when
|
|
// evaluating.
|
|
|
|
break;
|
|
}
|
|
pQNode->nd.op.eOperator = eOperator;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// Better be an arithmetic operator we are dealing with
|
|
// at this point.
|
|
|
|
flmAssert( isArithOp( eOperator));
|
|
|
|
// Arithmetic sub-expressions can only be operands
|
|
// of arithmetic or comparison operators
|
|
|
|
if (pParentNode)
|
|
{
|
|
if (!isCompareOp( pParentNode->nd.op.eOperator) &&
|
|
!isArithOp( pParentNode->nd.op.eOperator))
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_ILLEGAL_OPERAND);
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (eNodeType == FLM_XPATH_NODE)
|
|
{
|
|
if (bGetPredicates)
|
|
{
|
|
if (RC_BAD( rc = getPathPredicates( pParentNode,
|
|
&pQNode, pXPathContextComponent)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// NOTE: Upon return pQNode may no longer be
|
|
// an XPATH node. This branch of the tree may
|
|
// have been clipped as we evaluated predicates
|
|
// that were ANDed together, in which case
|
|
// pQNode will be pointing at a value node
|
|
// that has a value of FALSE. Either way,
|
|
// there should be no children at this point.
|
|
|
|
flmAssert( !pQNode->pFirstChild);
|
|
}
|
|
else if (eNodeType == FLM_FUNCTION_NODE)
|
|
{
|
|
|
|
// a function node should not have any children
|
|
|
|
flmAssert( !pQNode->pFirstChild);
|
|
|
|
// If this function is ORed into the context, set the
|
|
// bForceOptToScan flag on the context. This will
|
|
// force the context to scan when we optimize. Note that
|
|
// we cannot just set the bMustScan flag, as that flag is
|
|
// initialized by the optimization code to FALSE in the case
|
|
// of a non-intersect context.
|
|
|
|
if (bGetPredicates)
|
|
{
|
|
if (pParentNode)
|
|
{
|
|
if (pParentNode->nd.op.eOperator == XFLM_OR_OP)
|
|
{
|
|
pParentNode->pContext->bForceOptToScan = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// Need to have a context for later merging.
|
|
|
|
if (RC_BAD( rc = createOpContext( NULL, TRUE, pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQNode->pContext->bForceOptToScan = TRUE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flmAssert( eNodeType == FLM_VALUE_NODE);
|
|
|
|
// If bNotted is TRUE and we have a boolean value, change
|
|
// the value: FALSE ==> TRUE, TRUE ==> FALSE.
|
|
|
|
if (bNotted && pQNode->currVal.eValType == XFLM_BOOL_VAL)
|
|
{
|
|
if (pQNode->currVal.val.eBool == XFLM_TRUE)
|
|
{
|
|
pQNode->currVal.val.eBool = XFLM_FALSE;
|
|
}
|
|
else if (pQNode->currVal.val.eBool == XFLM_FALSE)
|
|
{
|
|
pQNode->currVal.val.eBool = XFLM_TRUE;
|
|
}
|
|
}
|
|
|
|
// Values can only be operands of arithmetic or comparison operators,
|
|
// unless they are boolean values, in which case they can only be
|
|
// operands of logical operators.
|
|
|
|
if (pParentNode)
|
|
{
|
|
if (pQNode->currVal.eValType == XFLM_BOOL_VAL)
|
|
{
|
|
if (!isLogicalOp( pParentNode->nd.op.eOperator))
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_ILLEGAL_OPERAND);
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!isCompareOp( pParentNode->nd.op.eOperator) &&
|
|
!isArithOp( pParentNode->nd.op.eOperator))
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_ILLEGAL_OPERAND);
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// A value node should not have any children
|
|
|
|
flmAssert( !pQNode->pFirstChild);
|
|
}
|
|
|
|
// Do traversal to child node, if any
|
|
|
|
if (pQNode->pFirstChild)
|
|
{
|
|
pParentNode = pQNode;
|
|
pQNode = pQNode->pFirstChild;
|
|
continue;
|
|
}
|
|
|
|
// Go back up the tree until we hit something that has
|
|
// a sibling.
|
|
|
|
while (!pQNode->pNextSib)
|
|
{
|
|
|
|
// If there are no more parents, we are done.
|
|
|
|
if ((pQNode = pQNode->pParent) == NULL)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
flmAssert( pQNode->eNodeType == FLM_OPERATOR_NODE);
|
|
|
|
// Evaluate arithmetic expressions if both operands are
|
|
// constants.
|
|
|
|
if (isArithOp( pQNode->nd.op.eOperator) &&
|
|
pQNode->pFirstChild->eNodeType == FLM_VALUE_NODE &&
|
|
pQNode->pLastChild->eNodeType == FLM_VALUE_NODE)
|
|
{
|
|
if (RC_BAD( rc = fqArithmeticOperator(
|
|
&pQNode->pFirstChild->currVal,
|
|
&pQNode->pLastChild->currVal,
|
|
pQNode->nd.op.eOperator,
|
|
&pQNode->currVal)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQNode->eNodeType = FLM_VALUE_NODE;
|
|
pQNode->currVal.uiFlags = VAL_IS_CONSTANT;
|
|
pQNode->pFirstChild = NULL;
|
|
pQNode->pLastChild = NULL;
|
|
}
|
|
else
|
|
{
|
|
|
|
// For the AND and OR operators, check the operands to
|
|
// see if they are boolean values. Boolean values can
|
|
// be weeded out of the criteria as we go back up the
|
|
// tree.
|
|
|
|
if (pQNode->nd.op.eOperator == XFLM_OR_OP ||
|
|
pQNode->nd.op.eOperator == XFLM_AND_OP)
|
|
{
|
|
pQNode = fqEvalLogicalOperands( pQNode);
|
|
if (!pQNode->pParent)
|
|
{
|
|
*ppExpr = pQNode;
|
|
}
|
|
}
|
|
}
|
|
|
|
pParentNode = pQNode->pParent;
|
|
}
|
|
|
|
// pQNode will NEVER be NULL if we get here, because we
|
|
// will jump to Exit in those cases.
|
|
|
|
pQNode = pQNode->pNextSib;
|
|
|
|
// Need to reset the bNotted flag to what it would have
|
|
// been as we traverse back up the tree.
|
|
|
|
bNotted = pParentNode->bNotted;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Determine if an XPATH component is getting the node's node id or
|
|
document id.
|
|
***************************************************************************/
|
|
FINLINE FLMBOOL isNodeOrDocIdComponent(
|
|
XPATH_COMPONENT * pXPathComponent
|
|
)
|
|
{
|
|
return( pXPathComponent->eXPathAxis == META_AXIS &&
|
|
(pXPathComponent->uiDictNum == XFLM_META_NODE_ID ||
|
|
pXPathComponent->uiDictNum == XFLM_META_DOCUMENT_ID)
|
|
? TRUE
|
|
: FALSE);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the meta data type for an xpath component, if any.
|
|
***************************************************************************/
|
|
FINLINE FLMUINT getMetaDataType(
|
|
XPATH_COMPONENT * pXPathComponent
|
|
)
|
|
{
|
|
return( pXPathComponent->eXPathAxis != META_AXIS
|
|
? 0
|
|
: pXPathComponent->uiDictNum);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the node ID constant from an FQVALUE node.
|
|
***************************************************************************/
|
|
FSTATIC RCODE fqGetNodeIdValue(
|
|
FQVALUE * pQValue
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
switch (pQValue->eValType)
|
|
{
|
|
case XFLM_UINT_VAL:
|
|
pQValue->eValType = XFLM_UINT64_VAL;
|
|
pQValue->val.ui64Val = (FLMUINT64)pQValue->val.uiVal;
|
|
break;
|
|
case XFLM_MISSING_VAL:
|
|
case XFLM_UINT64_VAL:
|
|
break;
|
|
case XFLM_INT_VAL:
|
|
pQValue->eValType = XFLM_UINT64_VAL;
|
|
pQValue->val.ui64Val = (FLMUINT64)((FLMINT64)(pQValue->val.iVal));
|
|
break;
|
|
case XFLM_INT64_VAL:
|
|
pQValue->eValType = XFLM_UINT64_VAL;
|
|
pQValue->val.ui64Val = (FLMUINT64)(pQValue->val.i64Val);
|
|
break;
|
|
default:
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_Q_INVALID_NODE_ID_VALUE);
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Optimize a predicate.
|
|
***************************************************************************/
|
|
RCODE F_Query::optimizePredicate(
|
|
XPATH_COMPONENT * pXPathComponent,
|
|
PATH_PRED * pPred
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
ICD * pIcd;
|
|
ICD * pTmpIcd;
|
|
XPATH_COMPONENT * pTmpXPathComponent;
|
|
FLMBOOL bCanCompareOnKey;
|
|
FLMBOOL bDoNodeMatch;
|
|
FLMBOOL bTmpCanCompareOnKey;
|
|
FLMBOOL bMustVerifyPath;
|
|
FSIndexCursor * pFSIndexCursor = NULL;
|
|
FLMUINT uiLeafBlocksBetween;
|
|
FLMUINT uiTotalRefs;
|
|
FLMBOOL bTotalsEstimated;
|
|
FLMUINT uiCost;
|
|
IF_BufferIStream * pBufferIStream = NULL;
|
|
F_AttrElmInfo defInfo;
|
|
|
|
// Special handling for app. defined source nodes
|
|
|
|
if (pXPathComponent->pNodeSource)
|
|
{
|
|
FLMBOOL bMustScan;
|
|
|
|
if (RC_OK( rc = pXPathComponent->pNodeSource->searchCost(
|
|
(IF_Db *)m_pDb, pPred->bNotted,
|
|
&pPred->OptInfo.uiCost,
|
|
&bMustScan)))
|
|
{
|
|
if (bMustScan)
|
|
{
|
|
pPred->OptInfo.eOptType = XFLM_QOPT_FULL_COLLECTION_SCAN;
|
|
}
|
|
pPred->pNodeSource = pXPathComponent->pNodeSource;
|
|
pPred->pNodeSource->AddRef();
|
|
}
|
|
goto Exit;
|
|
}
|
|
|
|
// A predicate of the form "A operator Value" will always
|
|
// return FALSE if A is missing, regardless of the operator.
|
|
// So will a predicate of "exists(A)"
|
|
// This is because we do not use default data for missing
|
|
// values. The only time it will return TRUE
|
|
// when A is missing is if the predicate is notted.
|
|
// Hence, a predicate that is notted cannot be optimized.
|
|
|
|
if (pPred->bNotted || (pPred->pContextNode && pPred->pContextNode->bNotted))
|
|
{
|
|
pPred->OptInfo.uiCost = ~((FLMUINT)0);
|
|
pPred->OptInfo.eOptType = XFLM_QOPT_FULL_COLLECTION_SCAN;
|
|
goto Exit;
|
|
}
|
|
|
|
// Special handling for node id and document id attributes.
|
|
// These will never be indexed, but we use a collection
|
|
// cursor for them, if necessary.
|
|
|
|
if (pXPathComponent->eXPathAxis == META_AXIS)
|
|
{
|
|
|
|
// Default is to do a full collection scan - may be changed below.
|
|
|
|
pPred->OptInfo.uiCost = ~((FLMUINT)0);
|
|
pPred->OptInfo.eOptType = XFLM_QOPT_FULL_COLLECTION_SCAN;
|
|
|
|
if (pXPathComponent->uiDictNum == XFLM_META_NODE_ID ||
|
|
pXPathComponent->uiDictNum == XFLM_META_DOCUMENT_ID)
|
|
{
|
|
FLMBOOL bDocumentIds =
|
|
pXPathComponent->uiDictNum == XFLM_META_DOCUMENT_ID
|
|
? TRUE
|
|
: FALSE;
|
|
|
|
// If the user has specified an index, we must set it up to
|
|
// do an index scan.
|
|
|
|
if (m_bIndexSet)
|
|
{
|
|
pPred->OptInfo.uiCost = ~((FLMUINT)0);
|
|
pPred->OptInfo.eOptType = XFLM_QOPT_FULL_COLLECTION_SCAN;
|
|
}
|
|
else if (pPred->eOperator == XFLM_APPROX_EQ_OP)
|
|
{
|
|
pPred->OptInfo.uiCost = 1;
|
|
|
|
// Value should have already been converted to a 64 bit
|
|
// unsigned value.
|
|
|
|
flmAssert( pPred->pFromValue->eValType == XFLM_UINT64_VAL);
|
|
pPred->OptInfo.ui64NodeId = pPred->pFromValue->val.ui64Val;
|
|
pPred->OptInfo.eOptType = XFLM_QOPT_SINGLE_NODE_ID;
|
|
pPred->OptInfo.bMustVerifyPath = TRUE;
|
|
}
|
|
else if (pPred->eOperator == XFLM_RANGE_OP)
|
|
{
|
|
|
|
// Value should have already been converted to a 64 bit
|
|
// unsigned value.
|
|
|
|
flmAssert( pPred->pFromValue->eValType == XFLM_UINT64_VAL);
|
|
pPred->OptInfo.ui64NodeId = pPred->pFromValue->val.ui64Val;
|
|
if (!pPred->bInclFrom)
|
|
{
|
|
pPred->OptInfo.ui64NodeId++;
|
|
}
|
|
|
|
// Value should have already been converted to a 64 bit
|
|
// unsigned value.
|
|
|
|
flmAssert( pPred->pUntilValue->eValType == XFLM_UINT64_VAL);
|
|
pPred->OptInfo.ui64EndNodeId = pPred->pUntilValue->val.ui64Val;
|
|
if (!pPred->bInclUntil)
|
|
{
|
|
pPred->OptInfo.ui64EndNodeId--;
|
|
}
|
|
if (pPred->OptInfo.ui64NodeId == pPred->OptInfo.ui64EndNodeId)
|
|
{
|
|
pPred->OptInfo.uiCost = 1;
|
|
pPred->OptInfo.eOptType = XFLM_QOPT_SINGLE_NODE_ID;
|
|
}
|
|
else
|
|
{
|
|
pPred->OptInfo.eOptType = XFLM_QOPT_NODE_ID_RANGE;
|
|
if ((pPred->pFSCollectionCursor = f_new FSCollectionCursor) == NULL)
|
|
{
|
|
rc = RC_SET( NE_XFLM_MEM);
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = pPred->pFSCollectionCursor->setupRange( m_pDb,
|
|
m_uiCollection, bDocumentIds,
|
|
pPred->OptInfo.ui64NodeId,
|
|
pPred->OptInfo.ui64EndNodeId,
|
|
&uiLeafBlocksBetween, &uiTotalRefs,
|
|
&bTotalsEstimated)))
|
|
{
|
|
pPred->pFSCollectionCursor->Release();
|
|
pPred->pFSCollectionCursor = NULL;
|
|
goto Exit;
|
|
}
|
|
pPred->OptInfo.uiCost = uiLeafBlocksBetween;
|
|
if (!pPred->OptInfo.uiCost)
|
|
{
|
|
pPred->OptInfo.uiCost = 1;
|
|
}
|
|
}
|
|
pPred->OptInfo.bMustVerifyPath = TRUE;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Only other operators allowed would be the NE
|
|
// operator. EXISTS would have been converted to
|
|
// a TRUE constant.
|
|
|
|
flmAssert( pPred->eOperator == XFLM_NE_OP);
|
|
}
|
|
}
|
|
goto Exit;
|
|
}
|
|
|
|
// Get the ICD chain
|
|
|
|
if (pXPathComponent->eNodeType == ELEMENT_NODE)
|
|
{
|
|
if (RC_BAD( rc = m_pDb->m_pDict->getElement( m_pDb,
|
|
pXPathComponent->uiDictNum,
|
|
&defInfo)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else if (pXPathComponent->eNodeType == ATTRIBUTE_NODE)
|
|
{
|
|
if (RC_BAD( rc = m_pDb->m_pDict->getAttribute( m_pDb,
|
|
pXPathComponent->uiDictNum, &defInfo)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
pIcd = defInfo.m_pFirstIcd;
|
|
|
|
// Get the indexes in the ICD chain that are suitable for this
|
|
// predicate.
|
|
|
|
for (; pIcd; pIcd = pIcd->pNextInChain)
|
|
{
|
|
|
|
// Stop at the first non-required ICD.
|
|
|
|
if (!(pIcd->uiFlags &
|
|
(ICD_REQUIRED_PIECE | ICD_REQUIRED_IN_SET)))
|
|
{
|
|
break;
|
|
}
|
|
|
|
// If this ICD is not a key component, we cannot use it.
|
|
// Also, it must be the FIRST key component, and none of the
|
|
// other components can be required.
|
|
|
|
if (pIcd->uiKeyComponent != 1)
|
|
{
|
|
continue;
|
|
}
|
|
pTmpIcd = pIcd->pNextKeyComponent;
|
|
while (pTmpIcd &&
|
|
!(pTmpIcd->uiFlags & ICD_REQUIRED_PIECE))
|
|
{
|
|
pTmpIcd = pTmpIcd->pNextKeyComponent;
|
|
}
|
|
if (pTmpIcd)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Check the following conditions of suitability:
|
|
// 1) Index must be on the collection we are searching.
|
|
// 2) Index must be on-line
|
|
|
|
if (pIcd->pIxd->uiCollectionNum != m_uiCollection ||
|
|
(pIcd->pIxd->uiFlags & (IXD_OFFLINE | IXD_SUSPENDED)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If the user has specified an index, and this is not
|
|
// that index, we will ignore it.
|
|
|
|
if (m_bIndexSet && pIcd->uiIndexNum != m_uiIndex)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Make sure the ICD's path is of less or equal specificity
|
|
// to the path for this predicate.
|
|
|
|
pTmpIcd = pIcd->pParent;
|
|
pTmpXPathComponent = pXPathComponent;
|
|
|
|
bMustVerifyPath = FALSE;
|
|
|
|
while (pTmpIcd)
|
|
{
|
|
|
|
// If this is the self axis, we stay on the ICD we are currently on.
|
|
// The XPATH component previous to/above this one is the same as
|
|
// this one.
|
|
|
|
if (pTmpXPathComponent->eXPathAxis == SELF_AXIS)
|
|
{
|
|
FLMUINT uiTmpDictNum = pTmpXPathComponent->uiDictNum;
|
|
eDomNodeType eTmpNodeType = pTmpXPathComponent->eNodeType;
|
|
|
|
if (pTmpXPathComponent->pPrev)
|
|
{
|
|
pTmpXPathComponent = pTmpXPathComponent->pPrev;
|
|
if (pTmpXPathComponent->pExpr)
|
|
{
|
|
bMustVerifyPath = TRUE;
|
|
}
|
|
}
|
|
else if ((pTmpXPathComponent =
|
|
pTmpXPathComponent->pXPathContext) == NULL)
|
|
{
|
|
break;
|
|
}
|
|
if (eTmpNodeType == ANY_NODE_TYPE ||
|
|
(eTmpNodeType == pTmpXPathComponent->eNodeType &&
|
|
(uiTmpDictNum == pTmpXPathComponent->uiDictNum ||
|
|
!uiTmpDictNum)))
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If the ICD is the root tag, then the path needs to be
|
|
// the root axis in order for it to be a match.
|
|
|
|
if (pTmpIcd->uiDictNum == ELM_ROOT_TAG)
|
|
{
|
|
|
|
// Root tag should not have a parent.
|
|
|
|
flmAssert( pTmpIcd->pParent == NULL);
|
|
|
|
// If the path is not off of the root, then the
|
|
// index's path is too specific to have indexed
|
|
|
|
if (pTmpXPathComponent->eXPathAxis == ROOT_AXIS)
|
|
{
|
|
|
|
// Should not be anything previous to a root axis.
|
|
|
|
flmAssert( pTmpXPathComponent->pPrev == NULL);
|
|
pTmpXPathComponent = NULL;
|
|
pTmpIcd = NULL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Cannot match on anything except parent/child relationships
|
|
|
|
if (pTmpXPathComponent->eXPathAxis != CHILD_AXIS &&
|
|
pTmpXPathComponent->eXPathAxis != ATTRIBUTE_AXIS)
|
|
{
|
|
bMustVerifyPath = TRUE;
|
|
break;
|
|
}
|
|
if (pTmpXPathComponent->pPrev)
|
|
{
|
|
pTmpXPathComponent = pTmpXPathComponent->pPrev;
|
|
if (pTmpXPathComponent->pExpr)
|
|
{
|
|
bMustVerifyPath = TRUE;
|
|
}
|
|
}
|
|
else if ((pTmpXPathComponent =
|
|
pTmpXPathComponent->pXPathContext) == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// See if the element is the same as the ICD.
|
|
// NOTE: At this point, the ICD MUST be an element, because attributes
|
|
// are not allowed as parent ICDs of another ICD.
|
|
|
|
if (pTmpXPathComponent->eNodeType != ELEMENT_NODE ||
|
|
pTmpXPathComponent->uiDictNum != pTmpIcd->uiDictNum)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Go to ICD's parent
|
|
|
|
pTmpIcd = pTmpIcd->pParent;
|
|
}
|
|
|
|
// If we get here and we did not get all the way up to the
|
|
// parent ICD, the index path is more specific than the XPATH.
|
|
|
|
if (pTmpIcd)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If there are more components in the XPATH, it is more specific
|
|
// than the ICD path, so we must verify the path.
|
|
|
|
if (!bMustVerifyPath && pTmpXPathComponent &&
|
|
(pTmpXPathComponent->eXPathAxis == ROOT_AXIS ||
|
|
pTmpXPathComponent->pPrev ||
|
|
pTmpXPathComponent->pXPathContext))
|
|
{
|
|
bMustVerifyPath = TRUE;
|
|
}
|
|
|
|
bCanCompareOnKey = TRUE;
|
|
|
|
// If the ICD is on element or attributes and the operator is not
|
|
// the exists operator, we must also fetch the node to evaluate
|
|
// the predicate.
|
|
|
|
if ((pIcd->uiFlags & ICD_PRESENCE) && pPred->eOperator != XFLM_EXISTS_OP)
|
|
{
|
|
bCanCompareOnKey = FALSE;
|
|
}
|
|
|
|
// If the comparison rules aren't the same as those specified
|
|
// on the ICD, we will need to read the node to do the comparison.
|
|
|
|
if (bCanCompareOnKey && defInfo.m_uiDataType == XFLM_TEXT_TYPE)
|
|
{
|
|
if (!(pPred->uiCompareRules & XFLM_COMP_CASE_INSENSITIVE))
|
|
{
|
|
if (pIcd->uiCompareRules & XFLM_COMP_CASE_INSENSITIVE)
|
|
{
|
|
bCanCompareOnKey = FALSE;
|
|
}
|
|
}
|
|
|
|
// Check comparison flags that must match exactly
|
|
|
|
if ((pPred->uiCompareRules &
|
|
(XFLM_COMP_COMPRESS_WHITESPACE |
|
|
XFLM_COMP_NO_WHITESPACE |
|
|
XFLM_COMP_NO_UNDERSCORES |
|
|
XFLM_COMP_NO_DASHES |
|
|
XFLM_COMP_WHITESPACE_AS_SPACE |
|
|
XFLM_COMP_IGNORE_LEADING_SPACE |
|
|
XFLM_COMP_IGNORE_TRAILING_SPACE)) !=
|
|
(pIcd->uiCompareRules &
|
|
(XFLM_COMP_COMPRESS_WHITESPACE |
|
|
XFLM_COMP_NO_WHITESPACE |
|
|
XFLM_COMP_NO_UNDERSCORES |
|
|
XFLM_COMP_NO_DASHES |
|
|
XFLM_COMP_WHITESPACE_AS_SPACE |
|
|
XFLM_COMP_IGNORE_LEADING_SPACE |
|
|
XFLM_COMP_IGNORE_TRAILING_SPACE)))
|
|
{
|
|
bCanCompareOnKey = FALSE;
|
|
}
|
|
}
|
|
|
|
// Need to select best metaphone for approximate equals
|
|
// on text value.
|
|
|
|
if (pPred->eOperator == XFLM_APPROX_EQ_OP &&
|
|
pIcd->uiFlags & ICD_METAPHONE &&
|
|
pPred->pFromValue->eValType == XFLM_UTF8_VAL)
|
|
{
|
|
FLMUINT uiMeta;
|
|
FLMUINT uiAltMeta;
|
|
FQVALUE metaValue;
|
|
FQVALUE * pSaveValue = pPred->pFromValue;
|
|
FLMUINT uiMetaCost;
|
|
|
|
if( RC_BAD( rc = FlmAllocBufferIStream( &pBufferIStream)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
uiCost = 0;
|
|
if (RC_BAD( rc = pBufferIStream->open(
|
|
(const char *)pPred->pFromValue->val.pucBuf,
|
|
pPred->pFromValue->uiDataLen)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// This is a little bit trickiness here. We need to set up the
|
|
// metaphone value as a XFLM_UTF8_VAL, but then set
|
|
// metaValue.val.uiVal. That is because the code that
|
|
// generates the key is expecting this - see kybldkey.cpp,
|
|
// flmAddNonTextKeyPiece.
|
|
|
|
metaValue.eValType = XFLM_UTF8_VAL;
|
|
for (;;)
|
|
{
|
|
if( RC_BAD( rc = f_getNextMetaphone(
|
|
pBufferIStream, &uiMeta, &uiAltMeta)))
|
|
{
|
|
if (rc != NE_XFLM_EOF_HIT)
|
|
{
|
|
goto Exit;
|
|
}
|
|
rc = NE_XFLM_OK;
|
|
break;
|
|
}
|
|
|
|
if (!uiMeta)
|
|
{
|
|
if (!uiAltMeta)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
uiMeta = uiAltMeta;
|
|
}
|
|
|
|
// Generate the from and until keys for this index. If the estimated
|
|
// cost is low enough, we will look no further for a better index.
|
|
|
|
if (pFSIndexCursor)
|
|
{
|
|
pFSIndexCursor->resetCursor();
|
|
}
|
|
else
|
|
{
|
|
if ((pFSIndexCursor = f_new FSIndexCursor) == NULL)
|
|
{
|
|
rc = RC_SET( NE_XFLM_MEM);
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// Temporarily change the from value in the predicate
|
|
// to point to the metaphone value. That is what the
|
|
// key will be generated on.
|
|
|
|
metaValue.val.uiVal = uiMeta;
|
|
pPred->pFromValue = &metaValue;
|
|
rc = pFSIndexCursor->setupKeys( m_pDb, pIcd->pIxd, pPred,
|
|
&bDoNodeMatch, &bTmpCanCompareOnKey,
|
|
&uiLeafBlocksBetween, &uiTotalRefs,
|
|
&bTotalsEstimated);
|
|
|
|
// Restore the from value in the predicate before
|
|
// going any further.
|
|
|
|
pPred->pFromValue = pSaveValue;
|
|
if (RC_BAD( rc))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Will always have to fetch the node, so cost should
|
|
// always include uiTotalRefs in this case.
|
|
|
|
uiMetaCost = uiLeafBlocksBetween + uiTotalRefs;
|
|
if (!uiMetaCost)
|
|
{
|
|
uiMetaCost = 1;
|
|
}
|
|
if (!uiCost || uiMetaCost < uiCost)
|
|
{
|
|
uiCost = uiMetaCost;
|
|
if (uiCost < MIN_OPT_COST)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( pBufferIStream)
|
|
{
|
|
pBufferIStream->Release();
|
|
pBufferIStream = NULL;
|
|
}
|
|
|
|
bTmpCanCompareOnKey = FALSE;
|
|
bDoNodeMatch = TRUE;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Generate the from and until keys for this index. If the estimated
|
|
// cost is low enough, we will look no further for a better index.
|
|
|
|
if (pFSIndexCursor)
|
|
{
|
|
pFSIndexCursor->resetCursor();
|
|
}
|
|
else
|
|
{
|
|
if ((pFSIndexCursor = f_new FSIndexCursor) == NULL)
|
|
{
|
|
rc = RC_SET( NE_XFLM_MEM);
|
|
goto Exit;
|
|
}
|
|
}
|
|
if (RC_BAD( rc = pFSIndexCursor->setupKeys( m_pDb, pIcd->pIxd, pPred,
|
|
&bDoNodeMatch, &bTmpCanCompareOnKey,
|
|
&uiLeafBlocksBetween, &uiTotalRefs,
|
|
&bTotalsEstimated)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!bTmpCanCompareOnKey)
|
|
{
|
|
bDoNodeMatch = TRUE;
|
|
}
|
|
uiCost = uiLeafBlocksBetween + uiTotalRefs;
|
|
}
|
|
|
|
// Could be that there are zero leaf blocks between and
|
|
// bDoRecMatch is FALSE. But we do not want a cost of
|
|
// zero.
|
|
|
|
if (!uiCost)
|
|
{
|
|
uiCost = 1;
|
|
}
|
|
|
|
if (!pPred->OptInfo.uiCost || uiCost < pPred->OptInfo.uiCost)
|
|
{
|
|
FSIndexCursor * pTmpFSIndexCursor;
|
|
F_NameTable * pNameTable = NULL;
|
|
|
|
// Exchange the temporary file system cursor and the
|
|
// file system cursor inside the predicate. Want to
|
|
// keep the temporary one and reuse the one that was
|
|
// inside the sub-query.
|
|
|
|
pTmpFSIndexCursor = pPred->pFSIndexCursor;
|
|
pPred->pFSIndexCursor = pFSIndexCursor;
|
|
pFSIndexCursor = pTmpFSIndexCursor;
|
|
pPred->OptInfo.eOptType = XFLM_QOPT_USING_INDEX;
|
|
pPred->OptInfo.uiIxNum = pIcd->pIxd->uiIndexNum;
|
|
pPred->OptInfo.szIxName [0] = 0;
|
|
if (RC_OK( m_pDb->getNameTable( &pNameTable)))
|
|
{
|
|
FLMUINT uiIxNameLen = sizeof( pPred->OptInfo.szIxName);
|
|
if (RC_BAD( pNameTable->getFromTagTypeAndNum(
|
|
m_pDb, ELM_INDEX_TAG,
|
|
pPred->OptInfo.uiIxNum, NULL,
|
|
(char *)pPred->OptInfo.szIxName,
|
|
&uiIxNameLen, NULL, NULL,
|
|
NULL, NULL, TRUE)))
|
|
{
|
|
pPred->OptInfo.szIxName [0] = 0;
|
|
}
|
|
}
|
|
if (pNameTable)
|
|
{
|
|
pNameTable->Release();
|
|
}
|
|
pPred->OptInfo.uiCost = uiCost;
|
|
pPred->OptInfo.bDoNodeMatch = bDoNodeMatch;
|
|
pPred->OptInfo.bMustVerifyPath = bMustVerifyPath;
|
|
pPred->OptInfo.bCanCompareOnKey =
|
|
(bTmpCanCompareOnKey && bCanCompareOnKey) ? TRUE : FALSE;
|
|
|
|
// If the cost is now low enough, we will quit looking
|
|
|
|
if (uiCost < MIN_OPT_COST)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no index was found, the predicate will force a full
|
|
// collection scan.
|
|
|
|
if (!pPred->OptInfo.uiCost)
|
|
{
|
|
pPred->OptInfo.uiCost = ~((FLMUINT)0);
|
|
pPred->OptInfo.eOptType = XFLM_QOPT_FULL_COLLECTION_SCAN;
|
|
}
|
|
|
|
Exit:
|
|
|
|
if( pBufferIStream)
|
|
{
|
|
pBufferIStream->Release();
|
|
pBufferIStream = NULL;
|
|
}
|
|
|
|
if (pFSIndexCursor)
|
|
{
|
|
pFSIndexCursor->Release();
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Optimize a context path.
|
|
***************************************************************************/
|
|
RCODE F_Query::optimizePath(
|
|
CONTEXT_PATH * pContextPath,
|
|
PATH_PRED * pSingleNodeIdPred,
|
|
FLMBOOL bIntersect
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
PATH_PRED * pPred;
|
|
XPATH_COMPONENT * pXPathComponent = pContextPath->pXPathComponent;
|
|
|
|
pPred = pContextPath->pFirstPred;
|
|
if (bIntersect)
|
|
{
|
|
pContextPath->uiCost = ~((FLMUINT)0);
|
|
pContextPath->pSelectedPred = NULL;
|
|
pContextPath->bMustScan = TRUE;
|
|
|
|
// If we already know we have a single node id predicate, there
|
|
// is no need to optimize any of the other predicates, because
|
|
// this one is guaranteed to have the lowest cost: 1.
|
|
|
|
if (pSingleNodeIdPred)
|
|
{
|
|
if (RC_BAD( rc = optimizePredicate( pXPathComponent,
|
|
pSingleNodeIdPred)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Cost better have returned as 1! and the optimization
|
|
// better have been XFLM_QOPT_SINGLE_NODE_ID.
|
|
|
|
flmAssert( pSingleNodeIdPred->OptInfo.uiCost == 1);
|
|
flmAssert( pSingleNodeIdPred->OptInfo.eOptType ==
|
|
XFLM_QOPT_SINGLE_NODE_ID);
|
|
pContextPath->uiCost = pSingleNodeIdPred->OptInfo.uiCost;
|
|
pContextPath->pSelectedPred = pSingleNodeIdPred;
|
|
pContextPath->bMustScan = FALSE;
|
|
}
|
|
else
|
|
{
|
|
while (pPred)
|
|
{
|
|
if (RC_BAD( rc = optimizePredicate( pXPathComponent, pPred)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (pPred->OptInfo.eOptType != XFLM_QOPT_FULL_COLLECTION_SCAN &&
|
|
(pPred->OptInfo.uiCost < pContextPath->uiCost ||
|
|
pContextPath->bMustScan))
|
|
{
|
|
pContextPath->uiCost = pPred->OptInfo.uiCost;
|
|
if (pContextPath->pSelectedPred)
|
|
{
|
|
if (pContextPath->pSelectedPred->pFSIndexCursor)
|
|
{
|
|
pContextPath->pSelectedPred->pFSIndexCursor->Release();
|
|
pContextPath->pSelectedPred->pFSIndexCursor = NULL;
|
|
}
|
|
else if (pContextPath->pSelectedPred->pFSCollectionCursor)
|
|
{
|
|
pContextPath->pSelectedPred->pFSCollectionCursor->Release();
|
|
pContextPath->pSelectedPred->pFSCollectionCursor = NULL;
|
|
}
|
|
else if (pContextPath->pSelectedPred->pNodeSource)
|
|
{
|
|
pContextPath->pSelectedPred->pNodeSource->Release();
|
|
pContextPath->pSelectedPred->pNodeSource = NULL;
|
|
}
|
|
}
|
|
pContextPath->pSelectedPred = pPred;
|
|
pContextPath->bMustScan = FALSE;
|
|
|
|
// No need to evaluate more predicates if the cost is
|
|
// MIN_OPT_COST or below.
|
|
|
|
if (pPred->OptInfo.uiCost < MIN_OPT_COST)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pPred->pFSIndexCursor)
|
|
{
|
|
pPred->pFSIndexCursor->Release();
|
|
pPred->pFSIndexCursor = NULL;
|
|
}
|
|
else if (pPred->pFSCollectionCursor)
|
|
{
|
|
pPred->pFSCollectionCursor->Release();
|
|
pPred->pFSCollectionCursor = NULL;
|
|
}
|
|
else if (pPred->pNodeSource)
|
|
{
|
|
pContextPath->pSelectedPred->pNodeSource->Release();
|
|
pContextPath->pSelectedPred->pNodeSource = NULL;
|
|
}
|
|
}
|
|
pPred = pPred->pNext;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pContextPath->uiCost = 0;
|
|
while (pPred)
|
|
{
|
|
if (RC_BAD( rc = optimizePredicate( pXPathComponent, pPred)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (~((FLMUINT)0) - pContextPath->uiCost > pPred->OptInfo.uiCost &&
|
|
pPred->OptInfo.eOptType != XFLM_QOPT_FULL_COLLECTION_SCAN)
|
|
{
|
|
pContextPath->uiCost += pPred->OptInfo.uiCost;
|
|
}
|
|
else
|
|
{
|
|
pContextPath->uiCost = ~((FLMUINT)0);
|
|
pContextPath->bMustScan = TRUE;
|
|
break;
|
|
}
|
|
pPred = pPred->pNext;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Optimize a context. It's child contexts have already been
|
|
optimized.
|
|
***************************************************************************/
|
|
RCODE F_Query::optimizeContext(
|
|
OP_CONTEXT * pContext,
|
|
CONTEXT_PATH * pSingleNodeIdPath,
|
|
PATH_PRED * pSingleNodeIdPred
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
OP_CONTEXT * pChildContext;
|
|
CONTEXT_PATH * pContextPath;
|
|
|
|
// If this context has no CONTEXT_PATHs and no child contexts,
|
|
// it had to have had only non-optimizable predicates, or
|
|
// no context would have been created. In that case, this context
|
|
// must be forced to scan.
|
|
|
|
if (pContext->bForceOptToScan ||
|
|
(!pContext->pFirstPath && !pContext->pFirstChild))
|
|
{
|
|
pContext->uiCost = ~((FLMUINT)0);
|
|
pContext->bMustScan = TRUE;
|
|
goto Exit;
|
|
}
|
|
|
|
// Optimize each of the context paths, if any.
|
|
// If this is an intersect context, select the context path
|
|
// one a cost that is lower than the context's current cost.
|
|
// The context's current cost may have already been set when
|
|
// its child contexts were optimized.
|
|
|
|
if (pContext->bIntersect)
|
|
{
|
|
pContext->bMustScan = TRUE;
|
|
pContext->uiCost = ~((FLMUINT)0);
|
|
|
|
// Determine what the lowest cost child context is.
|
|
// If we have a single node id path, we already know
|
|
// it is guaranteed to have the lowest cost: 1.
|
|
|
|
if (pSingleNodeIdPath)
|
|
{
|
|
if (RC_BAD( rc = optimizePath( pSingleNodeIdPath,
|
|
pSingleNodeIdPred, TRUE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Cost better have returned as one!
|
|
// Its bMustScan flag had also better be set to FALSE!
|
|
|
|
flmAssert( pSingleNodeIdPath->uiCost == 1);
|
|
flmAssert( !pSingleNodeIdPath->bMustScan);
|
|
pContext->uiCost = pSingleNodeIdPath->uiCost;
|
|
pContext->pSelectedPath = pSingleNodeIdPath;
|
|
pContext->pSelectedChild = NULL;
|
|
pContext->bMustScan = FALSE;
|
|
}
|
|
else
|
|
{
|
|
pChildContext = pContext->pFirstChild;
|
|
while (pChildContext)
|
|
{
|
|
if (!pChildContext->bMustScan &&
|
|
(pChildContext->uiCost < pContext->uiCost ||
|
|
pContext->bMustScan))
|
|
{
|
|
pContext->uiCost = pChildContext->uiCost;
|
|
pContext->pSelectedChild = pChildContext;
|
|
pContext->pSelectedPath = NULL;
|
|
pContext->bMustScan = FALSE;
|
|
}
|
|
pChildContext = pChildContext->pNextSib;
|
|
}
|
|
|
|
// Find the most optimal context path
|
|
|
|
pContextPath = pContext->pFirstPath;
|
|
while (pContextPath && pContext->uiCost >= MIN_OPT_COST)
|
|
{
|
|
if (RC_BAD( rc = optimizePath( pContextPath, NULL, TRUE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!pContextPath->bMustScan &&
|
|
(pContextPath->uiCost < pContext->uiCost ||
|
|
pContext->bMustScan))
|
|
{
|
|
pContext->uiCost = pContextPath->uiCost;
|
|
pContext->pSelectedPath = pContextPath;
|
|
pContext->pSelectedChild = NULL;
|
|
pContext->bMustScan = FALSE;
|
|
|
|
// If the cost is below MIN_OPT_COST, no need to do any more
|
|
// cost analysis.
|
|
|
|
if (pContextPath->uiCost < MIN_OPT_COST)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
pContextPath = pContextPath->pNext;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Have a UNION (OR) context
|
|
|
|
else
|
|
{
|
|
|
|
// In the case of union, there is no point in optimizing any
|
|
// of the context paths paths once we discover that a context
|
|
// must scan the database.
|
|
|
|
pContext->bMustScan = FALSE;
|
|
pContext->uiCost = 0;
|
|
|
|
// Add in the cost of each child context.
|
|
|
|
pChildContext = pContext->pFirstChild;
|
|
while (pChildContext)
|
|
{
|
|
if (pChildContext->bMustScan)
|
|
{
|
|
pContext->bMustScan = TRUE;
|
|
pContext->uiCost = ~((FLMUINT)0);
|
|
|
|
// Once we set the bMustScan flag, there is no point
|
|
// in going any further.
|
|
|
|
break;
|
|
}
|
|
else if (~((FLMUINT)0) - pContext->uiCost > pChildContext->uiCost)
|
|
{
|
|
pContext->uiCost += pChildContext->uiCost;
|
|
}
|
|
else
|
|
{
|
|
pContext->uiCost = ~((FLMUINT)0);
|
|
pContext->bMustScan = TRUE;
|
|
|
|
// Once we set the bMustScan flag, there is no point
|
|
// in going any further.
|
|
|
|
break;
|
|
}
|
|
pChildContext = pChildContext->pNextSib;
|
|
}
|
|
|
|
// In the case of a non-intersecting context, sum the costs of
|
|
// each context path into the cost for the context until we
|
|
// hit ~0, at which time we will mark the context as
|
|
// "must scan" and quit attempting to optimize it.
|
|
|
|
pContextPath = pContext->pFirstPath;
|
|
while (pContextPath && !pContext->bMustScan)
|
|
{
|
|
if (RC_BAD( rc = optimizePath( pContextPath, NULL, FALSE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (pContextPath->bMustScan)
|
|
{
|
|
pContext->uiCost = ~((FLMUINT)0);
|
|
pContext->bMustScan = TRUE;
|
|
|
|
// No need to do any more optimization once we have
|
|
// determined to do a collection scan.
|
|
|
|
break;
|
|
}
|
|
else if (~((FLMUINT)0) - pContext->uiCost > pContextPath->uiCost)
|
|
{
|
|
pContext->uiCost += pContextPath->uiCost;
|
|
}
|
|
else
|
|
{
|
|
pContext->uiCost = ~((FLMUINT)0);
|
|
pContext->bMustScan = TRUE;
|
|
|
|
// No need to do any more optimization once we have
|
|
// determined to do a collection scan.
|
|
|
|
break;
|
|
}
|
|
pContextPath = pContextPath->pNext;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Setup to scan an index - the index was specified by the user.
|
|
***************************************************************************/
|
|
RCODE F_Query::setupIndexScan( void)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
IXD * pIxd;
|
|
FLMBOOL bDoNodeMatch;
|
|
FLMBOOL bCanCompareOnKey;
|
|
|
|
flmAssert( m_uiIndex);
|
|
|
|
// If the index is not on-line or it is not associated
|
|
// with the collection, we have a problem.
|
|
|
|
if (RC_BAD( rc = m_pDb->m_pDict->getIndex( m_uiIndex, NULL,
|
|
&pIxd, FALSE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (pIxd->uiCollectionNum != m_uiCollection)
|
|
{
|
|
rc = RC_SET( NE_XFLM_BAD_IX);
|
|
goto Exit;
|
|
}
|
|
|
|
if ((m_pFSIndexCursor = f_new FSIndexCursor) == NULL)
|
|
{
|
|
rc = RC_SET( NE_XFLM_MEM);
|
|
}
|
|
|
|
// Setup to scan from beginning of key to end of key.
|
|
|
|
if (RC_BAD( rc = m_pFSIndexCursor->setupKeys( m_pDb, pIxd, NULL,
|
|
&bDoNodeMatch, &bCanCompareOnKey,
|
|
NULL, NULL, NULL)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
m_bScanIndex = TRUE;
|
|
m_bScan = FALSE;
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: This routine determines if an ICD has an descendents that are key
|
|
components.
|
|
***************************************************************************/
|
|
FSTATIC FLMBOOL haveChildKeyComponents(
|
|
ICD * pParentIcd)
|
|
{
|
|
ICD * pTmpIcd;
|
|
|
|
if ((pTmpIcd = pParentIcd->pFirstChild) == NULL)
|
|
{
|
|
return( FALSE);
|
|
}
|
|
for (;;)
|
|
{
|
|
if (pTmpIcd->uiKeyComponent)
|
|
{
|
|
return( TRUE);
|
|
}
|
|
if (pTmpIcd->pFirstChild)
|
|
{
|
|
pTmpIcd = pTmpIcd->pFirstChild;
|
|
}
|
|
else
|
|
{
|
|
while (!pTmpIcd->pNextSibling)
|
|
{
|
|
if ((pTmpIcd = pTmpIcd->pParent) == pParentIcd)
|
|
{
|
|
return( FALSE);
|
|
}
|
|
}
|
|
if (!pTmpIcd)
|
|
{
|
|
break;
|
|
}
|
|
pTmpIcd = pTmpIcd->pNextSibling;
|
|
}
|
|
}
|
|
return( FALSE);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Determines if the optimization index already has the order we need
|
|
for sorting.
|
|
***************************************************************************/
|
|
RCODE F_Query::checkSortIndex(
|
|
FLMUINT uiOptIndex)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
IXD * pOptIxd;
|
|
ICD * pSortIcd;
|
|
ICD * pOptIcd;
|
|
|
|
// Should not be called unless we have a sort key specified.
|
|
|
|
flmAssert( m_pSortIxd);
|
|
|
|
// Get the index definition.
|
|
|
|
if (RC_BAD( rc = m_pDb->m_pDict->getIndex( uiOptIndex, NULL, &pOptIxd, TRUE)))
|
|
{
|
|
RC_UNEXPECTED_ASSERT( rc);
|
|
goto Exit;
|
|
}
|
|
|
|
// Verify that the language is the same.
|
|
|
|
if (m_pSortIxd->uiLanguage != pOptIxd->uiLanguage)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Make sure the sort IXD is completely represented in the optimization IXD
|
|
// If we find all of the keys in the same context, the optimization
|
|
// index will work as the sort index. Even if the optimization index
|
|
// has more key components it will work, because it will provide the
|
|
// keys in the same order.
|
|
|
|
pSortIcd = m_pSortIxd->pIcdTree;
|
|
pOptIcd = pOptIxd->pIcdTree;
|
|
|
|
for (;;)
|
|
{
|
|
|
|
// See if there is a matching opt ICD in the list of siblings to
|
|
// opt ICD.
|
|
|
|
pOptIcd = (pOptIcd->pParent)
|
|
? pOptIcd->pParent->pFirstChild
|
|
: pOptIxd->pIcdTree;
|
|
while (pOptIcd)
|
|
{
|
|
if (pOptIcd->uiDictNum == pSortIcd->uiDictNum &&
|
|
(pOptIcd->uiFlags & ICD_IS_ATTRIBUTE) ==
|
|
(pSortIcd->uiFlags & ICD_IS_ATTRIBUTE))
|
|
{
|
|
|
|
// If the match ICD is a key component, we want a
|
|
// search ICD that is the same key component and compare rules.
|
|
|
|
if (pSortIcd->uiKeyComponent)
|
|
{
|
|
if (pOptIcd->uiKeyComponent == pSortIcd->uiKeyComponent &&
|
|
pOptIcd->uiFlags == pSortIcd->uiFlags)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
pOptIcd = pOptIcd->pNextSibling;
|
|
}
|
|
|
|
// Did we find a matching opt ICD?
|
|
|
|
if (!pOptIcd)
|
|
{
|
|
|
|
// If the sort ICD is a key component, or there are key ICDs
|
|
// subordinate to the sort ICD, then the indexes cannot match,
|
|
// because there is no matching context for the child key ICDs.
|
|
|
|
if (pSortIcd->uiKeyComponent || haveChildKeyComponents( pSortIcd))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
Check_Siblings:
|
|
|
|
while (!pSortIcd->pNextSibling)
|
|
{
|
|
if ((pSortIcd = pSortIcd->pParent) == NULL)
|
|
{
|
|
break;
|
|
}
|
|
pOptIcd = pOptIcd->pParent;
|
|
|
|
// If pSortIcd != NULL, pOptIcd better be also!
|
|
|
|
flmAssert( pOptIcd);
|
|
}
|
|
if (!pSortIcd)
|
|
{
|
|
|
|
// Done - index key components match.
|
|
|
|
break;
|
|
}
|
|
|
|
// pNextSibling better be non-NULL at this point.
|
|
// NOTE: Do not set pOptIcd to its sibling - it may not have one.
|
|
// That doesn't matter, because we will search for a matching
|
|
// sibling up above.
|
|
|
|
pSortIcd = pSortIcd->pNextSibling;
|
|
|
|
// No need to check this sort ICD if it is not a key
|
|
// component and it has no child ICDs.
|
|
|
|
if (!pSortIcd->uiKeyComponent && !pSortIcd->pFirstChild)
|
|
{
|
|
goto Check_Siblings;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// Go to the child nodes, if any
|
|
|
|
if (pSortIcd->pFirstChild)
|
|
{
|
|
if (!pOptIcd->pFirstChild)
|
|
{
|
|
|
|
// Sort ICD has a child, opt ICD doesn't. See if there are
|
|
// any index components subordinate to sort ICD. If so, the indexes
|
|
// are different. Otherwise, it really doesn't matter - we can
|
|
// simply proceed with sort ICD's siblings.
|
|
|
|
if (haveChildKeyComponents( pSortIcd))
|
|
{
|
|
goto Exit;
|
|
}
|
|
goto Check_Siblings;
|
|
}
|
|
else
|
|
{
|
|
pSortIcd = pSortIcd->pFirstChild;
|
|
pOptIcd = pOptIcd->pFirstChild;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
goto Check_Siblings;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_bEntriesAlreadyInOrder = TRUE;
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Optimize a query expression.
|
|
***************************************************************************/
|
|
RCODE F_Query::optimize( void)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pQNode = m_pQuery;
|
|
OP_CONTEXT * pContext;
|
|
|
|
if (m_bOptimized)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_ALREADY_OPTIMIZED);
|
|
goto Exit;
|
|
}
|
|
|
|
// We save the F_Database object so that we can always check and make
|
|
// sure we are associated with this database on any query operations
|
|
// that occur after optimization. -- Link it into the list of queries
|
|
// off of the F_Database object. NOTE: We may not always use the
|
|
// same F_Db object, but it must always be the same F_Database object.
|
|
|
|
m_pDatabase = m_pDb->m_pDatabase;
|
|
m_pNext = NULL;
|
|
m_pDatabase->lockMutex();
|
|
if ((m_pPrev = m_pDatabase->m_pLastQuery) != NULL)
|
|
{
|
|
m_pPrev->m_pNext = this;
|
|
}
|
|
else
|
|
{
|
|
m_pDatabase->m_pFirstQuery = this;
|
|
}
|
|
m_pDatabase->m_pLastQuery = this;
|
|
m_pDatabase->unlockMutex();
|
|
|
|
// Verify sort keys, if any
|
|
|
|
if (m_pSortIxd)
|
|
{
|
|
if (RC_BAD( rc = verifySortKeys()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// Make sure we have a completed expression
|
|
|
|
if( m_pCurExprState)
|
|
{
|
|
if ( m_pCurExprState->pPrev ||
|
|
m_pCurExprState->uiNestLevel ||
|
|
(m_pCurExprState->pLastNode &&
|
|
m_pCurExprState->pLastNode->eNodeType == FLM_OPERATOR_NODE))
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_INCOMPLETE_QUERY_EXPR);
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = getPredicates( &m_pCurExprState->pExpr, NULL, NULL)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
m_pQuery = m_pCurExprState->pExpr;
|
|
}
|
|
|
|
m_uiLanguage = m_pDb->getDefaultLanguage();
|
|
|
|
// An empty expression should scan the database and return everything.
|
|
|
|
if ((pQNode = m_pQuery) == NULL)
|
|
{
|
|
if (m_bIndexSet && m_uiIndex)
|
|
{
|
|
rc = setupIndexScan();
|
|
}
|
|
else
|
|
{
|
|
m_bScan = TRUE;
|
|
}
|
|
goto Exit;
|
|
}
|
|
|
|
// Handle the case of a value node or arithmetic expression at the root
|
|
// These types of expressions do not return results from the database.
|
|
|
|
if (pQNode->eNodeType == FLM_VALUE_NODE)
|
|
{
|
|
if (pQNode->currVal.eValType == XFLM_BOOL_VAL &&
|
|
pQNode->currVal.val.eBool == XFLM_TRUE)
|
|
{
|
|
m_bScan = TRUE;
|
|
}
|
|
else
|
|
{
|
|
m_bEmpty = TRUE;
|
|
}
|
|
}
|
|
else if (pQNode->eNodeType == FLM_OPERATOR_NODE &&
|
|
isArithOp( pQNode->nd.op.eOperator))
|
|
{
|
|
m_bEmpty = TRUE;
|
|
goto Exit;
|
|
}
|
|
|
|
// If the user explicitly said to NOT use an index, we will not
|
|
|
|
if (m_bIndexSet && !m_uiIndex)
|
|
{
|
|
m_bScan = TRUE;
|
|
goto Exit;
|
|
}
|
|
|
|
// Start with the context in the root node.
|
|
|
|
if ((pContext = pQNode->pContext) == NULL)
|
|
{
|
|
m_bScan = TRUE;
|
|
goto Exit;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
CONTEXT_PATH * pSingleNodeIdPath = NULL;
|
|
PATH_PRED * pSingleNodeIdPred = NULL;
|
|
|
|
// See if any of the context paths in this context have a
|
|
// single node ID predicate. If so, we can optimize it right
|
|
// away and ignore all of the other predicates.
|
|
|
|
if (pContext->bIntersect)
|
|
{
|
|
pSingleNodeIdPath = pContext->pFirstPath;
|
|
while (pSingleNodeIdPath)
|
|
{
|
|
if (isNodeOrDocIdComponent( pSingleNodeIdPath->pXPathComponent))
|
|
{
|
|
|
|
// See if any of the predicates are single-node id.
|
|
|
|
pSingleNodeIdPred = pSingleNodeIdPath->pFirstPred;
|
|
while (pSingleNodeIdPred)
|
|
{
|
|
if (pSingleNodeIdPred->pFromValue &&
|
|
pSingleNodeIdPred->pUntilValue)
|
|
{
|
|
|
|
// From and until values should have already been
|
|
// converted to appropriate node ids - 64 bit values.
|
|
|
|
flmAssert( pSingleNodeIdPred->pFromValue->eValType ==
|
|
XFLM_UINT64_VAL);
|
|
flmAssert( pSingleNodeIdPred->pUntilValue->eValType ==
|
|
XFLM_UINT64_VAL);
|
|
if (!pSingleNodeIdPred->bInclFrom)
|
|
{
|
|
pSingleNodeIdPred->pFromValue->val.ui64Val++;
|
|
pSingleNodeIdPred->bInclFrom = TRUE;
|
|
}
|
|
if (!pSingleNodeIdPred->bInclUntil)
|
|
{
|
|
pSingleNodeIdPred->pUntilValue->val.ui64Val--;
|
|
pSingleNodeIdPred->bInclUntil = TRUE;
|
|
}
|
|
if (pSingleNodeIdPred->pFromValue->val.ui64Val ==
|
|
pSingleNodeIdPred->pUntilValue->val.ui64Val)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
pSingleNodeIdPred = pSingleNodeIdPred->pNext;
|
|
}
|
|
}
|
|
if (pSingleNodeIdPred)
|
|
{
|
|
break;
|
|
}
|
|
|
|
pSingleNodeIdPath = pSingleNodeIdPath->pNext;
|
|
}
|
|
}
|
|
if (pContext->pFirstChild && !pSingleNodeIdPath)
|
|
{
|
|
pContext = pContext->pFirstChild;
|
|
continue;
|
|
}
|
|
|
|
// Optimize the context paths of the context we are on
|
|
|
|
if (RC_BAD( rc = optimizeContext( pContext,
|
|
pSingleNodeIdPath, pSingleNodeIdPred)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Go to sibling context, if any
|
|
|
|
while (!pContext->pNextSib)
|
|
{
|
|
if ((pContext = pContext->pParent) == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Child contexts have all been optimized and the cost
|
|
// reflected up to this context. But there may be
|
|
// CONTEXT_PATHs on this context that will change that
|
|
// cost.
|
|
|
|
if (RC_BAD( rc = optimizeContext( pContext, NULL, NULL)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// If pContext is NULL at this point, there are no
|
|
// other contexts to optimize.
|
|
|
|
if (!pContext)
|
|
{
|
|
break;
|
|
}
|
|
pContext = pContext->pNextSib;
|
|
|
|
// There has to have been a sibling context at this point.
|
|
|
|
flmAssert( pContext);
|
|
}
|
|
|
|
if (m_pQuery->pContext->bMustScan)
|
|
{
|
|
|
|
// If we were told to use an index, we will use that index
|
|
// and scan the entire index.
|
|
|
|
if (m_bIndexSet)
|
|
{
|
|
if (RC_BAD( rc = setupIndexScan()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_bScan = TRUE;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (RC_OK( rc) && !m_bEmpty)
|
|
{
|
|
FLMUINT uiOptIndex = 0;
|
|
|
|
if (m_bScan || m_bScanIndex)
|
|
{
|
|
if (m_bScan)
|
|
{
|
|
m_scanOptInfo.eOptType = XFLM_QOPT_FULL_COLLECTION_SCAN;
|
|
}
|
|
else
|
|
{
|
|
F_NameTable * pNameTable = NULL;
|
|
|
|
m_scanOptInfo.eOptType = XFLM_QOPT_USING_INDEX;
|
|
m_scanOptInfo.uiCost = ~((FLMUINT)0);
|
|
m_scanOptInfo.uiIxNum = m_uiIndex;
|
|
uiOptIndex = m_uiIndex;
|
|
m_scanOptInfo.bMustVerifyPath = TRUE;
|
|
m_scanOptInfo.bDoNodeMatch = TRUE;
|
|
m_scanOptInfo.bCanCompareOnKey = FALSE;
|
|
m_scanOptInfo.szIxName [0] = 0;
|
|
if (RC_OK( m_pDb->getNameTable( &pNameTable)))
|
|
{
|
|
FLMUINT uiIxNameLen = sizeof( m_scanOptInfo.szIxName);
|
|
|
|
if (RC_BAD( pNameTable->getFromTagTypeAndNum(
|
|
m_pDb, ELM_INDEX_TAG,
|
|
m_uiIndex, NULL,
|
|
(char *)m_scanOptInfo.szIxName,
|
|
&uiIxNameLen, NULL, NULL,
|
|
NULL, NULL, TRUE)))
|
|
{
|
|
m_scanOptInfo.szIxName [0] = 0;
|
|
}
|
|
}
|
|
if (pNameTable)
|
|
{
|
|
pNameTable->Release();
|
|
}
|
|
}
|
|
m_scanOptInfo.ui64KeysRead = 0;
|
|
m_scanOptInfo.ui64KeyHadDupDoc = 0;
|
|
m_scanOptInfo.ui64KeysPassed = 0;
|
|
m_scanOptInfo.ui64NodesRead = 0;
|
|
m_scanOptInfo.ui64NodesTested = 0;
|
|
m_scanOptInfo.ui64NodesPassed = 0;
|
|
m_scanOptInfo.ui64DocsRead = 0;
|
|
m_scanOptInfo.ui64DupDocsEliminated = 0;
|
|
m_scanOptInfo.ui64NodesFailedValidation = 0;
|
|
m_scanOptInfo.ui64DocsFailedValidation = 0;
|
|
m_scanOptInfo.ui64DocsPassed = 0;
|
|
m_pCurrOpt = &m_scanOptInfo;
|
|
rc = newSource();
|
|
}
|
|
else
|
|
{
|
|
FLMBOOL bHaveMultipleIndexes = FALSE;
|
|
|
|
// Need to initialize all of the optimization information.
|
|
|
|
m_pCurrContext = m_pQuery->pContext;
|
|
useLeafContext( TRUE);
|
|
do
|
|
{
|
|
m_pCurrOpt->ui64KeysRead = 0;
|
|
m_pCurrOpt->ui64KeyHadDupDoc = 0;
|
|
m_pCurrOpt->ui64KeysPassed = 0;
|
|
m_pCurrOpt->ui64NodesRead = 0;
|
|
m_pCurrOpt->ui64NodesTested = 0;
|
|
m_pCurrOpt->ui64NodesPassed = 0;
|
|
m_pCurrOpt->ui64DocsRead = 0;
|
|
m_pCurrOpt->ui64DupDocsEliminated = 0;
|
|
m_pCurrOpt->ui64NodesFailedValidation = 0;
|
|
m_pCurrOpt->ui64DocsFailedValidation = 0;
|
|
m_pCurrOpt->ui64DocsPassed = 0;
|
|
|
|
// See if we are using an index. Later, if it turns out
|
|
// we are only using one index, we will see if it matches
|
|
// the sort index.
|
|
|
|
if (m_pSortIxd && !bHaveMultipleIndexes)
|
|
{
|
|
if (m_pCurrPred->pNodeSource)
|
|
{
|
|
FLMUINT uiIndex;
|
|
|
|
if (RC_BAD( rc = m_pCurrPred->pNodeSource->getIndex(
|
|
(IF_Db *)m_pDb, &uiIndex,
|
|
&bHaveMultipleIndexes)))
|
|
{
|
|
break;
|
|
}
|
|
if (uiIndex)
|
|
{
|
|
if (uiOptIndex == 0)
|
|
{
|
|
uiOptIndex = uiIndex;
|
|
}
|
|
else if (uiIndex != uiOptIndex)
|
|
{
|
|
bHaveMultipleIndexes = TRUE;
|
|
}
|
|
}
|
|
}
|
|
else if (m_pCurrOpt->uiIxNum)
|
|
{
|
|
if (uiOptIndex == 0)
|
|
{
|
|
uiOptIndex = m_pCurrOpt->uiIxNum;
|
|
}
|
|
else if (m_pCurrOpt->uiIxNum != uiOptIndex)
|
|
{
|
|
bHaveMultipleIndexes = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
while (useNextPredicate());
|
|
if (bHaveMultipleIndexes || RC_BAD( rc))
|
|
{
|
|
uiOptIndex = 0;
|
|
}
|
|
}
|
|
|
|
// If we are sorting the results, see if the optimization
|
|
// index already has the keys in the order we need.
|
|
|
|
if (RC_OK( rc) && m_pSortIxd && uiOptIndex)
|
|
{
|
|
rc = checkSortIndex( uiOptIndex);
|
|
}
|
|
}
|
|
|
|
if (m_pSortIxd || m_bPositioningEnabled)
|
|
{
|
|
// Need to eliminate dups in case there are multiple sort key
|
|
// values in a document.
|
|
// NOTE: When a sort key is specified, if a document has multiple
|
|
// keys, we want them to appear sorted according to whatever is the
|
|
// "lowest" sort key in the document. If we create a result set,
|
|
// this will always work. However, if the entries are already "in order"
|
|
// we have a small problem, because the order in which the document
|
|
// is retrieved will then depend on the direction of traversal used
|
|
// by the application. By eliminating duplicate documents, the application
|
|
// can get the expected behavior of having the document sort according
|
|
// to its "lowest" key if it reads forward. However, if the application
|
|
// reads "backward" the document will be retrieved as if it were sorted
|
|
// by its "higest" key.
|
|
|
|
setDupHandling( TRUE);
|
|
|
|
// If we have sort keys and the optimization index is not in
|
|
// the same order as the sort index, we must create a result set.
|
|
// The same is true if the application asked to enable positioning.
|
|
|
|
if (m_bPositioningEnabled || !m_bEntriesAlreadyInOrder)
|
|
{
|
|
if (RC_OK( rc))
|
|
{
|
|
rc = createResultSet();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( RC_OK( rc))
|
|
{
|
|
m_bOptimized = TRUE;
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Set node's current value to missing. Also releases the stream
|
|
associated with the value, if any.
|
|
***************************************************************************/
|
|
FINLINE void fqReleaseNodeValue(
|
|
FQNODE * pQNode
|
|
)
|
|
{
|
|
if ((pQNode->currVal.eValType == XFLM_BINARY_VAL ||
|
|
pQNode->currVal.eValType == XFLM_UTF8_VAL) &&
|
|
(pQNode->currVal.uiFlags & VAL_IS_STREAM) &&
|
|
pQNode->currVal.val.pIStream)
|
|
{
|
|
pQNode->currVal.uiFlags &= (~(VAL_IS_STREAM));
|
|
pQNode->currVal.val.pIStream->Release();
|
|
pQNode->currVal.val.pIStream = NULL;
|
|
}
|
|
if (pQNode->eNodeType != FLM_VALUE_NODE)
|
|
{
|
|
pQNode->currVal.eValType = XFLM_MISSING_VAL;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Evaluate a simple operator.
|
|
***************************************************************************/
|
|
FSTATIC RCODE fqEvalOperator(
|
|
FLMUINT uiLanguage,
|
|
FQNODE * pQNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pLeftOperand;
|
|
FQNODE * pRightOperand;
|
|
XFlmBoolType eLeftBool;
|
|
XFlmBoolType eRightBool;
|
|
|
|
// Right now we are only able to do operator nodes.
|
|
|
|
flmAssert( pQNode->eNodeType == FLM_OPERATOR_NODE);
|
|
|
|
pLeftOperand = pQNode->pFirstChild;
|
|
pRightOperand = pQNode->pLastChild;
|
|
|
|
// See if either of this operator's operands have already
|
|
// passed. If so, we can skip the evaluation.
|
|
|
|
if (pLeftOperand->currVal.eValType == XFLM_PASSING_VAL ||
|
|
pRightOperand->currVal.eValType == XFLM_PASSING_VAL)
|
|
{
|
|
pQNode->currVal.eValType = XFLM_BOOL_VAL;
|
|
pQNode->currVal.val.eBool = XFLM_TRUE;
|
|
goto Exit;
|
|
}
|
|
|
|
if (pQNode->nd.op.eOperator != XFLM_AND_OP &&
|
|
pQNode->nd.op.eOperator != XFLM_OR_OP)
|
|
{
|
|
// If the left operand is an XPATH node that is the node id or
|
|
// document id, be sure to convert the right operand value to
|
|
// a node id value.
|
|
|
|
if (pLeftOperand->eNodeType == FLM_XPATH_NODE &&
|
|
pLeftOperand->nd.pXPath->pLastComponent->eXPathAxis == META_AXIS)
|
|
{
|
|
if (RC_BAD( rc = fqGetNodeIdValue( &pRightOperand->currVal)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// If the right operand is an XPATH node that is the node id or
|
|
// document id, be sure to convert the left operand value to
|
|
// a node id value.
|
|
|
|
if (pRightOperand->eNodeType == FLM_XPATH_NODE &&
|
|
pRightOperand->nd.pXPath->pLastComponent->eXPathAxis == META_AXIS)
|
|
{
|
|
if (RC_BAD( rc = fqGetNodeIdValue( &pLeftOperand->currVal)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
pQNode->currVal.eValType = XFLM_MISSING_VAL;
|
|
|
|
switch (pQNode->nd.op.eOperator)
|
|
{
|
|
case XFLM_AND_OP:
|
|
case XFLM_OR_OP:
|
|
eLeftBool = XFLM_UNKNOWN;
|
|
eRightBool = XFLM_UNKNOWN;
|
|
|
|
// Get the left operand
|
|
|
|
if (pLeftOperand->eNodeType == FLM_OPERATOR_NODE)
|
|
{
|
|
|
|
// This operator may not have been evaluated because of missing
|
|
// XPATH values in one or both operands, in which case
|
|
// its state will be XFLM_MISSING_VALUE. If it was evaluated,
|
|
// its state should show a boolean value.
|
|
|
|
if (pLeftOperand->currVal.eValType == XFLM_MISSING_VAL)
|
|
{
|
|
eLeftBool = (pLeftOperand->bNotted ? XFLM_TRUE : XFLM_FALSE);
|
|
}
|
|
else
|
|
{
|
|
flmAssert( pLeftOperand->currVal.eValType == XFLM_BOOL_VAL);
|
|
eLeftBool = pLeftOperand->currVal.val.eBool;
|
|
}
|
|
}
|
|
else if (pLeftOperand->eNodeType == FLM_XPATH_NODE)
|
|
{
|
|
if (!pLeftOperand->bNotted)
|
|
{
|
|
eLeftBool = (pLeftOperand->currVal.eValType != XFLM_MISSING_VAL)
|
|
? XFLM_TRUE
|
|
: XFLM_FALSE;
|
|
}
|
|
else
|
|
{
|
|
eLeftBool = (pLeftOperand->currVal.eValType != XFLM_MISSING_VAL)
|
|
? XFLM_FALSE
|
|
: XFLM_TRUE;
|
|
}
|
|
}
|
|
else if (pLeftOperand->eNodeType == FLM_VALUE_NODE)
|
|
{
|
|
flmAssert( pLeftOperand->currVal.eValType == XFLM_BOOL_VAL);
|
|
eLeftBool = pLeftOperand->currVal.val.eBool;
|
|
}
|
|
else
|
|
{
|
|
flmAssert( pLeftOperand->eNodeType == FLM_FUNCTION_NODE);
|
|
if (!pLeftOperand->bNotted)
|
|
{
|
|
eLeftBool = fqTestValue( pLeftOperand) ? XFLM_TRUE : XFLM_FALSE;
|
|
}
|
|
else
|
|
{
|
|
eLeftBool = fqTestValue( pLeftOperand) ? XFLM_FALSE : XFLM_TRUE;
|
|
}
|
|
}
|
|
|
|
// Get the right operand
|
|
|
|
if ( pRightOperand->eNodeType == FLM_OPERATOR_NODE)
|
|
{
|
|
|
|
// This operator may not have been evaluated because of missing
|
|
// XPATH values in one or both operands, in which case
|
|
// its state will be XFLM_MISSING_VALUE. If it was evaluated,
|
|
// its state should show a boolean value.
|
|
|
|
if (pRightOperand->currVal.eValType == XFLM_MISSING_VAL)
|
|
{
|
|
eRightBool = (pRightOperand->bNotted ? XFLM_TRUE : XFLM_FALSE);
|
|
}
|
|
else
|
|
{
|
|
flmAssert( pRightOperand->currVal.eValType == XFLM_BOOL_VAL);
|
|
eRightBool = pRightOperand->currVal.val.eBool;
|
|
}
|
|
}
|
|
else if (pRightOperand->eNodeType == FLM_XPATH_NODE)
|
|
{
|
|
if (!pRightOperand->bNotted)
|
|
{
|
|
eRightBool = (pRightOperand->currVal.eValType != XFLM_MISSING_VAL)
|
|
? XFLM_TRUE
|
|
: XFLM_FALSE;
|
|
}
|
|
else
|
|
{
|
|
eRightBool = (pRightOperand->currVal.eValType != XFLM_MISSING_VAL)
|
|
? XFLM_FALSE
|
|
: XFLM_TRUE;
|
|
}
|
|
}
|
|
else if (pRightOperand->eNodeType == FLM_VALUE_NODE)
|
|
{
|
|
flmAssert( pRightOperand->currVal.eValType == XFLM_BOOL_VAL);
|
|
eRightBool = pRightOperand->currVal.val.eBool;
|
|
}
|
|
else
|
|
{
|
|
flmAssert( pRightOperand->eNodeType == FLM_FUNCTION_NODE);
|
|
if (!pRightOperand->bNotted)
|
|
{
|
|
eRightBool = fqTestValue( pRightOperand) ? XFLM_TRUE : XFLM_FALSE;
|
|
}
|
|
else
|
|
{
|
|
eRightBool = fqTestValue( pRightOperand) ? XFLM_FALSE : XFLM_TRUE;
|
|
}
|
|
}
|
|
|
|
// Calculate the answer
|
|
|
|
pQNode->currVal.eValType = XFLM_BOOL_VAL;
|
|
if (pQNode->nd.op.eOperator == XFLM_AND_OP)
|
|
{
|
|
if (eLeftBool == XFLM_FALSE || eRightBool == XFLM_FALSE)
|
|
{
|
|
pQNode->currVal.val.eBool = XFLM_FALSE;
|
|
}
|
|
else if (eLeftBool == XFLM_UNKNOWN || eRightBool == XFLM_UNKNOWN)
|
|
{
|
|
pQNode->currVal.val.eBool = XFLM_UNKNOWN;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Both have to be XFLM_TRUE at this point
|
|
|
|
pQNode->currVal.val.eBool = XFLM_TRUE;
|
|
}
|
|
}
|
|
else // pQNode->nd.op.eOperator == XFLM_OR_OP
|
|
{
|
|
if (eLeftBool == XFLM_TRUE || eRightBool == XFLM_TRUE)
|
|
{
|
|
pQNode->currVal.val.eBool = XFLM_TRUE;
|
|
}
|
|
else if (eLeftBool == XFLM_UNKNOWN || eRightBool == XFLM_UNKNOWN)
|
|
{
|
|
pQNode->currVal.val.eBool = XFLM_UNKNOWN;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Both have to be XFLM_FALSE at this point
|
|
|
|
pQNode->currVal.val.eBool = XFLM_FALSE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case XFLM_EQ_OP:
|
|
case XFLM_APPROX_EQ_OP:
|
|
case XFLM_NE_OP:
|
|
case XFLM_LT_OP:
|
|
case XFLM_LE_OP:
|
|
case XFLM_GT_OP:
|
|
case XFLM_GE_OP:
|
|
pQNode->currVal.eValType = XFLM_BOOL_VAL;
|
|
if (RC_BAD( rc = fqCompareOperands( uiLanguage,
|
|
&pLeftOperand->currVal,
|
|
&pRightOperand->currVal,
|
|
pQNode->nd.op.eOperator,
|
|
pQNode->nd.op.uiCompareRules,
|
|
pQNode->nd.op.pOpComparer,
|
|
pQNode->bNotted,
|
|
&pQNode->currVal.val.eBool)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
|
|
case XFLM_BITAND_OP:
|
|
case XFLM_BITOR_OP:
|
|
case XFLM_BITXOR_OP:
|
|
case XFLM_MULT_OP:
|
|
case XFLM_DIV_OP:
|
|
case XFLM_MOD_OP:
|
|
case XFLM_PLUS_OP:
|
|
case XFLM_MINUS_OP:
|
|
case XFLM_NEG_OP:
|
|
if (RC_BAD( rc = fqArithmeticOperator( &pLeftOperand->currVal,
|
|
&pRightOperand->currVal,
|
|
pQNode->nd.op.eOperator,
|
|
&pQNode->currVal)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
Exit:
|
|
|
|
// Need to release node values in case we are holding on
|
|
// to an IStream.
|
|
|
|
if (pLeftOperand)
|
|
{
|
|
fqReleaseNodeValue( pLeftOperand);
|
|
}
|
|
if (pRightOperand)
|
|
{
|
|
fqReleaseNodeValue( pRightOperand);
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next value from an XPATH node.
|
|
***************************************************************************/
|
|
FSTATIC void fqResetIterator(
|
|
FQNODE * pQNode,
|
|
FLMBOOL bFullRelease,
|
|
FLMBOOL bUseKeyNodes
|
|
)
|
|
{
|
|
FXPATH * pXPath;
|
|
XPATH_COMPONENT * pXPathComponent;
|
|
|
|
// Node better be an XPATH node.
|
|
|
|
flmAssert( pQNode->eNodeType == FLM_XPATH_NODE);
|
|
pXPath = pQNode->nd.pXPath;
|
|
if (bFullRelease)
|
|
{
|
|
pXPath->bIsSource = FALSE;
|
|
pXPath->pSourceComponent = NULL;
|
|
pXPath->bHavePassingNode = FALSE;
|
|
}
|
|
pXPathComponent = pXPath->pLastComponent;
|
|
while (pXPathComponent)
|
|
{
|
|
if (bFullRelease)
|
|
{
|
|
pXPathComponent->bIsSource = FALSE;
|
|
pXPathComponent->pExprXPathSource = NULL;
|
|
pXPathComponent->pOptPred = NULL;
|
|
if (pXPathComponent->pKeyNode)
|
|
{
|
|
pXPathComponent->pKeyNode->Release();
|
|
pXPathComponent->pKeyNode = NULL;
|
|
}
|
|
}
|
|
else if (pXPathComponent->bIsSource && bUseKeyNodes)
|
|
{
|
|
break;
|
|
}
|
|
if (pXPathComponent->pCurrNode)
|
|
{
|
|
pXPathComponent->pCurrNode->Release();
|
|
pXPathComponent->pCurrNode = NULL;
|
|
}
|
|
|
|
if (bFullRelease && pXPathComponent->pExpr)
|
|
{
|
|
fqReleaseQueryExpr( pXPathComponent->pExpr);
|
|
}
|
|
|
|
pXPathComponent = pXPathComponent->pPrev;
|
|
}
|
|
pXPath->bGettingNodes = FALSE;
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the first, last, next or previous node from a node source object.
|
|
***************************************************************************/
|
|
RCODE F_Query::getNodeSourceNode(
|
|
FLMBOOL bForward,
|
|
IF_QueryNodeSource * pNodeSource,
|
|
IF_DOMNode * pContextNode,
|
|
IF_DOMNode ** ppCurrNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMUINT uiTimeLimit = m_uiTimeLimit;
|
|
FLMUINT uiCurrTime;
|
|
FLMUINT uiElapsedTime;
|
|
|
|
// Determine the timeout limit that is left.
|
|
|
|
if (uiTimeLimit)
|
|
{
|
|
uiCurrTime = FLM_GET_TIMER();
|
|
uiElapsedTime = FLM_ELAPSED_TIME( uiCurrTime, m_uiStartTime);
|
|
if (uiElapsedTime >= m_uiTimeLimit)
|
|
{
|
|
rc = RC_SET( NE_XFLM_TIMEOUT);
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
uiTimeLimit = FLM_TIMER_UNITS_TO_MILLI( (m_uiTimeLimit - uiElapsedTime));
|
|
|
|
// Always give at least one milli-second.
|
|
|
|
if (!uiTimeLimit)
|
|
{
|
|
uiTimeLimit = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (*ppCurrNode)
|
|
{
|
|
|
|
// Get next or previous node.
|
|
|
|
rc = (RCODE)(bForward
|
|
? pNodeSource->getNext( (IF_Db *)m_pDb, pContextNode, ppCurrNode,
|
|
uiTimeLimit, m_pQueryStatus)
|
|
: pNodeSource->getPrev( (IF_Db *)m_pDb, pContextNode, ppCurrNode,
|
|
uiTimeLimit, m_pQueryStatus));
|
|
}
|
|
else
|
|
{
|
|
|
|
// Get first or last node.
|
|
|
|
rc = (RCODE)(bForward
|
|
? pNodeSource->getFirst( (IF_Db *)m_pDb, pContextNode, ppCurrNode,
|
|
uiTimeLimit, m_pQueryStatus)
|
|
: pNodeSource->getLast( (IF_Db *)m_pDb, pContextNode, ppCurrNode,
|
|
uiTimeLimit, m_pQueryStatus));
|
|
}
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_EOF_HIT ||
|
|
rc == NE_XFLM_BOF_HIT)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
if (*ppCurrNode)
|
|
{
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
}
|
|
}
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next node from a ROOT_AXIS.
|
|
***************************************************************************/
|
|
RCODE F_Query::getRootAxisNode(
|
|
IF_DOMNode ** ppCurrNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
// Better be an element we are searching for
|
|
|
|
if (m_pCurrDoc->getNodeType() == DOCUMENT_NODE)
|
|
{
|
|
if (RC_BAD( rc = m_pCurrDoc->getFirstChild( m_pDb, ppCurrNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Search for an element node - it will be the root
|
|
// of the document
|
|
|
|
while ((*ppCurrNode)->getNodeType() != ELEMENT_NODE)
|
|
{
|
|
if (RC_BAD( rc = (*ppCurrNode)->getNextSibling( m_pDb, ppCurrNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*ppCurrNode = m_pCurrDoc;
|
|
(*ppCurrNode)->AddRef();
|
|
if ((*ppCurrNode)->getNodeType() != ELEMENT_NODE)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_DATA_ERROR);
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next/previous node in a document
|
|
***************************************************************************/
|
|
RCODE F_Query::walkDocument(
|
|
FLMBOOL bForward,
|
|
FLMBOOL bWalkAttributes,
|
|
FLMUINT uiAttrNameId,
|
|
IF_DOMNode ** ppCurrNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
if (!(*ppCurrNode))
|
|
{
|
|
*ppCurrNode = m_pCurrDoc;
|
|
(*ppCurrNode)->AddRef();
|
|
goto Exit;
|
|
}
|
|
|
|
if ((*ppCurrNode)->getNodeType() == ATTRIBUTE_NODE)
|
|
{
|
|
if (uiAttrNameId)
|
|
{
|
|
|
|
// This attribute node should be the node that we already processed.
|
|
// We simply want to go back to its parent node. No need to
|
|
// walk through all of the sibling attributes.
|
|
|
|
rc = RC_SET( NE_XFLM_DOM_NODE_NOT_FOUND);
|
|
}
|
|
else
|
|
{
|
|
rc = bForward
|
|
? (*ppCurrNode)->getNextSibling( m_pDb,
|
|
ppCurrNode)
|
|
: (*ppCurrNode)->getPreviousSibling( m_pDb,
|
|
ppCurrNode);
|
|
}
|
|
if (RC_OK( rc))
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Need to get the node's encompassing element node and
|
|
// process it now.
|
|
|
|
else if (RC_BAD( rc = (*ppCurrNode)->getParentNode( m_pDb, ppCurrNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_DATA_ERROR);
|
|
}
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// See if the node has a child
|
|
|
|
else if (RC_OK( rc = bForward
|
|
? (*ppCurrNode)->getFirstChild( m_pDb, ppCurrNode)
|
|
: (*ppCurrNode)->getLastChild( m_pDb, ppCurrNode)))
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
Walk_To_Attr_Nodes:
|
|
|
|
if (bWalkAttributes && (*ppCurrNode)->getNodeType() == ELEMENT_NODE)
|
|
{
|
|
if( uiAttrNameId)
|
|
{
|
|
rc = (*ppCurrNode)->getAttribute( m_pDb, uiAttrNameId, ppCurrNode);
|
|
}
|
|
else
|
|
{
|
|
rc = bForward
|
|
? (*ppCurrNode)->getFirstAttribute( m_pDb, ppCurrNode)
|
|
: (*ppCurrNode)->getLastAttribute( m_pDb, ppCurrNode);
|
|
}
|
|
if (RC_OK( rc))
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
}
|
|
}
|
|
else if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Go up tree until we find a sibling.
|
|
|
|
for (;;)
|
|
{
|
|
if (RC_OK( rc = bForward
|
|
? (*ppCurrNode)->getNextSibling( m_pDb,
|
|
ppCurrNode)
|
|
: (*ppCurrNode)->getPreviousSibling( m_pDb,
|
|
ppCurrNode)))
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
goto Walk_To_Attr_Nodes;
|
|
}
|
|
else if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
else if (RC_BAD( rc = (*ppCurrNode)->getParentNode( m_pDb,
|
|
ppCurrNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
break;
|
|
}
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next or previous child node.
|
|
***************************************************************************/
|
|
RCODE F_Query::getChildAxisNode(
|
|
FLMBOOL bForward,
|
|
IF_DOMNode * pContextNode,
|
|
FLMUINT uiChildNameId,
|
|
IF_DOMNode ** ppCurrNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
if (!pContextNode)
|
|
{
|
|
if (RC_BAD( rc = walkDocument( bForward, FALSE, 0, ppCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (*ppCurrNode)
|
|
{
|
|
// If name id is non-zero, the caller should have prevented us
|
|
// from coming back in here.
|
|
|
|
flmAssert( !uiChildNameId);
|
|
|
|
// Get next or previous sibling - should still be a child
|
|
// of our context node, whatever it was.
|
|
|
|
rc = (RCODE)(bForward
|
|
? (*ppCurrNode)->getNextSibling( m_pDb, ppCurrNode)
|
|
: (*ppCurrNode)->getPreviousSibling( m_pDb, ppCurrNode));
|
|
}
|
|
else if (uiChildNameId)
|
|
{
|
|
*ppCurrNode = pContextNode;
|
|
(*ppCurrNode)->AddRef();
|
|
rc = (*ppCurrNode)->getChildElement( m_pDb, uiChildNameId, ppCurrNode);
|
|
}
|
|
else
|
|
{
|
|
*ppCurrNode = pContextNode;
|
|
(*ppCurrNode)->AddRef();
|
|
rc = (RCODE)(bForward
|
|
? (*ppCurrNode)->getFirstChild( m_pDb, ppCurrNode)
|
|
: (*ppCurrNode)->getLastChild( m_pDb, ppCurrNode));
|
|
}
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
}
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next node from a PARENT_AXIS.
|
|
***************************************************************************/
|
|
RCODE F_Query::getParentAxisNode(
|
|
FLMBOOL bForward,
|
|
IF_DOMNode * pContextNode,
|
|
IF_DOMNode ** ppCurrNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMUINT64 ui64NodeId;
|
|
|
|
if (!pContextNode)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (RC_BAD( rc = walkDocument( bForward, FALSE, 0, ppCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!(*ppCurrNode))
|
|
{
|
|
break;
|
|
}
|
|
|
|
// This node must be a parent of another node,
|
|
// which means it must have at least one child node.
|
|
|
|
if (RC_BAD( rc = (*ppCurrNode)->getFirstChildId( m_pDb,
|
|
&ui64NodeId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (ui64NodeId)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// PARENT_AXIS always starts from the context node - it
|
|
// really doesn't matter what the last node was.
|
|
|
|
if (RC_BAD( rc = pContextNode->getParentNode( m_pDb, ppCurrNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
if (*ppCurrNode)
|
|
{
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
}
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next ancestor node.
|
|
***************************************************************************/
|
|
RCODE F_Query::getAncestorAxisNode(
|
|
FLMBOOL bForward,
|
|
FLMBOOL bIncludeSelf,
|
|
IF_DOMNode * pContextNode,
|
|
IF_DOMNode ** ppCurrNode)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMUINT64 ui64ContextNodeId;
|
|
FLMUINT64 ui64NodeId;
|
|
FLMUINT64 ui64ParentId;
|
|
FLMUINT64 ui64Tmp;
|
|
FLMUINT uiContextAttrNameId;
|
|
FLMUINT uiNameId;
|
|
FLMUINT uiTmp;
|
|
|
|
if (!pContextNode)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (RC_BAD( rc = walkDocument( bForward, FALSE, 0, ppCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (!(*ppCurrNode))
|
|
{
|
|
break;
|
|
}
|
|
|
|
// If we are including self, whatever node we get to matches
|
|
// the axis.
|
|
|
|
if (bIncludeSelf)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// This node must be an ancestor of another node,
|
|
// which means it must have at least one child node.
|
|
|
|
if (RC_BAD( rc = (*ppCurrNode)->getFirstChildId( m_pDb,
|
|
&ui64NodeId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (ui64NodeId)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( RC_BAD( rc = ((F_DOMNode *)pContextNode)->getNodeId( m_pDb,
|
|
&ui64ContextNodeId, &uiContextAttrNameId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (*ppCurrNode)
|
|
{
|
|
if (bForward)
|
|
{
|
|
if (RC_BAD( rc = (*ppCurrNode)->getParentNode( m_pDb, ppCurrNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
}
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( RC_BAD( rc = ((F_DOMNode *)(*ppCurrNode))->getNodeId( m_pDb,
|
|
&ui64NodeId, &uiNameId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If the current node is the context node, we
|
|
// have no further to go.
|
|
|
|
if( ui64NodeId == ui64ContextNodeId &&
|
|
uiContextAttrNameId == uiNameId)
|
|
{
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
goto Exit;
|
|
}
|
|
|
|
// Start from the context node, and go to just before
|
|
// the node we are on. If we don't hit it, this node
|
|
// is not an ancestor of our context node.
|
|
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = pContextNode;
|
|
(*ppCurrNode)->AddRef();
|
|
|
|
for (;;)
|
|
{
|
|
if (RC_BAD( rc = (*ppCurrNode)->getParentId(
|
|
m_pDb, &ui64ParentId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (ui64ParentId == ui64NodeId)
|
|
{
|
|
if (*ppCurrNode == pContextNode && !bIncludeSelf)
|
|
{
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (RC_BAD( rc = (*ppCurrNode)->getParentNode(
|
|
m_pDb, ppCurrNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
}
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bForward)
|
|
{
|
|
*ppCurrNode = pContextNode;
|
|
(*ppCurrNode)->AddRef();
|
|
|
|
// If including context node, we have what we want.
|
|
// Otherwise, we have to go to the parent.
|
|
|
|
if (bIncludeSelf)
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = (*ppCurrNode)->getParentNode( m_pDb, ppCurrNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
}
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Start at document root. Return it if it is not the
|
|
// context node, or if we are including self.
|
|
|
|
if( RC_BAD( rc = ((F_DOMNode *)m_pCurrDoc)->getNodeId( m_pDb,
|
|
&ui64Tmp, &uiTmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( ui64Tmp != ui64ContextNodeId || uiTmp != uiContextAttrNameId ||
|
|
bIncludeSelf)
|
|
{
|
|
*ppCurrNode = m_pCurrDoc;
|
|
(*ppCurrNode)->AddRef();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next node from a DESCENDANT_AXIS or a DESCENDANT_OR_SELF_AXIS.
|
|
***************************************************************************/
|
|
RCODE F_Query::getDescendantAxisNode(
|
|
FLMBOOL bForward,
|
|
FLMBOOL bIncludeSelf,
|
|
IF_DOMNode * pContextNode,
|
|
IF_DOMNode ** ppCurrNode)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMUINT64 ui64NodeId;
|
|
FLMUINT64 ui64ContextNodeId;
|
|
FLMUINT64 ui64Tmp;
|
|
FLMUINT uiContextAttrNameId;
|
|
FLMUINT uiTmp;
|
|
|
|
if (!pContextNode)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (RC_BAD( rc = walkDocument( bForward, FALSE, 0, ppCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!(*ppCurrNode))
|
|
{
|
|
break;
|
|
}
|
|
|
|
// If we are including self, whatever node we get to matches
|
|
// the axis.
|
|
|
|
if (bIncludeSelf)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// This node must be a descendant of some
|
|
// other node, so it must have a parent.
|
|
|
|
if (RC_BAD( rc = (*ppCurrNode)->getParentId( m_pDb, &ui64NodeId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (ui64NodeId)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( RC_BAD( rc = ((F_DOMNode *)pContextNode)->getNodeId( m_pDb,
|
|
&ui64ContextNodeId, &uiContextAttrNameId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (bForward)
|
|
{
|
|
if (!(*ppCurrNode))
|
|
{
|
|
*ppCurrNode = pContextNode;
|
|
(*ppCurrNode)->AddRef();
|
|
if (bIncludeSelf)
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// See if the node has a child
|
|
|
|
if (RC_OK( rc = (*ppCurrNode)->getFirstChild( m_pDb, ppCurrNode)))
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
|
|
// Go up tree until we find a sibling. - Don't
|
|
// go up past the context node.
|
|
|
|
for (;;)
|
|
{
|
|
|
|
// If we are on the context node, we are done.
|
|
|
|
if( RC_BAD( rc = ((F_DOMNode *)(*ppCurrNode))->getNodeId( m_pDb,
|
|
&ui64Tmp, &uiTmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( ui64Tmp == ui64ContextNodeId && uiTmp == uiContextAttrNameId)
|
|
{
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_OK( rc = (*ppCurrNode)->getNextSibling( m_pDb,
|
|
ppCurrNode)))
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
}
|
|
else if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
else if (RC_BAD( rc = (*ppCurrNode)->getParentNode( m_pDb,
|
|
ppCurrNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
}
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (*ppCurrNode)
|
|
{
|
|
|
|
// If we are going backwards and we are on the context node
|
|
// there are no more nodes to get.
|
|
|
|
if( RC_BAD( rc = ((F_DOMNode *)(*ppCurrNode))->getNodeId( m_pDb,
|
|
&ui64Tmp, &uiTmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( ui64Tmp == ui64ContextNodeId && uiTmp == uiContextAttrNameId)
|
|
{
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
goto Exit;
|
|
}
|
|
|
|
// See if the node has a previous sibling
|
|
|
|
if (RC_BAD( rc = (*ppCurrNode)->getPreviousSibling( m_pDb, ppCurrNode)))
|
|
{
|
|
if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = (*ppCurrNode)->getParentNode( m_pDb, ppCurrNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
}
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Go down to the last child of the previous sibling
|
|
// that was found.
|
|
|
|
Get_Last_Child:
|
|
|
|
for (;;)
|
|
{
|
|
if (RC_BAD( rc = (*ppCurrNode)->getLastChild( m_pDb, ppCurrNode)))
|
|
{
|
|
if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// We are positioned on the rightmost child now.
|
|
|
|
rc = NE_XFLM_OK;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*ppCurrNode = pContextNode;
|
|
(*ppCurrNode)->AddRef();
|
|
|
|
// Go down to the last child of the context node.
|
|
|
|
goto Get_Last_Child;
|
|
}
|
|
|
|
// If we arrive at the context node, we are done, unless
|
|
// we are including self.
|
|
|
|
if( RC_BAD( rc = ((F_DOMNode *)(*ppCurrNode))->getNodeId( m_pDb,
|
|
&ui64Tmp, &uiTmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( ui64Tmp == ui64ContextNodeId && uiTmp == uiContextAttrNameId &&
|
|
!bIncludeSelf)
|
|
{
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next node from a PRECEDING_SIBLING_AXIS or
|
|
FOLLOWING_SIBLING_AXIS.
|
|
***************************************************************************/
|
|
RCODE F_Query::getSibAxisNode(
|
|
FLMBOOL bForward,
|
|
FLMBOOL bPrevSibAxis,
|
|
IF_DOMNode * pContextNode,
|
|
IF_DOMNode ** ppCurrNode)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMUINT64 ui64NodeId;
|
|
FLMUINT64 ui64CurrNodeId;
|
|
FLMUINT64 ui64ContextNodeId;
|
|
FLMUINT uiCurrNameId;
|
|
FLMUINT uiContextAttrNameId;
|
|
|
|
if (!pContextNode)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (RC_BAD( rc = walkDocument( bForward, FALSE, 0, ppCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (!(*ppCurrNode))
|
|
{
|
|
break;
|
|
}
|
|
|
|
// This node must be the next or previous sibling to some
|
|
// node - which means it must have a previous or next sibling.
|
|
|
|
rc = (RCODE)(bPrevSibAxis
|
|
? (*ppCurrNode)->getNextSibId( m_pDb,
|
|
&ui64NodeId)
|
|
: (*ppCurrNode)->getPrevSibId( m_pDb,
|
|
&ui64NodeId));
|
|
if (RC_BAD( rc))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (ui64NodeId)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bForward)
|
|
{
|
|
if (!(*ppCurrNode))
|
|
{
|
|
*ppCurrNode = pContextNode;
|
|
(*ppCurrNode)->AddRef();
|
|
}
|
|
rc = (RCODE)(bPrevSibAxis
|
|
? (*ppCurrNode)->getPreviousSibling( m_pDb, ppCurrNode)
|
|
: (*ppCurrNode)->getNextSibling( m_pDb, ppCurrNode));
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
}
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (*ppCurrNode)
|
|
{
|
|
rc = (RCODE)(bPrevSibAxis
|
|
? (*ppCurrNode)->getNextSibling( m_pDb, ppCurrNode)
|
|
: (*ppCurrNode)->getPreviousSibling( m_pDb, ppCurrNode));
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
}
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FLMBOOL bAttr;
|
|
|
|
*ppCurrNode = pContextNode;
|
|
(*ppCurrNode)->AddRef();
|
|
|
|
bAttr = (*ppCurrNode)->getNodeType() == ATTRIBUTE_NODE
|
|
? TRUE
|
|
: FALSE;
|
|
|
|
// Go to the parent and get either the first or
|
|
// last child, depending on the axis. That is
|
|
// where we need to start from.
|
|
|
|
if (RC_BAD( rc = (*ppCurrNode)->getParentNode( m_pDb, ppCurrNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
|
|
// A node without a parent should not have any siblings!
|
|
|
|
rc = NE_XFLM_OK;
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
}
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Get the node's first or last child.
|
|
|
|
if (!bAttr)
|
|
{
|
|
rc = (RCODE)(bPrevSibAxis
|
|
? (*ppCurrNode)->getFirstChild( m_pDb, ppCurrNode)
|
|
: (*ppCurrNode)->getLastChild( m_pDb, ppCurrNode));
|
|
}
|
|
else
|
|
{
|
|
rc = (RCODE)(bPrevSibAxis
|
|
? (*ppCurrNode)->getFirstAttribute( m_pDb, ppCurrNode)
|
|
: (*ppCurrNode)->getLastAttribute( m_pDb, ppCurrNode));
|
|
}
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_DATA_ERROR);
|
|
}
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// If we landed on the context node, we are done - there will be
|
|
// no more siblings to get.
|
|
|
|
if( RC_BAD( rc = ((F_DOMNode *)(*ppCurrNode))->getNodeId( m_pDb,
|
|
&ui64CurrNodeId, &uiCurrNameId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( RC_BAD( rc = ((F_DOMNode *)pContextNode)->getNodeId( m_pDb,
|
|
&ui64ContextNodeId, &uiContextAttrNameId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( ui64CurrNodeId == ui64ContextNodeId &&
|
|
uiCurrNameId == uiContextAttrNameId)
|
|
{
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next node from a PRECEDING_AXIS or
|
|
FOLLOWING_AXIS.
|
|
***************************************************************************/
|
|
RCODE F_Query::getPrevOrAfterAxisNode(
|
|
FLMBOOL bForward,
|
|
FLMBOOL bPrevAxis,
|
|
IF_DOMNode * pContextNode,
|
|
IF_DOMNode ** ppCurrNode)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMUINT uiChildCnt;
|
|
IF_DOMNode * pParentNode = NULL;
|
|
IF_DOMNode * pSibNode = NULL;
|
|
IF_DOMNode * pHighestAncestorWithSib = NULL;
|
|
FLMUINT64 ui64NodeId;
|
|
FLMUINT64 ui64ContextId;
|
|
FLMUINT64 ui64Tmp;
|
|
|
|
if (!pContextNode)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (RC_BAD( rc = walkDocument( bForward, FALSE, 0, ppCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!(*ppCurrNode))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (pParentNode)
|
|
{
|
|
pParentNode->Release();
|
|
}
|
|
pParentNode = *ppCurrNode;
|
|
pParentNode->AddRef();
|
|
|
|
// This node must be previous to some other node if the
|
|
// axis is PRECEDING_AXIS (bPrevAxis == TRUE) or after some
|
|
// other node if the axis is FOLLOWING_AXIS
|
|
// (bPrevAxis == FALSE). This does not count descendants
|
|
// or ancestors or attribute nodes.
|
|
|
|
for (;;)
|
|
{
|
|
rc = (RCODE)(bPrevAxis
|
|
? pParentNode->getNextSibId( m_pDb, &ui64NodeId)
|
|
: pParentNode->getPrevSibId( m_pDb, &ui64NodeId));
|
|
if (RC_BAD( rc))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (ui64NodeId)
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = pParentNode->getParentNode( m_pDb,
|
|
&pParentNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
break;
|
|
}
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( RC_BAD( rc = pContextNode->getNodeId( m_pDb, &ui64ContextId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Context node better not be an attribute node. That is illegal.
|
|
// To be nice here, we will change the context node to be the
|
|
// attribute's encompassing element node.
|
|
|
|
if (pContextNode->getNodeType() == ATTRIBUTE_NODE)
|
|
{
|
|
if (RC_BAD( rc = pContextNode->getParentNode( m_pDb, &pContextNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_DATA_ERROR);
|
|
}
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if (bForward)
|
|
{
|
|
if (*ppCurrNode)
|
|
{
|
|
|
|
// Go to child of current node
|
|
|
|
rc = (RCODE)(bPrevAxis
|
|
? (*ppCurrNode)->getLastChild( m_pDb, ppCurrNode)
|
|
: (*ppCurrNode)->getFirstChild( m_pDb, ppCurrNode));
|
|
if (RC_OK( rc))
|
|
{
|
|
rc = incrNodesRead();
|
|
goto Exit;
|
|
}
|
|
else if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*ppCurrNode = pContextNode;
|
|
(*ppCurrNode)->AddRef();
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
|
|
rc = (RCODE)(bPrevAxis
|
|
? (*ppCurrNode)->getPreviousSibling( m_pDb, ppCurrNode)
|
|
: (*ppCurrNode)->getNextSibling( m_pDb, ppCurrNode));
|
|
if (RC_OK( rc))
|
|
{
|
|
rc = incrNodesRead();
|
|
goto Exit;
|
|
}
|
|
else if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Go to the parent node
|
|
|
|
if (RC_BAD( rc = (*ppCurrNode)->getParentNode( m_pDb, ppCurrNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// Searching backwards
|
|
|
|
if (!(*ppCurrNode))
|
|
{
|
|
*ppCurrNode = pContextNode;
|
|
(*ppCurrNode)->AddRef();
|
|
pHighestAncestorWithSib = NULL;
|
|
|
|
// Find highest parent that has a sibling
|
|
|
|
for (;;)
|
|
{
|
|
rc = (RCODE)(bPrevAxis
|
|
? (*ppCurrNode)->getPreviousSibling( m_pDb, &pSibNode)
|
|
: (*ppCurrNode)->getNextSibling( m_pDb, &pSibNode));
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
else
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (pHighestAncestorWithSib)
|
|
{
|
|
pHighestAncestorWithSib->Release();
|
|
}
|
|
pHighestAncestorWithSib = *ppCurrNode;
|
|
pHighestAncestorWithSib->AddRef();
|
|
}
|
|
|
|
// Go to node's parent
|
|
|
|
if (RC_BAD( rc = (*ppCurrNode)->getParentNode( m_pDb, ppCurrNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
|
|
// If none of the ancestry had a prev/next sibling
|
|
// we are done because there will be no nodes to
|
|
// process.
|
|
|
|
if (!pHighestAncestorWithSib)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Find the leftmost/rightmost sibling of the ancestor node,
|
|
// and go down to its leftmost/rightmost child.
|
|
// That is equivalent to going up to the parent node (which
|
|
// there must be one!) and then going down to the
|
|
// leftmost/rightmost child of that parent.
|
|
|
|
*ppCurrNode = pHighestAncestorWithSib;
|
|
(*ppCurrNode)->AddRef();
|
|
if (RC_OK( rc = (*ppCurrNode)->getParentNode( m_pDb, ppCurrNode)))
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
// Should not be possible, because we already know that
|
|
// pHighestAncestorWithSib has a sibling! - which
|
|
// necessitates it having a parent!
|
|
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_DATA_ERROR);
|
|
goto Exit;
|
|
}
|
|
|
|
// Now go down to the leftmost/rightmost child
|
|
|
|
uiChildCnt = 0;
|
|
for (;;)
|
|
{
|
|
rc = (RCODE)(bPrevAxis
|
|
? (*ppCurrNode)->getFirstChild( m_pDb, ppCurrNode)
|
|
: (*ppCurrNode)->getLastChild( m_pDb, ppCurrNode));
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
// If we didn't go down to any child, this is
|
|
// a problem, because we got to the parent node
|
|
// from a child node!
|
|
|
|
if (!uiChildCnt)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_DATA_ERROR);
|
|
}
|
|
else
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
}
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
uiChildCnt++;
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The following code is only executed if *ppCurrNode is non-NULL
|
|
// when we enter this method.
|
|
|
|
rc = (RCODE)(bPrevAxis
|
|
? (*ppCurrNode)->getNextSibling( m_pDb, ppCurrNode)
|
|
: (*ppCurrNode)->getPreviousSibling( m_pDb, ppCurrNode));
|
|
if (RC_OK( rc))
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If we have hit the context node, we are done
|
|
|
|
if( RC_BAD( rc = (*ppCurrNode)->getNodeId( m_pDb, &ui64Tmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( ui64Tmp == ui64ContextId)
|
|
{
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
goto Exit;
|
|
}
|
|
|
|
// Go to rightmost/leftmost child - If we hit the
|
|
// context node anywhere in here, we are done.
|
|
|
|
for (;;)
|
|
{
|
|
rc = (RCODE)(bPrevAxis
|
|
? (*ppCurrNode)->getFirstChild( m_pDb, ppCurrNode)
|
|
: (*ppCurrNode)->getLastChild( m_pDb, ppCurrNode));
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// See if we hit the context node.
|
|
|
|
if( RC_BAD( rc = (*ppCurrNode)->getNodeId( m_pDb, &ui64Tmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( ui64Tmp == ui64ContextId)
|
|
{
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
else if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = (*ppCurrNode)->getParentNode( m_pDb, ppCurrNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Should never hit the context node here
|
|
|
|
#ifdef FLM_DEBUG
|
|
if( RC_BAD( rc = (*ppCurrNode)->getNodeId( m_pDb, &ui64Tmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
flmAssert( ui64Tmp != ui64ContextId);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (pParentNode)
|
|
{
|
|
pParentNode->Release();
|
|
}
|
|
if (pSibNode)
|
|
{
|
|
pSibNode->Release();
|
|
}
|
|
if (pHighestAncestorWithSib)
|
|
{
|
|
pHighestAncestorWithSib->Release();
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next node from a ATTRIBUTE_AXIS or NAMESPACE_AXIS.
|
|
***************************************************************************/
|
|
RCODE F_Query::getAttrAxisNode(
|
|
FLMBOOL bForward,
|
|
FLMBOOL bAttrAxis,
|
|
FLMUINT uiAttrNameId,
|
|
IF_DOMNode * pContextNode,
|
|
IF_DOMNode ** ppCurrNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMBOOL bIsNamespaceDecl;
|
|
|
|
if (!pContextNode)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (RC_BAD( rc = walkDocument( bForward, TRUE, uiAttrNameId, ppCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!(*ppCurrNode))
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Must be an attribute node.
|
|
|
|
if ((*ppCurrNode)->getNodeType() != ATTRIBUTE_NODE)
|
|
{
|
|
continue;
|
|
}
|
|
if (bAttrAxis)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Must be a namespace attribute.
|
|
|
|
if (RC_BAD( rc = (*ppCurrNode)->isNamespaceDecl( m_pDb,
|
|
&bIsNamespaceDecl)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (bIsNamespaceDecl)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (;;)
|
|
{
|
|
if( *ppCurrNode)
|
|
{
|
|
flmAssert( (*ppCurrNode)->getNodeType() == ATTRIBUTE_NODE);
|
|
|
|
// Better not be testing for a specific name. This should
|
|
// not have been called if we already processed that
|
|
// attribute.
|
|
|
|
flmAssert( !uiAttrNameId);
|
|
|
|
// Get the next/previous sibling, if any
|
|
|
|
rc = (RCODE)(bForward
|
|
? (*ppCurrNode)->getNextSibling( m_pDb, ppCurrNode)
|
|
: (*ppCurrNode)->getPreviousSibling( m_pDb, ppCurrNode));
|
|
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
(*ppCurrNode) = pContextNode;
|
|
(*ppCurrNode)->AddRef();
|
|
|
|
// Context node better be an element node
|
|
|
|
flmAssert( (*ppCurrNode)->getNodeType() == ELEMENT_NODE);
|
|
|
|
// Get the first/last attribute of the node. If a specific
|
|
// attribute ID is specified, get it - no need to cycle through
|
|
// all of the attributes.
|
|
|
|
if (uiAttrNameId)
|
|
{
|
|
rc = (*ppCurrNode)->getAttribute( m_pDb, uiAttrNameId, ppCurrNode);
|
|
}
|
|
else
|
|
{
|
|
rc = (RCODE)(bForward
|
|
? (*ppCurrNode)->getFirstAttribute( m_pDb, ppCurrNode)
|
|
: (*ppCurrNode)->getLastAttribute( m_pDb, ppCurrNode));
|
|
}
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bAttrAxis)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Must be a namespace attribute.
|
|
|
|
if (RC_BAD( rc = (*ppCurrNode)->isNamespaceDecl( m_pDb,
|
|
&bIsNamespaceDecl)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (bIsNamespaceDecl)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Determine the inverted axis for a given axis type.
|
|
***************************************************************************/
|
|
FINLINE eXPathAxisTypes invertedAxis(
|
|
eXPathAxisTypes eAxis)
|
|
{
|
|
eXPathAxisTypes eInvertedAxis = ROOT_AXIS;
|
|
|
|
switch (eAxis)
|
|
{
|
|
case ROOT_AXIS:
|
|
|
|
// A root axis cannot be inverted.
|
|
|
|
flmAssert( 0);
|
|
eInvertedAxis = ROOT_AXIS;
|
|
break;
|
|
case CHILD_AXIS:
|
|
case ATTRIBUTE_AXIS:
|
|
case NAMESPACE_AXIS:
|
|
eInvertedAxis = PARENT_AXIS;
|
|
break;
|
|
case PARENT_AXIS:
|
|
eInvertedAxis = CHILD_AXIS;
|
|
break;
|
|
case ANCESTOR_AXIS:
|
|
eInvertedAxis = DESCENDANT_AXIS;
|
|
break;
|
|
case DESCENDANT_AXIS:
|
|
eInvertedAxis = ANCESTOR_AXIS;
|
|
break;
|
|
case FOLLOWING_SIBLING_AXIS:
|
|
eInvertedAxis = PRECEDING_SIBLING_AXIS;
|
|
break;
|
|
case PRECEDING_SIBLING_AXIS:
|
|
eInvertedAxis = FOLLOWING_SIBLING_AXIS;
|
|
break;
|
|
case FOLLOWING_AXIS:
|
|
eInvertedAxis = PRECEDING_AXIS;
|
|
break;
|
|
case PRECEDING_AXIS:
|
|
eInvertedAxis = FOLLOWING_AXIS;
|
|
break;
|
|
case SELF_AXIS:
|
|
eInvertedAxis = SELF_AXIS;
|
|
break;
|
|
case DESCENDANT_OR_SELF_AXIS:
|
|
eInvertedAxis = ANCESTOR_OR_SELF_AXIS;
|
|
break;
|
|
case ANCESTOR_OR_SELF_AXIS:
|
|
eInvertedAxis = DESCENDANT_OR_SELF_AXIS;
|
|
break;
|
|
case META_AXIS:
|
|
eInvertedAxis = SELF_AXIS;
|
|
break;
|
|
}
|
|
return( eInvertedAxis);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: See if the passed in node ID satisfies the occurrence criteria
|
|
for the xpath component.
|
|
***************************************************************************/
|
|
RCODE F_Query::verifyOccurrence(
|
|
FLMBOOL bUseKeyNodes,
|
|
XPATH_COMPONENT * pXPathComponent,
|
|
IF_DOMNode * pCurrNode,
|
|
FLMBOOL * pbPassed)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
IF_DOMNode * pContextNode = NULL;
|
|
IF_DOMNode * pTmpCurrNode = NULL;
|
|
FLMUINT64 ui64NodeId;
|
|
FLMUINT64 ui64Tmp;
|
|
FLMBOOL bGetContextNodes = FALSE;
|
|
XPATH_COMPONENT * pXPathContextComponent = pXPathComponent->pPrev
|
|
? pXPathComponent->pPrev
|
|
: pXPathComponent->pXPathContext;
|
|
|
|
if( RC_BAD( rc = pCurrNode->getNodeId( m_pDb, &ui64NodeId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (!pXPathContextComponent)
|
|
{
|
|
pContextNode = NULL;
|
|
}
|
|
else if (bUseKeyNodes && pXPathContextComponent->pKeyNode)
|
|
{
|
|
pContextNode = pXPathContextComponent->pKeyNode;
|
|
pContextNode->AddRef();
|
|
}
|
|
else if (pXPathContextComponent->pCurrNode)
|
|
{
|
|
pContextNode = pXPathContextComponent->pCurrNode;
|
|
pContextNode->AddRef();
|
|
}
|
|
else
|
|
{
|
|
|
|
// Need to get context nodes. May have to try multiple ones.
|
|
|
|
bGetContextNodes = TRUE;
|
|
if (RC_BAD( rc = getXPathComponentFromAxis( pCurrNode, TRUE, FALSE,
|
|
pXPathContextComponent, &pContextNode,
|
|
invertedAxis( pXPathComponent->eXPathAxis),
|
|
TRUE, FALSE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!pContextNode)
|
|
{
|
|
*pbPassed = FALSE;
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// *pbPassed better have been passed in as TRUE
|
|
// It will get set to FALSE if we don't find the node
|
|
// we are looking for.
|
|
|
|
flmAssert( *pbPassed);
|
|
|
|
for (;;)
|
|
{
|
|
if (RC_BAD( rc = getXPathComponentFromAxis( pContextNode, TRUE, FALSE,
|
|
pXPathComponent, &pTmpCurrNode,
|
|
pXPathComponent->eXPathAxis,
|
|
FALSE, FALSE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!pTmpCurrNode)
|
|
{
|
|
|
|
No_More_Nodes:
|
|
|
|
if (!bGetContextNodes)
|
|
{
|
|
*pbPassed = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
// If we didn't have a context node to begin with,
|
|
// attempt another one.
|
|
|
|
if (RC_BAD( rc = getXPathComponentFromAxis( pCurrNode, TRUE, FALSE,
|
|
pXPathContextComponent,
|
|
&pContextNode,
|
|
invertedAxis( pXPathComponent->eXPathAxis),
|
|
TRUE, FALSE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// No more context nodes to try. We are done.
|
|
|
|
if (!pContextNode)
|
|
{
|
|
*pbPassed = FALSE;
|
|
goto Exit;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if( RC_BAD( rc = pTmpCurrNode->getNodeId( m_pDb, &ui64Tmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( ui64Tmp == ui64NodeId)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// If we are dealing with a constant position, and we didn't
|
|
// find the node we were looking for, we are done. The
|
|
// above call would have found it. If we are dealing
|
|
// with an expression, there is no guarantee that the above
|
|
// code would have found just one occurrence of a matching
|
|
// node, and it may not have been the one we were looking
|
|
// for.
|
|
|
|
if (pXPathComponent->uiContextPosNeeded)
|
|
{
|
|
goto No_More_Nodes;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (pTmpCurrNode)
|
|
{
|
|
pTmpCurrNode->Release();
|
|
}
|
|
if (pContextNode)
|
|
{
|
|
pContextNode->Release();
|
|
}
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the node for an xpath component that is related to the
|
|
given xpath context component by the specified axis.
|
|
***************************************************************************/
|
|
RCODE F_Query::getXPathComponentFromAxis(
|
|
IF_DOMNode * pContextNode,
|
|
FLMBOOL bForward,
|
|
FLMBOOL bUseKeyNodes,
|
|
XPATH_COMPONENT * pXPathComponent,
|
|
IF_DOMNode ** ppCurrNode,
|
|
eXPathAxisTypes eAxis,
|
|
FLMBOOL bAxisInverted,
|
|
FLMBOOL bCountNodes)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
IF_DOMNode * pCurrNode = NULL;
|
|
FLMUINT uiOccurrence = 0;
|
|
FLMBOOL bCanCountOccurrence;
|
|
FLMBOOL bMatches;
|
|
FLMBOOL bHasContextPosTest = hasContextPosTest( pXPathComponent);
|
|
FLMUINT uiNameId;
|
|
|
|
// Remember the current node, and then set to NULL.
|
|
|
|
if (*ppCurrNode)
|
|
{
|
|
pCurrNode = *ppCurrNode;
|
|
pCurrNode->AddRef();
|
|
(*ppCurrNode)->Release();
|
|
*ppCurrNode = NULL;
|
|
}
|
|
|
|
// Determine if we can count occurrences.
|
|
|
|
if (bForward && !bAxisInverted)
|
|
{
|
|
uiOccurrence = 0;
|
|
bCanCountOccurrence = TRUE;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Cannot keep a running count if we are going backwards or if
|
|
// we are operating on an inverted axis.
|
|
|
|
bCanCountOccurrence = FALSE;
|
|
}
|
|
|
|
// Go until we get a node that passes, or until we have no more
|
|
// nodes to check.
|
|
|
|
for (;;)
|
|
{
|
|
if (pXPathComponent->pNodeSource)
|
|
{
|
|
|
|
// Axis is ignored for xpath components that have a node source.
|
|
|
|
if (RC_BAD( rc = getNodeSourceNode( bForward,
|
|
pXPathComponent->pNodeSource,
|
|
pContextNode, &pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
goto Test_Node;
|
|
}
|
|
|
|
// Type of search we do will depend on axis
|
|
|
|
switch (eAxis)
|
|
{
|
|
case ROOT_AXIS:
|
|
|
|
// There is only one root node to get, so if we already
|
|
// got it, we are done.
|
|
|
|
if (pCurrNode)
|
|
{
|
|
goto Exit;
|
|
}
|
|
flmAssert( pXPathComponent->eNodeType == ELEMENT_NODE);
|
|
if (RC_BAD( rc = getRootAxisNode( &pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Will only ever be one occurrence of a root node.
|
|
|
|
if (!bAxisInverted)
|
|
{
|
|
uiOccurrence = 0;
|
|
bCanCountOccurrence = TRUE;
|
|
}
|
|
break;
|
|
|
|
case CHILD_AXIS:
|
|
|
|
// For a context that requires all child elements to be unique,
|
|
// there will only be one child with that name ID.
|
|
// If we already got it, we are done.
|
|
|
|
if (pContextNode &&
|
|
(((F_DOMNode *)pContextNode)->getModeFlags() &
|
|
FDOM_HAVE_CELM_LIST) &&
|
|
pXPathComponent->uiDictNum)
|
|
{
|
|
if( pCurrNode)
|
|
{
|
|
if( RC_BAD( rc = pCurrNode->getNameId( m_pDb, &uiNameId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( uiNameId == pXPathComponent->uiDictNum)
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if (RC_BAD( rc = getChildAxisNode( bForward, pContextNode,
|
|
pXPathComponent->uiDictNum,
|
|
&pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = getChildAxisNode( bForward, pContextNode, 0,
|
|
&pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PARENT_AXIS:
|
|
|
|
// There is only one parent node to get if we are in a
|
|
// specific context, so if we already got it, we are
|
|
// done.
|
|
|
|
if (pCurrNode && pContextNode)
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = getParentAxisNode( bForward, pContextNode,
|
|
&pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Will only ever be one occurrence of a parent node.
|
|
|
|
if (!bAxisInverted)
|
|
{
|
|
uiOccurrence = 0;
|
|
bCanCountOccurrence = TRUE;
|
|
}
|
|
break;
|
|
|
|
case ANCESTOR_AXIS:
|
|
if (RC_BAD( rc = getAncestorAxisNode( bForward, FALSE,
|
|
pContextNode, &pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
|
|
case DESCENDANT_AXIS:
|
|
if (RC_BAD( rc = getDescendantAxisNode( bForward, FALSE,
|
|
pContextNode, &pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
|
|
case FOLLOWING_SIBLING_AXIS:
|
|
if (RC_BAD( rc = getSibAxisNode( bForward, FALSE,
|
|
pContextNode, &pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
|
|
case PRECEDING_SIBLING_AXIS:
|
|
if (RC_BAD( rc = getSibAxisNode( bForward, TRUE,
|
|
pContextNode, &pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
|
|
case FOLLOWING_AXIS:
|
|
if (RC_BAD( rc = getPrevOrAfterAxisNode( bForward, FALSE,
|
|
pContextNode, &pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
|
|
case PRECEDING_AXIS:
|
|
if (RC_BAD( rc = getPrevOrAfterAxisNode( bForward, TRUE,
|
|
pContextNode, &pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
|
|
case ATTRIBUTE_AXIS:
|
|
|
|
// For a specific context, there is only one attribute with
|
|
// a given name ID. If we already got it, we are done.
|
|
|
|
if( pCurrNode && pContextNode && pXPathComponent->uiDictNum)
|
|
{
|
|
if( RC_BAD( rc = pCurrNode->getNameId( m_pDb, &uiNameId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( uiNameId == pXPathComponent->uiDictNum)
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if (RC_BAD( rc = getAttrAxisNode( bForward, TRUE,
|
|
pXPathComponent->uiDictNum,
|
|
pContextNode,
|
|
&pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
|
|
case NAMESPACE_AXIS:
|
|
if (RC_BAD( rc = getAttrAxisNode( bForward, FALSE, 0, pContextNode,
|
|
&pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
|
|
case SELF_AXIS:
|
|
case META_AXIS:
|
|
|
|
// Within a given context, there is only one occurrence of the
|
|
// META or SELF axis. If we already got it, we are done.
|
|
|
|
if (pCurrNode && pContextNode)
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!pContextNode)
|
|
{
|
|
|
|
// If the node is the SELF_AXIS, there better be
|
|
// a context node!
|
|
|
|
flmAssert( eAxis != SELF_AXIS);
|
|
if (RC_BAD( rc = walkDocument( bForward, TRUE, 0, &pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pCurrNode = pContextNode;
|
|
pCurrNode->AddRef();
|
|
}
|
|
|
|
// Will only ever be one occurrence of a meta-axis node or
|
|
// a self-axis node.
|
|
|
|
if (!bAxisInverted)
|
|
{
|
|
uiOccurrence = 0;
|
|
bCanCountOccurrence = TRUE;
|
|
}
|
|
break;
|
|
|
|
case DESCENDANT_OR_SELF_AXIS:
|
|
if (RC_BAD( rc = getDescendantAxisNode( bForward, TRUE,
|
|
pContextNode, &pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
|
|
case ANCESTOR_OR_SELF_AXIS:
|
|
if (RC_BAD( rc = getAncestorAxisNode( bForward, TRUE,
|
|
pContextNode, &pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
}
|
|
|
|
Test_Node:
|
|
|
|
if (!pCurrNode)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (bCountNodes)
|
|
{
|
|
m_pCurrOpt->ui64NodesTested++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
bMatches = TRUE;
|
|
if (pXPathComponent->eNodeType != ANY_NODE_TYPE && eAxis != META_AXIS)
|
|
{
|
|
if (pCurrNode->getNodeType() != pXPathComponent->eNodeType)
|
|
{
|
|
bMatches = FALSE;
|
|
}
|
|
|
|
// Verify that the dictionary number matches.
|
|
|
|
else if (pXPathComponent->eNodeType == ELEMENT_NODE ||
|
|
pXPathComponent->eNodeType == ATTRIBUTE_NODE ||
|
|
pXPathComponent->eNodeType == DATA_NODE)
|
|
{
|
|
if (pXPathComponent->uiDictNum)
|
|
{
|
|
if( RC_BAD( rc = pCurrNode->getNameId( m_pDb, &uiNameId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( uiNameId != pXPathComponent->uiDictNum)
|
|
{
|
|
bMatches = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If there is an expression evaluate it.
|
|
// The expression must pass in order to
|
|
// keep this particular node.
|
|
|
|
if (bMatches && pXPathComponent->pExpr)
|
|
{
|
|
if (RC_BAD( rc = evalExpr( pCurrNode, TRUE, bUseKeyNodes,
|
|
pXPathComponent->pExpr, &bMatches, NULL)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// See if there is an occurrence test.
|
|
|
|
if (bMatches && bHasContextPosTest)
|
|
{
|
|
if (bCanCountOccurrence)
|
|
{
|
|
uiOccurrence++;
|
|
if (pXPathComponent->uiContextPosNeeded)
|
|
{
|
|
if (uiOccurrence != pXPathComponent->uiContextPosNeeded)
|
|
{
|
|
bMatches = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FLMUINT uiPosNeeded;
|
|
|
|
if (RC_BAD( rc = evalExpr( pCurrNode, TRUE, bUseKeyNodes,
|
|
pXPathComponent->pContextPosExpr,
|
|
NULL, NULL)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( fqGetPosition( &pXPathComponent->pContextPosExpr->currVal,
|
|
&uiPosNeeded)) ||
|
|
uiOccurrence != uiPosNeeded)
|
|
{
|
|
bMatches = FALSE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// Need to verify the context position because we couldn't
|
|
// calculate it as we went.
|
|
|
|
if (RC_BAD( rc = verifyOccurrence( bUseKeyNodes, pXPathComponent,
|
|
pCurrNode, &bMatches)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bMatches)
|
|
{
|
|
*ppCurrNode = pCurrNode;
|
|
|
|
// No need to AddRef on *ppCurrNode, just steal it from
|
|
// pCurrNode. Setting pCurrNode to NULL keeps it from
|
|
// being Release()'d below.
|
|
|
|
pCurrNode = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (pCurrNode)
|
|
{
|
|
pCurrNode->Release();
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get an FQVALUE from a DOM node.
|
|
***************************************************************************/
|
|
FSTATIC RCODE fqGetValueFromNode(
|
|
F_Db * pDb,
|
|
IF_DOMNode * pNode,
|
|
FQVALUE * pQValue,
|
|
FLMUINT uiMetaDataType
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMUINT uiDataType;
|
|
FLMUINT uiNumChars;
|
|
|
|
pQValue->uiFlags = 0;
|
|
if (uiMetaDataType)
|
|
{
|
|
switch (uiMetaDataType)
|
|
{
|
|
case XFLM_META_NODE_ID:
|
|
pQValue->eValType = XFLM_UINT64_VAL;
|
|
rc = pNode->getNodeId( pDb, &pQValue->val.ui64Val);
|
|
goto Exit;
|
|
case XFLM_META_DOCUMENT_ID:
|
|
pQValue->eValType = XFLM_UINT64_VAL;
|
|
rc = pNode->getDocumentId( pDb, &pQValue->val.ui64Val);
|
|
goto Exit;
|
|
case XFLM_META_PARENT_ID:
|
|
pQValue->eValType = XFLM_UINT64_VAL;
|
|
rc = pNode->getParentId( pDb, &pQValue->val.ui64Val);
|
|
goto Exit;
|
|
case XFLM_META_FIRST_CHILD_ID:
|
|
pQValue->eValType = XFLM_UINT64_VAL;
|
|
rc = pNode->getFirstChildId( pDb, &pQValue->val.ui64Val);
|
|
goto Exit;
|
|
case XFLM_META_LAST_CHILD_ID:
|
|
pQValue->eValType = XFLM_UINT64_VAL;
|
|
rc = pNode->getLastChildId( pDb, &pQValue->val.ui64Val);
|
|
goto Exit;
|
|
case XFLM_META_NEXT_SIBLING_ID:
|
|
pQValue->eValType = XFLM_UINT64_VAL;
|
|
rc = pNode->getNextSibId( pDb, &pQValue->val.ui64Val);
|
|
goto Exit;
|
|
case XFLM_META_PREV_SIBLING_ID:
|
|
pQValue->eValType = XFLM_UINT64_VAL;
|
|
rc = pNode->getPrevSibId( pDb, &pQValue->val.ui64Val);
|
|
goto Exit;
|
|
case XFLM_META_VALUE:
|
|
pQValue->eValType = XFLM_UINT64_VAL;
|
|
rc = pNode->getMetaValue( pDb, &pQValue->val.ui64Val);
|
|
goto Exit;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (RC_BAD( rc = pNode->getDataType( pDb, &uiDataType)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
switch (uiDataType)
|
|
{
|
|
case XFLM_NODATA_TYPE:
|
|
|
|
// Should have been set to missing on the outside
|
|
|
|
flmAssert( pQValue->eValType == XFLM_MISSING_VAL);
|
|
pQValue->eValType = XFLM_BOOL_VAL;
|
|
pQValue->val.eBool = XFLM_TRUE;
|
|
break;
|
|
case XFLM_TEXT_TYPE:
|
|
if (RC_BAD( rc = pNode->getTextIStream( pDb,
|
|
&pQValue->val.pIStream,
|
|
&uiNumChars)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQValue->eValType = XFLM_UTF8_VAL;
|
|
pQValue->uiFlags |= VAL_IS_STREAM;
|
|
break;
|
|
case XFLM_NUMBER_TYPE:
|
|
|
|
// First, see if we can get it as a UINT - the most common
|
|
// type.
|
|
|
|
if (RC_OK( rc = pNode->getUINT( pDb, &pQValue->val.uiVal)))
|
|
{
|
|
pQValue->eValType = XFLM_UINT_VAL;
|
|
}
|
|
else if (rc == NE_XFLM_CONV_NUM_OVERFLOW)
|
|
{
|
|
if (RC_BAD( rc = pNode->getUINT64( pDb,
|
|
&pQValue->val.ui64Val)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQValue->eValType = XFLM_UINT64_VAL;
|
|
}
|
|
else if (rc == NE_XFLM_CONV_NUM_UNDERFLOW)
|
|
{
|
|
if (RC_OK( rc = pNode->getINT( pDb, &pQValue->val.iVal)))
|
|
{
|
|
pQValue->eValType = XFLM_INT_VAL;
|
|
}
|
|
else if (rc == NE_XFLM_CONV_NUM_UNDERFLOW)
|
|
{
|
|
if (RC_BAD( rc = pNode->getINT64( pDb,
|
|
&pQValue->val.i64Val)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQValue->eValType = XFLM_INT64_VAL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
case XFLM_BINARY_TYPE:
|
|
if (RC_BAD( rc = pNode->getIStream( pDb, &pQValue->val.pIStream,
|
|
&uiDataType)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
flmAssert( uiDataType == XFLM_BINARY_TYPE);
|
|
pQValue->eValType = XFLM_BINARY_VAL;
|
|
pQValue->uiFlags |= VAL_IS_STREAM;
|
|
break;
|
|
default:
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_NOT_IMPLEMENTED);
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next value from an XPATH node.
|
|
***************************************************************************/
|
|
RCODE F_Query::getNextXPathValue(
|
|
IF_DOMNode * pContextNode,
|
|
FLMBOOL bForward,
|
|
FLMBOOL bUseKeyNodes,
|
|
FLMBOOL bXPathIsEntireExpr,
|
|
FQNODE * pQNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FXPATH * pXPath;
|
|
XPATH_COMPONENT * pXPathComponent;
|
|
|
|
// Node better be an XPATH node.
|
|
|
|
flmAssert( pQNode->eNodeType == FLM_XPATH_NODE);
|
|
pXPath = pQNode->nd.pXPath;
|
|
|
|
// If the old value type was a stream, get rid of it.
|
|
|
|
fqReleaseNodeValue( pQNode);
|
|
if (pXPath->bGettingNodes)
|
|
{
|
|
pXPathComponent = pXPath->pLastComponent;
|
|
}
|
|
else if (pXPath->bIsSource && bUseKeyNodes)
|
|
{
|
|
pXPathComponent = pXPath->pSourceComponent->pNext;
|
|
|
|
// This routine should never be called if the source component
|
|
// is the rightmost component. That is taken care of on
|
|
// the outside.
|
|
|
|
flmAssert( pXPathComponent);
|
|
}
|
|
else
|
|
{
|
|
pXPathComponent = pXPath->pFirstComponent;
|
|
}
|
|
|
|
// This loop will go until we get to the last xpath component and get
|
|
// a node. If it cannot get a node at one context, it will retreat
|
|
// to the prior context, until it gets back to the first xpath
|
|
// component, and there are no more to get
|
|
|
|
for (;;)
|
|
{
|
|
IF_DOMNode * pTmpContextNode;
|
|
|
|
if (!pXPathComponent->pPrev)
|
|
{
|
|
pTmpContextNode = pContextNode;
|
|
}
|
|
else if (bUseKeyNodes && pXPathComponent->pPrev->pKeyNode)
|
|
{
|
|
pTmpContextNode = pXPathComponent->pPrev->pKeyNode;
|
|
}
|
|
else
|
|
{
|
|
pTmpContextNode = pXPathComponent->pPrev->pCurrNode;
|
|
}
|
|
if (RC_BAD( rc = getXPathComponentFromAxis( pTmpContextNode,
|
|
bForward, bUseKeyNodes, pXPathComponent,
|
|
&pXPathComponent->pCurrNode,
|
|
pXPathComponent->eXPathAxis, FALSE,
|
|
bXPathIsEntireExpr && !pXPathComponent->pNext
|
|
? TRUE
|
|
: FALSE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If we didn't get any nodes that passed here, we have nothing
|
|
// to give back.
|
|
|
|
if (pXPathComponent->pCurrNode)
|
|
{
|
|
if ((pXPathComponent = pXPathComponent->pNext) == NULL)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if (pXPathComponent->pPrev &&
|
|
(!pXPathComponent->pPrev->bIsSource || !bUseKeyNodes))
|
|
{
|
|
|
|
// Was no node, go to prior context to get its next node.
|
|
|
|
pXPathComponent = pXPathComponent->pPrev;
|
|
}
|
|
else
|
|
{
|
|
|
|
// There was no prior context, so we do not have a value
|
|
// to return for this xpath. Note that
|
|
// pQNode->currVal.eValType should already have been set to
|
|
// XFLM_MISSING_VAL up above, so no need to set it here.
|
|
|
|
flmAssert( pQNode->currVal.eValType == XFLM_MISSING_VAL);
|
|
fqResetIterator( pQNode, FALSE, bUseKeyNodes);
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// Extract the value from the DOM node pointed to by the
|
|
// last component in the xpath.
|
|
|
|
pXPath->bGettingNodes = TRUE;
|
|
|
|
if (pQNode->pParent && isLogicalOp( pQNode->pParent->nd.op.eOperator))
|
|
{
|
|
pQNode->currVal.eValType = XFLM_BOOL_VAL;
|
|
pQNode->currVal.val.eBool = (pQNode->bNotted ? XFLM_FALSE : XFLM_TRUE);
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = fqGetValueFromNode( m_pDb,
|
|
pXPath->pLastComponent->pCurrNode,
|
|
&pQNode->currVal,
|
|
getMetaDataType( pXPath->pLastComponent))))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Test a value and return a TRUE/FALSE boolean.
|
|
***************************************************************************/
|
|
FSTATIC FLMBOOL fqTestValue(
|
|
FQNODE * pQueryExpr
|
|
)
|
|
{
|
|
FLMBOOL bPassed;
|
|
|
|
switch (pQueryExpr->currVal.eValType)
|
|
{
|
|
case XFLM_BOOL_VAL:
|
|
bPassed = (pQueryExpr->currVal.val.eBool == XFLM_TRUE) ? TRUE : FALSE;
|
|
break;
|
|
case XFLM_UINT_VAL:
|
|
bPassed = (pQueryExpr->currVal.val.uiVal) ? TRUE : FALSE;
|
|
break;
|
|
case XFLM_UINT64_VAL:
|
|
bPassed = (pQueryExpr->currVal.val.ui64Val) ? TRUE : FALSE;
|
|
break;
|
|
case XFLM_INT_VAL:
|
|
bPassed = (pQueryExpr->currVal.val.iVal) ? TRUE : FALSE;
|
|
break;
|
|
case XFLM_INT64_VAL:
|
|
bPassed = (pQueryExpr->currVal.val.i64Val) ? TRUE : FALSE;
|
|
break;
|
|
case XFLM_BINARY_VAL:
|
|
case XFLM_UTF8_VAL:
|
|
bPassed = (pQueryExpr->currVal.uiDataLen) ? TRUE : FALSE;
|
|
break;
|
|
default:
|
|
bPassed = FALSE;
|
|
break;
|
|
}
|
|
return( bPassed);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next value for a function.
|
|
***************************************************************************/
|
|
RCODE F_Query::getNextFunctionValue(
|
|
IF_DOMNode * pContextNode,
|
|
FLMBOOL bForward,
|
|
FQNODE * pCurrNode,
|
|
F_DynaBuf * pDynaBuf)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
ValIterator eIterator;
|
|
FLMBYTE ucValBuf [sizeof( FLMUINT64)];
|
|
FLMBYTE * pucVal;
|
|
IF_DOMNode * pNode = NULL;
|
|
FLMBOOL bPassed;
|
|
|
|
// Evaluate the parameter expression, if any.
|
|
|
|
if (pCurrNode->nd.pQFunction->pFirstArg)
|
|
{
|
|
if (RC_BAD( rc = evalExpr( pContextNode, TRUE, FALSE,
|
|
pCurrNode->nd.pQFunction->pFirstArg->pExpr,
|
|
&bPassed, &pNode)))
|
|
{
|
|
if (rc == NE_XFLM_EOF_HIT || rc == NE_XFLM_BOF_HIT)
|
|
{
|
|
flmAssert( pNode == NULL);
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
else
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bForward)
|
|
{
|
|
eIterator = (!pCurrNode->bUsedValue) ? GET_FIRST_VAL : GET_NEXT_VAL;
|
|
}
|
|
else
|
|
{
|
|
eIterator = (!pCurrNode->bUsedValue) ? GET_LAST_VAL : GET_PREV_VAL;
|
|
}
|
|
|
|
fqReleaseNodeValue( pCurrNode);
|
|
pCurrNode->currVal.uiDataLen = 0;
|
|
pCurrNode->currVal.uiFlags = 0;
|
|
pDynaBuf->truncateData( 0);
|
|
if (RC_BAD( rc = pCurrNode->nd.pQFunction->pFuncObj->getValue( (IF_Db *)m_pDb,
|
|
pNode, eIterator, &pCurrNode->currVal.eValType,
|
|
&pCurrNode->bLastValue, ucValBuf, pDynaBuf)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
pucVal = &ucValBuf [0];
|
|
switch (pCurrNode->currVal.eValType)
|
|
{
|
|
case XFLM_BOOL_VAL:
|
|
pCurrNode->currVal.val.eBool = *((XFlmBoolType *)pucVal);
|
|
break;
|
|
case XFLM_UINT_VAL:
|
|
pCurrNode->currVal.val.uiVal = *((FLMUINT *)pucVal);
|
|
break;
|
|
case XFLM_UINT64_VAL:
|
|
pCurrNode->currVal.val.ui64Val = *((FLMUINT64 *)pucVal);
|
|
break;
|
|
case XFLM_INT_VAL:
|
|
pCurrNode->currVal.val.iVal = *((FLMINT *)pucVal);
|
|
break;
|
|
case XFLM_INT64_VAL:
|
|
pCurrNode->currVal.val.i64Val = *((FLMINT64 *)pucVal);
|
|
break;
|
|
case XFLM_BINARY_VAL:
|
|
case XFLM_UTF8_VAL:
|
|
pCurrNode->currVal.uiDataLen = pDynaBuf->getDataLength();
|
|
pCurrNode->currVal.val.pucBuf = (FLMBYTE *)pDynaBuf->getBufferPtr();
|
|
break;
|
|
case XFLM_MISSING_VAL:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (pNode)
|
|
{
|
|
pNode->Release();
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Reset the entire query tree. It could have been left in a partial
|
|
evaluation state from the last document evaluated.
|
|
***************************************************************************/
|
|
FSTATIC void fqResetQueryTree(
|
|
FQNODE * pQueryTree,
|
|
FLMBOOL bUseKeyNodes,
|
|
FLMBOOL bResetAllXPaths)
|
|
{
|
|
FQNODE * pCurrNode = pQueryTree;
|
|
|
|
for (;;)
|
|
{
|
|
fqReleaseNodeValue( pCurrNode);
|
|
pCurrNode->bUsedValue = FALSE;
|
|
pCurrNode->bLastValue = FALSE;
|
|
if (pCurrNode->pFirstChild)
|
|
{
|
|
pCurrNode = pCurrNode->pFirstChild;
|
|
}
|
|
else
|
|
{
|
|
if (pCurrNode->eNodeType == FLM_XPATH_NODE)
|
|
{
|
|
|
|
// Don't reset the iterator if this xpath node is
|
|
// the root node of the outermost query.
|
|
|
|
if (bResetAllXPaths ||
|
|
pCurrNode->pParent ||
|
|
pCurrNode->nd.pXPath->pFirstComponent->pXPathContext)
|
|
{
|
|
fqResetIterator( pCurrNode, FALSE, bUseKeyNodes);
|
|
}
|
|
}
|
|
while (!pCurrNode->pNextSib)
|
|
{
|
|
if ((pCurrNode = pCurrNode->pParent) == NULL)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (!pCurrNode)
|
|
{
|
|
break;
|
|
}
|
|
pCurrNode = pCurrNode->pNextSib;
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Evaluate an operand in a query expression. If it is an operand of a
|
|
boolean operator (AND/OR), we may be able to short-circuit the
|
|
evaluation of the other operand. This will be propagated up the
|
|
query tree as far as possible.
|
|
If it is an operand of some other operator, we check to see if we
|
|
have both operands. If not, we move *ppCurrNode to get the next
|
|
sibling so we can evalute the other operand.
|
|
VISIT: We could short-circuit arithmetic operations if an operand
|
|
is missing.
|
|
***************************************************************************/
|
|
FSTATIC RCODE fqTryEvalOperator(
|
|
FLMUINT uiLanguage,
|
|
FQNODE ** ppCurrNode)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pCurrNode = *ppCurrNode;
|
|
XFlmBoolType eBoolVal;
|
|
XFlmBoolType eBoolPartialEval;
|
|
|
|
for (;;)
|
|
{
|
|
if (!pCurrNode->pParent)
|
|
{
|
|
pCurrNode = NULL;
|
|
break;
|
|
}
|
|
|
|
// If the current node's parent is an AND or OR
|
|
// operator, see if we even need to go to the next
|
|
// sibling.
|
|
|
|
flmAssert( pCurrNode->pParent->eNodeType == FLM_OPERATOR_NODE);
|
|
if (pCurrNode->pParent->nd.op.eOperator == XFLM_AND_OP ||
|
|
pCurrNode->pParent->nd.op.eOperator == XFLM_OR_OP)
|
|
{
|
|
eBoolVal = XFLM_UNKNOWN;
|
|
eBoolPartialEval = pCurrNode->pParent->nd.op.eOperator == XFLM_AND_OP
|
|
? XFLM_FALSE
|
|
: XFLM_TRUE;
|
|
if (pCurrNode->eNodeType == FLM_OPERATOR_NODE)
|
|
{
|
|
|
|
// It may not have been evaluated because of missing
|
|
// XPATH values in one or both operands, in which case
|
|
// its state will be XFLM_MISSING_VALUE. If it was
|
|
// evaluated, its state should show a boolean value.
|
|
|
|
if (pCurrNode->currVal.eValType == XFLM_MISSING_VAL)
|
|
{
|
|
eBoolVal = (pCurrNode->bNotted ? XFLM_TRUE : XFLM_FALSE);
|
|
}
|
|
else
|
|
{
|
|
flmAssert( pCurrNode->currVal.eValType == XFLM_BOOL_VAL);
|
|
eBoolVal = pCurrNode->currVal.val.eBool;
|
|
}
|
|
}
|
|
else if (pCurrNode->eNodeType == FLM_XPATH_NODE)
|
|
{
|
|
if (!pCurrNode->bNotted)
|
|
{
|
|
eBoolVal = (pCurrNode->currVal.eValType == XFLM_MISSING_VAL)
|
|
? XFLM_FALSE
|
|
: XFLM_TRUE;
|
|
}
|
|
else
|
|
{
|
|
eBoolVal = (pCurrNode->currVal.eValType == XFLM_MISSING_VAL)
|
|
? XFLM_TRUE
|
|
: XFLM_FALSE;
|
|
}
|
|
}
|
|
else if (pCurrNode->eNodeType == FLM_VALUE_NODE)
|
|
{
|
|
|
|
// Only allowed value node underneath a logical operator is
|
|
// a boolean value that has a value of XFLM_UNKNOWN.
|
|
// XFLM_FALSE and XFLM_TRUE will already have been weeded out.
|
|
|
|
flmAssert( pCurrNode->currVal.eValType == XFLM_BOOL_VAL &&
|
|
pCurrNode->currVal.val.eBool == XFLM_UNKNOWN);
|
|
|
|
// No need to set eBoolVal to XFLM_UNKNOWN, because it will never
|
|
// match eBoolPartialEval in the test below. eBoolPartialEval
|
|
// is always either XFLM_FALSE or XFLM_TRUE.
|
|
|
|
// eBoolVal = XFLM_UNKNOWN;
|
|
|
|
}
|
|
else
|
|
{
|
|
flmAssert( pCurrNode->eNodeType == FLM_FUNCTION_NODE);
|
|
if (!pCurrNode->bNotted)
|
|
{
|
|
eBoolVal = fqTestValue( pCurrNode) ? XFLM_TRUE : XFLM_FALSE;
|
|
}
|
|
else
|
|
{
|
|
eBoolVal = fqTestValue( pCurrNode) ? XFLM_FALSE : XFLM_TRUE;
|
|
}
|
|
}
|
|
if (eBoolVal == eBoolPartialEval)
|
|
{
|
|
pCurrNode = pCurrNode->pParent;
|
|
pCurrNode->currVal.eValType = XFLM_BOOL_VAL;
|
|
pCurrNode->currVal.val.eBool = eBoolVal;
|
|
}
|
|
else if (pCurrNode->pNextSib)
|
|
{
|
|
pCurrNode = pCurrNode->pNextSib;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
pCurrNode = pCurrNode->pParent;
|
|
if (RC_BAD( rc = fqEvalOperator( uiLanguage, pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// Visit: can shortcircuit comparison operators if the
|
|
// value is missing.
|
|
|
|
if (pCurrNode->pNextSib)
|
|
{
|
|
pCurrNode = pCurrNode->pNextSib;
|
|
break;
|
|
}
|
|
pCurrNode = pCurrNode->pParent;
|
|
|
|
// All operands are now present - do evaluation
|
|
|
|
if (RC_BAD( rc = fqEvalOperator( uiLanguage, pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If this is a comparison operator, see if we need to
|
|
// do existential or universal comparison.
|
|
|
|
if (isCompareOp( pCurrNode->nd.op.eOperator))
|
|
{
|
|
|
|
// Evaluation should have forced current value to be a
|
|
// boolean TRUE or FALSE value.
|
|
|
|
flmAssert( pCurrNode->currVal.eValType == XFLM_BOOL_VAL);
|
|
if ((isUniversal( pCurrNode) &&
|
|
pCurrNode->currVal.val.eBool == XFLM_TRUE) ||
|
|
(isExistential( pCurrNode) &&
|
|
pCurrNode->currVal.val.eBool == XFLM_FALSE))
|
|
{
|
|
|
|
// Go back down right hand side to get next rightmost
|
|
// operand
|
|
|
|
while (pCurrNode->pLastChild)
|
|
{
|
|
pCurrNode = pCurrNode->pLastChild;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
*ppCurrNode = pCurrNode;
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Backup the query tree
|
|
***************************************************************************/
|
|
FSTATIC FQNODE * fqBackupTree(
|
|
FQNODE * pCurrNode,
|
|
FLMBOOL * pbGetNodeValue
|
|
)
|
|
{
|
|
flmAssert( !(*pbGetNodeValue));
|
|
|
|
// This was the last value, backup
|
|
|
|
pCurrNode->bUsedValue = FALSE;
|
|
pCurrNode->bLastValue = FALSE;
|
|
|
|
// If parent node is a logical operator, there is
|
|
// nothing more we can do on this branch, so we fall
|
|
// through and process the thing.
|
|
|
|
if (pCurrNode->pParent &&
|
|
!isLogicalOp( pCurrNode->pParent->nd.op.eOperator))
|
|
{
|
|
while (!pCurrNode->pPrevSib)
|
|
{
|
|
if ((pCurrNode = pCurrNode->pParent) == NULL)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Don't backup any higher than comparison operators
|
|
// We are only backing up so we can do existential
|
|
// and universal on them with multiple left and right
|
|
// operands.
|
|
|
|
flmAssert( pCurrNode->eNodeType == FLM_OPERATOR_NODE);
|
|
if (isCompareOp( pCurrNode->nd.op.eOperator))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// If we came up to a parent that is a comparison operator
|
|
// we are done - there is noplace else to backup to, so
|
|
// we will fall through.
|
|
|
|
if (!isCompareOp( pCurrNode->nd.op.eOperator))
|
|
{
|
|
|
|
// has to be a prev sib at this point.
|
|
|
|
pCurrNode = pCurrNode->pPrevSib;
|
|
|
|
// Go down to rightmost child
|
|
|
|
while (pCurrNode->pLastChild)
|
|
{
|
|
pCurrNode = pCurrNode->pLastChild;
|
|
}
|
|
*pbGetNodeValue = TRUE;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( pCurrNode);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the value of a function node, or move to another node.
|
|
***************************************************************************/
|
|
RCODE F_Query::getFuncValue(
|
|
IF_DOMNode * pContextNode,
|
|
FLMBOOL bForward,
|
|
FQNODE ** ppCurrNode,
|
|
FLMBOOL * pbGetNodeValue,
|
|
F_DynaBuf * pDynaBuf)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pCurrNode = *ppCurrNode;
|
|
|
|
// We currently only support user-defined functions.
|
|
|
|
flmAssert( pCurrNode->nd.pQFunction->pFuncObj);
|
|
flmAssert( !(*pbGetNodeValue));
|
|
|
|
if (pCurrNode->bLastValue)
|
|
{
|
|
pCurrNode = fqBackupTree( pCurrNode, pbGetNodeValue);
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = getNextFunctionValue( pContextNode,
|
|
bForward, pCurrNode,
|
|
pDynaBuf)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Handle case where the function call is the only thing in the
|
|
// query.
|
|
|
|
if (!pCurrNode->pParent)
|
|
{
|
|
FLMBOOL bTmpPassed;
|
|
FLMBOOL bFinal;
|
|
|
|
if (pCurrNode->currVal.eValType == XFLM_MISSING_VAL)
|
|
{
|
|
bFinal = TRUE;
|
|
|
|
// No more values. If it is universal, and we got to this
|
|
// point, it means that all prior values passed, so we set
|
|
// bTmpPassed to TRUE in that case. If it is existential and
|
|
// we got to this point, it means that all prior values
|
|
// failed, so we set bTmpPassed to FALSE in that case.
|
|
|
|
bTmpPassed = isUniversal( pCurrNode);
|
|
}
|
|
else
|
|
{
|
|
bTmpPassed = fqTestValue( pCurrNode);
|
|
bFinal = (pCurrNode->bLastValue ||
|
|
(isUniversal( pCurrNode) && !bTmpPassed) ||
|
|
(isExistential( pCurrNode) && bTmpPassed))
|
|
? TRUE
|
|
: FALSE;
|
|
}
|
|
fqReleaseNodeValue( pCurrNode);
|
|
if (!bFinal)
|
|
{
|
|
*pbGetNodeValue = TRUE;
|
|
goto Exit;
|
|
}
|
|
pCurrNode->currVal.eValType = XFLM_BOOL_VAL;
|
|
pCurrNode->currVal.val.eBool = bTmpPassed ? XFLM_TRUE : XFLM_FALSE;
|
|
|
|
// Must set to NULL so that evalExpr will break out of loop.
|
|
|
|
pCurrNode = NULL;
|
|
goto Exit;
|
|
}
|
|
|
|
// Handle case where it is not the top node of the query.
|
|
|
|
if (pCurrNode->currVal.eValType != XFLM_MISSING_VAL)
|
|
{
|
|
pCurrNode->bUsedValue = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (!pCurrNode->bUsedValue)
|
|
{
|
|
|
|
// Need to handle missing value if we have not done so yet.
|
|
|
|
pCurrNode->bUsedValue = TRUE;
|
|
pCurrNode->bLastValue = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// This was the last value, backup
|
|
|
|
pCurrNode = fqBackupTree( pCurrNode, pbGetNodeValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
*ppCurrNode = pCurrNode;
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the value of an XPATH node or move to another node.
|
|
***************************************************************************/
|
|
RCODE F_Query::getXPathValue(
|
|
IF_DOMNode * pContextNode,
|
|
FLMBOOL bForward,
|
|
FQNODE ** ppCurrNode,
|
|
FLMBOOL * pbGetNodeValue,
|
|
FLMBOOL bUseKeyNodes,
|
|
FLMBOOL bXPathIsEntireExpr
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pCurrNode = *ppCurrNode;
|
|
|
|
flmAssert( !(*pbGetNodeValue));
|
|
flmAssert( !(*pbGetNodeValue));
|
|
|
|
// See if value is already set from an index.
|
|
|
|
if (pCurrNode->nd.pXPath->bHavePassingNode && bUseKeyNodes)
|
|
{
|
|
if (pCurrNode->bUsedValue)
|
|
{
|
|
pCurrNode->currVal.eValType = XFLM_MISSING_VAL;
|
|
}
|
|
else
|
|
{
|
|
pCurrNode->currVal.eValType = XFLM_PASSING_VAL;
|
|
if (bXPathIsEntireExpr)
|
|
{
|
|
m_pCurrOpt->ui64NodesTested++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (bUseKeyNodes && pCurrNode->nd.pXPath->bIsSource &&
|
|
pCurrNode->nd.pXPath->pLastComponent->bIsSource)
|
|
{
|
|
fqReleaseNodeValue( pCurrNode);
|
|
if (!pCurrNode->bUsedValue)
|
|
{
|
|
XPATH_COMPONENT * pLastComponent =
|
|
pCurrNode->nd.pXPath->pLastComponent;
|
|
|
|
if (RC_BAD( rc = fqGetValueFromNode( m_pDb,
|
|
pLastComponent->pKeyNode,
|
|
&pCurrNode->currVal,
|
|
getMetaDataType( pLastComponent))))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pCurrNode->bUsedValue = TRUE;
|
|
if (bXPathIsEntireExpr)
|
|
{
|
|
m_pCurrOpt->ui64NodesTested++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = getNextXPathValue( pContextNode,
|
|
bForward, bUseKeyNodes,
|
|
bXPathIsEntireExpr, pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// If this expression is an XPATH, set pCurrNode to NULL so that
|
|
// it will cause evalExpr to exit from its loop and return
|
|
// the node we have found. No need to attempt to evaluate
|
|
// an operators, there are none.
|
|
|
|
if (!pCurrNode->pParent)
|
|
{
|
|
pCurrNode = NULL;
|
|
goto Exit;
|
|
}
|
|
|
|
if (pCurrNode->currVal.eValType != XFLM_MISSING_VAL)
|
|
{
|
|
pCurrNode->bUsedValue = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fqResetIterator( pCurrNode, FALSE, bUseKeyNodes);
|
|
|
|
// See if we used the "missing" value in evaluating
|
|
// the operator. Need to if we have not yet.
|
|
// Otherwise, we backup in the tree because we will
|
|
// have used at least one value.
|
|
|
|
if (!pCurrNode->bUsedValue)
|
|
{
|
|
pCurrNode->bUsedValue = TRUE;
|
|
}
|
|
else
|
|
{
|
|
pCurrNode = fqBackupTree( pCurrNode, pbGetNodeValue);
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
*ppCurrNode = pCurrNode;
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Set the return value for an expression.
|
|
***************************************************************************/
|
|
RCODE F_Query::setExprReturnValue(
|
|
FLMBOOL bUseKeyNodes,
|
|
FQNODE * pQueryExpr,
|
|
FLMBOOL * pbPassed,
|
|
IF_DOMNode ** ppNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
if (pQueryExpr->eNodeType == FLM_XPATH_NODE)
|
|
{
|
|
if (pQueryExpr->currVal.eValType != XFLM_MISSING_VAL)
|
|
{
|
|
|
|
// Need to clear the current node's value.
|
|
|
|
fqReleaseNodeValue( pQueryExpr);
|
|
if (ppNode)
|
|
{
|
|
|
|
// *ppNode should have been initialized to NULL at the
|
|
// beginning of the evalExpr routine.
|
|
|
|
flmAssert( *ppNode == NULL);
|
|
if (bUseKeyNodes &&
|
|
pQueryExpr->nd.pXPath->pLastComponent->pKeyNode)
|
|
{
|
|
*ppNode = pQueryExpr->nd.pXPath->pLastComponent->pKeyNode;
|
|
}
|
|
else
|
|
{
|
|
*ppNode = pQueryExpr->nd.pXPath->pLastComponent->pCurrNode;
|
|
}
|
|
(*ppNode)->AddRef();
|
|
m_pCurrOpt->ui64NodesPassed++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
if (pbPassed)
|
|
{
|
|
*pbPassed = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// *pbPassed should have been initialized to FALSE in evalExpr,
|
|
// if pbPassed is non-NULL.
|
|
|
|
flmAssert( !pbPassed || !(*pbPassed));
|
|
|
|
// *ppNode will already have been set to NULL - at beginning
|
|
// of evalExpr - so no need to do it here. Just assert
|
|
// that either ppNode is NULL or *ppNode is NULL.
|
|
|
|
flmAssert( !ppNode || *ppNode == NULL);
|
|
|
|
// If pCurrNode or pKeyNode is non-NULL it means we found one, but
|
|
// it doesn't have any data. That's ok, because this is
|
|
// only an exists test, and it should pass.
|
|
|
|
if (bUseKeyNodes &&
|
|
pQueryExpr->nd.pXPath->pLastComponent->pKeyNode)
|
|
{
|
|
if (pbPassed)
|
|
{
|
|
*pbPassed = TRUE;
|
|
}
|
|
if (ppNode)
|
|
{
|
|
*ppNode = pQueryExpr->nd.pXPath->pLastComponent->pKeyNode;
|
|
(*ppNode)->AddRef();
|
|
}
|
|
m_pCurrOpt->ui64NodesPassed++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else if (pQueryExpr->nd.pXPath->pLastComponent->pCurrNode)
|
|
{
|
|
if (pbPassed)
|
|
{
|
|
*pbPassed = TRUE;
|
|
}
|
|
if (ppNode)
|
|
{
|
|
*ppNode = pQueryExpr->nd.pXPath->pLastComponent->pCurrNode;
|
|
(*ppNode)->AddRef();
|
|
}
|
|
m_pCurrOpt->ui64NodesPassed++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This routine is only called for expressions that are just an
|
|
// XPATH. If it is the outermost XPATH, we don't want to
|
|
// reset the iterator, as that is what we are using to
|
|
// iterate with.
|
|
|
|
if (pQueryExpr->nd.pXPath->pFirstComponent->pXPathContext)
|
|
{
|
|
fqResetIterator( pQueryExpr, FALSE, bUseKeyNodes);
|
|
}
|
|
}
|
|
else if (pbPassed)
|
|
{
|
|
*pbPassed = fqTestValue( pQueryExpr);
|
|
if (ppNode)
|
|
{
|
|
if (*pbPassed)
|
|
{
|
|
*ppNode = m_pCurrDoc;
|
|
(*ppNode)->AddRef();
|
|
}
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Evaluate a query expression.
|
|
***************************************************************************/
|
|
RCODE F_Query::evalExpr(
|
|
IF_DOMNode * pContextNode,
|
|
FLMBOOL bForward,
|
|
FLMBOOL bUseKeyNodes,
|
|
FQNODE * pQueryExpr,
|
|
FLMBOOL * pbPassed,
|
|
IF_DOMNode ** ppNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pCurrNode = pQueryExpr;
|
|
FLMBOOL bXPathIsEntireExpr = FALSE;
|
|
FLMBYTE ucDynaBuf[ 64];
|
|
F_DynaBuf dynaBuf( ucDynaBuf, sizeof( ucDynaBuf));
|
|
FLMBOOL bGetNodeValue;
|
|
|
|
// IMPORTANT NOTE: A non-NULL ppNode should only be passed in for the
|
|
// very outermost evalExpr, not for nested ones. We use this to determine
|
|
// whether to count documents read and documents passed.
|
|
|
|
if (ppNode && *ppNode)
|
|
{
|
|
(*ppNode)->Release();
|
|
*ppNode = NULL;
|
|
}
|
|
if (pbPassed)
|
|
{
|
|
*pbPassed = FALSE;
|
|
}
|
|
|
|
// If the query is empty, it passes
|
|
|
|
if (!pQueryExpr)
|
|
{
|
|
if (pbPassed)
|
|
{
|
|
*pbPassed = TRUE;
|
|
}
|
|
if (ppNode)
|
|
{
|
|
*ppNode = m_pCurrDoc;
|
|
(*ppNode)->AddRef();
|
|
}
|
|
goto Exit;
|
|
}
|
|
|
|
if (pQueryExpr->eNodeType == FLM_XPATH_NODE &&
|
|
!pQueryExpr->nd.pXPath->pFirstComponent->pXPathContext)
|
|
{
|
|
bXPathIsEntireExpr = TRUE;
|
|
}
|
|
fqResetQueryTree( pQueryExpr, bUseKeyNodes, m_bResetAllXPaths);
|
|
m_bResetAllXPaths = FALSE;
|
|
|
|
// Perform the evaluation
|
|
|
|
pCurrNode = pQueryExpr;
|
|
for (;;)
|
|
{
|
|
while (pCurrNode->pFirstChild)
|
|
{
|
|
pCurrNode = pCurrNode->pFirstChild;
|
|
}
|
|
|
|
// We should be positioned on a leaf node that is either a
|
|
// value, a function, or an xpath.
|
|
|
|
bGetNodeValue = FALSE;
|
|
if (pCurrNode->eNodeType == FLM_VALUE_NODE)
|
|
{
|
|
if (!pCurrNode->pParent)
|
|
{
|
|
pCurrNode = NULL;
|
|
break;
|
|
}
|
|
if (pCurrNode->bUsedValue)
|
|
{
|
|
pCurrNode = fqBackupTree( pCurrNode, &bGetNodeValue);
|
|
}
|
|
else
|
|
{
|
|
pCurrNode->bUsedValue = TRUE;
|
|
}
|
|
}
|
|
else if (pCurrNode->eNodeType == FLM_FUNCTION_NODE)
|
|
{
|
|
if (RC_BAD( rc = getFuncValue( pContextNode, bForward,
|
|
&pCurrNode, &bGetNodeValue,
|
|
&dynaBuf)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// Better be an xpath at this point
|
|
|
|
flmAssert( pCurrNode->eNodeType == FLM_XPATH_NODE);
|
|
if (RC_BAD( rc = getXPathValue( pContextNode, bForward, &pCurrNode,
|
|
&bGetNodeValue, bUseKeyNodes,
|
|
bXPathIsEntireExpr)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
if (!pCurrNode)
|
|
{
|
|
break;
|
|
}
|
|
if (bGetNodeValue)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// When we get to this point, we have at least one leaf
|
|
// level operand in hand - pCurrNode.
|
|
// See if we can evaluate the operator of pCurrNode.
|
|
// This will take care of any short-circuiting evaluation
|
|
// that can be done.
|
|
|
|
if (RC_BAD( rc = fqTryEvalOperator( m_uiLanguage, &pCurrNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!pCurrNode)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (RC_BAD( rc = setExprReturnValue( bUseKeyNodes, pQueryExpr,
|
|
pbPassed, ppNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Release the resources of a query expression
|
|
***************************************************************************/
|
|
FSTATIC void fqReleaseQueryExpr(
|
|
FQNODE * pQNode
|
|
)
|
|
{
|
|
// Reset the entire query tree. It could have been left in a partial
|
|
// evaluation state from the last document evaluated.
|
|
|
|
for (;;)
|
|
{
|
|
fqReleaseNodeValue( pQNode);
|
|
pQNode->bUsedValue = FALSE;
|
|
pQNode->bLastValue = FALSE;
|
|
if (pQNode->pFirstChild)
|
|
{
|
|
pQNode = pQNode->pFirstChild;
|
|
}
|
|
else
|
|
{
|
|
if (pQNode->eNodeType == FLM_XPATH_NODE)
|
|
{
|
|
fqResetIterator( pQNode, TRUE, FALSE);
|
|
}
|
|
while (!pQNode->pNextSib)
|
|
{
|
|
if ((pQNode = pQNode->pParent) == NULL)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (!pQNode)
|
|
{
|
|
break;
|
|
}
|
|
pQNode = pQNode->pNextSib;
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Release the resources of a query
|
|
***************************************************************************/
|
|
void FLMAPI F_Query::resetQuery( void)
|
|
{
|
|
if (m_pQuery)
|
|
{
|
|
fqReleaseQueryExpr( m_pQuery);
|
|
}
|
|
|
|
m_eState = XFLM_QUERY_NOT_POSITIONED;
|
|
|
|
if (m_pCurrDoc)
|
|
{
|
|
m_pCurrDoc->Release();
|
|
m_pCurrDoc = NULL;
|
|
}
|
|
|
|
if (m_pCurrNode)
|
|
{
|
|
m_pCurrNode->Release();
|
|
m_pCurrNode = NULL;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get leaf predicate for the current context, changing current
|
|
context, context path, and predicate as needed.
|
|
***************************************************************************/
|
|
void F_Query::useLeafContext(
|
|
FLMBOOL bGetFirst)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (m_pCurrContext->bIntersect)
|
|
{
|
|
if (m_pCurrContext->pSelectedChild)
|
|
{
|
|
flmAssert( !m_pCurrContext->pSelectedPath);
|
|
m_pCurrContext = m_pCurrContext->pSelectedChild;
|
|
}
|
|
else
|
|
{
|
|
flmAssert( m_pCurrContext->pSelectedPath);
|
|
m_pCurrContextPath = m_pCurrContext->pSelectedPath;
|
|
m_pCurrPred = m_pCurrContextPath->pSelectedPred;
|
|
flmAssert( m_pCurrContextPath && m_pCurrPred);
|
|
m_pCurrOpt = &m_pCurrPred->OptInfo;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// In a non-intersect context, pSelectedChild should NOT have
|
|
// been set. Nor should pSelectedPath.
|
|
|
|
flmAssert( !m_pCurrContext->pSelectedChild);
|
|
flmAssert( !m_pCurrContext->pSelectedPath);
|
|
|
|
if (bGetFirst)
|
|
{
|
|
if (m_pCurrContext->pFirstChild)
|
|
{
|
|
m_pCurrContext = m_pCurrContext->pFirstChild;
|
|
}
|
|
else
|
|
{
|
|
m_pCurrContextPath = m_pCurrContext->pFirstPath;
|
|
flmAssert( m_pCurrContextPath);
|
|
|
|
// In a non-intersect context, pSelectedPred should NOT have
|
|
// been set.
|
|
|
|
flmAssert( !m_pCurrContextPath->pSelectedPred);
|
|
m_pCurrPred = m_pCurrContextPath->pFirstPred;
|
|
flmAssert( m_pCurrPred);
|
|
m_pCurrOpt = &m_pCurrPred->OptInfo;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_pCurrContext->pLastChild)
|
|
{
|
|
m_pCurrContext = m_pCurrContext->pLastChild;
|
|
}
|
|
else
|
|
{
|
|
m_pCurrContextPath = m_pCurrContext->pLastPath;
|
|
flmAssert( m_pCurrContextPath);
|
|
|
|
// In a non-intersect context, pSelectedPred should NOT have
|
|
// been set.
|
|
|
|
flmAssert( !m_pCurrContextPath->pSelectedPred);
|
|
m_pCurrPred = m_pCurrContextPath->pLastPred;
|
|
flmAssert( m_pCurrPred);
|
|
m_pCurrOpt = &m_pCurrPred->OptInfo;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get next predicate to evaluate for a query.
|
|
***************************************************************************/
|
|
FLMBOOL F_Query::useNextPredicate( void)
|
|
{
|
|
FLMBOOL bGotNext = FALSE;
|
|
|
|
// If we are in a non-intersecting context, exhaust its
|
|
// context paths and predicates before going up a level.
|
|
|
|
if (!m_pCurrContext->bIntersect)
|
|
{
|
|
if (m_pCurrPred)
|
|
{
|
|
if (m_pCurrPred->pNext)
|
|
{
|
|
m_pCurrPred = m_pCurrPred->pNext;
|
|
m_pCurrOpt = &m_pCurrPred->OptInfo;
|
|
bGotNext = TRUE;
|
|
goto Exit;
|
|
}
|
|
if (m_pCurrContextPath->pNext)
|
|
{
|
|
m_pCurrContextPath = m_pCurrContextPath->pNext;
|
|
|
|
// In a non-intersect context, pSelectedPred should NOT have
|
|
// been set.
|
|
|
|
flmAssert( !m_pCurrContextPath->pSelectedPred);
|
|
m_pCurrPred = m_pCurrContextPath->pFirstPred;
|
|
m_pCurrOpt = &m_pCurrPred->OptInfo;
|
|
bGotNext = TRUE;
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// Go up one context level, if there is one.
|
|
|
|
if (!m_pCurrContext->pParent)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Parent better be an intersecting context
|
|
|
|
m_pCurrContext = m_pCurrContext->pParent;
|
|
flmAssert( m_pCurrContext->bIntersect);
|
|
}
|
|
|
|
// Go back up the context tree to get next context.
|
|
|
|
for (;;)
|
|
{
|
|
OP_CONTEXT * pParent = m_pCurrContext->pParent;
|
|
|
|
// If there is no parent context, we are done.
|
|
|
|
if (!pParent)
|
|
{
|
|
break;
|
|
}
|
|
if (m_pCurrContext->bIntersect)
|
|
{
|
|
|
|
// Parent context should be non-intersecting, so we should
|
|
// go to the sibling context, if there is one.
|
|
|
|
flmAssert( !pParent->bIntersect);
|
|
if (m_pCurrContext->pNextSib)
|
|
{
|
|
m_pCurrContext = m_pCurrContext->pNextSib;
|
|
|
|
// Travel down this part of the context tree to get the
|
|
// "leaf-most" context, context path, and predicate.
|
|
|
|
useLeafContext( TRUE);
|
|
bGotNext = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_pCurrContext->pFirstPath)
|
|
{
|
|
m_pCurrContextPath = m_pCurrContext->pFirstPath;
|
|
|
|
// In a non-intersect context, pSelectedPred should NOT have
|
|
// been set.
|
|
|
|
flmAssert( !m_pCurrContextPath->pSelectedPred);
|
|
m_pCurrPred = m_pCurrContextPath->pFirstPred;
|
|
m_pCurrOpt = &m_pCurrPred->OptInfo;
|
|
bGotNext = TRUE;
|
|
goto Exit;
|
|
}
|
|
|
|
// Parent context better be pointing to this context as its
|
|
// "selected" context.
|
|
|
|
flmAssert( pParent->pSelectedChild == m_pCurrContext);
|
|
|
|
// Parent also better be an intersecting context.
|
|
|
|
flmAssert( pParent->bIntersect);
|
|
}
|
|
m_pCurrContext = pParent;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( bGotNext);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get previous predicate to evaluate for a query.
|
|
***************************************************************************/
|
|
FLMBOOL F_Query::usePrevPredicate( void)
|
|
{
|
|
FLMBOOL bGotPrev = FALSE;
|
|
|
|
// If we are in a non-intersecting context, exhaust its
|
|
// context paths and predicates before going up a level.
|
|
|
|
if (!m_pCurrContext->bIntersect)
|
|
{
|
|
if (m_pCurrPred)
|
|
{
|
|
if (m_pCurrPred->pPrev)
|
|
{
|
|
m_pCurrPred = m_pCurrPred->pPrev;
|
|
m_pCurrOpt = &m_pCurrPred->OptInfo;
|
|
bGotPrev = TRUE;
|
|
goto Exit;
|
|
}
|
|
if (m_pCurrContextPath->pPrev)
|
|
{
|
|
m_pCurrContextPath = m_pCurrContextPath->pPrev;
|
|
|
|
// In a non-intersect context, pSelectedPred should NOT have
|
|
// been set.
|
|
|
|
flmAssert( !m_pCurrContextPath->pSelectedPred);
|
|
m_pCurrPred = m_pCurrContextPath->pLastPred;
|
|
m_pCurrOpt = &m_pCurrPred->OptInfo;
|
|
bGotPrev = TRUE;
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// Go up one context level, if there is one.
|
|
|
|
if (!m_pCurrContext->pParent)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Parent better be an intersecting context
|
|
|
|
m_pCurrContext = m_pCurrContext->pParent;
|
|
flmAssert( m_pCurrContext->bIntersect);
|
|
}
|
|
|
|
// Go back up the context tree to get previous context.
|
|
|
|
for (;;)
|
|
{
|
|
OP_CONTEXT * pParent = m_pCurrContext->pParent;
|
|
|
|
// If there is no parent context, we are done.
|
|
|
|
if (!pParent)
|
|
{
|
|
break;
|
|
}
|
|
if (m_pCurrContext->bIntersect)
|
|
{
|
|
|
|
// Parent context should be non-intersecting, so we should
|
|
// go to the sibling context, if there is one.
|
|
|
|
flmAssert( !pParent->bIntersect);
|
|
if (m_pCurrContext->pPrevSib)
|
|
{
|
|
m_pCurrContext = m_pCurrContext->pPrevSib;
|
|
|
|
// Travel down this part of the context tree to get the
|
|
// "leaf-most" context, context path, and predicate.
|
|
|
|
useLeafContext( FALSE);
|
|
bGotPrev = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_pCurrContext->pLastPath)
|
|
{
|
|
m_pCurrContextPath = m_pCurrContext->pLastPath;
|
|
|
|
// In a non-intersect context, pSelectedPred should NOT have
|
|
// been set.
|
|
|
|
flmAssert( !m_pCurrContextPath->pSelectedPred);
|
|
m_pCurrPred = m_pCurrContextPath->pLastPred;
|
|
m_pCurrOpt = &m_pCurrPred->OptInfo;
|
|
bGotPrev = TRUE;
|
|
goto Exit;
|
|
}
|
|
|
|
// Parent context better be pointing to this context as its
|
|
// "selected" context.
|
|
|
|
flmAssert( pParent->pSelectedChild == m_pCurrContext);
|
|
|
|
// Parent also better be an intersecting context.
|
|
|
|
flmAssert( pParent->bIntersect);
|
|
}
|
|
m_pCurrContext = pParent;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( bGotPrev);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next/previous node for an application predicate.
|
|
***************************************************************************/
|
|
RCODE F_Query::getAppNode(
|
|
FLMBOOL * pbFirstLast,
|
|
FLMBOOL bForward,
|
|
XPATH_COMPONENT * pXPathComponent
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
IF_QueryNodeSource * pNodeSource = m_pCurrPred->pNodeSource;
|
|
FLMUINT uiCurrTime;
|
|
FLMUINT uiElapsedTime;
|
|
FLMUINT uiTimeLimit = m_uiTimeLimit;
|
|
FLMUINT64 ui64DocId;
|
|
|
|
for (;;)
|
|
{
|
|
|
|
// Reset the timeout value everytime through the loop, if it
|
|
// is non-zero.
|
|
|
|
if (uiTimeLimit)
|
|
{
|
|
uiCurrTime = FLM_GET_TIMER();
|
|
uiElapsedTime = FLM_ELAPSED_TIME( uiCurrTime, m_uiStartTime);
|
|
if (uiElapsedTime >= m_uiTimeLimit)
|
|
{
|
|
rc = RC_SET( NE_XFLM_TIMEOUT);
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
uiTimeLimit = FLM_TIMER_UNITS_TO_MILLI( (m_uiTimeLimit - uiElapsedTime));
|
|
|
|
// Always give at least one milli-second.
|
|
|
|
if (!uiTimeLimit)
|
|
{
|
|
uiTimeLimit = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pXPathComponent->pKeyNode)
|
|
{
|
|
pXPathComponent->pKeyNode->Release();
|
|
pXPathComponent->pKeyNode = NULL;
|
|
}
|
|
|
|
// Get the next or previous key from the index.
|
|
|
|
if (bForward)
|
|
{
|
|
rc = (RCODE)(*pbFirstLast
|
|
? pNodeSource->getFirst( (IF_Db *)m_pDb, NULL,
|
|
&pXPathComponent->pKeyNode,
|
|
uiTimeLimit, m_pQueryStatus)
|
|
: pNodeSource->getNext( (IF_Db *)m_pDb, NULL,
|
|
&pXPathComponent->pKeyNode,
|
|
uiTimeLimit, m_pQueryStatus));
|
|
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_EOF_HIT)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rc = (RCODE)(*pbFirstLast
|
|
? pNodeSource->getLast( (IF_Db *)m_pDb, NULL,
|
|
&pXPathComponent->pKeyNode,
|
|
uiTimeLimit, m_pQueryStatus)
|
|
: pNodeSource->getPrev( (IF_Db *)m_pDb, NULL,
|
|
&pXPathComponent->pKeyNode,
|
|
uiTimeLimit, m_pQueryStatus));
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_BOF_HIT)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
goto Exit;
|
|
}
|
|
}
|
|
*pbFirstLast = FALSE;
|
|
|
|
// If we are eliminating duplicates, see if the document
|
|
// has already been processed. If so, skip the key.
|
|
|
|
if (RC_BAD( rc = pXPathComponent->pKeyNode->getDocumentId( (IF_Db *)m_pDb,
|
|
&ui64DocId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (m_pDocIdSet)
|
|
{
|
|
if (RC_BAD( rc = m_pDocIdSet->findMatch( &ui64DocId, NULL)))
|
|
{
|
|
if (rc != NE_XFLM_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Document has already been passed, go to next/prev key.
|
|
|
|
m_pCurrOpt->ui64KeyHadDupDoc++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// At this point, the key has passed at least the predicate function.
|
|
// Get the document node.
|
|
|
|
if (RC_BAD( rc = m_pDb->getNode( m_uiCollection, ui64DocId, &m_pCurrDoc)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Found one!
|
|
|
|
break;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get an FQVALUE from a key.
|
|
***************************************************************************/
|
|
FSTATIC RCODE fqGetValueFromKey(
|
|
FLMUINT uiDataType,
|
|
F_DataVector * pKey,
|
|
FQVALUE * pQValue,
|
|
FLMBYTE ** ppucValue,
|
|
FLMUINT uiValueBufSize
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
pQValue->uiFlags = 0;
|
|
switch (uiDataType)
|
|
{
|
|
case XFLM_NODATA_TYPE:
|
|
|
|
// Should have been set to missing on the outside
|
|
|
|
flmAssert( pQValue->eValType == XFLM_MISSING_VAL);
|
|
pQValue->eValType = XFLM_BOOL_VAL;
|
|
pQValue->val.eBool = XFLM_TRUE;
|
|
break;
|
|
case XFLM_TEXT_TYPE:
|
|
pQValue->uiDataLen = pKey->getDataLength( 0) + 1;
|
|
if (pQValue->uiDataLen > uiValueBufSize)
|
|
{
|
|
if (RC_BAD( rc = f_alloc( pQValue->uiDataLen, ppucValue)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
pQValue->val.pucBuf = *ppucValue;
|
|
if (RC_BAD( rc = pKey->getUTF8( 0, pQValue->val.pucBuf,
|
|
&pQValue->uiDataLen)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQValue->eValType = XFLM_UTF8_VAL;
|
|
break;
|
|
case XFLM_NUMBER_TYPE:
|
|
|
|
// First, see if we can get it as a UINT - the most common
|
|
// type.
|
|
|
|
if (RC_OK( rc = pKey->getUINT( 0, &pQValue->val.uiVal)))
|
|
{
|
|
pQValue->eValType = XFLM_UINT_VAL;
|
|
}
|
|
else if (rc == NE_XFLM_CONV_NUM_OVERFLOW)
|
|
{
|
|
if (RC_BAD( rc = pKey->getUINT64( 0,
|
|
&pQValue->val.ui64Val)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQValue->eValType = XFLM_UINT64_VAL;
|
|
}
|
|
else if (rc == NE_XFLM_CONV_NUM_UNDERFLOW)
|
|
{
|
|
if (RC_OK( rc = pKey->getINT( 0, &pQValue->val.iVal)))
|
|
{
|
|
pQValue->eValType = XFLM_INT_VAL;
|
|
}
|
|
else if (rc == NE_XFLM_CONV_NUM_UNDERFLOW)
|
|
{
|
|
if (RC_BAD( rc = pKey->getINT64( 0,
|
|
&pQValue->val.i64Val)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQValue->eValType = XFLM_INT64_VAL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
case XFLM_BINARY_TYPE:
|
|
pQValue->uiDataLen = pKey->getDataLength( 0) + 1;
|
|
if (pQValue->uiDataLen > uiValueBufSize)
|
|
{
|
|
if (RC_BAD( rc = f_alloc( pQValue->uiDataLen, ppucValue)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
pQValue->val.pucBuf = *ppucValue;
|
|
if (RC_BAD( rc = pKey->getBinary( 0, pQValue->val.pucBuf,
|
|
&pQValue->uiDataLen)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pQValue->eValType = XFLM_BINARY_VAL;
|
|
break;
|
|
default:
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_NOT_IMPLEMENTED);
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Test a value against the specified predicate.
|
|
***************************************************************************/
|
|
FSTATIC RCODE fqPredCompare(
|
|
FLMUINT uiLanguage,
|
|
PATH_PRED * pPred,
|
|
FQVALUE * pQValue,
|
|
FLMBOOL * pbPasses
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
XFlmBoolType eBool;
|
|
|
|
switch (pPred->eOperator)
|
|
{
|
|
case XFLM_EXISTS_OP:
|
|
|
|
// We already know this one passes.
|
|
|
|
*pbPasses = TRUE;
|
|
goto Exit;
|
|
case XFLM_NE_OP:
|
|
case XFLM_APPROX_EQ_OP:
|
|
if (RC_BAD( rc = fqCompareOperands( uiLanguage,
|
|
pQValue,
|
|
pPred->pFromValue,
|
|
pPred->eOperator,
|
|
pPred->uiCompareRules,
|
|
pPred->pOpComparer,
|
|
pPred->bNotted,
|
|
&eBool)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
case XFLM_MATCH_OP:
|
|
|
|
// From value of predicate better be a constant
|
|
// with wildcards set.
|
|
|
|
flmAssert( pPred->pFromValue->uiFlags & VAL_IS_CONSTANT);
|
|
flmAssert( pPred->pFromValue->uiFlags & VAL_HAS_WILDCARDS);
|
|
if (RC_BAD( rc = fqCompareOperands( uiLanguage,
|
|
pQValue,
|
|
pPred->pFromValue,
|
|
XFLM_EQ_OP,
|
|
pPred->uiCompareRules,
|
|
pPred->pOpComparer,
|
|
pPred->bNotted,
|
|
&eBool)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
case XFLM_RANGE_OP:
|
|
eBool = XFLM_TRUE;
|
|
|
|
// Take care of ==, > and >=
|
|
|
|
if (pPred->pFromValue)
|
|
{
|
|
eQueryOperators eOperator;
|
|
|
|
if (pPred->pUntilValue == pPred->pFromValue)
|
|
{
|
|
eOperator = XFLM_EQ_OP;
|
|
}
|
|
else
|
|
{
|
|
eOperator = pPred->bInclFrom ? XFLM_GE_OP : XFLM_GT_OP;
|
|
}
|
|
if (RC_BAD( rc = fqCompareOperands( uiLanguage,
|
|
pQValue,
|
|
pPred->pFromValue,
|
|
eOperator,
|
|
pPred->uiCompareRules,
|
|
pPred->pOpComparer,
|
|
pPred->bNotted,
|
|
&eBool)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// Take care of < and <= if we are still TRUE
|
|
|
|
if (eBool == XFLM_TRUE && pPred->pUntilValue &&
|
|
pPred->pUntilValue != pPred->pFromValue)
|
|
{
|
|
if (RC_BAD( rc = fqCompareOperands( uiLanguage,
|
|
pQValue,
|
|
pPred->pUntilValue,
|
|
pPred->bInclUntil ? XFLM_LE_OP : XFLM_LT_OP,
|
|
pPred->uiCompareRules,
|
|
pPred->pOpComparer,
|
|
pPred->bNotted,
|
|
&eBool)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
*pbPasses = FALSE;
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_NOT_IMPLEMENTED);
|
|
goto Exit;
|
|
}
|
|
|
|
// If the value didn't pass the predicate, we should return
|
|
// a FALSE in *pbPasses.
|
|
|
|
*pbPasses = (eBool == XFLM_FALSE) ? FALSE : TRUE;
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Test the key against the specified predicate.
|
|
***************************************************************************/
|
|
RCODE F_Query::testKey(
|
|
F_DataVector * pKey,
|
|
PATH_PRED * pPred,
|
|
FLMBOOL * pbPasses,
|
|
IF_DOMNode ** ppPassedNode)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMUINT64 ui64NodeId;
|
|
FQVALUE currVal;
|
|
FLMUINT uiDataType;
|
|
FLMBYTE ucKeyValue [128];
|
|
FLMBYTE * pucKeyValue = &ucKeyValue [0];
|
|
|
|
currVal.eValType = XFLM_MISSING_VAL;
|
|
|
|
// At this point, all use of notted predicates should have been
|
|
// weeded out.
|
|
|
|
flmAssert( !pPred->bNotted);
|
|
*pbPasses = TRUE;
|
|
|
|
// First see if the key passes the criteria of the predicate.
|
|
// If we can use the key, use it. Otherwise, fetch the node
|
|
// and do the comparison.
|
|
|
|
if (pPred->OptInfo.bCanCompareOnKey)
|
|
{
|
|
// No need to retrieve the data if it is an exists operator
|
|
|
|
if (pPred->eOperator != XFLM_EXISTS_OP)
|
|
{
|
|
if ((uiDataType = pKey->getDataType( 0)) == XFLM_UNKNOWN_TYPE)
|
|
{
|
|
|
|
// No data - component was missing.
|
|
|
|
*pbPasses = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = fqGetValueFromKey( uiDataType, pKey, &currVal,
|
|
&pucKeyValue, sizeof( ucKeyValue))))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Compare on the key
|
|
|
|
if (RC_BAD( rc = fqPredCompare( m_uiLanguage, pPred,
|
|
&currVal, pbPasses)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!(*pbPasses))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Have to get the node whether we compare on it or not
|
|
|
|
if ((ui64NodeId = pKey->getID( 0)) == 0)
|
|
{
|
|
|
|
// No data - component was missing.
|
|
|
|
*pbPasses = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
if( pKey->isAttr( 0))
|
|
{
|
|
rc = m_pDb->getAttribute( m_uiCollection,
|
|
ui64NodeId, pKey->getNameId( 0), ppPassedNode);
|
|
}
|
|
else
|
|
{
|
|
rc = m_pDb->getNode( m_uiCollection, ui64NodeId, ppPassedNode);
|
|
}
|
|
|
|
if( RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_DATA_ERROR);
|
|
}
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// See if we need to do the comparison on the node.
|
|
|
|
if (pPred->OptInfo.bDoNodeMatch && pPred->eOperator != XFLM_EXISTS_OP)
|
|
{
|
|
if (RC_BAD( rc = fqGetValueFromNode( m_pDb, *ppPassedNode, &currVal, 0)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Do the comparison
|
|
|
|
if (RC_BAD( rc = fqPredCompare( m_uiLanguage, pPred,
|
|
&currVal, pbPasses)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (!(*pbPasses))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// At this point, the key has passed at least the predicate comparison.
|
|
// Get the document node
|
|
|
|
if (RC_BAD( rc = m_pDb->getNode( m_uiCollection,
|
|
pKey->getDocumentID(), &m_pCurrDoc)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
|
|
// If we cannot retrieve the node, we have a corruption
|
|
// in the database.
|
|
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_DATA_ERROR);
|
|
}
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (pucKeyValue != &ucKeyValue [0])
|
|
{
|
|
f_free( &pucKeyValue);
|
|
}
|
|
|
|
if ((currVal.eValType == XFLM_BINARY_VAL ||
|
|
currVal.eValType == XFLM_UTF8_VAL) &&
|
|
(currVal.uiFlags & VAL_IS_STREAM) &&
|
|
currVal.val.pIStream)
|
|
{
|
|
currVal.val.pIStream->Release();
|
|
}
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Mark all of the XPATH nodes that are the same as the one in
|
|
pXPathComponent as having a passing value.
|
|
***************************************************************************/
|
|
FSTATIC void fqMarkXPathNodeListPassed(
|
|
PATH_PRED * pPred
|
|
)
|
|
{
|
|
PATH_PRED_NODE * pXPathNodeList;
|
|
|
|
pXPathNodeList = pPred->pXPathNodeList;
|
|
while (pXPathNodeList)
|
|
{
|
|
pXPathNodeList->pXPathNode->nd.pXPath->bHavePassingNode = TRUE;
|
|
pXPathNodeList = pXPathNodeList->pNext;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next/previous key for an xpath component.
|
|
***************************************************************************/
|
|
RCODE F_Query::getKey(
|
|
FLMBOOL * pbFirstLast,
|
|
FLMBOOL bForward,
|
|
XPATH_COMPONENT * pXPathComponent
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
PATH_PRED * pPred = pXPathComponent->pOptPred;
|
|
FSIndexCursor * pFSIndexCursor = pPred->pFSIndexCursor;
|
|
F_DataVector key;
|
|
FLMBOOL bPassed;
|
|
FLMBOOL bSkipCurrKey = FALSE;
|
|
FLMBOOL bHasContextPosTest = hasContextPosTest( pXPathComponent);
|
|
|
|
// Better be rightmost xpath component.
|
|
|
|
flmAssert( !pXPathComponent->pNext);
|
|
|
|
// Component could have an expression, but it should not be one
|
|
// we are optimizing on this time around.
|
|
|
|
flmAssert( !pXPathComponent->pExprXPathSource);
|
|
|
|
for (;;)
|
|
{
|
|
|
|
// Release the current key node, if any
|
|
|
|
if (pXPathComponent->pKeyNode)
|
|
{
|
|
pXPathComponent->pKeyNode->Release();
|
|
pXPathComponent->pKeyNode = NULL;
|
|
}
|
|
|
|
// Get the next or previous key from the index.
|
|
|
|
if (bForward)
|
|
{
|
|
rc = (RCODE)(*pbFirstLast
|
|
? pFSIndexCursor->firstKey( m_pDb, &key)
|
|
: pFSIndexCursor->nextKey( m_pDb, &key, bSkipCurrKey));
|
|
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_EOF_HIT)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rc = (RCODE)(*pbFirstLast
|
|
? pFSIndexCursor->lastKey( m_pDb, &key)
|
|
: pFSIndexCursor->prevKey( m_pDb, &key, bSkipCurrKey));
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_BOF_HIT)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
goto Exit;
|
|
}
|
|
}
|
|
*pbFirstLast = FALSE;
|
|
m_pCurrOpt->ui64KeysRead++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If we are eliminating duplicates, see if the document
|
|
// has already been processed. If so, skip the key.
|
|
|
|
if (m_pDocIdSet)
|
|
{
|
|
FLMUINT64 ui64DocId = key.getDocumentID();
|
|
|
|
if (RC_BAD( rc = m_pDocIdSet->findMatch( &ui64DocId, NULL)))
|
|
{
|
|
if (rc != NE_XFLM_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Document has already been passed, go to next/prev key.
|
|
|
|
m_pCurrOpt->ui64KeyHadDupDoc++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Evaluate the key.
|
|
|
|
if (RC_BAD( rc = testKey( &key, pPred, &bPassed,
|
|
&pXPathComponent->pKeyNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!bPassed)
|
|
{
|
|
// If this is a case-sensitive index or a case-insensitive compare,
|
|
// we can skip the key. Otherwise, we cannot skip any keys.
|
|
|
|
if (!(pFSIndexCursor->m_pIxd->pFirstKey->uiCompareRules &
|
|
XFLM_COMP_CASE_INSENSITIVE) ||
|
|
(pPred->uiCompareRules & XFLM_COMP_CASE_INSENSITIVE))
|
|
{
|
|
bSkipCurrKey = TRUE;
|
|
}
|
|
continue;
|
|
}
|
|
bSkipCurrKey = FALSE;
|
|
m_pCurrOpt->ui64KeysPassed++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If the xpath component has an expression, evaluate it
|
|
|
|
if (pXPathComponent->pExpr)
|
|
{
|
|
if (RC_BAD( rc = evalExpr( pXPathComponent->pKeyNode,
|
|
bForward, TRUE, pXPathComponent->pExpr,
|
|
&bPassed, NULL)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!bPassed)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (bHasContextPosTest)
|
|
{
|
|
|
|
// Need to verify the context position of the found node.
|
|
|
|
if (RC_BAD( rc = verifyOccurrence( FALSE, pXPathComponent,
|
|
pXPathComponent->pKeyNode, &bPassed)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!bPassed)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// There may be more than one XPATH that this predicate
|
|
// covers. Set them up so they don't have to be evaluated
|
|
// if at all possible.
|
|
|
|
fqMarkXPathNodeListPassed( pPred);
|
|
break;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Test a node's metadata against the specified predicate.
|
|
***************************************************************************/
|
|
RCODE F_Query::testMetaData(
|
|
IF_DOMNode * pNode,
|
|
FLMUINT uiMetaDataType,
|
|
PATH_PRED * pPred,
|
|
FLMBOOL * pbPasses
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMUINT64 ui64DocId;
|
|
FQVALUE currVal;
|
|
|
|
currVal.eValType = XFLM_MISSING_VAL;
|
|
|
|
// At this point, all use of notted predicates should have been
|
|
// weeded out.
|
|
|
|
flmAssert( !pPred->bNotted);
|
|
*pbPasses = TRUE;
|
|
|
|
// See if we need to do the comparison on the node.
|
|
|
|
if (pPred->eOperator != XFLM_EXISTS_OP)
|
|
{
|
|
if (RC_BAD( rc = fqGetValueFromNode( m_pDb, pNode, &currVal,
|
|
uiMetaDataType)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Do the comparison
|
|
|
|
if (RC_BAD( rc = fqPredCompare( m_uiLanguage, pPred,
|
|
&currVal, pbPasses)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (!(*pbPasses))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// At this point, the node has passed at least the predicate comparison.
|
|
// Get the document node, if we don't already have it.
|
|
|
|
if( !(((F_DOMNode *)pNode)->isRootNode()))
|
|
{
|
|
if (RC_BAD( rc = pNode->getDocumentId( m_pDb, &ui64DocId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = m_pDb->getNode( m_uiCollection,
|
|
ui64DocId, &m_pCurrDoc)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
|
|
// If we cannot retrieve the node, we have a corruption
|
|
// in the database.
|
|
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_DATA_ERROR);
|
|
}
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pCurrDoc = pNode;
|
|
m_pCurrDoc->AddRef();
|
|
}
|
|
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
if ((currVal.eValType == XFLM_BINARY_VAL ||
|
|
currVal.eValType == XFLM_UTF8_VAL) &&
|
|
(currVal.uiFlags & VAL_IS_STREAM) &&
|
|
currVal.val.pIStream)
|
|
{
|
|
currVal.val.pIStream->Release();
|
|
}
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next/previous node for an xpath component.
|
|
***************************************************************************/
|
|
RCODE F_Query::getANode(
|
|
FLMBOOL * pbFirstLast,
|
|
FLMBOOL bForward,
|
|
XPATH_COMPONENT * pXPathComponent
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
PATH_PRED * pPred = pXPathComponent->pOptPred;
|
|
FSCollectionCursor * pFSCollectionCursor = pPred->pFSCollectionCursor;
|
|
FLMBOOL bPassed;
|
|
IF_DOMNode * pNode = NULL;
|
|
FLMUINT64 ui64NodeId;
|
|
FLMBOOL bHasContextPosTest = hasContextPosTest( pXPathComponent);
|
|
|
|
// Better be rightmost xpath component.
|
|
|
|
flmAssert( !pXPathComponent->pNext);
|
|
|
|
// Component could have an expression, but it should not be one
|
|
// we are optimizing on this time around.
|
|
|
|
flmAssert( !pXPathComponent->pExprXPathSource);
|
|
|
|
for (;;)
|
|
{
|
|
if (pNode)
|
|
{
|
|
pNode->Release();
|
|
pNode = NULL;
|
|
}
|
|
|
|
// See if we need to continue from the current node in the current
|
|
// document, or release it and get another node.
|
|
|
|
if ((pNode = pXPathComponent->pKeyNode) != NULL)
|
|
{
|
|
pXPathComponent->pKeyNode = NULL;
|
|
|
|
// No need to do pNode->AddRef(), because we will just
|
|
// steal the AddRef that was on pCurrNode.
|
|
|
|
if (getMetaDataType( pXPathComponent) != XFLM_META_DOCUMENT_ID)
|
|
{
|
|
pNode->Release();
|
|
pNode = NULL;
|
|
}
|
|
else
|
|
{
|
|
|
|
// If we are doing document nodes, see if we can continue in the
|
|
// document - because all of the nodes in the document should
|
|
// be processed.
|
|
|
|
if (RC_BAD( rc = walkDocument( bForward, TRUE, 0, &pNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// If we didn't get a node back, and
|
|
// If pFSCollectionCursor is NULL, it means we
|
|
// are doing a single node, and we have already
|
|
// done that single node (or document), so we can
|
|
// quit.
|
|
|
|
if (!pNode && !pFSCollectionCursor)
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// Get the next or previous node from the collection, if we
|
|
// didn't get a node from the loop above.
|
|
|
|
if (!pNode)
|
|
{
|
|
if (pFSCollectionCursor)
|
|
{
|
|
if (bForward)
|
|
{
|
|
rc = (RCODE)(*pbFirstLast
|
|
? pFSCollectionCursor->firstNode( m_pDb, &pNode,
|
|
&ui64NodeId)
|
|
: pFSCollectionCursor->nextNode( m_pDb, &pNode,
|
|
&ui64NodeId));
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_EOF_HIT)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rc = (RCODE)(*pbFirstLast
|
|
? pFSCollectionCursor->lastNode( m_pDb, &pNode,
|
|
&ui64NodeId)
|
|
: pFSCollectionCursor->prevNode( m_pDb, &pNode,
|
|
&ui64NodeId));
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_BOF_HIT)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
else if (*pbFirstLast)
|
|
{
|
|
|
|
// Getting a single node.
|
|
|
|
if (getMetaDataType( pXPathComponent) == XFLM_META_DOCUMENT_ID)
|
|
{
|
|
if (RC_BAD( rc = m_pDb->getDocument( m_uiCollection,
|
|
XFLM_EXACT, pPred->OptInfo.ui64NodeId,
|
|
&pNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = m_pDb->getNode( m_uiCollection,
|
|
pPred->OptInfo.ui64NodeId,
|
|
&pNode)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
goto Exit;
|
|
}
|
|
*pbFirstLast = FALSE;
|
|
m_pCurrOpt->ui64NodesRead++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// If we are eliminating duplicates, see if the document
|
|
// has already been processed. If so, skip the key.
|
|
|
|
if (m_pDocIdSet)
|
|
{
|
|
FLMUINT64 ui64DocId;
|
|
|
|
if (RC_BAD( rc = pNode->getDocumentId( m_pDb, &ui64DocId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = m_pDocIdSet->findMatch( &ui64DocId, NULL)))
|
|
{
|
|
if (rc != NE_XFLM_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Document has already been passed, go to next/prev node.
|
|
|
|
m_pCurrOpt->ui64KeyHadDupDoc++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Evaluate the node.
|
|
|
|
if (RC_BAD( rc = testMetaData( pNode, getMetaDataType( pXPathComponent),
|
|
pPred, &bPassed)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (bPassed)
|
|
{
|
|
pXPathComponent->pKeyNode = pNode;
|
|
pXPathComponent->pKeyNode->AddRef();
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If the xpath component has an expression, evaluate it
|
|
|
|
if (pXPathComponent->pExpr)
|
|
{
|
|
if (RC_BAD( rc = evalExpr( pXPathComponent->pKeyNode,
|
|
bForward, TRUE, pXPathComponent->pExpr,
|
|
&bPassed, NULL)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!bPassed)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (bHasContextPosTest)
|
|
{
|
|
|
|
// Need to verify the context position of the found node.
|
|
|
|
if (RC_BAD( rc = verifyOccurrence( FALSE, pXPathComponent,
|
|
pXPathComponent->pKeyNode, &bPassed)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!bPassed)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// There may be more than one XPATH that this predicate
|
|
// covers. Set them up so they don't have to be evaluated
|
|
// if at all possible.
|
|
|
|
fqMarkXPathNodeListPassed( pPred);
|
|
break;
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (pNode)
|
|
{
|
|
pNode->Release();
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get a context node for the passed in xpath component.
|
|
***************************************************************************/
|
|
RCODE F_Query::getContextNode(
|
|
FLMBOOL bForward,
|
|
XPATH_COMPONENT * pXPathComponent
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
// Component better be the left-most xpath component.
|
|
|
|
flmAssert( !pXPathComponent->pPrev);
|
|
|
|
// See if we can now get a node for the XPATH context
|
|
// component.
|
|
|
|
if (RC_BAD( rc = getXPathComponentFromAxis( pXPathComponent->pKeyNode,
|
|
bForward, TRUE, pXPathComponent->pXPathContext,
|
|
&pXPathComponent->pXPathContext->pKeyNode,
|
|
invertedAxis( pXPathComponent->eXPathAxis),
|
|
TRUE, FALSE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next value for an XPATH component that has an expression
|
|
that is the source for the query.
|
|
***************************************************************************/
|
|
RCODE F_Query::getNextIndexNode(
|
|
FLMBOOL * pbFirstLast,
|
|
FLMBOOL bForward,
|
|
FQNODE * pExprXPathSource,
|
|
FLMBOOL bSkipCurrKey)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FXPATH * pSourceXPath;
|
|
XPATH_COMPONENT * pXPathComp;
|
|
FLMUINT64 ui64NodeId;
|
|
FLMUINT64 ui64Tmp;
|
|
|
|
// This routine should only be called for components that have an
|
|
// expression that is serving as the source for the query.
|
|
// Both pExpr and pXPathSource must be non-NULL.
|
|
|
|
flmAssert( pExprXPathSource->eNodeType == FLM_XPATH_NODE);
|
|
|
|
pSourceXPath = pExprXPathSource->nd.pXPath;
|
|
flmAssert( pSourceXPath->bIsSource);
|
|
|
|
// Release all pCurrNodes that come AFTER the source component.
|
|
// Start from last component and go backwards.
|
|
|
|
pXPathComp = pSourceXPath->pLastComponent;
|
|
while (pXPathComp)
|
|
{
|
|
if (pXPathComp->bIsSource)
|
|
{
|
|
break;
|
|
}
|
|
if (pXPathComp->pCurrNode)
|
|
{
|
|
pXPathComp->pCurrNode->Release();
|
|
pXPathComp->pCurrNode = NULL;
|
|
}
|
|
pXPathComp = pXPathComp->pPrev;
|
|
}
|
|
|
|
// If this is the first time in, we need to go right to the
|
|
// component in the XPath that is the key source.
|
|
|
|
if (bSkipCurrKey)
|
|
{
|
|
|
|
// Release all of the DOM nodes up to and including the one
|
|
// that is in the source component. pXPathComp should be
|
|
// pointing to the source component when we are done.
|
|
|
|
pXPathComp = pSourceXPath->pFirstComponent;
|
|
for (;;)
|
|
{
|
|
if (pXPathComp == pSourceXPath->pSourceComponent)
|
|
{
|
|
|
|
// Only release pKeyNode if we are not on the
|
|
// optimization predicate. Otherwise, we will
|
|
// allow the call to getKey or getANode to do it.
|
|
// This is mainly for the benefit of getANode, so it
|
|
// can properly handle the document ID case, where it
|
|
// needs to know the last node it was on inside a
|
|
// document so it can continue walking from there.
|
|
|
|
if (!pXPathComp->pOptPred && pXPathComp->pKeyNode)
|
|
{
|
|
pXPathComp->pKeyNode->Release();
|
|
pXPathComp->pKeyNode = NULL;
|
|
}
|
|
break;
|
|
}
|
|
if (pXPathComp->pKeyNode)
|
|
{
|
|
pXPathComp->pKeyNode->Release();
|
|
pXPathComp->pKeyNode = NULL;
|
|
}
|
|
pXPathComp = pXPathComp->pNext;
|
|
}
|
|
flmAssert( pXPathComp->bIsSource);
|
|
}
|
|
else if (!pSourceXPath->pFirstComponent->pKeyNode)
|
|
{
|
|
pXPathComp = pSourceXPath->pSourceComponent;
|
|
flmAssert( pXPathComp->bIsSource);
|
|
}
|
|
else
|
|
{
|
|
pXPathComp = pSourceXPath->pFirstComponent;
|
|
}
|
|
for (;;)
|
|
{
|
|
if (pXPathComp->bIsSource)
|
|
{
|
|
if (pXPathComp->pOptPred)
|
|
{
|
|
if (pXPathComp->pOptPred->pFSIndexCursor)
|
|
{
|
|
if (RC_BAD( rc = getKey( pbFirstLast, bForward, pXPathComp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else if (pXPathComp->pOptPred->pNodeSource)
|
|
{
|
|
if (RC_BAD( rc = getAppNode( pbFirstLast, bForward, pXPathComp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// NOTE: pXPathComp->pOptPred->pFSCollectionCursor may be NULL
|
|
// if getting a single node (eOptType == XFLM_QOPT_SINGLE_NODE_ID)
|
|
|
|
if (RC_BAD( rc = getANode( pbFirstLast, bForward, pXPathComp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if (!pXPathComp->pKeyNode)
|
|
{
|
|
// Didn't get a node.
|
|
// Cannot go any further than this - means we are
|
|
// out of keys for this predicate.
|
|
|
|
if (m_pCurrDoc)
|
|
{
|
|
m_pCurrDoc->Release();
|
|
m_pCurrDoc = NULL;
|
|
}
|
|
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flmAssert( pXPathComp->pExprXPathSource && pXPathComp->pExpr);
|
|
|
|
// First see if we can get another context node without going
|
|
// down to get another key.
|
|
|
|
if (pXPathComp->pKeyNode)
|
|
{
|
|
|
|
// Got a node from expression, get context node for that node.
|
|
|
|
if (RC_BAD( rc = getContextNode( bForward,
|
|
pXPathComp->pExprXPathSource->nd.pXPath->pFirstComponent)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
while (!pXPathComp->pKeyNode)
|
|
{
|
|
if (RC_BAD( rc = getNextIndexNode( pbFirstLast, bForward,
|
|
pXPathComp->pExprXPathSource,
|
|
bSkipCurrKey)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// See if we got a node from below. If not, there is nothing
|
|
// more we can do at this level.
|
|
|
|
if (!pXPathComp->pExprXPathSource->nd.pXPath->pFirstComponent->pKeyNode)
|
|
{
|
|
if (pXPathComp->pKeyNode)
|
|
{
|
|
pXPathComp->pKeyNode->Release();
|
|
pXPathComp->pKeyNode = NULL;
|
|
}
|
|
goto Exit;
|
|
}
|
|
|
|
// Got a node from expression, get context node for that node.
|
|
|
|
if (RC_BAD( rc = getContextNode( bForward,
|
|
pXPathComp->pExprXPathSource->nd.pXPath->pFirstComponent)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// There has to be a next to this node - because the source
|
|
// component has to be somewhere after this component.
|
|
|
|
flmAssert( pXPathComp->pNext);
|
|
if (RC_BAD( rc = getXPathComponentFromAxis(
|
|
pXPathComp->pNext->pKeyNode, bForward, TRUE,
|
|
pXPathComp, &pXPathComp->pKeyNode,
|
|
invertedAxis( pXPathComp->pNext->eXPathAxis),
|
|
TRUE, FALSE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If we didn't get a node, go to next component and try to
|
|
// get its next node.
|
|
|
|
if (!pXPathComp->pKeyNode)
|
|
{
|
|
pXPathComp = pXPathComp->pNext;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// At this point we better have gotten a node.
|
|
|
|
flmAssert( pXPathComp->pKeyNode);
|
|
|
|
// If this is the left-most xpath component and we got a node
|
|
// and the component's axis is the root axis, verify that the
|
|
// node is, in fact the root node.
|
|
|
|
if (!pXPathComp->pPrev && pXPathComp->eXPathAxis == ROOT_AXIS)
|
|
{
|
|
FLMUINT64 ui64DocId;
|
|
|
|
if( RC_BAD( rc = m_pCurrDoc->getNodeId( m_pDb, &ui64DocId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( RC_BAD( rc = pXPathComp->pKeyNode->getNodeId( m_pDb, &ui64Tmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( ui64DocId != ui64Tmp )
|
|
{
|
|
|
|
if (RC_BAD( rc = pXPathComp->pKeyNode->getParentId(
|
|
m_pDb, &ui64NodeId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if ( ui64NodeId != ui64DocId || m_pCurrDoc->getNodeType() != DOCUMENT_NODE)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// See if we can go to a previous component. If not, we are done.
|
|
|
|
if ((pXPathComp = pXPathComp->pPrev) == NULL)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Set up to use the current predicate as the source for the query.
|
|
***************************************************************************/
|
|
RCODE F_Query::setupCurrPredicate(
|
|
FLMBOOL bForward
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FXPATH * pXPath;
|
|
XPATH_COMPONENT * pXPathContextComponent;
|
|
FLMBOOL bFirstLast;
|
|
|
|
// Clear all state information.
|
|
|
|
resetQuery();
|
|
|
|
if (RC_BAD( rc = newSource()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// m_pCurrPred has already been set. We just need to set up each
|
|
// XPATH appropriately.
|
|
|
|
// Could be multiple XPATHs associated with the predicate, but it
|
|
// is only necessary to set up one of them to point to the predicate.
|
|
// We could pick any of them, but we take the first one in the list.
|
|
|
|
pXPath = m_pCurrPred->pXPathNodeList->pXPathNode->nd.pXPath;
|
|
pXPath->bIsSource = TRUE;
|
|
|
|
// The source component for this XPATH will always be the
|
|
// last component, because predicates are always optimized on
|
|
// the last component of an XPATH.
|
|
|
|
pXPath->pSourceComponent = pXPath->pLastComponent;
|
|
pXPath->pSourceComponent->bIsSource = TRUE;
|
|
pXPath->pSourceComponent->pOptPred = m_pCurrPred;
|
|
|
|
// See if this XPATH is nested inside of another XPATH
|
|
// component. If so, that XPATH component must be marked
|
|
// as the source, and its XPATH must also be marked as the
|
|
// source. That XPATH component must also point to this
|
|
// particular XPATH node as the expression XPATH source for
|
|
// the context component.
|
|
|
|
m_pExprXPathSource = m_pCurrPred->pXPathNodeList->pXPathNode;
|
|
pXPathContextComponent = pXPath->pSourceComponent->pXPathContext;
|
|
while (pXPathContextComponent)
|
|
{
|
|
|
|
// Get the context component's XPATH
|
|
|
|
pXPath = pXPathContextComponent->pXPathNode->nd.pXPath;
|
|
pXPath->bIsSource = TRUE;
|
|
pXPath->pSourceComponent = pXPathContextComponent;
|
|
pXPathContextComponent->bIsSource = TRUE;
|
|
pXPathContextComponent->pExprXPathSource = m_pExprXPathSource;
|
|
|
|
// Setup for the next higher context, if any
|
|
|
|
m_pExprXPathSource = pXPathContextComponent->pXPathNode;
|
|
pXPathContextComponent = pXPathContextComponent->pXPathContext;
|
|
}
|
|
|
|
// Now get the first or last index node
|
|
|
|
bFirstLast = TRUE;
|
|
if (RC_BAD( rc = getNextIndexNode( &bFirstLast, bForward,
|
|
m_pExprXPathSource, FALSE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: See if a node/document passes all of the conditions for a query.
|
|
Also increment the necessary counters.
|
|
If it passes and we are building a result set, add it to the
|
|
result set.
|
|
***************************************************************************/
|
|
RCODE F_Query::testPassed(
|
|
IF_DOMNode ** ppNode,
|
|
FLMBOOL * pbPassed,
|
|
FLMBOOL * pbEliminatedDup)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMBOOL bReportRSStatus = FALSE;
|
|
FLMBOOL bReportQueryStatus = FALSE;
|
|
|
|
*pbEliminatedDup = FALSE;
|
|
if (!m_pQuery || m_pQuery->eNodeType != FLM_XPATH_NODE || m_bRemoveDups)
|
|
{
|
|
|
|
// NOTE: If the document passed, but was eliminated by
|
|
// duplicate checking, we will NOT increment documents read,
|
|
// because we know that we have read the document before.
|
|
// If the document failed, we also need to increment documents read,
|
|
// even though we don't really know if we read it before. In this
|
|
// case, we may end up getting a larger document read count than
|
|
// we should because we could count the same document as being
|
|
// read more than once.
|
|
|
|
if (*pbPassed)
|
|
{
|
|
if (RC_BAD( rc = checkIfDup( ppNode, pbPassed)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (*pbPassed)
|
|
{
|
|
m_pCurrOpt->ui64DocsRead++;
|
|
bReportQueryStatus = TRUE;
|
|
if (m_pSortResultSet)
|
|
{
|
|
m_ui64RSDocsRead++;
|
|
bReportRSStatus = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*pbEliminatedDup = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pCurrOpt->ui64DocsRead++;
|
|
bReportQueryStatus = TRUE;
|
|
if (m_pSortResultSet)
|
|
{
|
|
m_ui64RSDocsRead++;
|
|
bReportRSStatus = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (RC_BAD( rc = validateNode( *ppNode, pbPassed)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (*pbPassed)
|
|
{
|
|
m_pCurrOpt->ui64DocsPassed++;
|
|
bReportQueryStatus = TRUE;
|
|
|
|
// If we have a sort key and the sort order is different than
|
|
// the optimization index, or the application requested that
|
|
// we build a result set so it could do positioning, add the
|
|
// document to the result set.
|
|
|
|
if (m_pSortResultSet)
|
|
{
|
|
if (RC_BAD( rc = addToResultSet()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
bReportRSStatus = TRUE;
|
|
}
|
|
}
|
|
|
|
if (bReportQueryStatus)
|
|
{
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
if (bReportRSStatus && m_pQueryStatus)
|
|
{
|
|
if (RC_BAD( rc = m_pQueryStatus->resultSetStatus(
|
|
m_ui64RSDocsRead, m_ui64RSDocsPassed,
|
|
m_bEntriesAlreadyInOrder)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next node from the index.
|
|
***************************************************************************/
|
|
RCODE F_Query::nextFromIndex(
|
|
FLMBOOL bEvalCurrDoc,
|
|
FLMUINT uiNumToSkip,
|
|
FLMUINT * puiNumSkipped,
|
|
IF_DOMNode ** ppNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMBOOL bPassed;
|
|
FLMBOOL bFirst;
|
|
|
|
if (!bEvalCurrDoc)
|
|
{
|
|
bFirst = FALSE;
|
|
if (RC_BAD( rc = getNextIndexNode( &bFirst, TRUE,
|
|
m_pExprXPathSource, TRUE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
|
|
// If m_pCurrDoc is non-NULL, it means we are set up
|
|
// to call evalExpr.
|
|
|
|
while (m_pCurrDoc)
|
|
{
|
|
FLMBOOL bPassedEval;
|
|
FLMBOOL bEliminatedDup;
|
|
|
|
if (RC_BAD( rc = evalExpr( NULL, TRUE, TRUE,
|
|
m_pQuery, &bPassed, ppNode)))
|
|
{
|
|
if( rc == NE_XFLM_DOM_NODE_DELETED)
|
|
{
|
|
m_bResetAllXPaths = TRUE;
|
|
rc = NE_XFLM_OK;
|
|
goto Next_Index_Node;
|
|
}
|
|
goto Exit;
|
|
}
|
|
|
|
bPassedEval = bPassed;
|
|
|
|
if (RC_BAD( rc = testPassed( ppNode, &bPassed, &bEliminatedDup)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (bPassed)
|
|
{
|
|
m_eState = (m_eState == XFLM_QUERY_AT_BOF ||
|
|
m_eState == XFLM_QUERY_NOT_POSITIONED)
|
|
? XFLM_QUERY_AT_FIRST
|
|
: XFLM_QUERY_POSITIONED;
|
|
if (puiNumSkipped)
|
|
{
|
|
(*puiNumSkipped)++;
|
|
}
|
|
if (uiNumToSkip <= 1)
|
|
{
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
|
|
// puiNumSkipped will always be non-NULL in the case
|
|
// where uiNumToSkip > 1
|
|
|
|
flmAssert( puiNumSkipped);
|
|
if (*puiNumSkipped >= uiNumToSkip)
|
|
{
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
bPassed = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// At this point we know that we failed to pass. If we
|
|
// passed evalExpr, though, we need to call it again so it
|
|
// can iterate through all possible values. No need to call
|
|
// it again if it was eliminated because it was a duplicate howerver.
|
|
|
|
if (bPassedEval && !bEliminatedDup)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Next_Index_Node:
|
|
|
|
// Get the next node from the index.
|
|
// NOTE: m_pCurrDoc will be set to NULL if there are no
|
|
// more nodes to get from this particular predicate.
|
|
|
|
bFirst = FALSE;
|
|
if (RC_BAD( rc = getNextIndexNode( &bFirst, TRUE,
|
|
m_pExprXPathSource, FALSE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// If we get here, the loop above failed to get
|
|
// anything.
|
|
|
|
flmAssert( !m_pCurrDoc);
|
|
|
|
// Try the next predicate.
|
|
|
|
if (!useNextPredicate())
|
|
{
|
|
rc = RC_SET( NE_XFLM_EOF_HIT);
|
|
m_eState = XFLM_QUERY_AT_EOF;
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = setupCurrPredicate( TRUE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the previous node from the index.
|
|
***************************************************************************/
|
|
RCODE F_Query::prevFromIndex(
|
|
FLMBOOL bEvalCurrDoc,
|
|
FLMUINT uiNumToSkip,
|
|
FLMUINT * puiNumSkipped,
|
|
IF_DOMNode ** ppNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMBOOL bPassed;
|
|
FLMBOOL bLast;
|
|
|
|
if (!bEvalCurrDoc)
|
|
{
|
|
bLast = FALSE;
|
|
if (RC_BAD( rc = getNextIndexNode( &bLast, FALSE,
|
|
m_pExprXPathSource, TRUE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
|
|
// If m_pCurrDoc is non-NULL, it means we are set up
|
|
// to call evalExpr.
|
|
|
|
while (m_pCurrDoc)
|
|
{
|
|
FLMBOOL bPassedEval;
|
|
FLMBOOL bEliminatedDup;
|
|
|
|
if (RC_BAD( rc = evalExpr( NULL, FALSE, TRUE,
|
|
m_pQuery, &bPassed, ppNode)))
|
|
{
|
|
if( rc == NE_XFLM_DOM_NODE_DELETED)
|
|
{
|
|
m_bResetAllXPaths = TRUE;
|
|
rc = NE_XFLM_OK;
|
|
goto Prev_Index_Node;
|
|
}
|
|
goto Exit;
|
|
}
|
|
bPassedEval = bPassed;
|
|
|
|
if (RC_BAD( rc = testPassed( ppNode, &bPassed, &bEliminatedDup)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (bPassed)
|
|
{
|
|
m_eState = (m_eState == XFLM_QUERY_AT_EOF ||
|
|
m_eState == XFLM_QUERY_NOT_POSITIONED)
|
|
? XFLM_QUERY_AT_LAST
|
|
: XFLM_QUERY_POSITIONED;
|
|
if (puiNumSkipped)
|
|
{
|
|
(*puiNumSkipped)++;
|
|
}
|
|
if (uiNumToSkip <= 1)
|
|
{
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
|
|
// puiNumSkipped will always be non-NULL in the case
|
|
// where uiNumToSkip > 1
|
|
|
|
flmAssert( puiNumSkipped);
|
|
if (*puiNumSkipped >= uiNumToSkip)
|
|
{
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
bPassed = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// At this point we know that we failed to pass. If we
|
|
// passed evalExpr, though, we need to call it again so it
|
|
// can iterate through all possible values. No need to call
|
|
// it again if it was eliminated because it was a duplicate howerver.
|
|
|
|
if (bPassedEval && !bEliminatedDup)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Prev_Index_Node:
|
|
|
|
// Get the previous node from the index.
|
|
// NOTE: m_pCurrDoc will be set to NULL if there are no
|
|
// more nodes to get from this particular predicate.
|
|
|
|
bLast = FALSE;
|
|
if (RC_BAD( rc = getNextIndexNode( &bLast, FALSE,
|
|
m_pExprXPathSource, FALSE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// If we get here, the loop above failed to get
|
|
// anything.
|
|
|
|
flmAssert( !m_pCurrDoc);
|
|
|
|
// Try the previous predicate.
|
|
|
|
if (!usePrevPredicate())
|
|
{
|
|
rc = RC_SET( NE_XFLM_BOF_HIT);
|
|
m_eState = XFLM_QUERY_AT_BOF;
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = setupCurrPredicate( FALSE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next/previous document from the index we are scanning.
|
|
***************************************************************************/
|
|
RCODE F_Query::getDocFromIndexScan(
|
|
FLMBOOL bFirstLast,
|
|
FLMBOOL bForward
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
F_DataVector key;
|
|
FLMUINT64 ui64DocId;
|
|
|
|
for (;;)
|
|
{
|
|
if (bForward)
|
|
{
|
|
rc = (RCODE)(bFirstLast
|
|
? m_pFSIndexCursor->firstKey( m_pDb, &key)
|
|
: m_pFSIndexCursor->nextKey( m_pDb, &key, FALSE));
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_EOF_HIT)
|
|
{
|
|
m_eState = XFLM_QUERY_AT_EOF;
|
|
}
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rc = (RCODE)(bFirstLast
|
|
? m_pFSIndexCursor->lastKey( m_pDb, &key)
|
|
: m_pFSIndexCursor->prevKey( m_pDb, &key, FALSE));
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (rc == NE_XFLM_BOF_HIT)
|
|
{
|
|
m_eState = XFLM_QUERY_AT_BOF;
|
|
}
|
|
goto Exit;
|
|
}
|
|
}
|
|
m_pCurrOpt->ui64KeysRead++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If we do not have a duplicate checking set, we can
|
|
// break out of the loop and get the document associated
|
|
// with this key. Otherwise, we need to see if the
|
|
// document associated with this key has already been
|
|
// processed. If so, we will go to the next key.
|
|
|
|
if (!m_pDocIdSet)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// If we are eliminating duplicates, see if the document
|
|
// has already been processed. If so, skip the key.
|
|
|
|
ui64DocId = key.getDocumentID();
|
|
if (RC_BAD( rc = m_pDocIdSet->findMatch( &ui64DocId, NULL)))
|
|
{
|
|
if (rc != NE_XFLM_NOT_FOUND)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Document has not been processed.
|
|
|
|
rc = NE_XFLM_OK;
|
|
break;
|
|
}
|
|
|
|
// Document has already been passed, go to next/prev key.
|
|
|
|
m_pCurrOpt->ui64KeyHadDupDoc++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
bFirstLast = FALSE;
|
|
}
|
|
|
|
// Retrieve the document node.
|
|
|
|
if (RC_BAD( rc = m_pDb->getNode( m_uiCollection,
|
|
key.getDocumentID(),
|
|
&m_pCurrDoc)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
|
|
// If we cannot retrieve the node, we have a corruption
|
|
// in the database.
|
|
|
|
rc = RC_SET_AND_ASSERT( NE_XFLM_DATA_ERROR);
|
|
}
|
|
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = incrNodesRead()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the next node from scanning sequentially through documents.
|
|
***************************************************************************/
|
|
RCODE F_Query::nextFromScan(
|
|
FLMBOOL bFirstDoc,
|
|
FLMUINT uiNumToSkip,
|
|
FLMUINT * puiNumSkipped,
|
|
IF_DOMNode ** ppNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMBOOL bPassed;
|
|
FLMBOOL bEliminatedDup;
|
|
|
|
if (bFirstDoc ||
|
|
(m_pQuery && !m_bRemoveDups &&
|
|
m_pQuery->eNodeType == FLM_XPATH_NODE))
|
|
{
|
|
goto Eval_Doc;
|
|
}
|
|
|
|
// Read until we get a document/node that passes.
|
|
|
|
for (;;)
|
|
{
|
|
if (m_bScan)
|
|
{
|
|
if (RC_BAD( rc = m_pCurrDoc->getNextDocument( m_pDb, &m_pCurrDoc)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
m_eState = XFLM_QUERY_AT_EOF;
|
|
rc = RC_SET( NE_XFLM_EOF_HIT);
|
|
}
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = getDocFromIndexScan( FALSE, TRUE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
m_bResetAllXPaths = TRUE;
|
|
|
|
Eval_Doc:
|
|
|
|
// See if the document passes.
|
|
|
|
if (RC_BAD( rc = evalExpr( NULL, TRUE, TRUE,
|
|
m_pQuery, &bPassed, ppNode)))
|
|
{
|
|
if( rc == NE_XFLM_DOM_NODE_DELETED)
|
|
{
|
|
m_bResetAllXPaths = TRUE;
|
|
rc = NE_XFLM_OK;
|
|
continue;
|
|
}
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
if (bPassed && m_bScanIndex)
|
|
{
|
|
m_pCurrOpt->ui64KeysPassed++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if (RC_BAD( rc = testPassed( ppNode, &bPassed, &bEliminatedDup)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (bPassed)
|
|
{
|
|
m_eState = (m_eState == XFLM_QUERY_AT_BOF ||
|
|
m_eState == XFLM_QUERY_NOT_POSITIONED)
|
|
? XFLM_QUERY_AT_FIRST
|
|
: XFLM_QUERY_POSITIONED;
|
|
if (puiNumSkipped)
|
|
{
|
|
(*puiNumSkipped)++;
|
|
}
|
|
if (uiNumToSkip <= 1)
|
|
{
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
|
|
// puiNumSkipped will always be non-NULL in the case
|
|
// where uiNumToSkip > 1
|
|
|
|
flmAssert( puiNumSkipped);
|
|
if (*puiNumSkipped >= uiNumToSkip)
|
|
{
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
bPassed = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get the previous node from scanning sequentially through documents.
|
|
***************************************************************************/
|
|
RCODE F_Query::prevFromScan(
|
|
FLMBOOL bLastDoc,
|
|
FLMUINT uiNumToSkip,
|
|
FLMUINT * puiNumSkipped,
|
|
IF_DOMNode ** ppNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMBOOL bPassed;
|
|
FLMBOOL bEliminatedDup;
|
|
|
|
if (bLastDoc ||
|
|
(m_pQuery && !m_bRemoveDups &&
|
|
m_pQuery->eNodeType == FLM_XPATH_NODE))
|
|
{
|
|
goto Eval_Doc;
|
|
}
|
|
|
|
// Read until we get a document/node that passes.
|
|
|
|
for (;;)
|
|
{
|
|
|
|
// Go to the previous document
|
|
|
|
if (m_bScan)
|
|
{
|
|
if (RC_BAD( rc = m_pCurrDoc->getPreviousDocument( m_pDb, &m_pCurrDoc)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
m_eState = XFLM_QUERY_AT_BOF;
|
|
rc = RC_SET( NE_XFLM_BOF_HIT);
|
|
}
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = getDocFromIndexScan( FALSE, FALSE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
m_bResetAllXPaths = TRUE;
|
|
|
|
Eval_Doc:
|
|
|
|
// See if the document passes.
|
|
|
|
if (RC_BAD( rc = evalExpr( NULL, FALSE, TRUE,
|
|
m_pQuery, &bPassed, ppNode)))
|
|
{
|
|
if( rc == NE_XFLM_DOM_NODE_DELETED)
|
|
{
|
|
m_bResetAllXPaths = TRUE;
|
|
rc = NE_XFLM_OK;
|
|
continue;
|
|
}
|
|
goto Exit;
|
|
}
|
|
|
|
if (bPassed && m_bScanIndex)
|
|
{
|
|
m_pCurrOpt->ui64KeysPassed++;
|
|
if (RC_BAD( rc = queryStatus()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if (RC_BAD( rc = testPassed( ppNode, &bPassed, &bEliminatedDup)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (bPassed)
|
|
{
|
|
m_eState = (m_eState == XFLM_QUERY_AT_EOF ||
|
|
m_eState == XFLM_QUERY_NOT_POSITIONED)
|
|
? XFLM_QUERY_AT_LAST
|
|
: XFLM_QUERY_POSITIONED;
|
|
if (puiNumSkipped)
|
|
{
|
|
(*puiNumSkipped)++;
|
|
}
|
|
if (uiNumToSkip <= 1)
|
|
{
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
|
|
// puiNumSkipped will always be non-NULL in the case
|
|
// where uiNumToSkip > 1
|
|
|
|
flmAssert( puiNumSkipped);
|
|
if (*puiNumSkipped >= uiNumToSkip)
|
|
{
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
bPassed = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get first node/document that passes query expression.
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::getFirst(
|
|
IF_Db * ifpDb,
|
|
IF_DOMNode ** ppNode,
|
|
FLMUINT uiTimeLimit
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
// If we are building on a background thread and this is not the
|
|
// background thread, we need to get the results from the result
|
|
// set that is being built.
|
|
|
|
if ((m_pSortResultSet && m_uiBuildThreadId != f_threadId()) ||
|
|
m_bResultSetPopulated)
|
|
{
|
|
rc = getFirstFromResultSet( ifpDb, ppNode, uiTimeLimit);
|
|
goto Exit;
|
|
}
|
|
|
|
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 the query can never evaluate to TRUE, return EOF without
|
|
// doing anything.
|
|
|
|
if (m_bEmpty)
|
|
{
|
|
m_eState = XFLM_QUERY_AT_EOF;
|
|
rc = RC_SET( NE_XFLM_EOF_HIT);
|
|
goto Exit;
|
|
}
|
|
|
|
// See if we need to build a result set.
|
|
|
|
if ((m_pSortResultSet && m_uiBuildThreadId != f_threadId()) ||
|
|
m_bResultSetPopulated)
|
|
{
|
|
rc = getFirstFromResultSet( ifpDb, ppNode, uiTimeLimit);
|
|
goto Exit;
|
|
}
|
|
|
|
// Anytime we go back to the first, we must free whatever list
|
|
// of document IDs we have collected so far.
|
|
|
|
if (m_bRemoveDups && m_pDocIdSet)
|
|
{
|
|
m_pDocIdSet->Release();
|
|
m_pDocIdSet = NULL;
|
|
}
|
|
|
|
if ((m_uiTimeLimit = uiTimeLimit) != 0)
|
|
{
|
|
m_uiTimeLimit = FLM_MILLI_TO_TIMER_UNITS( uiTimeLimit);
|
|
m_uiStartTime = FLM_GET_TIMER();
|
|
}
|
|
if (m_bScan)
|
|
{
|
|
|
|
// Start at the beginning of the collection.
|
|
|
|
if (RC_BAD( rc = m_pDb->getFirstDocument( m_uiCollection, &m_pCurrDoc)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
m_eState = XFLM_QUERY_AT_EOF;
|
|
rc = RC_SET( NE_XFLM_EOF_HIT);
|
|
}
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = nextFromScan( TRUE, 0, NULL, ppNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else if (m_bScanIndex)
|
|
{
|
|
if (RC_BAD( rc = getDocFromIndexScan( TRUE, TRUE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = nextFromScan( TRUE, 0, NULL, ppNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pCurrContext = m_pQuery->pContext;
|
|
|
|
// Position the context to lowest selected context,
|
|
// context path, and predicate.
|
|
|
|
// NOTE: Because the m_bScan flag is not set, we are guaranteed
|
|
// that the following traversal scheme will not encounter any
|
|
// contexts, context paths, or predicates that require scanning, even
|
|
// though there may have been some during optimization. They cannot
|
|
// have been the selected contexts, context paths, or predicates
|
|
// without ultimately causing the m_bScan flag to have been set.
|
|
|
|
useLeafContext( TRUE);
|
|
|
|
// Setup predicate and get the first node.
|
|
|
|
if (RC_BAD( rc = setupCurrPredicate( TRUE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = nextFromIndex( TRUE, 0, NULL, ppNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (m_pCurrNode)
|
|
{
|
|
m_pCurrNode->Release();
|
|
m_pCurrNode = NULL;
|
|
}
|
|
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (m_pCurrDoc)
|
|
{
|
|
m_pCurrDoc->Release();
|
|
m_pCurrDoc = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pCurrNode = *ppNode;
|
|
m_pCurrNode->AddRef();
|
|
}
|
|
|
|
m_uiTimeLimit = 0;
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get last node/document that passes query expression.
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::getLast(
|
|
IF_Db * ifpDb,
|
|
IF_DOMNode ** ppNode,
|
|
FLMUINT uiTimeLimit
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
// If we are building on a background thread and this is not the
|
|
// background thread, we need to get the results from the result
|
|
// set that is being built.
|
|
|
|
if ((m_pSortResultSet && m_uiBuildThreadId != f_threadId()) ||
|
|
m_bResultSetPopulated)
|
|
{
|
|
rc = getLastFromResultSet( ifpDb, ppNode, uiTimeLimit);
|
|
goto Exit;
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
|
|
if ((m_pSortResultSet && m_uiBuildThreadId != f_threadId()) ||
|
|
m_bResultSetPopulated)
|
|
{
|
|
rc = getLastFromResultSet( ifpDb, ppNode, uiTimeLimit);
|
|
goto Exit;
|
|
}
|
|
|
|
// Anytime we go to the last, we must free whatever list
|
|
// of document IDs we have collected so far.
|
|
|
|
if (m_bRemoveDups && m_pDocIdSet)
|
|
{
|
|
m_pDocIdSet->Release();
|
|
m_pDocIdSet = NULL;
|
|
}
|
|
|
|
if ((m_uiTimeLimit = uiTimeLimit) != 0)
|
|
{
|
|
m_uiTimeLimit = FLM_MILLI_TO_TIMER_UNITS( uiTimeLimit);
|
|
m_uiStartTime = FLM_GET_TIMER();
|
|
}
|
|
if (m_bScan)
|
|
{
|
|
|
|
// Start at the end of the collection.
|
|
|
|
if (RC_BAD( rc = m_pDb->getLastDocument( m_uiCollection, &m_pCurrDoc)))
|
|
{
|
|
if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
|
|
{
|
|
m_eState = XFLM_QUERY_AT_BOF;
|
|
rc = RC_SET( NE_XFLM_BOF_HIT);
|
|
}
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = prevFromScan( TRUE, 0, NULL, ppNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else if (m_bScanIndex)
|
|
{
|
|
if (RC_BAD( rc = getDocFromIndexScan( TRUE, FALSE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = prevFromScan( TRUE, 0, NULL, ppNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pCurrContext = m_pQuery->pContext;
|
|
|
|
// Position the context to lowest selected context,
|
|
// context path, and predicate.
|
|
|
|
// NOTE: Because the m_bScan flag is not set, we are guaranteed
|
|
// that the following traversal scheme will not encounter any
|
|
// contexts, context paths, or predicates that require scanning, even
|
|
// though there may have been some during optimization. They cannot
|
|
// have been the selected contexts, context paths, or predicates
|
|
// without ultimately causing the m_bScan flag to have been set.
|
|
|
|
useLeafContext( FALSE);
|
|
|
|
// Setup predicate and get the last node.
|
|
|
|
if (RC_BAD( rc = setupCurrPredicate( FALSE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = prevFromIndex( TRUE, 0, NULL, ppNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (m_pCurrNode)
|
|
{
|
|
m_pCurrNode->Release();
|
|
m_pCurrNode = NULL;
|
|
}
|
|
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (m_pCurrDoc)
|
|
{
|
|
m_pCurrDoc->Release();
|
|
m_pCurrDoc = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pCurrNode = *ppNode;
|
|
m_pCurrNode->AddRef();
|
|
}
|
|
|
|
m_uiTimeLimit = 0;
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get next node/document that passes query expression.
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::getNext(
|
|
IF_Db * ifpDb,
|
|
IF_DOMNode ** ppNode,
|
|
FLMUINT uiTimeLimit,
|
|
FLMUINT uiNumToSkip,
|
|
FLMUINT * puiNumSkipped
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMUINT uiNumSkipped;
|
|
|
|
// If we are building on a background thread and this is not the
|
|
// background thread, we need to get the results from the result
|
|
// set that is being built.
|
|
|
|
if ((m_pSortResultSet && m_uiBuildThreadId != f_threadId()) ||
|
|
m_bResultSetPopulated)
|
|
{
|
|
rc = getNextFromResultSet( ifpDb, ppNode, uiTimeLimit, uiNumToSkip,
|
|
puiNumSkipped);
|
|
goto Exit;
|
|
}
|
|
|
|
m_pDb = (F_Db *)ifpDb;
|
|
if (ppNode && *ppNode)
|
|
{
|
|
(*ppNode)->Release();
|
|
*ppNode = NULL;
|
|
}
|
|
|
|
// 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 (!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;
|
|
}
|
|
switch (m_eState)
|
|
{
|
|
case XFLM_QUERY_AT_EOF:
|
|
rc = RC_SET( NE_XFLM_EOF_HIT);
|
|
goto Exit;
|
|
case XFLM_QUERY_AT_BOF:
|
|
case XFLM_QUERY_NOT_POSITIONED:
|
|
if (RC_OK( rc = getFirst( ifpDb, ppNode, uiTimeLimit)))
|
|
{
|
|
if (puiNumSkipped)
|
|
{
|
|
*puiNumSkipped = 1;
|
|
}
|
|
if (uiNumToSkip <= 1)
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
default:
|
|
if( !m_pCurrNode)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_NOT_POSITIONED);
|
|
goto Exit;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Optimization has to already have occurred.
|
|
|
|
flmAssert( m_bOptimized);
|
|
|
|
// Make sure the passed in F_Db matches the one associated with
|
|
// the query.
|
|
|
|
if (m_pDb->m_pDatabase != m_pDatabase)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_MISMATCHED_DB);
|
|
goto Exit;
|
|
}
|
|
|
|
// If we have been positioned, we better have a current document and node
|
|
|
|
flmAssert( m_pCurrDoc && m_pCurrNode);
|
|
|
|
if ((m_uiTimeLimit = uiTimeLimit) != 0)
|
|
{
|
|
m_uiTimeLimit = FLM_MILLI_TO_TIMER_UNITS( uiTimeLimit);
|
|
m_uiStartTime = FLM_GET_TIMER();
|
|
}
|
|
if (m_bScan || m_bScanIndex)
|
|
{
|
|
if (RC_BAD( rc = nextFromScan( FALSE, uiNumToSkip,
|
|
puiNumSkipped, ppNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = nextFromIndex(
|
|
(m_pQuery && !m_bRemoveDups &&
|
|
m_pQuery->eNodeType == FLM_XPATH_NODE &&
|
|
!m_pQuery->nd.pXPath->pLastComponent->bIsSource)
|
|
? TRUE
|
|
: FALSE, uiNumToSkip, puiNumSkipped, ppNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (m_pCurrNode)
|
|
{
|
|
m_pCurrNode->Release();
|
|
m_pCurrNode = NULL;
|
|
}
|
|
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (m_pCurrDoc)
|
|
{
|
|
m_pCurrDoc->Release();
|
|
m_pCurrDoc = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pCurrNode = *ppNode;
|
|
m_pCurrNode->AddRef();
|
|
}
|
|
|
|
m_uiTimeLimit = 0;
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get previous node/document that passes query expression.
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::getPrev(
|
|
IF_Db * ifpDb,
|
|
IF_DOMNode ** ppNode,
|
|
FLMUINT uiTimeLimit,
|
|
FLMUINT uiNumToSkip,
|
|
FLMUINT * puiNumSkipped
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMUINT uiNumSkipped;
|
|
|
|
// If we are building on a background thread and this is not the
|
|
// background thread, we need to get the results from the result
|
|
// set that is being built.
|
|
|
|
if ((m_pSortResultSet && m_uiBuildThreadId != f_threadId()) ||
|
|
m_bResultSetPopulated)
|
|
{
|
|
rc = getPrevFromResultSet( ifpDb, ppNode, uiTimeLimit, uiNumToSkip,
|
|
puiNumSkipped);
|
|
goto Exit;
|
|
}
|
|
|
|
m_pDb = (F_Db *)ifpDb;
|
|
if (ppNode && *ppNode)
|
|
{
|
|
(*ppNode)->Release();
|
|
*ppNode = NULL;
|
|
}
|
|
|
|
// 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 (!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;
|
|
}
|
|
switch (m_eState)
|
|
{
|
|
case XFLM_QUERY_AT_BOF:
|
|
rc = RC_SET( NE_XFLM_BOF_HIT);
|
|
goto Exit;
|
|
case XFLM_QUERY_AT_EOF:
|
|
case XFLM_QUERY_NOT_POSITIONED:
|
|
if (RC_OK( rc = getLast( ifpDb, ppNode, uiTimeLimit)))
|
|
{
|
|
if (puiNumSkipped)
|
|
{
|
|
*puiNumSkipped = 1;
|
|
}
|
|
if (uiNumToSkip <= 1)
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
default:
|
|
if( !m_pCurrNode)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_NOT_POSITIONED);
|
|
goto Exit;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Optimization has to already have occurred.
|
|
|
|
flmAssert( m_bOptimized);
|
|
|
|
// Make sure the passed in F_Db matches the one associated with
|
|
// the query.
|
|
|
|
if (m_pDb->m_pDatabase != m_pDatabase)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_MISMATCHED_DB);
|
|
goto Exit;
|
|
}
|
|
|
|
// If we have been positioned, we better have a current document and node
|
|
|
|
flmAssert( m_pCurrDoc && m_pCurrNode);
|
|
|
|
if ((m_uiTimeLimit = uiTimeLimit) != 0)
|
|
{
|
|
m_uiTimeLimit = FLM_MILLI_TO_TIMER_UNITS( uiTimeLimit);
|
|
m_uiStartTime = FLM_GET_TIMER();
|
|
}
|
|
if (m_bScan || m_bScanIndex)
|
|
{
|
|
if (RC_BAD( rc = prevFromScan( FALSE, uiNumToSkip,
|
|
puiNumSkipped, ppNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RC_BAD( rc = prevFromIndex(
|
|
(m_pQuery && !m_bRemoveDups &&
|
|
m_pQuery->eNodeType == FLM_XPATH_NODE &&
|
|
!m_pQuery->nd.pXPath->pLastComponent->bIsSource)
|
|
? TRUE
|
|
: FALSE, uiNumToSkip, puiNumSkipped, ppNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (m_pCurrNode)
|
|
{
|
|
m_pCurrNode->Release();
|
|
m_pCurrNode = NULL;
|
|
}
|
|
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (m_pCurrDoc)
|
|
{
|
|
m_pCurrDoc->Release();
|
|
m_pCurrDoc = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pCurrNode = *ppNode;
|
|
m_pCurrNode->AddRef();
|
|
}
|
|
|
|
m_uiTimeLimit = 0;
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get current document that passes query expression.
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::getCurrent(
|
|
IF_Db * ifpDb,
|
|
IF_DOMNode ** ppNode)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
// If we are building on a background thread and this is not the
|
|
// background thread, we need to get the results from the result
|
|
// set that is being built.
|
|
|
|
if ((m_pSortResultSet && m_uiBuildThreadId != f_threadId()) ||
|
|
m_bResultSetPopulated)
|
|
{
|
|
rc = getCurrentFromResultSet( ifpDb, ppNode);
|
|
goto Exit;
|
|
}
|
|
|
|
m_pDb = (F_Db *)ifpDb;
|
|
if (ppNode && *ppNode)
|
|
{
|
|
(*ppNode)->Release();
|
|
*ppNode = NULL;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
switch (m_eState)
|
|
{
|
|
case XFLM_QUERY_AT_BOF:
|
|
case XFLM_QUERY_NOT_POSITIONED:
|
|
rc = RC_SET( NE_XFLM_BOF_HIT);
|
|
goto Exit;
|
|
case XFLM_QUERY_AT_EOF:
|
|
rc = RC_SET( NE_XFLM_EOF_HIT);
|
|
goto Exit;
|
|
default:
|
|
if( !m_pCurrNode)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_NOT_POSITIONED);
|
|
goto Exit;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Optimization has to already have occurred.
|
|
|
|
flmAssert( m_bOptimized);
|
|
|
|
// Make sure the passed in F_Db matches the one associated with
|
|
// the query.
|
|
|
|
if (m_pDb->m_pDatabase != m_pDatabase)
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_MISMATCHED_DB);
|
|
goto Exit;
|
|
}
|
|
|
|
// If we have been positioned, we better have a current document and
|
|
// a current node id.
|
|
|
|
flmAssert( m_pCurrDoc && m_pCurrNode);
|
|
|
|
if( *ppNode)
|
|
{
|
|
(*ppNode)->Release();
|
|
}
|
|
|
|
*ppNode = m_pCurrNode;
|
|
(*ppNode)->AddRef();
|
|
|
|
Exit:
|
|
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (m_pCurrDoc)
|
|
{
|
|
m_pCurrDoc->Release();
|
|
m_pCurrDoc = NULL;
|
|
}
|
|
|
|
if (m_pCurrNode)
|
|
{
|
|
m_pCurrNode->Release();
|
|
m_pCurrNode = NULL;
|
|
}
|
|
}
|
|
|
|
m_uiTimeLimit = 0;
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Get statistics and optimization information.
|
|
***************************************************************************/
|
|
RCODE FLMAPI F_Query::getStatsAndOptInfo(
|
|
FLMUINT * puiNumOptInfos,
|
|
XFLM_OPT_INFO ** ppOptInfo)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
XFLM_OPT_INFO * pOptInfo;
|
|
FLMUINT uiOptInfoCount;
|
|
|
|
if (!m_bOptimized)
|
|
{
|
|
*puiNumOptInfos = 0;
|
|
*ppOptInfo = NULL;
|
|
goto Exit;
|
|
}
|
|
|
|
if (m_bScan || m_bEmpty)
|
|
{
|
|
if (RC_BAD( rc = f_alloc( sizeof( XFLM_OPT_INFO), ppOptInfo)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
f_memcpy( *ppOptInfo, &m_scanOptInfo, sizeof( XFLM_OPT_INFO));
|
|
*puiNumOptInfos = 1;
|
|
}
|
|
else
|
|
{
|
|
OP_CONTEXT * pSaveCurrContext = m_pCurrContext;
|
|
CONTEXT_PATH * pSaveCurrContextPath = m_pCurrContextPath;
|
|
PATH_PRED * pSaveCurrPred = m_pCurrPred;
|
|
FQNODE * pSaveExprXPathSource = m_pExprXPathSource;
|
|
|
|
// Count the number of contexts.
|
|
|
|
m_pCurrContext = m_pQuery->pContext;
|
|
*puiNumOptInfos = 0;
|
|
useLeafContext( TRUE);
|
|
do
|
|
{
|
|
if (m_pCurrPred->pNodeSource)
|
|
{
|
|
if (RC_BAD( rc = m_pCurrPred->pNodeSource->getOptInfoCount(
|
|
(IF_Db *)m_pDb,
|
|
&uiOptInfoCount)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
(*puiNumOptInfos) += uiOptInfoCount;
|
|
}
|
|
else
|
|
{
|
|
(*puiNumOptInfos)++;
|
|
}
|
|
} while (useNextPredicate());
|
|
|
|
// Allocate the opt info array.
|
|
|
|
if (RC_OK( rc = f_alloc( sizeof( XFLM_OPT_INFO) * (*puiNumOptInfos),
|
|
ppOptInfo)))
|
|
{
|
|
pOptInfo = *ppOptInfo;
|
|
m_pCurrContext = m_pQuery->pContext;
|
|
useLeafContext( TRUE);
|
|
do
|
|
{
|
|
if (m_pCurrPred->pNodeSource)
|
|
{
|
|
if (RC_BAD( rc = m_pCurrPred->pNodeSource->getOptInfoCount(
|
|
(IF_Db *)m_pDb,
|
|
&uiOptInfoCount)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = m_pCurrPred->pNodeSource->getOptInfo(
|
|
(IF_Db *)m_pDb,
|
|
pOptInfo, uiOptInfoCount)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pOptInfo += uiOptInfoCount;
|
|
}
|
|
else
|
|
{
|
|
f_memcpy( pOptInfo, m_pCurrOpt, sizeof( XFLM_OPT_INFO));
|
|
pOptInfo++;
|
|
}
|
|
} while (useNextPredicate());
|
|
}
|
|
|
|
// Restore the current predicate.
|
|
|
|
m_pCurrContext = pSaveCurrContext;
|
|
m_pCurrContextPath = pSaveCurrContextPath;
|
|
m_pCurrPred = pSaveCurrPred;
|
|
m_pExprXPathSource = pSaveExprXPathSource;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Free the optimization info structure.
|
|
***************************************************************************/
|
|
void FLMAPI F_Query::freeStatsAndOptInfo(
|
|
XFLM_OPT_INFO ** ppOptInfo)
|
|
{
|
|
if (*ppOptInfo)
|
|
{
|
|
f_free( ppOptInfo);
|
|
}
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
Desc: Create an empty query object and return it's interface...
|
|
****************************************************************************/
|
|
RCODE FLMAPI F_DbSystem::createIFQuery(
|
|
IF_Query ** ppQuery)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
F_Query * pQuery = NULL;
|
|
|
|
if ((pQuery = f_new F_Query) == NULL)
|
|
{
|
|
rc = RC_SET( NE_XFLM_MEM);
|
|
goto Exit;
|
|
}
|
|
|
|
*ppQuery = pQuery;
|
|
pQuery = NULL;
|
|
|
|
Exit:
|
|
|
|
if( pQuery)
|
|
{
|
|
pQuery->Release();
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Parse a query using the passed in query string. The uiQueryType
|
|
parameter is intended to identify the type of query syntax used.
|
|
The XPATH syntax is currently the only sypported type.
|
|
****************************************************************************/
|
|
RCODE F_Query::setupQueryExpr(
|
|
FLMBOOL bUnicode,
|
|
IF_Db * ifpDb,
|
|
const void * pvQuery)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
F_XPath XPath;
|
|
F_Db * pDb = (F_Db *)ifpDb;
|
|
|
|
// Reset the query object totally.
|
|
|
|
clearQuery();
|
|
|
|
if (!bUnicode)
|
|
{
|
|
if (RC_BAD( rc = XPath.parseQuery( pDb, (char *)pvQuery, this)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flmAssert( 0);
|
|
}
|
|
|
|
// We need to make sure that from this point forward, we are using the same
|
|
// database object.
|
|
|
|
m_pDatabase = pDb->m_pDatabase;
|
|
|
|
Exit:
|
|
|
|
return rc;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Comparison function for comparing node ids.
|
|
****************************************************************************/
|
|
FSTATIC int nodeIdCompareFunc(
|
|
void * pvData1,
|
|
void * pvData2,
|
|
void * // pvUserData
|
|
)
|
|
{
|
|
if (*((FLMUINT64 *)pvData1) < *((FLMUINT64 *)pvData2))
|
|
{
|
|
return( -1);
|
|
}
|
|
else if (*((FLMUINT64 *)pvData1) > *((FLMUINT64 *)pvData2))
|
|
{
|
|
return( 1);
|
|
}
|
|
else
|
|
{
|
|
return( 0);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Allocate a result set for duplicate checking.
|
|
****************************************************************************/
|
|
RCODE F_Query::allocDupCheckSet( void)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
char szTmpDir [F_PATH_MAX_SIZE];
|
|
|
|
if (m_pDocIdSet)
|
|
{
|
|
m_pDocIdSet->Release();
|
|
m_pDocIdSet = NULL;
|
|
}
|
|
|
|
if ((m_pDocIdSet = f_new FDynSearchSet) == NULL)
|
|
{
|
|
rc = RC_SET( NE_XFLM_MEM);
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = gv_pXFlmDbSystem->getTempDir( szTmpDir)))
|
|
{
|
|
if (rc == NE_FLM_IO_PATH_NOT_FOUND)
|
|
{
|
|
rc = NE_XFLM_OK;
|
|
}
|
|
else
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if (!szTmpDir [0])
|
|
{
|
|
if (RC_BAD( rc = gv_XFlmSysData.pFileSystem->pathReduce(
|
|
m_pDb->m_pDatabase->m_pszDbPath, szTmpDir, NULL)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if (RC_BAD( rc = m_pDocIdSet->setup( szTmpDir, sizeof( FLMUINT64))))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
m_pDocIdSet->setCompareFunc( nodeIdCompareFunc, NULL);
|
|
|
|
Exit:
|
|
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (m_pDocIdSet)
|
|
{
|
|
m_pDocIdSet->Release();
|
|
m_pDocIdSet = NULL;
|
|
}
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Check to see if we have already passed the document that *ppNode
|
|
is in. If so, return FALSE in *pbPassed.
|
|
NOTE: This routine will return the root node of the document if it still
|
|
passes.
|
|
****************************************************************************/
|
|
RCODE F_Query::checkIfDup(
|
|
IF_DOMNode ** ppNode,
|
|
FLMBOOL * pbPassed)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FLMUINT64 ui64DocId;
|
|
|
|
// If we have not yet allocated the result set, do it now.
|
|
|
|
if (!m_pDocIdSet)
|
|
{
|
|
if (RC_BAD( rc = allocDupCheckSet()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// See if we can add the document id to the result set
|
|
|
|
if( RC_BAD( rc = m_pCurrDoc->getNodeId( m_pDb, &ui64DocId)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (RC_BAD( rc = m_pDocIdSet->addEntry( &ui64DocId)))
|
|
{
|
|
if (rc == NE_XFLM_EXISTS)
|
|
{
|
|
*pbPassed = FALSE;
|
|
rc = NE_XFLM_OK;
|
|
m_pCurrOpt->ui64DupDocsEliminated++;
|
|
}
|
|
goto Exit;
|
|
}
|
|
|
|
// When eliminating duplicates, we always return the root node
|
|
// of the document.
|
|
|
|
(*ppNode)->Release();
|
|
*ppNode = m_pCurrDoc;
|
|
(*ppNode)->AddRef();
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Setup duplicate handling for a query.
|
|
****************************************************************************/
|
|
void FLMAPI F_Query::setDupHandling(
|
|
FLMBOOL bRemoveDups
|
|
)
|
|
{
|
|
// Should not be able to change this after optimization has occurred.
|
|
|
|
flmAssert( !m_bOptimized);
|
|
m_bRemoveDups = bRemoveDups;
|
|
if (!bRemoveDups && m_pDocIdSet)
|
|
{
|
|
m_pDocIdSet->Release();
|
|
m_pDocIdSet = NULL;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Set an index for the query.
|
|
****************************************************************************/
|
|
RCODE FLMAPI F_Query::setIndex(
|
|
FLMUINT uiIndex
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
// Cannot set the index if we have already optimized the query
|
|
|
|
if (m_bOptimized)
|
|
{
|
|
rc = RC_SET( NE_XFLM_ILLEGAL_OP);
|
|
goto Exit;
|
|
}
|
|
m_uiIndex = uiIndex;
|
|
m_bIndexSet = TRUE;
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Set an index for the query.
|
|
****************************************************************************/
|
|
RCODE FLMAPI F_Query::getIndex(
|
|
IF_Db * ifpDb,
|
|
FLMUINT * puiIndex,
|
|
FLMBOOL * pbHaveMultiple
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
if (m_bIndexSet)
|
|
{
|
|
*puiIndex = m_uiIndex;
|
|
*pbHaveMultiple = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
// Optimize the query to determine the index.
|
|
|
|
m_pDb = (F_Db *)ifpDb;
|
|
if (!m_bOptimized)
|
|
{
|
|
|
|
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;
|
|
}
|
|
|
|
// 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 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;
|
|
}
|
|
|
|
if (RC_BAD( rc = optimize()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
*pbHaveMultiple = FALSE;
|
|
if (m_bScan || m_bEmpty)
|
|
{
|
|
*puiIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
OP_CONTEXT * pSaveCurrContext = m_pCurrContext;
|
|
CONTEXT_PATH * pSaveCurrContextPath = m_pCurrContextPath;
|
|
PATH_PRED * pSaveCurrPred = m_pCurrPred;
|
|
FQNODE * pSaveExprXPathSource = m_pExprXPathSource;
|
|
|
|
// See if more than one index is being used.
|
|
|
|
m_pCurrContext = m_pQuery->pContext;
|
|
useLeafContext( TRUE);
|
|
*puiIndex = 0;
|
|
do
|
|
{
|
|
if (m_pCurrPred->pNodeSource)
|
|
{
|
|
FLMUINT uiIndex;
|
|
|
|
if (RC_BAD( rc = m_pCurrPred->pNodeSource->getIndex( ifpDb, &uiIndex,
|
|
pbHaveMultiple)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (uiIndex)
|
|
{
|
|
if (*puiIndex == 0)
|
|
{
|
|
*puiIndex = uiIndex;
|
|
}
|
|
if (*pbHaveMultiple)
|
|
{
|
|
break;
|
|
}
|
|
if (uiIndex != *puiIndex)
|
|
{
|
|
*pbHaveMultiple = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (m_pCurrOpt->uiIxNum)
|
|
{
|
|
if (*puiIndex == 0)
|
|
{
|
|
*puiIndex = m_pCurrOpt->uiIxNum;
|
|
}
|
|
else if (m_pCurrOpt->uiIxNum != *puiIndex)
|
|
{
|
|
*pbHaveMultiple = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
} while (useNextPredicate());
|
|
|
|
// Restore the current predicate.
|
|
|
|
m_pCurrContext = pSaveCurrContext;
|
|
m_pCurrContextPath = pSaveCurrContextPath;
|
|
m_pCurrPred = pSaveCurrPred;
|
|
m_pExprXPathSource = pSaveExprXPathSource;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Make a copy of a value.
|
|
****************************************************************************/
|
|
RCODE F_Query::copyValue(
|
|
FQVALUE * pDestValue,
|
|
FQVALUE * pSrcValue
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
|
|
pDestValue->eValType = pSrcValue->eValType;
|
|
pDestValue->uiFlags = pSrcValue->uiFlags;
|
|
|
|
// Cannot copy stream values.
|
|
|
|
flmAssert( !(pDestValue->uiFlags & VAL_IS_STREAM));
|
|
pDestValue->uiDataLen = pSrcValue->uiDataLen;
|
|
switch (pDestValue->eValType)
|
|
{
|
|
case XFLM_BOOL_VAL:
|
|
pDestValue->val.eBool = pSrcValue->val.eBool;
|
|
break;
|
|
case XFLM_UINT_VAL:
|
|
pDestValue->val.uiVal = pSrcValue->val.uiVal;
|
|
break;
|
|
case XFLM_UINT64_VAL:
|
|
pDestValue->val.ui64Val = pSrcValue->val.ui64Val;
|
|
break;
|
|
case XFLM_INT_VAL:
|
|
pDestValue->val.iVal = pSrcValue->val.iVal;
|
|
break;
|
|
case XFLM_INT64_VAL:
|
|
pDestValue->val.i64Val = pSrcValue->val.i64Val;
|
|
break;
|
|
case XFLM_BINARY_VAL:
|
|
case XFLM_UTF8_VAL:
|
|
if (pDestValue->uiDataLen)
|
|
{
|
|
if (RC_BAD( rc = m_pool.poolAlloc( pDestValue->uiDataLen,
|
|
(void **)&pDestValue->val.pucBuf)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
f_memcpy( pDestValue->val.pucBuf, pSrcValue->val.pucBuf,
|
|
pDestValue->uiDataLen);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Make a copy of a value.
|
|
****************************************************************************/
|
|
RCODE F_Query::copyXPath(
|
|
XPATH_COMPONENT * pXPathContext,
|
|
FQNODE * pDestNode,
|
|
FXPATH ** ppDestXPath,
|
|
FXPATH * pSrcXPath
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FXPATH * pDestXPath;
|
|
XPATH_COMPONENT * pXPathComponent;
|
|
XPATH_COMPONENT * pTmpXPathComponent;
|
|
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( FXPATH),
|
|
(void **)&pDestXPath)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
*ppDestXPath = pDestXPath;
|
|
pXPathComponent = pSrcXPath->pFirstComponent;
|
|
while (pXPathComponent)
|
|
{
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( XPATH_COMPONENT),
|
|
(void **)&pTmpXPathComponent)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if ((pTmpXPathComponent->pPrev = pDestXPath->pLastComponent) != NULL)
|
|
{
|
|
pTmpXPathComponent->pPrev->pNext = pTmpXPathComponent;
|
|
}
|
|
else
|
|
{
|
|
pDestXPath->pFirstComponent = pTmpXPathComponent;
|
|
}
|
|
pDestXPath->pLastComponent = pTmpXPathComponent;
|
|
|
|
pTmpXPathComponent->pXPathContext = pXPathContext;
|
|
pTmpXPathComponent->pXPathNode = pDestNode;
|
|
pTmpXPathComponent->eXPathAxis = pXPathComponent->eXPathAxis;
|
|
pTmpXPathComponent->eNodeType = pXPathComponent->eNodeType;
|
|
pTmpXPathComponent->uiDictNum = pXPathComponent->uiDictNum;
|
|
pTmpXPathComponent->uiContextPosNeeded = pXPathComponent->uiContextPosNeeded;
|
|
if (pXPathComponent->pNodeSource)
|
|
{
|
|
if (RC_BAD( rc = pXPathComponent->pNodeSource->copy(
|
|
&pTmpXPathComponent->pNodeSource)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Call objectAddRef to add the node source to the list of objects
|
|
// we have an AddRef on. Then call Release, because the copy()
|
|
// call above would have also done an AddRef()
|
|
|
|
if (RC_BAD( rc = objectAddRef( pTmpXPathComponent->pNodeSource)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pTmpXPathComponent->pNodeSource->Release();
|
|
}
|
|
if (pXPathComponent->pContextPosExpr)
|
|
{
|
|
if (RC_BAD( rc = copyExpr( pTmpXPathComponent,
|
|
&pTmpXPathComponent->pContextPosExpr,
|
|
pXPathComponent->pContextPosExpr)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
if (pXPathComponent->pExpr)
|
|
{
|
|
if (RC_BAD( rc = copyExpr( pTmpXPathComponent,
|
|
&pTmpXPathComponent->pExpr,
|
|
pXPathComponent->pExpr)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
pXPathComponent = pXPathComponent->pNext;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Make a copy of a function.
|
|
****************************************************************************/
|
|
RCODE F_Query::copyFunction(
|
|
XPATH_COMPONENT * pXPathContext,
|
|
FQFUNCTION ** ppDestFunc,
|
|
FQFUNCTION * pSrcFunc
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQFUNCTION * pDestFunc;
|
|
FQEXPR * pExpr;
|
|
FQEXPR * pTmpExpr;
|
|
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( FQFUNCTION),
|
|
(void **)&pDestFunc)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
*ppDestFunc = pDestFunc;
|
|
pDestFunc->eFunction = pSrcFunc->eFunction;
|
|
if (pSrcFunc->pFuncObj)
|
|
{
|
|
|
|
// Need to clone the object, because it may have state info.
|
|
// on where it is at in iterating through values.
|
|
|
|
if (RC_BAD( pSrcFunc->pFuncObj->cloneSelf( &pDestFunc->pFuncObj)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (RC_BAD( rc = objectAddRef( pDestFunc->pFuncObj)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Need to release once because cloneSelf will have done
|
|
// an AddRef() - only need the AddRef done by objectAddRef()
|
|
|
|
pDestFunc->pFuncObj->Release();
|
|
}
|
|
|
|
pExpr = pSrcFunc->pFirstArg;
|
|
while (pExpr)
|
|
{
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( FQEXPR),
|
|
(void **)&pTmpExpr)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if ((pTmpExpr->pPrev = pDestFunc->pLastArg) != NULL)
|
|
{
|
|
pTmpExpr->pPrev->pNext = pTmpExpr;
|
|
}
|
|
else
|
|
{
|
|
pDestFunc->pFirstArg = pTmpExpr;
|
|
}
|
|
pDestFunc->pLastArg = pTmpExpr;
|
|
if (RC_BAD( rc = copyExpr( pXPathContext, &pTmpExpr->pExpr,
|
|
pExpr->pExpr)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pExpr = pExpr->pNext;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Make a copy of a node.
|
|
****************************************************************************/
|
|
RCODE F_Query::copyNode(
|
|
XPATH_COMPONENT * pXPathContext,
|
|
FQNODE ** ppDestNode,
|
|
FQNODE * pSrcNode
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pDestNode;
|
|
|
|
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( FQNODE), (void **)&pDestNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
*ppDestNode = pDestNode;
|
|
pDestNode->eNodeType = pSrcNode->eNodeType;
|
|
pDestNode->bNotted = pSrcNode->bNotted;
|
|
switch (pSrcNode->eNodeType)
|
|
{
|
|
case FLM_OPERATOR_NODE:
|
|
pDestNode->nd.op.eOperator = pSrcNode->nd.op.eOperator;
|
|
pDestNode->nd.op.uiCompareRules = pSrcNode->nd.op.uiCompareRules;
|
|
pDestNode->nd.op.pOpComparer = pSrcNode->nd.op.pOpComparer;
|
|
if (pDestNode->nd.op.pOpComparer)
|
|
{
|
|
if (RC_BAD( rc = objectAddRef( pDestNode->nd.op.pOpComparer)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
break;
|
|
case FLM_VALUE_NODE:
|
|
if (RC_BAD( rc = copyValue( &pDestNode->currVal,
|
|
&pSrcNode->currVal)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
case FLM_XPATH_NODE:
|
|
if (RC_BAD( rc = copyXPath( pXPathContext, pDestNode,
|
|
&pDestNode->nd.pXPath,
|
|
pSrcNode->nd.pXPath)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
case FLM_FUNCTION_NODE:
|
|
if (RC_BAD( rc = copyFunction( pXPathContext,
|
|
&pDestNode->nd.pQFunction,
|
|
pSrcNode->nd.pQFunction)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
break;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Copy an expression
|
|
****************************************************************************/
|
|
RCODE F_Query::copyExpr(
|
|
XPATH_COMPONENT * pXPathContext,
|
|
FQNODE ** ppDestExpr,
|
|
FQNODE * pSrcExpr
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
FQNODE * pTmpQNode;
|
|
FQNODE * pQNode = pSrcExpr;
|
|
FQNODE * pParent = NULL;
|
|
FQNODE * pPrevSib = NULL;
|
|
|
|
if (!pQNode)
|
|
{
|
|
*ppDestExpr = NULL;
|
|
goto Exit; // rc = NE_XFLM_OK;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
if (RC_BAD( rc = copyNode( pXPathContext, &pTmpQNode, pQNode)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!(*ppDestExpr))
|
|
{
|
|
*ppDestExpr = pTmpQNode;
|
|
}
|
|
pTmpQNode->pParent = pParent;
|
|
if (pParent)
|
|
{
|
|
if ((pTmpQNode->pPrevSib = pPrevSib) == NULL)
|
|
{
|
|
pParent->pFirstChild = pTmpQNode;
|
|
}
|
|
else
|
|
{
|
|
pParent->pLastChild = pTmpQNode;
|
|
}
|
|
}
|
|
if (pQNode->pFirstChild)
|
|
{
|
|
pParent = pTmpQNode;
|
|
pPrevSib = NULL;
|
|
pQNode = pQNode->pFirstChild;
|
|
}
|
|
else
|
|
{
|
|
while (!pQNode->pNextSib)
|
|
{
|
|
pParent = pTmpQNode->pParent;
|
|
if ((pQNode = pQNode->pParent) == NULL)
|
|
{
|
|
|
|
flmAssert( !pParent);
|
|
break;
|
|
}
|
|
}
|
|
if (!pQNode)
|
|
{
|
|
break;
|
|
}
|
|
pQNode = pQNode->pNextSib;
|
|
pPrevSib = pParent;
|
|
pParent = pPrevSib->pParent;
|
|
}
|
|
}
|
|
|
|
if (RC_BAD( rc = getPredicates( ppDestExpr, NULL, pXPathContext)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Copy criteria from another query object.
|
|
****************************************************************************/
|
|
RCODE FLMAPI F_Query::copyCriteria(
|
|
IF_Query * pSrcQuery
|
|
)
|
|
{
|
|
RCODE rc = NE_XFLM_OK;
|
|
EXPR_STATE * pExprState = ((F_Query *)pSrcQuery)->m_pCurExprState;
|
|
|
|
// Verify that the source query is in a "copyable" state
|
|
|
|
if (pExprState)
|
|
{
|
|
if (pExprState->pPrev || pExprState->uiNestLevel ||
|
|
(pExprState->pLastNode &&
|
|
pExprState->pLastNode->eNodeType == FLM_OPERATOR_NODE))
|
|
{
|
|
rc = RC_SET( NE_XFLM_Q_INCOMPLETE_QUERY_EXPR);
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// Clear out the existing query, if any.
|
|
|
|
clearQuery();
|
|
|
|
if (RC_BAD( rc = copyExpr( NULL, &m_pQuery,
|
|
((F_Query *)pSrcQuery)->m_pQuery)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|