//------------------------------------------------------------------------------
// Desc:	This file contains the main routines for building of index keys,
//			and adding them to the database.
//
// Tabs:	3
//
//		Copyright (c) 1990-1992, 1994-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: kybuild.cpp 3115 2006-01-19 13:24:39 -0700 (Thu, 19 Jan 2006) dsanders $
//------------------------------------------------------------------------------

#include "flaimsys.h"

#define STACK_DATA_BUF_SIZE		64

typedef struct NodeTraverseTag *	NODE_TRAV_p;
typedef struct AnchorNodeTag *	ANCHOR_NODE_p;

typedef struct AnchorNodeTag
{
	FLMUINT64			ui64AnchorNodeId;
	FLMUINT				uiAnchorNameId;
	eDomNodeType		eNodeType;
	FLMBOOL				bSeeIfRepeatingSibs;
	ANCHOR_NODE_p		pNext;
} ANCHOR_NODE;

typedef struct NodeTraverseTag
{
	ANCHOR_NODE *	pAnchorNode;
	F_DOMNode *		pNode;
	FLMBOOL			bInNodeSubtree;
	ICD *				pIcd;
	FLMUINT			uiSibIcdAttrs;
	FLMUINT			uiSibIcdElms;
	FLMBOOL			bTraverseChildren;
	FLMBOOL			bTraverseSibs;
	NODE_TRAV_p		pParent;
	NODE_TRAV_p		pChild;
} NODE_TRAV;

// Local function prototypes

FSTATIC RCODE kyAddIDsToKey(
	FLMUINT64		ui64DocumentID,
	IXD *				pIxd,
	CDL_HDR *		pCdlTbl,
	FLMBYTE *		pucKeyBuf,
	FLMUINT			uiIDBufSize,
	FLMUINT *		puiIDLen);

FSTATIC RCODE kySeeIfRepeatingSibs(
	F_Db *			pDb,
	F_DOMNode *		pNode,
	FLMBOOL *		pbHadRepeatingSib);

FSTATIC RCODE kyFindChildNode(
	F_Db *			pDb,
	F_Pool *			pPool,
	NODE_TRAV **	ppTrav,
	FLMBOOL *		pbGotChild,
	FLMBOOL *		pbHadRepeatingSib);

FSTATIC RCODE kyFindSibNode(
	F_Db *			pDb,
	NODE_TRAV *		pTrav,
	FLMBOOL			bTestFirstNode,
	FLMBOOL *		pbGotSib,
	FLMBOOL *		pbHadRepeatingSib);

/****************************************************************************
Desc:	Append node IDs to the key buffer.
****************************************************************************/
FSTATIC RCODE kyAddIDsToKey(
	FLMUINT64		ui64DocumentID,
	IXD *				pIxd,
	CDL_HDR *		pCdlTbl,
	FLMBYTE *		pucKeyBuf,
	FLMUINT			uiIDBufSize,
	FLMUINT *		puiIDLen)
{
	RCODE				rc = NE_XFLM_OK;
	FLMBYTE *		pucIDs = pucKeyBuf;
	FLMUINT			uiSenLen;
	FLMUINT			uiIDLen = 0;
	ICD *				pIcd;
	FLMUINT64		ui64Id;
	FLMBYTE			ucTmpSen [FLM_MAX_NUM_BUF_SIZE];
	FLMBYTE *		pucTmpSen;

	// Our ID buffer can never exceed MAX_ID_SIZE bytes (which is always
	// defined to be 256), because it has a trailing
	// length byte that we put on it - which can only represent up to
	// 255 bytes.

	if (uiIDBufSize > MAX_ID_SIZE)
	{
		uiIDBufSize = MAX_ID_SIZE;
	}

	// Put document ID into buffer.  If there is room for at least nine
	// bytes, we can encode the ID right into the buffer safely.  Otherwise,
	// we have to use a temporary buffer and see if there is room.
	
	if (uiIDBufSize - uiIDLen >= 9)
	{
		uiIDLen += f_encodeSEN( ui64DocumentID, &pucIDs);
	}
	else
	{
		pucTmpSen = &ucTmpSen [0];
		uiSenLen = f_encodeSEN( ui64DocumentID, &pucTmpSen);
		if (uiSenLen + uiIDLen > uiIDBufSize)
		{
			rc = RC_SET( NE_XFLM_KEY_OVERFLOW);
			goto Exit;
		}
		f_memcpy( pucIDs, ucTmpSen, uiSenLen);
		uiIDLen += uiSenLen;
		pucIDs += uiSenLen;
	}

	// Append the key component NODE IDs to the key

	for (pIcd = pIxd->pFirstKey; pIcd; pIcd = pIcd->pNextKeyComponent)
	{
		ui64Id = (FLMUINT64)(pCdlTbl [pIcd->uiCdl].pCdlList &&
									pCdlTbl [pIcd->uiCdl].pCdlList->pNode
									? pCdlTbl [pIcd->uiCdl].pCdlList->pNode->getIxNodeId()
									: (FLMUINT64)0);
									
		// Put node ID into buffer.  If there is room for at least nine
		// bytes, we can encode the ID right into the buffer safely.  Otherwise,
		// we have to use a temporary buffer and see if there is room.
		
		if (uiIDBufSize - uiIDLen >= 9)
		{
			uiIDLen += f_encodeSEN( ui64Id, &pucIDs);
		}
		else
		{
			pucTmpSen = &ucTmpSen [0];
			uiSenLen = f_encodeSEN( ui64Id, &pucTmpSen);
			if (uiSenLen + uiIDLen > uiIDBufSize)
			{
				rc = RC_SET( NE_XFLM_KEY_OVERFLOW);
				goto Exit;
			}
			f_memcpy( pucIDs, ucTmpSen, uiSenLen);
			uiIDLen += uiSenLen;
			pucIDs += uiSenLen;
		}
	}

	// Append the data NODE IDs to the key

	for (pIcd = pIxd->pFirstData; pIcd; pIcd = pIcd->pNextDataComponent)
	{
		ui64Id = (FLMUINT64)(pCdlTbl [pIcd->uiCdl].pCdlList &&
									pCdlTbl [pIcd->uiCdl].pCdlList->pNode
					? pCdlTbl [pIcd->uiCdl].pCdlList->pNode->getIxNodeId()
					: (FLMUINT64)0);

		// Put node ID into buffer.  If there is room for at least nine
		// bytes, we can encode the ID right into the buffer safely.  Otherwise,
		// we have to use a temporary buffer and see if there is room.
		
		if (uiIDBufSize - uiIDLen >= 9)
		{
			uiIDLen += f_encodeSEN( ui64Id, &pucIDs);
		}
		else
		{
			pucTmpSen = &ucTmpSen [0];
			uiSenLen = f_encodeSEN( ui64Id, &pucTmpSen);
			if (uiSenLen + uiIDLen > uiIDBufSize)
			{
				rc = RC_SET( NE_XFLM_KEY_OVERFLOW);
				goto Exit;
			}
			f_memcpy( pucIDs, ucTmpSen, uiSenLen);
			uiIDLen += uiSenLen;
			pucIDs += uiSenLen;
		}
	}

	// Append the context NODE IDs to the key

	for (pIcd = pIxd->pFirstContext; pIcd; pIcd = pIcd->pNextKeyComponent)
	{
		ui64Id = (FLMUINT64)(pCdlTbl [pIcd->uiCdl].pCdlList &&
									pCdlTbl [pIcd->uiCdl].pCdlList->pNode
					? pCdlTbl [pIcd->uiCdl].pCdlList->pNode->getIxNodeId()
					: (FLMUINT64)0);

		// Put node ID into buffer.  If there is room for at least nine
		// bytes, we can encode the ID right into the buffer safely.  Otherwise,
		// we have to use a temporary buffer and see if there is room.
		
		if (uiIDBufSize - uiIDLen >= 9)
		{
			uiIDLen += f_encodeSEN( ui64Id, &pucIDs);
		}
		else
		{
			pucTmpSen = &ucTmpSen [0];
			uiSenLen = f_encodeSEN( ui64Id, &pucTmpSen);
			if (uiSenLen + uiIDLen > uiIDBufSize)
			{
				rc = RC_SET( NE_XFLM_KEY_OVERFLOW);
				goto Exit;
			}
			f_memcpy( pucIDs, ucTmpSen, uiSenLen);
			uiIDLen += uiSenLen;
			pucIDs += uiSenLen;
		}
	}

	*puiIDLen = uiIDLen;

Exit:

	return( rc);
}

/*****************************************************************************
Desc: F_OldNodeList destructor
*****************************************************************************/
F_OldNodeList::~F_OldNodeList()
{
	if (m_pNodeList)
	{
		f_free( &m_pNodeList);
	}
	m_pool.poolFree();
}

/*****************************************************************************
Desc: Find a node in the old node list.
*****************************************************************************/
FLMBOOL F_OldNodeList::findNodeInList(
	eDomNodeType	eNodeType,
	FLMUINT			uiCollection,
	FLMUINT64		ui64NodeId,
	FLMUINT			uiNameId,
	FLMBYTE **		ppucData,
	FLMUINT *		puiDataLen,
	FLMUINT *		puiInsertPos)
{
	FLMBOOL			bFound = FALSE;
	FLMUINT			uiTblSize;
	FLMUINT			uiLow;
	FLMUINT			uiMid;
	FLMUINT			uiHigh;
	eDomNodeType	eTblNodeType;
	FLMUINT			uiTblCollection;
	FLMUINT			uiTblNameId;
	FLMUINT64		ui64TblNodeId;

	// Do binary search in the table

	if ((uiTblSize = m_uiNodeCount) == 0)
	{
		*puiInsertPos = 0;
		goto Exit;
	}

	uiHigh = --uiTblSize;
	uiLow = 0;
	for (;;)
	{
		uiMid = (uiLow + uiHigh) / 2;
		eTblNodeType = m_pNodeList[ uiMid].eNodeType;
		uiTblCollection = m_pNodeList[ uiMid].uiCollection;
		ui64TblNodeId = m_pNodeList[ uiMid].ui64NodeId;
		uiTblNameId = m_pNodeList[ uiMid].uiNameId;

		flmAssert( eTblNodeType != INVALID_NODE);
		flmAssert( uiTblCollection);
		flmAssert( ui64TblNodeId);
		flmAssert( uiTblNameId);

		if( eTblNodeType == eNodeType &&
			 uiTblCollection == uiCollection && 
			 ui64TblNodeId == ui64NodeId &&
			 uiTblNameId == uiNameId)
		{
			bFound = TRUE;
			*ppucData = m_pNodeList [uiMid].pucData;
			*puiDataLen = m_pNodeList [uiMid].uiDataLen;
			*puiInsertPos = uiMid;
			goto Exit;
		}

		// Check if we are done

		if( uiLow >= uiHigh)
		{
			// Done, item not found

			if( eNodeType >= eTblNodeType)
			{
				goto CmpGreaterEq;
			}

			if( uiCollection >= uiTblCollection)
			{
				goto CmpGreaterEq;
			}

			if( ui64NodeId >= ui64TblNodeId)
			{
				goto CmpGreaterEq;
			}

			if( uiNameId >= uiTblNameId)
			{
CmpGreaterEq:
				*puiInsertPos = uiMid + 1;
				goto Exit;
			}

			*puiInsertPos = uiMid;
			goto Exit;
		}

		if( eNodeType >= eTblNodeType)
		{
			goto CmpGreaterEq2;
		}

		if( uiCollection >= uiTblCollection)
		{
			goto CmpGreaterEq2;
		}

		if( ui64NodeId >= ui64TblNodeId)
		{
			goto CmpGreaterEq2;
		}

		if( uiNameId >= uiTblNameId)
		{
CmpGreaterEq2:
			if (uiMid == uiTblSize)
			{
				*puiInsertPos = uiMid + 1;
				goto Exit;
			}
			uiLow = uiMid + 1;
			continue;
		}

		if (uiMid == 0)
		{
			*puiInsertPos = 0;
			goto Exit;
		}
		uiHigh = uiMid - 1;
		continue;
	}

Exit:

	return( bFound);
}

/*****************************************************************************
Desc: Add an old node to the old node list.
*****************************************************************************/
RCODE F_OldNodeList::addNodeToList(
	F_Db *			pDb,
	F_DOMNode *		pNode)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT				uiInsertPos;
	FLMBYTE *			pucData;
	FLMUINT				uiDataType;
	FLMUINT				uiDataLen;
	FLMUINT				uiChars;
	FLMUINT				uiBufSize;
	FLMUINT				uiCollection;
	FLMUINT				uiNameId;
	FLMUINT64			ui64NodeId;
	
	if( RC_BAD( rc = pNode->getCollection( pDb, &uiCollection)))
	{
		goto Exit;
	}

	if( RC_BAD( rc = pNode->getDataType( pDb, &uiDataType)))
	{
		goto Exit;
	}

	ui64NodeId = pNode->getIxNodeId();
	uiNameId = pNode->getNameId();
	
	// See if the node is already there
	
	if( !findNodeInList( pNode->getNodeType(),
		uiCollection, ui64NodeId,
		uiNameId, &pucData, &uiDataLen, &uiInsertPos))
	{
		// Expand the size of the table.
		
		if (m_uiNodeCount == m_uiListSize)
		{
			if (RC_BAD( rc = f_realloc( (m_uiListSize + 20) *
												sizeof( OLD_NODE_DATA), &m_pNodeList)))
			{
				goto Exit;
			}
			m_uiListSize += 20;
		}

		if (uiInsertPos < m_uiNodeCount)
		{
			f_memmove( &m_pNodeList [uiInsertPos + 1],
						  &m_pNodeList [uiInsertPos],
						  sizeof( OLD_NODE_DATA) * (m_uiNodeCount - uiInsertPos));
		}

		m_pNodeList [uiInsertPos].eNodeType = pNode->getNodeType();
		m_pNodeList [uiInsertPos].uiCollection = uiCollection;
		m_pNodeList [uiInsertPos].ui64NodeId = ui64NodeId;
		m_pNodeList [uiInsertPos].uiNameId = uiNameId;
		m_uiNodeCount++;
		
		// Set up the data - either unicode or binary.
		
		if( uiDataType == XFLM_BINARY_TYPE)
		{
			// Get the length first.
			
			if( RC_BAD( rc = pNode->getDataLength( pDb, &uiBufSize)))
			{
				goto Exit;
			}
			
			// Allocate the space needed.
			
			if (RC_BAD( rc = m_pool.poolAlloc( uiBufSize,
										(void **)&m_pNodeList [uiInsertPos].pucData)))
			{
				goto Exit;
			}
			
			// Go back again and get the data now.

			if( RC_BAD( rc = pNode->getBinary( pDb, m_pNodeList [uiInsertPos].pucData,
				0, uiBufSize, NULL)))
			{
				goto Exit;
			}

			m_pNodeList [uiInsertPos].uiDataLen = uiBufSize;
		}
		else
		{
			flmAssert( uiDataType == XFLM_TEXT_TYPE);
			
			// Get the length first.

			if( RC_BAD( rc = pNode->getUnicodeChars( pDb, &uiChars)))
			{
				goto Exit;
			}
			
			// Allocate the space needed.
			
			uiBufSize = (uiChars + 1) * sizeof( FLMUNICODE);
			if (RC_BAD( rc = m_pool.poolAlloc( uiBufSize,
										(void **)&m_pNodeList [uiInsertPos].pucData)))
			{
				goto Exit;
			}
			
			// Go back again and get the data now.

			if( RC_BAD( rc = pNode->getUnicode( pDb, 
				(FLMUNICODE *)m_pNodeList [uiInsertPos].pucData,
				uiBufSize, 0, FLM_MAX_UINT)))
			{
				goto Exit;
			}

			m_pNodeList [uiInsertPos].uiDataLen = uiBufSize;
		}
	}
	
Exit:

	return( rc);
}

/*****************************************************************************
Desc: Release all of the nodes in the list.
*****************************************************************************/
void F_OldNodeList::resetList( void)
{
	m_pool.poolReset( NULL);
	m_uiNodeCount = 0;
}

/****************************************************************************
Desc:	Add an index key to the buffers
****************************************************************************/
RCODE F_Db::addToKrefTbl(
	FLMUINT	uiKeyLen,
	FLMUINT	uiDataLen)
{
	RCODE				rc = NE_XFLM_OK;
	KREF_ENTRY *	pKref;
	FLMUINT			uiSizeNeeded;
	FLMBYTE *		pucDest;

	// If the table is FULL, expand the table

	if (m_uiKrefCount == m_uiKrefTblSize)
	{
		FLMUINT		uiAllocSize;
		FLMUINT		uiOrigKrefTblSize = m_uiKrefTblSize;

		if (m_uiKrefTblSize > 0x8000 / sizeof( KREF_ENTRY *))
		{
			m_uiKrefTblSize += 4096;
		}
		else
		{
			m_uiKrefTblSize *= 2;
		}

		uiAllocSize = m_uiKrefTblSize * sizeof( KREF_ENTRY *);

		rc = f_realloc( uiAllocSize, &m_pKrefTbl);
		if (RC_BAD(rc))
		{
			m_uiKrefTblSize = uiOrigKrefTblSize;
			rc = RC_SET( NE_XFLM_MEM);
			goto Exit;
		}
	}

	// Allocate memory for the key's KREF and the key itself.
	// We allocate one extra byte so we can zero terminate the key
	// below.  The extra zero character is to ensure that the compare
	// in the qsort routine will work.

	uiSizeNeeded = sizeof( KREF_ENTRY) + uiKeyLen + 1 + uiDataLen;

	if (RC_BAD( rc = m_pKrefPool->poolAlloc( uiSizeNeeded,
										(void **)&pKref)))
	{
		goto Exit;
	}
	
	m_pKrefTbl [ m_uiKrefCount++] = pKref;
	m_uiTotalKrefBytes += uiSizeNeeded;

	// Fill in all of the fields in the KREF structure.

	pKref->ui16IxNum = (FLMUINT16)m_keyGenInfo.pIxd->uiIndexNum;
	pKref->bDelete = m_keyGenInfo.bAddKeys ? FALSE : TRUE;
	pKref->ui16KeyLen = (FLMUINT16)uiKeyLen;
	pKref->uiSequence = m_uiKrefCount;
	pKref->uiDataLen = uiDataLen;

	// Copy the key to just after the KREF structure.

	pucDest = (FLMBYTE *)(&pKref [1]);
 	f_memcpy( pucDest, m_keyGenInfo.pucKeyBuf, uiKeyLen);

	// Null terminate the key so compare in qsort will work.

	pucDest [uiKeyLen] = 0;
	if (uiDataLen)
	{
		f_memcpy( pucDest + uiKeyLen + 1, m_keyGenInfo.pucData, uiDataLen);
	}

Exit:

	return( rc);
}

/****************************************************************************
Desc:	Verifies that all of the nodes in the CDL list are in contexts that
		are related according to the index definition.
****************************************************************************/
RCODE F_Db::verifyKeyContext(
	FLMBOOL *		pbVerified)
{
	RCODE				rc = NE_XFLM_OK;
	CDL *				pCdl;
	CDL *				pParentCdl;
	ICD *				pIcd;

	*pbVerified = FALSE;
	pIcd = m_keyGenInfo.pIxd->pIcdTree;

	// Do in-order traversal, from leaf ICDs up.

	while (pIcd->pFirstChild)
	{
		pIcd = pIcd->pFirstChild;
	}

	for (;;)
	{
		if ((pCdl = m_keyGenInfo.pCdlTbl [pIcd->uiCdl].pCdlList) != NULL)
		{

			// If this is a "missing" placeholder and the
			// component is required, we cannot build the key

			if (!pCdl->pNode && pIcd->uiKeyComponent &&
				(pIcd->uiFlags & ICD_REQUIRED_PIECE))
			{
				goto Exit;
			}

			// If the ICD has a parent, see if the parent has the
			// correct node id.

			if (pIcd->pParent)
			{
				pParentCdl = m_keyGenInfo.pCdlTbl [pIcd->pParent->uiCdl].pCdlList;
				
				if( !pParentCdl || !pParentCdl->pNode)
				{
					goto Exit;
				}
				
				if( pParentCdl->pNode->getNodeId() != pCdl->ui64ParentId)
				{
					goto Exit;
				}
			}
		}
		else
		{
			if (pIcd->uiKeyComponent && (pIcd->uiFlags & ICD_REQUIRED_PIECE))
			{
				// This better already have been checked

				flmAssert( 0);
				goto Exit;
			}
		}

		// See if there is a sibling

		if (pIcd->pNextSibling)
		{
			pIcd = pIcd->pNextSibling;
			while (pIcd->pFirstChild)
			{
				pIcd = pIcd->pFirstChild;
			}
		}
		else
		{
			if ((pIcd = pIcd->pParent) == NULL)
			{
				break;
			}
		}
	}
	*pbVerified = TRUE;

Exit:

	return( rc);
}

/****************************************************************************
Desc:		Build the data part for a key.
Notes:	This routine is recursive in nature.  Will recurse the number of
			data components defined in the index.
****************************************************************************/
RCODE F_Db::buildData(
	ICD *			pIcd,
	FLMUINT		uiKeyLen,
	FLMUINT		uiDataLen
	)
{
	RCODE			rc = NE_XFLM_OK;
	FLMUINT		uiCdl;
	CDL *			pFirstCdl;
	CDL *			pCdl;
	FLMUINT		uiDataComponentLen;
	F_DOMNode *	pNode = NULL;
	FLMBYTE		ucTmpSen [FLM_MAX_NUM_BUF_SIZE];
	FLMBYTE *	pucTmpSen;
	FLMUINT		uiSENLen;
	FLMBOOL		bVerified;
	FLMUINT		uiIDLen;

	uiCdl = pIcd->uiCdl;
	pFirstCdl = m_keyGenInfo.pCdlTbl [uiCdl].pCdlList;
	pCdl = pFirstCdl;
	if (!m_keyGenInfo.bUseSubtreeNodes)
	{

		// If we are not using nodes in the sub-tree, skip any
		// that are in the sub-tree.

		while (pCdl && pCdl->bInNodeSubtree)
		{
			pCdl = pCdl->pNext;
		}
	}

	// Go through all of the data CDL - even the ones that are NULL

	for (;;)
	{

		// Data components cannot be root tags.

		flmAssert( pIcd->uiDictNum != ELM_ROOT_TAG);

		if (pNode)
		{
			pNode->Release();
			pNode = NULL;
		}

		m_keyGenInfo.pCdlTbl [uiCdl].pCdlList = pCdl;

		if (pCdl)
		{

			// NOTE: pNode could be NULL because it is a "missing" placeholder

			pNode = pCdl->pNode;
		}

		if (pNode)
		{
			pNode->AddRef();
			if (RC_BAD( rc = pNode->getDataLength( this, &uiDataComponentLen)))
			{
				goto Exit;
			}
		}
		else
		{
			uiDataComponentLen = 0;
		}

		// Output the length of the data as a SEN value

		pucTmpSen = &ucTmpSen [0];
		uiSENLen = f_encodeSEN( uiDataComponentLen, &pucTmpSen);
		if (uiDataComponentLen + uiSENLen + uiDataLen > m_keyGenInfo.uiDataBufSize)
		{
			FLMUINT	uiNewSize = uiDataComponentLen + uiSENLen + uiDataLen + 512;

			// Allocate the data buffer if it has not been allocated.  Otherwise,
			// realloc it.

			if (!m_keyGenInfo.bDataBufAllocated)
			{
				FLMBYTE *	pucNewData;

				if (RC_BAD( rc = f_alloc( uiNewSize, &pucNewData)))
				{
					goto Exit;
				}

				if( uiDataLen)
				{
					f_memcpy( pucNewData, m_keyGenInfo.pucData, uiDataLen);
				}
				m_keyGenInfo.pucData = pucNewData;
				m_keyGenInfo.bDataBufAllocated = TRUE;
			}
			else
			{
				// Reallocate the buffer.

				if (RC_BAD( rc = f_realloc( uiNewSize, &m_keyGenInfo.pucData)))
				{
					goto Exit;
				}
			}

			m_keyGenInfo.uiDataBufSize = uiNewSize;
		}
		f_memcpy( m_keyGenInfo.pucData + uiDataLen, ucTmpSen, uiSENLen);
		if (uiDataComponentLen)
		{
			if (RC_BAD( rc = pNode->getData( this,
												m_keyGenInfo.pucData + uiDataLen + uiSENLen,
												&uiDataComponentLen)))
			{
				goto Exit;
			}
		}

		// If this is the last data CDL, append IDs to the
		// key and output the key and data to the KREF.
		// Otherwise, recurse down.

		if (pIcd->pNextDataComponent)
		{
			if (RC_BAD( rc = buildData( pIcd->pNextDataComponent, uiKeyLen,
										uiDataLen + uiDataComponentLen + uiSENLen)))
			{
				goto Exit;
			}
		}
		else if (m_keyGenInfo.pIxd->pFirstContext)
		{
			if (RC_BAD( rc = buildContext( m_keyGenInfo.pIxd->pFirstContext, uiKeyLen,
									uiDataLen + uiDataComponentLen + uiSENLen)))
			{
				goto Exit;
			}
		}
		else
		{
			if (RC_BAD( rc = verifyKeyContext( &bVerified)))
			{
				goto Exit;
			}
			if (bVerified)
			{
				if (RC_BAD( rc = kyAddIDsToKey( m_keyGenInfo.ui64DocumentID,
											m_keyGenInfo.pIxd,
											m_keyGenInfo.pCdlTbl,
											&m_keyGenInfo.pucKeyBuf [uiKeyLen],
											XFLM_MAX_KEY_SIZE - uiKeyLen, &uiIDLen)))
				{
					goto Exit;
				}
				if (RC_BAD( rc = addToKrefTbl( uiKeyLen + uiIDLen,
										uiDataLen + uiDataComponentLen + uiSENLen)))
				{
					goto Exit;
				}
			}
		}

		// Get the next CDL, if any

		if (pCdl)
		{
			pCdl = pCdl->pNext;
			if (!m_keyGenInfo.bUseSubtreeNodes)
			{

				// If we are not using nodes in the sub-tree, skip any
				// that are in the sub-tree.

				while (pCdl && pCdl->bInNodeSubtree)
				{
					pCdl = pCdl->pNext;
				}
			}
		}
		if (!pCdl)
		{
			goto Exit;
		}
	}

Exit:

	if (pNode)
	{
		pNode->Release();
	}

	m_keyGenInfo.pCdlTbl [uiCdl].pCdlList = pFirstCdl;

	return( rc);
}

/****************************************************************************
Desc:		Go through the context components of a key.
Notes:	This routine is recursive in nature.  Will recurse the number of
			context components defined in the index.
****************************************************************************/
RCODE F_Db::buildContext(
	ICD *			pIcd,
	FLMUINT		uiKeyLen,
	FLMUINT		uiDataLen
	)
{
	RCODE			rc = NE_XFLM_OK;
	FLMUINT		uiCdl;
	CDL *			pFirstCdl;
	CDL *			pCdl;
	FLMUINT		uiIDLen;
	F_DOMNode *	pNode = NULL;
	FLMBOOL		bVerified;

	uiCdl = pIcd->uiCdl;
	pFirstCdl = m_keyGenInfo.pCdlTbl [uiCdl].pCdlList;
	pCdl = pFirstCdl;
	if (!m_keyGenInfo.bUseSubtreeNodes)
	{

		// If we are not using nodes in the sub-tree, skip any
		// that are in the sub-tree.

		while (pCdl && pCdl->bInNodeSubtree)
		{
			pCdl = pCdl->pNext;
		}
	}

	// Go through all of the context CDLs - even the ones that are NULL

	for (;;)
	{
		if (pNode)
		{
			pNode->Release();
			pNode = NULL;
		}
		m_keyGenInfo.pCdlTbl [uiCdl].pCdlList = pCdl;
		if (pCdl)
		{

			// NOTE: pNode could be NULL because it is a "missing" placeholder

			if ((pNode = pCdl->pNode) != NULL)
			{
				pNode->AddRef();
			}
		}

		// If this is the last context CDL, append IDs to the
		// key and output the key and data to the KREF.
		// Otherwise, recurse down.

		if (pIcd->pNextKeyComponent)
		{
			if (RC_BAD( rc = buildContext( pIcd->pNextKeyComponent,
										uiKeyLen, uiDataLen)))
			{
				goto Exit;
			}
		}
		else
		{
			if (RC_BAD( rc = verifyKeyContext( &bVerified)))
			{
				goto Exit;
			}
			if (bVerified)
			{
				if (RC_BAD( rc = kyAddIDsToKey( m_keyGenInfo.ui64DocumentID,
											m_keyGenInfo.pIxd,
											m_keyGenInfo.pCdlTbl,
											&m_keyGenInfo.pucKeyBuf [uiKeyLen],
											XFLM_MAX_KEY_SIZE - uiKeyLen, &uiIDLen)))
				{
					goto Exit;
				}
				if (RC_BAD( rc = addToKrefTbl( uiKeyLen + uiIDLen, uiDataLen)))
				{
					goto Exit;
				}
			}
		}

		// Get the next CDL, if any

		if (pCdl)
		{
			pCdl = pCdl->pNext;
			if (!m_keyGenInfo.bUseSubtreeNodes)
			{

				// If we are not using nodes in the sub-tree, skip any
				// that are in the sub-tree.

				while (pCdl && pCdl->bInNodeSubtree)
				{
					pCdl = pCdl->pNext;
				}
			}
		}
		if (!pCdl)
		{
			goto Exit;
		}
	}

Exit:

	if (pNode)
	{
		pNode->Release();
	}

	m_keyGenInfo.pCdlTbl [uiCdl].pCdlList = pFirstCdl;

	return( rc);
}

/****************************************************************************
Desc:	Finish the current key component.  If there is a next one call
		build keys.  Otherwise, go on to doing data and context pieces.
****************************************************************************/
RCODE F_Db::finishKeyComponent(
	ICD *		pIcd,
	FLMUINT	uiKeyLen
	)
{
	RCODE	rc = NE_XFLM_OK;
	
	if (pIcd->pNextKeyComponent)
	{
		flmAssert( m_keyGenInfo.bIsCompound);
		if (RC_BAD( rc = buildKeys( pIcd->pNextKeyComponent, uiKeyLen)))
		{
			goto Exit;
		}
	}
	else
	{
		if (m_keyGenInfo.pIxd->pFirstData)
		{
			if (RC_BAD( rc = buildData( m_keyGenInfo.pIxd->pFirstData, uiKeyLen, 0)))
			{
				goto Exit;
			}
		}
		else if (m_keyGenInfo.pIxd->pFirstContext)
		{
			if (RC_BAD( rc = buildContext( m_keyGenInfo.pIxd->pFirstContext, uiKeyLen, 0)))
			{
				goto Exit;
			}
		}
		else
		{
			FLMBOOL	bVerified;
			
			if (RC_BAD( rc = verifyKeyContext( &bVerified)))
			{
				goto Exit;
			}
			if (bVerified)
			{
				FLMUINT	uiIDLen;

				if (RC_BAD( rc = kyAddIDsToKey( m_keyGenInfo.ui64DocumentID,
											m_keyGenInfo.pIxd,
											m_keyGenInfo.pCdlTbl,
											&m_keyGenInfo.pucKeyBuf [uiKeyLen],
											XFLM_MAX_KEY_SIZE - uiKeyLen,
											&uiIDLen)))
				{
					goto Exit;
				}
				if (RC_BAD( rc = addToKrefTbl( uiKeyLen + uiIDLen, 0)))
				{
					goto Exit;
				}
			}
		}
	}
	
Exit:

	return( rc);
}

/****************************************************************************
Desc:	Generate the keys for a text component.
****************************************************************************/
RCODE F_Db::genTextKeyComponents(
	F_DOMNode *	pNode,
	ICD *			pIcd,
	FLMUINT		uiKeyLen,
	FLMBYTE **	ppucTmpBuf,
	FLMUINT *	puiTmpBufSize,
	void **		ppvMark)
{
	RCODE						rc = NE_XFLM_OK;
	IF_PosIStream *		pIStream = NULL;
	FLMUINT					uiNumChars;
	FLMUINT					uiStrBytes;
	FLMUINT					uiSubstrChars;
	FLMUINT					uiMeta;
	FLMBOOL					bEachWord = FALSE;
	FLMBOOL					bMetaphone = FALSE;
	FLMBOOL					bSubstring = FALSE;
	FLMBOOL					bWholeString = FALSE;
	FLMBOOL					bHadAtLeastOneString = FALSE;
	FLMBOOL					bDataTruncated;
	FLMUINT					uiSaveKeyLen;
	FLMUINT					uiElmLen;
	FLMUINT					uiKeyLenPos = uiKeyLen;
	FLMUINT					uiCompareRules = pIcd->uiCompareRules;
	F_NodeBufferIStream	nodeBufferIStream;
	IF_BufferIStream *	pBufferIStream = NULL;
	
	uiKeyLen += 2;
	uiSaveKeyLen = uiKeyLen;
	
	if (!pNode)
	{
		goto No_Strings;
	}
	
	if( RC_BAD( rc = FlmAllocBufferIStream( &pBufferIStream)))
	{
		goto Exit;
	}
	
	if (RC_BAD( rc = pNode->getTextIStream( this, 
		&nodeBufferIStream, &pIStream, &uiNumChars)))
	{
		goto Exit;
	}
	
	if (!uiNumChars)
	{
No_Strings:

		// Save the key component length
		
		UW2FBA( 0, &m_keyGenInfo.pucKeyBuf [uiKeyLenPos]);

		if( pIStream)
		{
			pIStream->Release();
			pIStream = NULL;
		}
		
		rc = finishKeyComponent( pIcd, uiKeyLen);
		goto Exit;
	}

	if (pIcd->uiFlags & ICD_EACHWORD)
	{
		bEachWord = TRUE;
		
		// OR in the compressing of spaces, because we only want to treat
		// spaces as delimiters between words.
	
		uiCompareRules |= XFLM_COMP_COMPRESS_WHITESPACE; 
	}
	else if (pIcd->uiFlags & ICD_METAPHONE)
	{
		bMetaphone = TRUE;
	}
	else if (pIcd->uiFlags & ICD_SUBSTRING)
	{
		bSubstring = TRUE;
	}
	else
	{
		bWholeString = TRUE;
	}

	// Loop on each word or substring in the value

	for (;;)
	{
		uiKeyLen = uiSaveKeyLen;
		bDataTruncated = FALSE;

		if (bWholeString)
		{
			uiElmLen = XFLM_MAX_KEY_SIZE - uiKeyLen;
			if( RC_BAD( rc = KYCollateValue( &m_keyGenInfo.pucKeyBuf [uiKeyLen],
							&uiElmLen,
							pIStream, XFLM_TEXT_TYPE,
							pIcd->uiFlags, pIcd->uiCompareRules,
							pIcd->uiLimit, NULL, NULL,
							m_keyGenInfo.pIxd->uiLanguage,
							FALSE, FALSE,
							&bDataTruncated, NULL)))
			{
				goto Exit;
			}
		}
		else if (bEachWord)
		{
			if (*ppucTmpBuf == NULL)
			{
				*ppvMark = m_tempPool.poolMark();
				*puiTmpBufSize = (FLMUINT)XFLM_MAX_KEY_SIZE + 8;
				if (RC_BAD( rc = m_tempPool.poolAlloc( *puiTmpBufSize,
												(void **)ppucTmpBuf)))
				{
					goto Exit;
				}
			}
	
			uiStrBytes = *puiTmpBufSize;
			if( RC_BAD( rc = KYEachWordParse( pIStream, &uiCompareRules,
				pIcd->uiLimit, *ppucTmpBuf, &uiStrBytes)))
			{
				goto Exit;
			}

			if (!uiStrBytes)
			{
				if (!bHadAtLeastOneString)
				{
					goto No_Strings;
				}
				break;
			}

			if (RC_BAD( rc = pBufferIStream->openStream( 
				(const char *)*ppucTmpBuf, uiStrBytes)))
			{
				goto Exit;
			}
			
			// Pass 0 for compare rules because KYEachWordParse will already
			// have taken care of them - except for XFLM_COMP_CASE_INSENSITIVE.

			uiElmLen = XFLM_MAX_KEY_SIZE - uiKeyLen;
			rc = KYCollateValue( &m_keyGenInfo.pucKeyBuf [uiKeyLen],
										&uiElmLen,
										pBufferIStream, XFLM_TEXT_TYPE,
										pIcd->uiFlags,
										pIcd->uiCompareRules & XFLM_COMP_CASE_INSENSITIVE,
										pIcd->uiLimit,
										NULL, NULL,
										m_keyGenInfo.pIxd->uiLanguage,
										FALSE, FALSE, &bDataTruncated, NULL);
			pBufferIStream->closeStream();

			if( RC_BAD( rc))
			{
				RC_UNEXPECTED_ASSERT( rc);
				goto Exit;
			}
			bHadAtLeastOneString = TRUE;
		}
		else if (bMetaphone)
		{
			FLMBYTE	ucStorageBuf[ FLM_MAX_NUM_BUF_SIZE];
			FLMUINT	uiStorageLen;

			if( RC_BAD( rc = f_getNextMetaphone( pIStream, &uiMeta)))
			{
				if( rc != NE_XFLM_EOF_HIT)
				{
					goto Exit;
				}
				rc = NE_XFLM_OK;
				if (!bHadAtLeastOneString)
				{
					goto No_Strings;
				}
				break;
			}

			uiStorageLen = FLM_MAX_NUM_BUF_SIZE;
			if( RC_BAD( rc = flmNumber64ToStorage( uiMeta,
				&uiStorageLen, ucStorageBuf, FALSE, FALSE)))
			{
				goto Exit;
			}

			if (RC_BAD( rc = pBufferIStream->openStream( 
				(const char *)ucStorageBuf, uiStorageLen)))
			{
				goto Exit;
			}

			// Pass 0 for compare rules - only applies to strings.
			
			uiElmLen = XFLM_MAX_KEY_SIZE - uiKeyLen;
			rc = KYCollateValue( &m_keyGenInfo.pucKeyBuf [uiKeyLen],
										&uiElmLen,
										pBufferIStream, XFLM_NUMBER_TYPE,
										pIcd->uiFlags, 0,
										pIcd->uiLimit,
										NULL, NULL,
										m_keyGenInfo.pIxd->uiLanguage,
										FALSE, FALSE, NULL, NULL);
			pBufferIStream->closeStream();

			if( RC_BAD( rc))
			{
				RC_UNEXPECTED_ASSERT( rc);
				goto Exit;
			}
			bHadAtLeastOneString = TRUE;
		}
		else
		{
			flmAssert( bSubstring);
			if (*ppucTmpBuf == NULL)
			{
				*ppvMark = m_tempPool.poolMark();
				*puiTmpBufSize = (FLMUINT)XFLM_MAX_KEY_SIZE + 8;
				if (RC_BAD( rc = m_tempPool.poolAlloc( *puiTmpBufSize,
												(void **)ppucTmpBuf)))
				{
					goto Exit;
				}
			}
			uiStrBytes = *puiTmpBufSize;
			
			if( RC_BAD( rc = KYSubstringParse( pIStream, &uiCompareRules,
				pIcd->uiLimit, *ppucTmpBuf, &uiStrBytes, &uiSubstrChars)))
			{
				goto Exit;
			}

			if (!uiStrBytes)
			{
				if (!bHadAtLeastOneString)
				{
					goto No_Strings;
				}
				break;
			}

			if (bHadAtLeastOneString && uiSubstrChars == 1 && !m_keyGenInfo.bIsAsia)
			{
				break;
			}

			if (RC_BAD( rc = pBufferIStream->openStream( 
				(const char *)*ppucTmpBuf, uiStrBytes)))
			{
				goto Exit;
			}
			
			// Pass 0 for compare rules, because KYSubstringParse has already
			// taken care of them, except for XFLM_COMP_CASE_INSENSITIVE

			uiElmLen = XFLM_MAX_KEY_SIZE - uiKeyLen;
			rc = KYCollateValue( &m_keyGenInfo.pucKeyBuf [uiKeyLen],
										&uiElmLen,
										pBufferIStream, XFLM_TEXT_TYPE,
										pIcd->uiFlags,
										pIcd->uiCompareRules & XFLM_COMP_CASE_INSENSITIVE,
										pIcd->uiLimit,
										NULL, NULL,
										m_keyGenInfo.pIxd->uiLanguage,
										bHadAtLeastOneString ? FALSE : TRUE, FALSE,
										&bDataTruncated, NULL);

			pBufferIStream->closeStream();

			if( RC_BAD( rc))
			{
				RC_UNEXPECTED_ASSERT( rc);
				goto Exit;
			}
			bHadAtLeastOneString = TRUE;
		}

		uiKeyLen += uiElmLen;
		
		// Save the key component length
		
		if (!bDataTruncated)
		{
			UW2FBA( (FLMUINT16)uiElmLen, &m_keyGenInfo.pucKeyBuf [uiKeyLenPos]);
		}
		else
		{
			UW2FBA( (FLMUINT16)(uiElmLen | TRUNCATED_FLAG),
								&m_keyGenInfo.pucKeyBuf [uiKeyLenPos]);
								
			// If we are deleting, save the node into the list of "old" nodes
			// We do this so that the compare routine can look for the node
			// if it has to compare the full value.
			
			if (!m_keyGenInfo.bAddKeys)
			{
				if (!m_pOldNodeList)
				{
					if ((m_pOldNodeList = f_new F_OldNodeList) == NULL)
					{
						rc = RC_SET( NE_XFLM_MEM);
						goto Exit;
					}
				}
				if (RC_BAD( rc = m_pOldNodeList->addNodeToList( this, pNode)))
				{
					goto Exit;
				}
			}
		}

		if (RC_BAD( rc = finishKeyComponent( pIcd, uiKeyLen)))
		{
			goto Exit;
		}

		if (bWholeString)
		{
			break;
		}
	}
	
Exit:

	if( pBufferIStream)
	{
		pBufferIStream->Release();
	}

	if (pIStream)
	{
		pIStream->Release();
		pIStream = NULL;
	}

	return( rc);
}

/****************************************************************************
Desc:	Generate the keys for other data types besides text.
****************************************************************************/
RCODE F_Db::genOtherKeyComponent(
	F_DOMNode *	pNode,
	ICD *			pIcd,
	FLMUINT		uiKeyLen)
{
	RCODE						rc = NE_XFLM_OK;
	FLMUINT					uiElmLen;
	FLMUINT					uiKeyLenPos = uiKeyLen;
	FLMBOOL					bDataTruncated;
	IF_PosIStream *		pIStream = NULL;
	F_NodeBufferIStream	bufferIStream;
	
	uiKeyLen += 2;
	if (!pNode)
	{
		
No_Data:

		// Save the key component length
		
		UW2FBA( 0, &m_keyGenInfo.pucKeyBuf [uiKeyLenPos]);
		
		rc = finishKeyComponent( pIcd, uiKeyLen);
		goto Exit;
	}
	
	if (pIcd->uiFlags & ICD_PRESENCE)
	{
		FLMUINT	uiNameId = pIcd->uiDictNum;
		
		// If we are indexing ELM_ROOT_TAG, we
		// need to get the name id from the node.
		
		if (uiNameId == ELM_ROOT_TAG)
		{
			if (RC_BAD( rc = pNode->getNameId( this, &uiNameId)))
			{
				goto Exit;
			}
		}
		
		f_UINT32ToBigEndian( (FLMUINT32)uiNameId, 
			&m_keyGenInfo.pucKeyBuf [uiKeyLen]);
		uiKeyLen += 4;
		
		// Save the key component length.
		
		UW2FBA( 4, &m_keyGenInfo.pucKeyBuf [uiKeyLenPos]);
	}
	else
	{
		// If it is not a presence index, we cannot be indexing
		// ELM_ROOT_TAG on an element node.
		
		flmAssert( pIcd->uiDictNum != ELM_ROOT_TAG);
					  
		if (RC_BAD( rc = pNode->getIStream( this, &bufferIStream, &pIStream)))
		{
			goto Exit;
		}

		if (!pIStream->remainingSize())
		{
			goto No_Data;
		}
			
		// Compute number of bytes left

		uiElmLen = XFLM_MAX_KEY_SIZE - uiKeyLen;
		bDataTruncated = FALSE;
		
		// Pass zero for compare rules - these are not strings.
		
		if( RC_BAD( rc = KYCollateValue( &m_keyGenInfo.pucKeyBuf [uiKeyLen],
						&uiElmLen, pIStream, icdGetDataType( pIcd), pIcd->uiFlags,
						0, pIcd->uiLimit, NULL, NULL,
						m_keyGenInfo.pIxd->uiLanguage,
						FALSE, FALSE,
						&bDataTruncated, NULL)))
		{
			goto Exit;
		}
		uiKeyLen += uiElmLen;
		
		// Save the key component length.
		
		if (!bDataTruncated)
		{
			UW2FBA( (FLMUINT16)uiElmLen,
						&m_keyGenInfo.pucKeyBuf [uiKeyLenPos]);
		}
		else
		{
			UW2FBA( (FLMUINT16)(uiElmLen | TRUNCATED_FLAG),
						&m_keyGenInfo.pucKeyBuf [uiKeyLenPos]);
						
			// If we are deleting, save the node into the list of "old" nodes
			// We do this so that the compare routine can look for the node
			// if it has to compare the full value.
			
			if (!m_keyGenInfo.bAddKeys)
			{
				if (!m_pOldNodeList)
				{
					if ((m_pOldNodeList = f_new F_OldNodeList) == NULL)
					{
						rc = RC_SET( NE_XFLM_MEM);
						goto Exit;
					}
				}
				
				if (RC_BAD( rc = m_pOldNodeList->addNodeToList( this, pNode)))
				{
					goto Exit;
				}
			}
		}

		// Better have an F_IStream at this point!

		flmAssert( pIStream);
		pIStream->Release();
		pIStream = NULL;
	}
	
	if (RC_BAD( rc = finishKeyComponent( pIcd, uiKeyLen)))
	{
		goto Exit;
	}
	
Exit:

	if (pIStream)
	{
		pIStream->Release();
	}
	return( rc);
}

/****************************************************************************
Desc:		Build all compound keys from the CDL table.
Notes:	This routine is recursive in nature.  Will recurse the number of
			key components defined in the index.
****************************************************************************/
RCODE F_Db::buildKeys(
	ICD *				pIcd,
	FLMUINT			uiKeyLen
	)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT				uiCdl = pIcd->uiCdl;
	CDL *					pFirstCdl = m_keyGenInfo.pCdlTbl [uiCdl].pCdlList;
	CDL *					pCdl = pFirstCdl;
	FLMBYTE *	   	pucTmpBuf = NULL;
	void *				pvMark = NULL;
	FLMUINT				uiTmpBufSize = 0;
	F_DOMNode *			pNode = NULL;

	flmAssert( m_keyGenInfo.bIsCompound || pIcd->uiKeyComponent == 1);

	// Do each CDL.  If there is no CDL for this level, must
	// still do at least once so that if there are other
	// components or data after this component, we will
	// do a recursive call or generate a key.

	if (!m_keyGenInfo.bUseSubtreeNodes)
	{

		// Skip any CDLs in the subtree if we are not using
		// sub-tree nodes at this point.

		while (pCdl && pCdl->bInNodeSubtree)
		{
			pCdl = pCdl->pNext;
		}
	}
	for (;;)
	{
		
		// Need to set the current CDL into the table so that
		// when we append the IDs we will have the right ones.
		// This will be restored to the first item in the
		// table at Exit.

		if (pNode)
		{
			pNode->Release();
			pNode = NULL;
		}
		m_keyGenInfo.pCdlTbl [uiCdl].pCdlList = pCdl;
		if (pCdl)
		{

			// pNode could be NULL because it is a placeholder for a
			// "missing" value

			if ((pNode = pCdl->pNode) != NULL)
			{
				pNode->AddRef();
			}
		}

		// Generate the key component

		if (icdGetDataType( pIcd) == XFLM_TEXT_TYPE &&
			 !(pIcd->uiFlags & ICD_PRESENCE))
		{
			if (RC_BAD( rc = genTextKeyComponents( pNode, pIcd, uiKeyLen,
										&pucTmpBuf, &uiTmpBufSize, &pvMark)))
			{
				goto Exit;
			}
		}
		else
		{
			if (RC_BAD( rc = genOtherKeyComponent( pNode, pIcd, uiKeyLen)))
			{
				goto Exit;
			}
		}
		
		// Go to the next CDL, if any

		if (pCdl)
		{
			pCdl = pCdl->pNext;
			if (!m_keyGenInfo.bUseSubtreeNodes)
			{

				// Skip any CDLs in the subtree if we are not using
				// sub-tree nodes at this point.

				while (pCdl && pCdl->bInNodeSubtree)
				{
					pCdl = pCdl->pNext;
				}
			}
		}
		if (!pCdl)
		{
			break;
		}
	}

Exit:

	if (pNode)
	{
		pNode->Release();
	}

	if (pvMark)
	{
		m_tempPool.poolReset( pvMark);
	}

	// Restore the CDL table entry to point to the
	// beginning of the list

	m_keyGenInfo.pCdlTbl [uiCdl].pCdlList = pFirstCdl;
	return( rc);
}

/****************************************************************************
Desc:	Build all keys from combinations of CDLs.  Add keys to KREF table.
****************************************************************************/
RCODE F_Db::buildKeys(
	FLMUINT64	ui64DocumentID,
	IXD *			pIxd,
	CDL_HDR *	pCdlTbl,
	FLMBOOL		bUseSubtreeNodes,
	FLMBOOL		bAddKeys)
{
	RCODE			rc = NE_XFLM_OK;
	FLMUINT		uiNumKeysHavingCdl;
	CDL *			pCdl;
	ICD *			pIcd;
	FLMBYTE		ucDataBuf [STACK_DATA_BUF_SIZE];

	m_keyGenInfo.bDataBufAllocated = FALSE;
	
	// Do a quick check to make sure we have all required pieces

	uiNumKeysHavingCdl = 0;
	pIcd = pIxd->pFirstKey;
	while (pIcd)
	{
		pCdl = pCdlTbl [pIcd->uiCdl].pCdlList;
		if (!bUseSubtreeNodes)
		{

			// Skip any nodes in the subtree if we are not using
			// sub-tree nodes at this point.  Also skip any
			// "missing" placeholders.

			while (pCdl && (pCdl->bInNodeSubtree || !pCdl->pNode))
			{
				pCdl = pCdl->pNext;
			}
		}
		else
		{

			// Skip any "missing" placeholders.

			while (pCdl && !pCdl->pNode)
			{
				pCdl = pCdl->pNext;
			}
		}
		if (!pCdl)
		{
			if (pIcd->uiFlags & ICD_REQUIRED_PIECE)
			{
				goto Exit;	// Nothing to generate, a required piece is missing.
			}
		}
		else
		{
			uiNumKeysHavingCdl++;
		}
		pIcd = pIcd->pNextKeyComponent;
	}

	// If none of the key pieces had a CDL, we cannot generate a key either.

	if (!uiNumKeysHavingCdl)
	{
		goto Exit;
	}

	// Build all of the keys
	
							  
	m_keyGenInfo.ui64DocumentID = ui64DocumentID;
	m_keyGenInfo.pIxd = pIxd;
	m_keyGenInfo.bIsAsia = (FLMBOOL)(pIxd->uiLanguage >= FLM_FIRST_DBCS_LANG &&
							  					pIxd->uiLanguage <= FLM_LAST_DBCS_LANG)
											  ? TRUE
											  : FALSE;
	m_keyGenInfo.bIsCompound = pIxd->uiNumKeyComponents > 1 ? TRUE : FALSE;
	m_keyGenInfo.pCdlTbl = pCdlTbl;
	m_keyGenInfo.bUseSubtreeNodes = bUseSubtreeNodes;
	m_keyGenInfo.bAddKeys = bAddKeys;
	m_keyGenInfo.pucKeyBuf = m_pucKrefKeyBuf;
	m_keyGenInfo.pucData = &ucDataBuf [0];
	m_keyGenInfo.uiDataBufSize = sizeof( ucDataBuf);
	m_keyGenInfo.bDataBufAllocated = FALSE;
	if (RC_BAD( rc = buildKeys( pIxd->pFirstKey, 0)))
	{
		goto Exit;
	}

Exit:

	if (m_keyGenInfo.bDataBufAllocated)
	{
		f_free( &m_keyGenInfo.pucData);
		m_keyGenInfo.bDataBufAllocated = FALSE;
	}

	return( rc);
}

/****************************************************************************
Desc:	See if the passed in node has any other siblings with the same ID.
		The passed in node must be an element node.
****************************************************************************/
FSTATIC RCODE kySeeIfRepeatingSibs(
	F_Db *		pDb,
	F_DOMNode *	pNode,
	FLMBOOL *	pbHadRepeatingSib)
{
	RCODE			rc = NE_XFLM_OK;
	F_DOMNode *	pTmpNode = NULL;
	FLMUINT		uiNameId;
	FLMUINT		uiTmpNameId;

	flmAssert( pNode->getNodeType() == ELEMENT_NODE);

	if (RC_BAD( rc = pNode->getNameId( pDb, &uiNameId)))
	{
		goto Exit;
	}

	// Traverse next siblings

	pTmpNode = pNode;
	pTmpNode->AddRef();
	for (;;)
	{
		if( RC_BAD( rc = pTmpNode->getNextSibling( pDb,
										(IF_DOMNode **)&pTmpNode)))
		{
			if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
			{
				goto Exit;
			}
			rc = NE_XFLM_OK;
			break;
		}
		
		if( pTmpNode->getNodeType() == ELEMENT_NODE)
		{
			if (RC_BAD( rc = pTmpNode->getNameId( pDb, &uiTmpNameId)))
			{
				goto Exit;
			}
			if (uiTmpNameId == uiNameId)
			{
				*pbHadRepeatingSib = TRUE;
				goto Exit;
			}
		}
	}

	// Traverse previous siblings

	pTmpNode->Release();
	pTmpNode = pNode;
	pTmpNode->AddRef();
	
	for (;;)
	{
		if( RC_BAD( rc = pTmpNode->getPreviousSibling( pDb,
										(IF_DOMNode **)&pTmpNode)))
		{
			if( rc != NE_XFLM_DOM_NODE_NOT_FOUND)
			{
				goto Exit;
			}
			
			rc = NE_XFLM_OK;
			break;
		}
		
		if (pTmpNode->getNodeType() == ELEMENT_NODE)
		{
			if (RC_BAD( rc = pTmpNode->getNameId( pDb, &uiTmpNameId)))
			{
				goto Exit;
			}
			
			if (uiTmpNameId == uiNameId)
			{
				*pbHadRepeatingSib = TRUE;
				goto Exit;
			}
		}
	}

Exit:

	if (pTmpNode)
	{
		pTmpNode->Release();
	}

	return( rc);
}

/****************************************************************************
Desc:	Get a child node for current traversal node.
****************************************************************************/
FSTATIC RCODE kyFindChildNode(
	F_Db *			pDb,
	F_Pool *			pPool,
	NODE_TRAV **	ppTrav,
	FLMBOOL *		pbGotChild,
	FLMBOOL *		pbHadRepeatingSib)
{
	RCODE				rc = NE_XFLM_OK;
	F_DOMNode *		pNode = NULL;
	ICD *				pIcd;
	FLMUINT			uiNameId;
	NODE_TRAV *		pTrav = *ppTrav;
	FLMUINT			uiElmIcdCount = 0;
	FLMUINT			uiAttrIcdCount = 0;
	FLMBOOL			bTraverseElms;
	FLMBOOL			bTraverseAttrs;
	ANCHOR_NODE *	pAnchorNode = pTrav->pAnchorNode;
	FLMUINT			uiAttrNameId = 0;
	
	if( pAnchorNode && 
		pTrav->pNode->getNodeId() == pAnchorNode->ui64AnchorNodeId)
	{
		pAnchorNode = pAnchorNode->pNext;
	}
	else
	{
		pAnchorNode = NULL;
	}

	*pbGotChild = FALSE;

	// Determine if we need to traverse elements, attributes,
	// or both.

	pIcd = pTrav->pIcd->pFirstChild;
	while (pIcd)
	{
		if (pIcd->uiFlags & ICD_IS_ATTRIBUTE)
		{
			uiAttrNameId = pIcd->uiDictNum;
			uiAttrIcdCount++;
		}
		else
		{
			uiElmIcdCount++;
		}
		pIcd = pIcd->pNextSibling;

	}
	bTraverseElms = (uiElmIcdCount) ? TRUE : FALSE;
	bTraverseAttrs = (uiAttrIcdCount) ? TRUE : FALSE;

	flmAssert( pTrav->pNode->getNodeType() == ELEMENT_NODE);

	if (bTraverseElms)
	{
		bTraverseElms = FALSE;
		if (RC_BAD( rc = pTrav->pNode->getFirstChild( pDb, 
			(IF_DOMNode **)&pNode)) && rc == NE_XFLM_DOM_NODE_NOT_FOUND)
		{
			if (bTraverseAttrs)
			{
				bTraverseAttrs = FALSE;
				rc = pTrav->pNode->getFirstAttribute( pDb,
								(IF_DOMNode **)&pNode);
			}
			else
			{
				rc = NE_XFLM_OK;
				goto Exit;
			}
		}
	}
	else
	{
		flmAssert( bTraverseAttrs);
		bTraverseAttrs = FALSE;
		
		// If we only have a single attribute we are searching for,
		// it will be quicker to call getAttribute than to cycle through
		// all of the attributes.
		
		if (uiAttrIcdCount == 1)
		{
			rc = pTrav->pNode->getAttribute( (IF_Db *)pDb, uiAttrNameId,
									(IF_DOMNode **)&pNode); 
		}
		else
		{
			rc = pTrav->pNode->getFirstAttribute( pDb, (IF_DOMNode **)&pNode);
		}
	}
	if (RC_BAD( rc))
	{
		if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
		{
			rc = NE_XFLM_OK;
		}
		goto Exit;
	}

	// Follow siblings until we have a match to any of the sibling ICDs
	// Attribute nodes are also considered siblings.

	for (;;)
	{
		eDomNodeType		eNodeType = pNode->getNodeType();

		// Skip any nodes that are not element or attribute nodes.

		if( eNodeType != ELEMENT_NODE && eNodeType != ATTRIBUTE_NODE)
		{
			goto Next_Node;
		}

		if (RC_BAD( rc = pNode->getNameId( pDb, &uiNameId)))
		{
			goto Exit;
		}

		// If the node has the same name id as the anchor node,
		// we need to skip it, unless it is the anchor node itself

		if( pAnchorNode &&
			 uiNameId == pAnchorNode->uiAnchorNameId &&
			 pNode->getNodeType() == pAnchorNode->eNodeType &&
			 pNode->getIxNodeId() != pAnchorNode->ui64AnchorNodeId)
		{

			// Better not be repeated attribute nodes

			flmAssert( pAnchorNode->eNodeType != ATTRIBUTE_NODE);
			if (pAnchorNode->bSeeIfRepeatingSibs)
			{
				*pbHadRepeatingSib = TRUE;
			}
			pIcd = NULL;
		}
		else
		{
			pIcd = pTrav->pIcd->pFirstChild;
			if (pNode->getNodeType() == ELEMENT_NODE)
			{
				while (pIcd &&
						 (pIcd->uiDictNum != uiNameId ||
						  (pIcd->uiFlags & ICD_IS_ATTRIBUTE)))
				{
					pIcd = pIcd->pNextSibling;
				}
			}
			else // pNode->getNodeType() == ATTRIBUTE_NODE
			{
				while (pIcd &&
						 (pIcd->uiDictNum != uiNameId ||
						  !(pIcd->uiFlags & ICD_IS_ATTRIBUTE)))
				{
					pIcd = pIcd->pNextSibling;
				}
			}
		}
		if (pIcd)
		{
			if (!pTrav->pChild)
			{
				NODE_TRAV *	pNewTrav;

				if (RC_BAD( rc = pPool->poolCalloc( sizeof( NODE_TRAV),
											(void **)&pNewTrav)))
				{
					goto Exit;
				}
				pNewTrav->pParent = pTrav;
				pTrav->pChild = pNewTrav;
			}
			else
			{
				f_memset( pTrav->pChild, 0, sizeof( NODE_TRAV));
				pTrav->pChild->pParent = pTrav;
			}

			*ppTrav = pTrav = pTrav->pChild;
			pTrav->pNode = pNode;
			pTrav->pNode->AddRef();
			pTrav->pAnchorNode = pAnchorNode;
			pTrav->uiSibIcdElms = uiElmIcdCount;
			pTrav->uiSibIcdAttrs = uiAttrIcdCount;
			pTrav->pIcd = pIcd;
			if (pNode->getNodeType() == ATTRIBUTE_NODE)
			{

				// There will only be one occurrence of any given
				// attribute node, so once we have found it, there
				// is no need to traverse any other siblings if
				// there are no other sibling ICDs that are
				// attributes.  At this point, the number of ICDs
				// that are elements is irrelevant, because we would
				// have already traversed through all of them.

				pTrav->bTraverseSibs = uiAttrIcdCount > 1 ? TRUE : FALSE;

				// Attributes don't have children

				pTrav->bTraverseChildren = FALSE;

				flmAssert( !pIcd->pFirstChild);
			}
			else
			{
				FLMBOOL	bIsAnchor = FALSE;
						
				if( pAnchorNode)
				{
					if( pNode->getNodeId() == pAnchorNode->ui64AnchorNodeId)
					{
						bIsAnchor = TRUE;
					}
				}

				// If there are attribute ICDs or more than one
				// element ICD, or there is exactly one element
				// ICD, but this is the anchor node, then no
				// need to traverse siblings.

				if (uiAttrIcdCount || uiElmIcdCount > 1 || !bIsAnchor)
				{
					pTrav->bTraverseSibs = TRUE;
				}
				else
				{
					pTrav->bTraverseSibs = FALSE;

					// No need to specially traverse siblings searching
					// for repeating siblings if bTraverseSibs is set
					// to TRUE.

					if (bIsAnchor && pAnchorNode->bSeeIfRepeatingSibs &&
						 !(*pbHadRepeatingSib))
					{
						if (RC_BAD( rc = kySeeIfRepeatingSibs( pDb, pNode,
													pbHadRepeatingSib)))
						{
							goto Exit;
						}
					}
				}

				// If we have a child ICD, we need to traverse child nodes.

				pTrav->bTraverseChildren = pIcd->pFirstChild
													? TRUE
													: FALSE;
			}
			*pbGotChild = TRUE;
			goto Exit;	// Will return NE_XFLM_OK
		}

Next_Node:

		// Try the next sibling node

		if (RC_BAD( rc = pNode->getNextSibling( pDb, (IF_DOMNode **)&pNode)))
		{
			if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
			{
				goto Exit;
			}
			rc = NE_XFLM_OK;

			// If we have not yet traversed attributes, do it now.

			if (!bTraverseAttrs)
			{
				break;
			}
			bTraverseAttrs = FALSE;
			
			// If there is only one attribute, it is quicker to call
			// getAttribute than it is to cycle through the attributes.
			
			if (uiAttrIcdCount == 1)
			{
				rc = pTrav->pNode->getAttribute( (IF_Db *)pDb, uiAttrNameId,
										(IF_DOMNode **)&pNode);
			}
			else
			{
				rc = pTrav->pNode->getFirstAttribute( pDb,
												(IF_DOMNode **)&pNode);
			}
			if (RC_BAD( rc))
			{
				if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
				{
					rc = NE_XFLM_OK;
				}
				goto Exit;
			}
		}
	}

Exit:

	if (!(*pbGotChild))
	{
		pTrav->bTraverseChildren = FALSE;
	}

	if (pNode)
	{
		pNode->Release();
	}
	return( rc);
}

/****************************************************************************
Desc:	Get a sibling node for current traversal node.
****************************************************************************/
FSTATIC RCODE kyFindSibNode(
	F_Db *		pDb,
	NODE_TRAV *	pTrav,
	FLMBOOL		bTestFirstNode,
	FLMBOOL *	pbGotSib,
	FLMBOOL *	pbHadRepeatingSib
	)
{
	RCODE				rc = NE_XFLM_OK;
	F_DOMNode *		pNode = NULL;
	ICD *				pIcd;
	FLMUINT			uiNameId;
	FLMBOOL			bTraverseAttrs = pTrav->uiSibIcdAttrs ? TRUE : FALSE;
	FLMBOOL			bTraverseElms = pTrav->uiSibIcdElms ? TRUE : FALSE;
	FLMBOOL			bGetNextNode;
	ANCHOR_NODE *	pAnchorNode = pTrav->pAnchorNode;
	eDomNodeType	eNodeType;

	*pbGotSib = FALSE;

	pNode = pTrav->pNode;
	pNode->AddRef();
	eNodeType = pNode->getNodeType();

	if( eNodeType == ELEMENT_NODE)
	{
		flmAssert( bTraverseElms);
	}
	else
	{
		flmAssert( eNodeType != DATA_NODE);
		flmAssert( bTraverseAttrs);
		bTraverseAttrs = FALSE;
	}
	bTraverseElms = FALSE;

	// Follow siblings until we have a match to any of the sibling ICDs
	// Attribute nodes are also considered siblings.

	bGetNextNode = bTestFirstNode ? FALSE : TRUE;
	for (;;)
	{
		if (bGetNextNode)
		{
			if (RC_BAD( rc = pNode->getNextSibling( pDb, (IF_DOMNode **)&pNode)))
			{
				if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
				{
					goto Exit;
				}
				rc = NE_XFLM_OK;

				// If we have not yet traversed attributes, do it now.

				if (!bTraverseAttrs)
				{
					break;
				}
				bTraverseAttrs = FALSE;
				if (RC_OK( rc = pTrav->pNode->getParentNode( pDb,
														(IF_DOMNode **)&pNode)))
				{
					rc = pNode->getFirstAttribute( pDb, (IF_DOMNode **)&pNode);
				}
				if (RC_BAD( rc))
				{
					if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
					{
						rc = NE_XFLM_OK;
					}
					goto Exit;
				}
			}
		}
		else
		{

			// Set to TRUE for next time around.

			bGetNextNode = TRUE;
		}

		// Skip any nodes that are not element or attribute nodes

		eNodeType = pNode->getNodeType();

		if( eNodeType != ELEMENT_NODE &&
			 eNodeType != ATTRIBUTE_NODE)
		{
			continue;
		}

		if (RC_BAD( rc = pNode->getNameId( pDb, &uiNameId)))
		{
			goto Exit;
		}

		// If the node has the same name id as the anchor node,
		// we need to skip it, unless it is the anchor node itself

		if (pAnchorNode &&
			 eNodeType == pAnchorNode->eNodeType &&
			 uiNameId == pAnchorNode->uiAnchorNameId &&
			 pNode->getIxNodeId() != pAnchorNode->ui64AnchorNodeId)
		{

			// Better not be repeated attribute nodes

			flmAssert( pAnchorNode->eNodeType != ATTRIBUTE_NODE);
			if (pAnchorNode->bSeeIfRepeatingSibs)
			{
				*pbHadRepeatingSib = TRUE;
			}
			pIcd = NULL;
		}
		else if( eNodeType == ELEMENT_NODE)
		{

			// Search forward for a matching ICD.

			pIcd = pTrav->pIcd;
			while (pIcd &&
					 (pIcd->uiDictNum != uiNameId ||
					  (pIcd->uiFlags & ICD_IS_ATTRIBUTE)))
			{
				pIcd = pIcd->pNextSibling;
			}

			// If didn't find a matching ICD in the forward direction,
			// search backward.

			if (!pIcd)
			{
				pIcd = pTrav->pIcd->pPrevSibling;
				while (pIcd &&
						 (pIcd->uiDictNum != uiNameId ||
						  (pIcd->uiFlags & ICD_IS_ATTRIBUTE)))
				{
					pIcd = pIcd->pPrevSibling;
				}
			}
		}
		else
		{
			flmAssert( eNodeType == ATTRIBUTE_NODE);

			// Search forward for a matching ICD.

			pIcd = pTrav->pIcd;
			while (pIcd &&
					 (pIcd->uiDictNum != uiNameId ||
					  !(pIcd->uiFlags & ICD_IS_ATTRIBUTE)))
			{
				pIcd = pIcd->pNextSibling;
			}

			// If didn't find a matching ICD in the forward direction,
			// search backward.

			if (!pIcd)
			{
				pIcd = pTrav->pIcd->pPrevSibling;
				while (pIcd &&
						 (pIcd->uiDictNum != uiNameId ||
						  !(pIcd->uiFlags & ICD_IS_ATTRIBUTE)))
				{
					pIcd = pIcd->pPrevSibling;
				}
			}
		}

		if (pIcd)
		{
			pTrav->pNode->Release();
			pTrav->pNode = pNode;
			pTrav->pNode->AddRef();
			pTrav->pIcd = pIcd;

			if( eNodeType == ATTRIBUTE_NODE)
			{

				// Attributes don't have children

				pTrav->bTraverseChildren = FALSE;
				flmAssert( !pIcd->pFirstChild);
			}
			else
			{
				// If we have a child ICD, we need to traverse child nodes.

				pTrav->bTraverseChildren = pIcd->pFirstChild
													? TRUE
													: FALSE;
			}

			*pbGotSib = TRUE;
			goto Exit;
		}
	}

Exit:

	if (!(*pbGotSib))
	{
		pTrav->bTraverseSibs = FALSE;
	}

	if (pNode)
	{
		pNode->Release();
	}

	return( rc);
}

/****************************************************************************
Desc:	Release all nodes a CDL table is pointing to.
****************************************************************************/
void kyReleaseCdls(
	IXD *			pIxd,
	CDL_HDR *	pCdlTbl
	)
{
	FLMUINT	uiLoop;
	CDL *		pCdl;

	if (pCdlTbl)
	{
		for (uiLoop = 0; uiLoop < pIxd->uiNumIcds; uiLoop++)
		{
			pCdl = pCdlTbl [uiLoop].pCdlList;
			while (pCdl)
			{

				// NOTE: pNode can be NULL because it may be a
				// placeholder for a "missing" component

				if (pCdl->pNode)
				{
					pCdl->pNode->Release();
				}
				pCdl = pCdl->pNext;
			}
			pCdlTbl [uiLoop].pCdlList = NULL;
		}
	}
}

/****************************************************************************
Desc:	Generate index keys from a given node for a particular
		index.
****************************************************************************/
RCODE F_Db::genIndexKeys(
	FLMUINT64			ui64DocumentID,
	F_DOMNode *			pIxNode,
	IXD *					pIxd,
	ICD *					pIcd,
	IxAction				eAction)
{
	RCODE					rc = NE_XFLM_OK;
	ICD *					pTmpIcd;
	ICD *					pChildIcd;
	CDL_HDR *			pCdlTbl = NULL;
	CDL *					pCdl;
	ANCHOR_NODE *		pAnchorNodeList;
	ANCHOR_NODE *		pAnchorNode;
	NODE_TRAV *			pTrav = NULL;
	FLMBOOL				bGotNode;
	F_DOMNode *			pParent = NULL;
	F_DOMNode *			pTmpNode = NULL;
	FLMUINT				uiParentNameId;
	FLMUINT				uiIxNodeName;
	FLMBOOL				bInNodeSubtree;
	FLMBOOL				bHadRepeatingSib = FALSE;
	eDomNodeType		eIxNodeType = pIxNode->getNodeType();
	FLMUINT64			ui64IxNodeId = pIxNode->getIxNodeId();

	pAnchorNodeList = NULL;

	if( RC_BAD( rc = pIxNode->getNameId( this, &uiIxNodeName)))
	{
		goto Exit;
	}
	
	// Follow links back up to highest parent.  If the path doesn't
	// match what we have in the index definition, then this node
	// is irrelevant to generating keys in this index.

	if (RC_BAD( rc = m_tempPool.poolCalloc( sizeof( ANCHOR_NODE),
												(void **)&pAnchorNode)))
	{
		goto Exit;
	}
	
	pAnchorNode->eNodeType = eIxNodeType;
	pAnchorNode->ui64AnchorNodeId = ui64IxNodeId;
	pAnchorNode->uiAnchorNameId = uiIxNodeName;

	// If the action is link/unlink, and the node is an element node
	// because it is possible for elements to be repeated, we need
	// to see if there is another sibling node of this same name.
	// If there is another sibling with this same name, we do not
	// need to add the "without" keys back in on the unlink action.
	// Nor do we need to delete the "without" keys on the link
	// action.

	if ((eAction == IX_UNLINK_NODE || eAction == IX_LINK_NODE) &&
		 eIxNodeType == ELEMENT_NODE)
	{
		pAnchorNode->bSeeIfRepeatingSibs = TRUE;
	}
	
	pAnchorNodeList = pAnchorNode;
	pTmpIcd = pIcd;
	pParent = pIxNode;
	pParent->AddRef();
	
	while (pTmpIcd->pParent)
	{
		pTmpIcd = pTmpIcd->pParent;
		if (RC_BAD( rc = pParent->getParentNode( this, (IF_DOMNode **)&pParent)))
		{
			if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
			{
				rc = NE_XFLM_OK;
			}
			goto Exit;
		}
		
		if (RC_BAD( rc = pParent->getNameId( this, &uiParentNameId)))
		{
			goto Exit;
		}
		
		if (pTmpIcd->uiDictNum == ELM_ROOT_TAG)
		{
			// If this node has a parent, it is not the root node, so
			// we don't match the ICD.

			if (pParent->getParentId())
			{
				goto Exit;		// Will return NE_XFLM_OK
			}

			// Better not be any more parent ICDs.

			flmAssert( !pTmpIcd->pParent);
		}
		else
		{
			if (pTmpIcd->uiDictNum != uiParentNameId)
			{
				goto Exit;		// Will return NE_XFLM_OK
			}
		}
		
		if (RC_BAD( rc = m_tempPool.poolCalloc( sizeof( ANCHOR_NODE),
													(void **)&pAnchorNode)))
		{
			goto Exit;
		}
		
		pAnchorNode->eNodeType = pParent->getNodeType();
		flmAssert( pAnchorNode->eNodeType == ELEMENT_NODE);
		pAnchorNode->ui64AnchorNodeId = pParent->getNodeId();
		pAnchorNode->uiAnchorNameId = uiParentNameId;
		pAnchorNode->pNext = pAnchorNodeList;
		pAnchorNodeList = pAnchorNode;
	}

	// Allocate a CDL table for the index.

	if (RC_BAD( rc = m_tempPool.poolCalloc( sizeof( CDL_HDR) *
								pIxd->uiNumIcds, (void **)&pCdlTbl)))
	{
		goto Exit;
	}

	// Create a traversal node for the root node we arrived at.

	if (RC_BAD( rc = m_tempPool.poolCalloc( sizeof( NODE_TRAV),
											(void **)&pTrav)))
	{
		goto Exit;
	}

	pTrav->pNode = pParent;
	pTrav->pNode->AddRef();
	pTrav->pIcd = pTmpIcd;
	pTrav->pAnchorNode = pAnchorNodeList;

	// Count the number of sibling ICDs that are attributes versus elements

	while (pTmpIcd)
	{
		if (pTmpIcd->uiFlags & ICD_IS_ATTRIBUTE)
		{
			pTrav->uiSibIcdAttrs++;
		}
		else
		{
			pTrav->uiSibIcdElms++;
		}
		pTmpIcd = pTmpIcd->pNextSibling;
	}

	pTmpIcd = pTrav->pIcd->pPrevSibling;

	while (pTmpIcd)
	{
		if (pTmpIcd->uiFlags & ICD_IS_ATTRIBUTE)
		{
			pTrav->uiSibIcdAttrs++;
		}
		else
		{
			pTrav->uiSibIcdElms++;
		}

		pTmpIcd = pTmpIcd->pPrevSibling;
	}

#ifdef FLM_DEBUG
	if (pTrav->pIcd->uiDictNum == ELM_ROOT_TAG)
	{
		flmAssert( !pTrav->uiSibIcdAttrs && pTrav->uiSibIcdElms == 1);
		flmAssert( pTrav->pNode->getNodeType() == ELEMENT_NODE);
	}
#endif

	// See if we need to do this node's siblings.  If it is an
	// attribute node and there are no other attribute ICDs and
	// no element ICDs, or if it is an element node and there are
	// no other element ICDs and no attribute ICDs, we don't need
	// to do the node's siblings.  Otherwise, we do, so we will
	// go up to the node's parent and back down to the first
	// child element or first attribute.

	pTrav->bTraverseSibs = FALSE;
	if ((pTrav->pNode->getNodeType() == ATTRIBUTE_NODE &&
		  (pTrav->uiSibIcdAttrs > 1 || pTrav->uiSibIcdElms)) ||
		 (pTrav->pNode->getNodeType() == ELEMENT_NODE &&
		  (pTrav->uiSibIcdAttrs || pTrav->uiSibIcdElms > 1)))
	{
		if (RC_BAD( rc = pTrav->pNode->getParentNode( this,
			(IF_DOMNode **)&pTmpNode)))
		{
			if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
			{
				goto Exit;
			}
			rc = NE_XFLM_OK;
		}
		else if ((pTrav->pNode->getNodeType() == ATTRIBUTE_NODE &&
					 pTrav->uiSibIcdElms) ||
					(pTrav->pNode->getNodeType() == ELEMENT_NODE &&
					pTrav->uiSibIcdElms > 1))
		{
			if (RC_BAD( rc = pTmpNode->getFirstChild( this,
				(IF_DOMNode **)&pTmpNode)))
			{
				if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
				{
					goto Exit;
				}

				rc = NE_XFLM_OK;

				// No sibling elements, do we need to do attributes?

				if ((pTrav->pNode->getNodeType() == ATTRIBUTE_NODE &&
					  pTrav->uiSibIcdAttrs > 1) ||
					 (pTrav->pNode->getNodeType() == ELEMENT_NODE &&
					  pTrav->uiSibIcdAttrs))
				{
					goto Get_First_Attribute;
				}
			}
			else
			{
				pTrav->pNode->Release();
				pTrav->pNode = pTmpNode;
				pTrav->pNode->AddRef();
				pTmpNode->Release();
				pTmpNode = NULL;
				pTrav->bTraverseSibs = TRUE;
			}
		}
		else
		{
Get_First_Attribute:
			if (RC_BAD( rc = pTmpNode->getFirstAttribute( this,
											(IF_DOMNode **)&pTmpNode)))
			{
				if (rc != NE_XFLM_DOM_NODE_NOT_FOUND)
				{
					goto Exit;
				}
				rc = NE_XFLM_OK;
			}
			else
			{
				pTrav->pNode->Release();
				pTrav->pNode = pTmpNode;
				pTrav->pNode->AddRef();
				pTmpNode->Release();
				pTmpNode = NULL;
				pTrav->bTraverseSibs = TRUE;
			}
		}
	}

	// Position to a sibling node to start on.  At this point, we should
	// be guaranteed to at least find the node we started on

	if (pTrav->bTraverseSibs)
	{
		if (RC_BAD( rc = kyFindSibNode( this, pTrav, TRUE, &bGotNode,
													&bHadRepeatingSib)))
		{
			goto Exit;
		}
		flmAssert( bGotNode);
	}
	else if (pTrav->pNode->getNodeType() == ATTRIBUTE_NODE)
	{
		pTrav->bTraverseChildren = FALSE;
	}
	else	// ELEMENT_NODE
	{

		// If we have a child ICD, we need to traverse child nodes.

		pTrav->bTraverseChildren = pTrav->pIcd->pFirstChild
											? TRUE
											: FALSE;

		// If this is our primary anchor node, we need to see
		// if there are repeating siblings.  No need to do it
		// in the other above cases, because if bTraversSibs is
		// TRUE, it will happen inside kyFindSibNode, and if
		// the node type is an attribute node, they should never
		// have repeated siblings.

		if (pTrav->pAnchorNode &&
			 pTrav->pAnchorNode->bSeeIfRepeatingSibs)
		{
			if (RC_BAD( rc = kySeeIfRepeatingSibs( this, pTrav->pNode,
										&bHadRepeatingSib)))
			{
				goto Exit;
			}
		}
	}

	// Follow all links from the current node and ICD,
	// populating CDL tables as we go.
	
	if( pTrav->pNode->getNodeType() == eIxNodeType && 
		 pTrav->pNode->getNameId() == uiIxNodeName &&
		 pTrav->pNode->getIxNodeId() == ui64IxNodeId)
	{
		pTrav->bInNodeSubtree = TRUE;
	}
	
	for (;;)
	{

		pCdl = pCdlTbl [pTrav->pIcd->uiCdl].pCdlList;
		if (pCdl && !pCdl->pNode &&
			 pTrav->pNode->getParentId() &&
			 pCdl->ui64ParentId == pTrav->pNode->getParentId())
		{
			pCdl->pNode = pTrav->pNode;
			pCdl->bInNodeSubtree = pTrav->bInNodeSubtree;
			pCdl->pNode->AddRef();
		}
		else
		{
			if (RC_BAD( rc = m_tempPool.poolAlloc( sizeof( CDL),
											(void **)&pCdl)))
			{
				goto Exit;
			}

			pCdl->pNode = pTrav->pNode;
			pCdl->ui64ParentId = pTrav->pNode->getParentId();
			pCdl->bInNodeSubtree = pTrav->bInNodeSubtree;
			pCdl->pNode->AddRef();
			pCdl->pNext = pCdlTbl [pTrav->pIcd->uiCdl].pCdlList;
			pCdlTbl [pTrav->pIcd->uiCdl].pCdlList = pCdl;
		}

		// Add "missing" place-holders for any child ICDs

		pChildIcd = pTrav->pIcd->pFirstChild;
		while (pChildIcd)
		{
			if (RC_BAD( rc = m_tempPool.poolAlloc(
										sizeof( CDL), (void **)&pCdl)))
			{
				goto Exit;
			}
			
			pCdl->pNode = NULL;
			pCdl->bInNodeSubtree = pTrav->bInNodeSubtree;
			pCdl->ui64ParentId = pTrav->pNode->getNodeId();
			pCdl->pNext = pCdlTbl [pChildIcd->uiCdl].pCdlList;
			pCdlTbl [pChildIcd->uiCdl].pCdlList = pCdl;
			pChildIcd = pChildIcd->pNextSibling;
		}

Next_Node:

		if (pTrav->bTraverseChildren)
		{
			bInNodeSubtree = pTrav->bInNodeSubtree;
			if (RC_BAD( rc = kyFindChildNode( this, &m_tempPool,
									&pTrav, &bGotNode, &bHadRepeatingSib)))
			{
				goto Exit;
			}
			
			if (!bGotNode)
			{
				goto Next_Node;
			}
			
			if( bInNodeSubtree || 
				 (pTrav->pNode->getNodeType() == eIxNodeType &&
				  pTrav->pNode->getNameId() == uiIxNodeName &&
				  pTrav->pNode->getIxNodeId() == ui64IxNodeId))
			{
				pTrav->bInNodeSubtree = TRUE;
			}
		}
		else if (pTrav->bTraverseSibs)
		{
			// Here we are using bInNodeSubtree to indicate whether the entire
			// sibling list is in the node's subtree.  It could be that the
			// current node is the subtree, but that does not mean that the
			// entire sibling list is in the sub-tree.

			if (!pTrav->bInNodeSubtree)
			{
				bInNodeSubtree = FALSE;
			}
			else
			{
				// Is this sibling list in the node subtree?
				// If bInNodeSubtree is TRUE, and this is not the original
				// sub-tree node, then this sibling list has to be subordinate
				// to the original sub-tree node.

				if( pTrav->pNode->getNodeType() != eIxNodeType || 
					pTrav->pNode->getNameId() != uiIxNodeName ||
					pTrav->pNode->getIxNodeId() != ui64IxNodeId)
				{
					bInNodeSubtree = TRUE;
				}
				else
				{
					bInNodeSubtree = FALSE;
				}
			}
			
			if (RC_BAD( rc = kyFindSibNode( this, pTrav, FALSE, &bGotNode,
										&bHadRepeatingSib)))
			{
				goto Exit;
			}
			
			if (!bGotNode)
			{
				goto Next_Node;
			}

			// If the entire sibling list was in the node sub-tree or this
			// node is the subtree, then we are in the subtree.

			if( bInNodeSubtree || 
				 (pTrav->pNode->getNodeType() == eIxNodeType &&
				  pTrav->pNode->getNameId() == uiIxNodeName &&
				  pTrav->pNode->getIxNodeId() == ui64IxNodeId))
			{
				pTrav->bInNodeSubtree = TRUE;
			}
			else
			{
				pTrav->bInNodeSubtree = FALSE;
			}
		}
		else
		{
			if (pTrav->pParent)
			{
				pTrav->pNode->Release();
				pTrav->pNode = NULL;
				pTrav = pTrav->pParent;
				pTrav->bTraverseChildren = FALSE;
				goto Next_Node;
			}
			else
			{
				break;
			}
		}
	}

	// Generate the keys from the combinations of CDLs.

	switch (eAction)
	{
		case IX_UNLINK_NODE:
		{
			if (RC_BAD( rc = buildKeys( ui64DocumentID, pIxd, pCdlTbl,
													TRUE, FALSE)))
			{
				goto Exit;
			}

			if (!bHadRepeatingSib)
			{
				if (RC_BAD( rc = buildKeys( ui64DocumentID, pIxd, pCdlTbl,
														FALSE, TRUE)))
				{
					goto Exit;
				}
			}
			break;
		}

		case IX_LINK_NODE:
		{
			if( !bHadRepeatingSib)
			{
				if (RC_BAD( rc = buildKeys( ui64DocumentID, pIxd, pCdlTbl,
														FALSE, FALSE)))
				{
					goto Exit;
				}
			}

			if (RC_BAD( rc = buildKeys( ui64DocumentID, pIxd, pCdlTbl,
													TRUE, TRUE)))
			{
				goto Exit;
			}
			break;
		}

		case IX_DEL_NODE_VALUE:
		{
			if (RC_BAD( rc = buildKeys( ui64DocumentID, pIxd, pCdlTbl,
										TRUE, FALSE)))
			{
				goto Exit;
			}
			break;
		}

		case IX_ADD_NODE_VALUE:
		{
			if (RC_BAD( rc = buildKeys( ui64DocumentID, pIxd, pCdlTbl,
										TRUE, TRUE)))
			{
				goto Exit;
			}
			break;
		}

		default:
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_NOT_IMPLEMENTED);
			goto Exit;
		}
	}

Exit:

	// Release the nodes CDL table

	kyReleaseCdls( pIxd, pCdlTbl);

	// Release any nodes in the traversal list.

	if (pTrav)
	{

		// Go to top of list

		while (pTrav->pParent)
		{
			pTrav = pTrav->pParent;
		}

		// Visit each NODE_TRAV node and release whatever
		// DOM node it is pointing to.

		while (pTrav)
		{
			if (pTrav->pNode)
			{
				pTrav->pNode->Release();
				pTrav->pNode = NULL;
			}
			pTrav = pTrav->pChild;
		}
	}

	if (pParent)
	{
		pParent->Release();
	}

	return( rc);
}

/****************************************************************************
Desc:	Routine that is called before and after changing a node - so we can
		delete and add any keys from the database.
****************************************************************************/
RCODE F_Db::updateIndexKeys(
	FLMUINT			uiCollectionNum,
	F_DOMNode *		pIxNode,
	IxAction			eAction,
	FLMBOOL			bStartOfUpdate,
	FLMBOOL *		pbIsIndexed)
{
	RCODE				rc = NE_XFLM_OK;
	FLMBOOL			bIsRoot;
	FLMBOOL			bIsIndexed = FALSE;
	ICD *				pIcd;
	FLMUINT			uiIcdDictNum;
	IXD *				pIxd;
	void *			pvMark = NULL;
	FLMUINT64		ui64DocumentID;
	FLMUINT			uiNameId;
	F_DOMNode *		pNode = NULL;
	F_AttrElmInfo	defInfo;
	IxAction			eTmpAction;

	if( bStartOfUpdate)
	{
		if (RC_BAD( rc = krefCntrlCheck()))
		{
			goto Exit;
		}
		if (m_pOldNodeList)
		{
			m_pOldNodeList->resetList();
		}
	}
	
	if( RC_BAD( rc = pIxNode->getNameId( this, &uiNameId)))
	{
		goto Exit;
	}

	if( !uiNameId)
	{
		goto Exit;
	}

	pNode = pIxNode;
	pNode->AddRef();

	// Lookup the node's ICD list

	switch( pNode->getNodeType())
	{
		case ELEMENT_NODE:
		{
			if (RC_BAD( rc = m_pDict->getElement( this, uiNameId, &defInfo)))
			{
				goto Exit;
			}
			
			break;
		}

		case ATTRIBUTE_NODE:
		{
			if (RC_BAD( rc = m_pDict->getAttribute( this, uiNameId, &defInfo)))
			{
				goto Exit;
			}
			
			break;
		}

		case DATA_NODE:
		{
			// Need to operate on the parent node, which should be an element
			// node.

			if (RC_BAD( rc = m_pDict->getElement( this, uiNameId, &defInfo)))
			{
				goto Exit;
			}
			
			if (RC_BAD( rc = pNode->getParentNode( this, (IF_DOMNode **)&pNode)))
			{
				if (rc == NE_XFLM_DOM_NODE_NOT_FOUND)
				{
					// Data node not yet linked to a parent node, so
					// it won't affect indexing.

					rc = NE_XFLM_OK;
				}
				
				goto Exit;
			}

			// Name ID of element better be the same as what we found in the
			// data node, and node type better be an element node.

			flmAssert( pNode->getNameId() == uiNameId);
			flmAssert( pNode->getNodeType() == ELEMENT_NODE);

			// Action cannot be link/unlink on data nodes.  Higher level needs
			// to take care of.  They should first delete keys on the
			// parent node, then unlink/link the node, then call add keys on
			// the parent node.

			flmAssert( eAction != IX_LINK_NODE && eAction != IX_UNLINK_NODE);
			break;
		}

		default:
		{

			// If the node is not an element or an attribute, it will not have
			// any effect on indexing.

			goto Exit;
		}
	}

	bIsRoot = pNode->isRootNode();

	if( (pIcd = defInfo.m_pFirstIcd) == NULL)
	{
		if( !bIsRoot || !m_pDict->m_pRootIcdList)
		{
			goto Exit;
		}
		else
		{
			pIcd = m_pDict->m_pRootIcdList;
		}
	}

	// The node may be indexed so we need to process the ICD list

	pvMark = m_tempPool.poolMark();
	bIsIndexed = TRUE;
	ui64DocumentID = pNode->getDocumentId();

	// Process each index

	for (;;)
	{
		pIxd = pIcd->pIxd;
		uiIcdDictNum = pIcd->uiDictNum;

		// See if this ICD's index is on the collection we are doing.
		// Also, make sure if we are doing a specific index, that this is
		// that index.

		if (pIxd->uiCollectionNum != uiCollectionNum)
		{
			goto Next_Index;
		}

		// See if this document has been indexed for this index.

		if (ui64DocumentID > pIxd->ui64LastDocIndexed)
		{
			goto Next_Index;
		}

		if ((eTmpAction = eAction) == IX_LINK_AND_ADD_NODE)
		{
			if (!pIcd->pParent && !pIcd->pNextSibling && !pIcd->pPrevSibling)
			{
				if (!pIcd->uiKeyComponent && !pIcd->uiDataComponent)
				{

					// If this ICD is not a key or data component, it has no
					// effect on index keys if we are only modifying its value.

					goto Next_Index;
				}
				else
				{
					eTmpAction = IX_ADD_NODE_VALUE;
				}
			}
			else
			{
				eTmpAction = IX_LINK_NODE;
			}
		}
		else if (eTmpAction == IX_LINK_NODE || eTmpAction == IX_UNLINK_NODE)
		{

			// IX_LINK_NODE and IX_UNLINK_NODE are called when a node is linked
			// to its parent.  Thus, if this ICD is the root ICD, it will have
			// no effect on index keys.

			if (!pIcd->pParent && !pIcd->pNextSibling && !pIcd->pPrevSibling)
			{
				goto Next_Index;
			}
		}
		else
		{
			if (!pIcd->uiKeyComponent && !pIcd->uiDataComponent)
			{

				// If this ICD is not a key or data component, it has no
				// effect on index keys if we are only modifying its value.

				goto Next_Index;
			}
		}

		// Generate index keys for this index.

		if (RC_BAD( rc = genIndexKeys( ui64DocumentID, pNode, pIxd,
									pIcd, eTmpAction)))
		{
			goto Exit;
		}

Next_Index:

		m_tempPool.poolReset( pvMark);
		if ((pIcd = pIcd->pNextInChain) == NULL)
		{
			if (!bIsRoot || uiIcdDictNum == ELM_ROOT_TAG)
			{
				break;
			}

			// When done processing regular ICDs, process the
			// root ICD list, if this node is a root node.

			if ((pIcd = m_pDict->m_pRootIcdList) == NULL)
			{
				break;
			}
		}
	}

Exit:

	if( pvMark)
	{
		m_tempPool.poolReset( pvMark);
	}

	if (pNode)
	{
		pNode->Release();
	}
	
	if( pbIsIndexed)
	{
		*pbIsIndexed = bIsIndexed;
	}

	return( rc);
}
