//-------------------------------------------------------------------------
// Desc:	Sweep database to check field usage.
// Tabs:	3
//
//		Copyright (c) 1996-2006 Novell, Inc. All Rights Reserved.
//
//		This program is free software; you can redistribute it and/or
//		modify it under the terms of version 2 of the GNU General Public
//		License as published by the Free Software Foundation.
//
//		This program is distributed in the hope that it will be useful,
//		but WITHOUT ANY WARRANTY; without even the implied warranty of
//		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//		GNU General Public License for more details.
//
//		You should have received a copy of the GNU General Public License
//		along with this program; if not, contact Novell, Inc.
//
//		To contact Novell about this file by physical or electronic mail,
//		you may find current contact information at www.novell.com
//
// $Id: flsweep.cpp 12329 2006-01-20 17:49:30 -0700 (Fri, 20 Jan 2006) ahodgkinson $
//-------------------------------------------------------------------------

#include "flaimsys.h"

/****************************************************************************
Desc:
****************************************************************************/
class DbDict : public F_Object
{
public:

	DbDict()
	{
		m_puiStateTbl = NULL;
		m_bInternalTrans = FALSE;
	}

	~DbDict();

	RCODE init(
		FDB *			pDb,
		FLMBOOL		bInternalTrans,
		FLMUINT		uiMode,
		FLMBOOL *	pbFoundPurgeField);

	FINLINE FLMUINT getState(
		FLMUINT		uiFieldID)
	{
		if( uiFieldID > m_uiTblSize)
		{
			return( 0);
		}
		else
		{
			return( m_puiStateTbl[ uiFieldID]);
		}
	}

	RCODE changeState(
		FLMUINT		uiFieldID,
		FLMUINT		uiNewState);

	RCODE finish( void);

private:

	FDB *				m_pDb;
	FLMBOOL			m_bInternalTrans;
	FLMUINT *		m_puiStateTbl;
	FLMUINT			m_uiTblSize;
};

/****************************************************************************
Desc:
****************************************************************************/
class DbWalk : public F_Object
{
public:

	SWEEP_INFO		m_SwpInfo;

	DbWalk()
	{
		f_memset( &m_SwpInfo, 0, sizeof( SWEEP_INFO));
		m_pDb = NULL;
		m_bInternalTrans = FALSE;
		m_uiCallbackFreq = 0;
		m_fnStatusHook = NULL;
		m_UserData = 0;
		m_uiNextLFile = 0;
		m_uiRecsRead = 0;
		m_uiLastDrn = 0;
	}

	~DbWalk()
	{
	}

	FINLINE void init(
		FDB *			pDb,
		FLMBOOL		bInternalTrans,
		FLMUINT		uiCallbackFreq,
		STATUS_HOOK fnStatusHook,
		void *	  	UserData)
	{
		m_pDb = pDb;
		m_bInternalTrans = bInternalTrans;
		m_uiCallbackFreq = uiCallbackFreq;
		m_fnStatusHook = fnStatusHook;
		m_UserData = UserData;
	}

	RCODE nextContainer(
		FLMUINT *		puiContainer);

	RCODE nextRecord(
		FlmRecord **	ppRecord);

	RCODE updateRecord(
		FLMUINT			uiDrn,
		FlmRecord *		pRecord);

private:

	FDB *				m_pDb;
	FLMBOOL			m_bInternalTrans;
	FLMUINT			m_uiCallbackFreq;
	STATUS_HOOK		m_fnStatusHook;
	void *			m_UserData;
	FLMUINT			m_uiNextLFile;
	FLMUINT			m_uiRecsRead;
	FLMUINT			m_uiLastDrn;
};

/****************************************************************************
Desc:		Provides the ability to scan a FLAIM database for the purpose of
			performing maintenance activities and collecting statistics.
Notes:	During a database sweep, the user may perform one or all of the
			following:

			 1) Check field and record template usage:  When FlmDbSweep finds
			 occurrences of fields or records that have a status of 'checking',
			 their status will be changed to 'active'.  If no occurence of a
			 particular field/template is found during the database sweep, the
			 status of the item will be change from 'checking' to 'unused'.
	
			 Note:  An 'unused' status associated with a field indicates that no
			 occurances of the field were found within any data records. The field
			 may still be referenced from an index or record template definition.
			 It is the user's responsibility to remove any dictionary references
			 to the 'unused' item before attempting to delete it.
	
			 2) Purge field and record templates from a database:  This option
			 will remove any occurances of fields or records that have a 'purge'
			 status.  Before removing occurances, the dictionary is checked to
			 verify that no other dictionary definitions reference the item to be
			 purged.  If references are found, an error is returned.  Otherwise,
			 'purged' items will be deleted.
	
			 3) Visit database items:  This option allows the user to visit
			 all items within the database and gather statistics about the
			 database and it's contents.
****************************************************************************/
FLMEXP RCODE FLMAPI FlmDbSweep(
	HFDB				hDb,
	FLMUINT			uiSweepMode,
	FLMUINT			uiCallbackFreq,
	STATUS_HOOK 	fnStatusHook,
	void *			UserData)
{
	RCODE				rc = FERR_OK;
	DbDict *			pDbDict = NULL;
	DbWalk *			pDbWalk = NULL;
	FlmRecord *		pRecord = NULL;
	FLMUINT			uiContainer;
	FLMUINT			uiState;
	SWEEP_INFO 		SwpInfo;
	FLMBOOL			bStartedTrans = FALSE;
	FDB *				pDb = (FDB *) hDb;
	FLMUINT			uiEncState;

	if( IsInCSMode( pDb))
	{
		fdbInitCS( pDb);
		rc = RC_SET( FERR_NOT_IMPLEMENTED);
		goto ExitCS;
	}

	if( (pDbWalk = f_new DbWalk) == NULL)
	{
		rc = RC_SET( FERR_MEM);
		goto Exit;
	}

	f_memset( &SwpInfo, 0, sizeof( SWEEP_INFO));
	
	if( RC_BAD( rc = fdbInit( pDb, FLM_NO_TRANS,
		FDB_TRANS_GOING_OK, 0, NULL)))
	{
		goto Exit;
	}
	
	if( pDb->uiTransType == FLM_READ_TRANS)
	{
		rc = RC_SET_AND_ASSERT( FERR_ILLEGAL_TRANS_OP);
		goto Exit;
	}
	else if( pDb->uiTransType == FLM_NO_TRANS)
	{
		if( RC_BAD( rc = flmBeginDbTrans( pDb, FLM_READ_TRANS,
			0, FLM_DONT_POISON_CACHE)))
		{
			goto Exit;
		}

		bStartedTrans = TRUE;
	}
			
	pDbWalk->init( pDb, bStartedTrans, uiCallbackFreq, fnStatusHook, UserData);
	SwpInfo.hDb = pDbWalk->m_SwpInfo.hDb = hDb;

	// Only initialize a DbDict if needed
	
	if( (uiSweepMode & SWEEP_CHECKING_FLDS) || 
		(uiSweepMode & SWEEP_PURGED_FLDS))
	{
		FLMBOOL		bFoundPurgeField;

		if( (pDbDict = f_new DbDict) == NULL)
		{
			rc = RC_SET( FERR_MEM);
			goto Exit;
		}

		if( RC_BAD( rc = pDbDict->init( pDb, bStartedTrans, 
			uiSweepMode, &bFoundPurgeField)))
		{
			goto Exit;
		}

		// If user is performing purge field sweep and dictionary contains no
		// purged fields then just return.

		if( uiSweepMode == SWEEP_PURGED_FLDS && !bFoundPurgeField)
		{
			goto Exit;
		}
	}

	for (;;)
	{
		// Get the next container

		if( RC_BAD( rc = pDbWalk->nextContainer( &uiContainer)))
		{
			if( rc == FERR_EOF_HIT)
			{
				// No more containers to process.
				
				rc = FERR_OK;
				break;
			}
			else
			{
				goto Exit;
			}
		}

		if( !pDbDict &&
			 !(uiCallbackFreq & EACH_RECORD || uiCallbackFreq & EACH_FIELD))
		{
			// User is performing a status sweep and they are not visiting
			// each record or field.  Go to next container.
			
			continue;
		}

		SwpInfo.uiContainer = uiContainer;

		// Visit each record in the container.

		for( ;;)
		{
			FLMBOOL		bRecChanged;
			void *		pvField;
			void *		pvPrevField;
			FLMUINT		uiDrn;

			if( RC_BAD( rc = pDbWalk->nextRecord( &pRecord)))
			{
				if( rc == FERR_EOF_HIT)
				{
					rc = FERR_OK;
					break;
				}
				else
				{
					goto Exit;
				}
			}

			bRecChanged = FALSE;
			uiDrn = pRecord->getID();
			SwpInfo.uiRecId = uiDrn;

			for( pvField = pRecord->root(); pvField; 
				pvField = pRecord->next( pvField))
			{
				SwpInfo.pRecord = pRecord;
				SwpInfo.pvField = pvField;

				// Call the field call-back

				if( (uiCallbackFreq & EACH_FIELD) && fnStatusHook)
				{
					if( RC_BAD( rc = (fnStatusHook)( FLM_SWEEP_STATUS,
							(void *)&SwpInfo, (void *)EACH_FIELD, UserData)))
					{
						if( rc == FERR_EOF_HIT)
						{
							// User returned FERR_EOF_HIT, means skip to next record.

							rc = FERR_OK;
							break;
						}
						else
						{
							goto Exit;
						}
					}
				}

				// Continue to next field if no DbDict object.

				if( !pDbDict)
				{
					continue;
				}

				uiState = pDbDict->getState( pRecord->getFieldID( pvField));
				
				// If the field is encrypted, we need to check the state of the 
				// encryption definition record as well, since it is being
				// referenced by this field.
				
				if( pRecord->isEncryptedField( pvField))
				{
					uiEncState = pDbDict->getState( 
											pRecord->getEncryptionID( pvField));
				}
				else
				{
					uiEncState = 0;
				}
				
				if( !uiState && !uiEncState)
				{
					continue;
				}

				if( (uiState == ITT_FLD_STATE_CHECKING ||
					  uiState == ITT_FLD_STATE_PURGE ||
					  uiEncState == ITT_ENC_STATE_CHECKING ||
					  uiEncState == ITT_ENC_STATE_PURGE) &&
					 (uiCallbackFreq & EACH_CHANGE) && fnStatusHook)
				{
					if( RC_BAD( rc = (fnStatusHook)( FLM_SWEEP_STATUS,
						(void *)&SwpInfo, (void *)EACH_CHANGE, UserData)))
					{
						goto Exit;
					}
				}

				if( uiState == ITT_FLD_STATE_CHECKING)
				{
					// Change the field's state to 'active'

					if( RC_BAD( rc = pDbDict->changeState(
						pRecord->getFieldID( pvField), ITT_FLD_STATE_ACTIVE)))
					{
						goto Exit;
					}
				}
				else if( uiState == ITT_FLD_STATE_PURGE)
				{
					// If needed, create writeable version of record and
					// reposition to field

					if( pRecord->isReadOnly())
					{
						FlmRecord *	pTmpRec;
						FLMUINT		uiFieldID = pRecord->getFieldID( pvField);
						
						if( (pTmpRec = pRecord->copy()) == NULL)
						{
							rc = RC_SET( FERR_MEM);
							goto Exit;
						}
						
						pRecord->Release();
						pRecord = pTmpRec;

						pvField = pRecord->find( pRecord->root(), uiFieldID);

						// Should always be able to re-find the field.

						flmAssert( pvField);
					}
					
					// Remove the purged field from the record.

					bRecChanged = TRUE;
					if( pvField == pRecord->root())
					{
						// Passing a NULL pRecord to updateRecord will delete
						// the record.

						pRecord->Release();
						pRecord = NULL;

						// Get out of the for loop - nothing more to do with
						// this record

						break;
					}
					else
					{
						// Must save the previous field before removing the
						// field so we can set pvField to it after removing
						// pvField.

						pvPrevField = pRecord->prev( pvField);
						pRecord->remove( pvField);
						pvField = pvPrevField;
					}
				}

				// Check the EncDef state, independant of the field state.  If the
				// field has been purged, we don't need to update the EncDef
				// record since it is no longer being referenced by this field.
				
				if( uiEncState == ITT_ENC_STATE_CHECKING && 
					 uiState != ITT_FLD_STATE_PURGE)
				{
					// Change the EncDef record's state to 'active'
	
					if( RC_BAD( rc = pDbDict->changeState(
						pRecord->getEncryptionID( pvField), ITT_ENC_STATE_ACTIVE)))
					{
						goto Exit;
					}
				}
				else if( uiEncState == ITT_ENC_STATE_PURGE &&
							uiState != ITT_FLD_STATE_PURGE)
				{
					FLMUINT				uiDataLength = pRecord->getDataLength( pvField);
					const FLMBYTE *	pucDataSource = pRecord->getDataPtr( pvField);
					FLMBYTE *			pucDestPtr;
					
					// If the EncDef record has a state of purge, then we must
					// change the field of this record so that it is no longer
					// encrypted.
					
					if( RC_BAD( rc = pRecord->allocStorageSpace( pvField,
						pRecord->getDataType( pvField), uiDataLength, 0, 0, 0,
						&pucDestPtr, NULL)))
					{
						goto Exit;
					}
					
					f_memmove(pucDestPtr, pucDataSource, uiDataLength);
					bRecChanged = TRUE;
				}
			}	

			// Record was changed because a purged field was found.

			if( bRecChanged)
			{
				if( RC_BAD( rc = pDbWalk->updateRecord( uiDrn, pRecord)))
				{
					goto Exit;
				}
			}
		}
	}

	// Now complete any changes needed within the dictionary

	if( pDbDict)
	{
		rc = pDbDict->finish();
	}

Exit:

	if( pDbWalk)
	{
		pDbWalk->Release();
		pDbWalk = NULL;	
	}
	
	if( pDbDict)			
	{
		pDbDict->Release();
		pDbWalk = NULL;
	}

	if( bStartedTrans)
	{
		if(  pDb->uiTransType != FLM_NO_TRANS)
		{
			(void)flmAbortDbTrans( pDb);
		}
	}

ExitCS:

	flmExit( FLM_DB_SWEEP, pDb, rc);

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

/****************************************************************************
Desc:	Get the next container within this database
****************************************************************************/
RCODE DbWalk::nextContainer(
	FLMUINT *	puiContainer)
{
	LFILE *		pLFileTbl = (LFILE *)m_pDb->pDict->pLFileTbl;
	FLMBOOL		bFoundContainer = FALSE;
	RCODE			rc = FERR_OK;
	FLMUINT		uiTblSize;

	for( uiTblSize = m_pDb->pDict->uiLFileCnt;
		  m_uiNextLFile < uiTblSize;
		  m_uiNextLFile++)
	{
		if( pLFileTbl [m_uiNextLFile].uiLfType == LF_CONTAINER &&
			 (pLFileTbl [m_uiNextLFile].uiLfNum == FLM_DATA_CONTAINER ||
			  pLFileTbl [m_uiNextLFile].uiLfNum < FLM_DICT_CONTAINER))
		{
			// We've found the next container to visit.

			m_SwpInfo.uiContainer = *puiContainer =
			pLFileTbl [m_uiNextLFile].uiLfNum;

			// Note: don't need to release pRecord, because an addRef wasn't done.

			m_SwpInfo.pRecord = NULL;
			m_SwpInfo.pvField = NULL;

			if( (m_uiCallbackFreq & EACH_CONTAINER) && m_fnStatusHook)
			{
				if( RC_BAD( rc = (m_fnStatusHook)( FLM_SWEEP_STATUS,
					(void *)&m_SwpInfo, (void *)EACH_CONTAINER, m_UserData)))
				{
					if( rc == FERR_EOF_HIT)
					{

						// User wants to skip this container.

						continue;
					}
					else
					{
						goto Exit;
					}
				}
			}

			// Perform needed setup to read this containers records.

			m_uiRecsRead = 0;
			m_uiLastDrn = 0;
			m_uiNextLFile++;
			bFoundContainer = TRUE;
			break;
		}
	}

	if( !bFoundContainer)
	{
		rc = RC_SET( FERR_EOF_HIT);
		goto Exit;
	}

Exit:

	return( rc);
}

/****************************************************************************
Desc:	Returns the next record within the current container.
****************************************************************************/
RCODE DbWalk::nextRecord(
	FlmRecord **	ppRecord)
{
	RCODE				rc = FERR_OK;
	FLMUINT			uiDrn;

	// Loop till we get a record or hit end of container.

	for( ;;)
	{
		if( m_bInternalTrans)
		{
			// Abort and start a new read transaction every 1000 records.
	
			if( (m_uiRecsRead % 1000) == 0)
			{
AbortTrans:
	
				if( m_pDb->uiTransType != FLM_NO_TRANS)
				{
					(void)flmAbortDbTrans( m_pDb);
				}
	
				if( RC_BAD( rc = flmBeginDbTrans( m_pDb, FLM_READ_TRANS,
					0, FLM_DONT_POISON_CACHE)))
				{
					goto Exit;
				}
			}
		}

		if( *ppRecord)
		{
			(*ppRecord)->Release();
			*ppRecord = NULL;
		}

		if( RC_BAD( rc = FlmRecordRetrieve( (HFDB)m_pDb, m_SwpInfo.uiContainer,
			m_uiLastDrn, FO_EXCL, ppRecord, &uiDrn)))
		{
			if( rc == FERR_OLD_VIEW)
			{
				if( m_bInternalTrans)
				{
					goto AbortTrans;
				}
				
				goto Exit;
			}

			// It is possible for the container to go away in the
			// middle of walking through it, because we are stopping
			// and starting transactions.

			if( rc == FERR_BAD_CONTAINER)
			{
				// Must abort the transaction because FERR_BAD_CONTAINER
				// will not allow the transaction to continue
				// if we are in an update transaction.
				
				if( !m_bInternalTrans)
				{
					goto Exit;
				}
				
				if( m_pDb->uiTransType != FLM_NO_TRANS)
				{
					(void)flmAbortDbTrans( m_pDb);
				}
				
				if( RC_BAD( rc = flmBeginDbTrans( m_pDb, FLM_READ_TRANS,
					0, FLM_DONT_POISON_CACHE)))
				{
					goto Exit;
				}

				// Change error code so that it looks like we have
				// hit the end of the container.

				rc = RC_SET( FERR_EOF_HIT);
			}
			
			goto Exit;
		}
		
		m_uiLastDrn = uiDrn;
		m_uiRecsRead++;
		m_SwpInfo.pRecord = *ppRecord;
		m_SwpInfo.pvField = NULL;

		// Make STATUS_HOOK callback

		if( (m_uiCallbackFreq & EACH_RECORD) && m_fnStatusHook)
		{
			if( (rc = (m_fnStatusHook)( FLM_SWEEP_STATUS, (void *)&m_SwpInfo,
				(void *) EACH_RECORD, m_UserData)) == FERR_EOF_HIT)
			{
				// User wants to skip this record.
				
				continue;
			}
			else
			{
				// Return this record
				
				break;
			}
		}
		else
		{
			// Return the record
			
			break;
		}
	}

Exit:

	return( rc);
}

/****************************************************************************
Desc:	The last record returned from DbWalk::nextRecord contained 'purged' 
		fields which have been removed. This function will now replace the 
		old record with the new record. This record may be == NULL, in which
		case the record will be deleted.
****************************************************************************/
RCODE DbWalk::updateRecord(
	FLMUINT			uiDrn,
	FlmRecord *		pRecord)
{
	RCODE				rc = FERR_OK;
	FLMBOOL			bRestartTrans = FALSE;

	if( m_bInternalTrans)
	{
		if( m_pDb->uiTransType != FLM_NO_TRANS)
		{
			(void)flmAbortDbTrans( m_pDb);
			bRestartTrans = TRUE;
		}
	}

	// Either modify or delete the specified record

	rc = ( pRecord)
			? FlmRecordModify( m_pDb, m_SwpInfo.uiContainer,
										uiDrn, pRecord, FLM_AUTO_TRANS | FLM_NO_TIMEOUT)
			: FlmRecordDelete( m_pDb,	m_SwpInfo.uiContainer,
										uiDrn, FLM_AUTO_TRANS | FLM_NO_TIMEOUT);
	if( RC_BAD( rc))
	{
		goto Exit;
	}

	// Now (if needed) restart the read transaction.

	if( bRestartTrans)
	{
		flmAssert( m_bInternalTrans);
		
		if( RC_BAD( rc = flmBeginDbTrans( m_pDb, FLM_READ_TRANS,
								0, FLM_DONT_POISON_CACHE)))
		{
			goto Exit;
		}
	}

Exit:

	return( rc);
}


/****************************************************************************
Desc:
****************************************************************************/
DbDict::~DbDict()
{
	if( m_pDb)
	{
		m_pDb->bFldStateUpdOk = FALSE;
	}

	if( m_puiStateTbl)
	{
		f_free( &m_puiStateTbl);
	}
}

/****************************************************************************
Desc:		Read the dictionary and create a internal table that records
			the state of field templates within the specified dictionary
****************************************************************************/
RCODE DbDict::init(
	FDB *			pDb,
	FLMBOOL		bInternalTrans,
	FLMUINT		uiMode,
	FLMBOOL *	pbFoundPurgeField)
{
	RCODE			rc = FERR_OK;
	ITT *			pItt = NULL;
	FLMUINT		uiItem;
	FLMUINT		uiCount;
	FLMUINT		uiStateMask = 0;

	*pbFoundPurgeField = FALSE;
	m_pDb = pDb;
	m_bInternalTrans = bInternalTrans;

	// Need to set a flag that which tells lower level FLAIM code
	//	that its okay to:
	//		1) Set a field state to 'unused' and
	//		2) Delete a field thats in a 'purged' state.
	
	m_pDb->bFldStateUpdOk = TRUE;

	// Allocate state table
	
	m_uiTblSize = m_pDb->pDict->uiIttCnt;
	
	if( RC_BAD( rc = f_calloc( 
		(m_uiTblSize * sizeof( FLMUINT)), &m_puiStateTbl)))
	{
		goto Exit;
	}

	uiCount = m_pDb->pDict->uiIttCnt;
	pItt = m_pDb->pDict->pIttTbl;

	if( uiMode & SWEEP_CHECKING_FLDS)
	{
		uiStateMask |= ITT_FLD_STATE_CHECKING;
	}

	if( uiMode & SWEEP_PURGED_FLDS)
	{
		uiStateMask |= ITT_FLD_STATE_PURGE;
	}

	// Now loop through the ITT table and set the correct states within
	// the DbDict's state table
	
	for( uiItem = 0; uiItem < uiCount; pItt++, uiItem++)
	{
		// Make sure the entry is a field or an encryption definition record
		
		if( ITT_IS_FIELD( pItt))
		{
			m_puiStateTbl[ uiItem] = (pItt->uiType & uiStateMask);
			if( m_puiStateTbl[ uiItem] == ITT_FLD_STATE_PURGE)
			{
				*pbFoundPurgeField = TRUE;

				// Make sure this field is not references and
				// can therefore be deleted
				
				if( RC_BAD( rc = flmCheckDictFldRefs( m_pDb->pDict, uiItem)))
				{
					goto Exit;
				}
			}
		}
		else if( ITT_IS_ENCDEF( pItt) && !m_pDb->pFile->bInLimitedMode)
		{
			if( RC_BAD( rc = fdictGetEncInfo( m_pDb, uiItem, NULL,
				&m_puiStateTbl[ uiItem])))
			{
				goto Exit;
			}
			
			if( m_puiStateTbl[ uiItem] == ITT_ENC_STATE_PURGE)
			{
				*pbFoundPurgeField = TRUE;

				// Make sure this field is not references and
				// can therefore be deleted.
				
				if( RC_BAD( rc = flmCheckDictEncDefRefs( m_pDb->pDict, uiItem)))
				{
					goto Exit;
				}
			}
		}
	}

Exit:

	return( rc);
}

/****************************************************************************
Desc:	Change the specified field's state.
		Currently this is only for "Checking" -> "Active"
****************************************************************************/
RCODE DbDict::changeState(
	FLMUINT		uiFieldID,
	FLMUINT		uiNewState)
{
	RCODE			rc = FERR_OK;
	FLMBOOL		bRestartTrans = FALSE;

	if( m_puiStateTbl[ uiFieldID] != ITT_FLD_STATE_CHECKING)
	{
		return( RC_SET( FERR_FAILURE));
	}

	if( m_bInternalTrans)
	{
		if( m_pDb->uiTransType != FLM_NO_TRANS)
		{
			(void)flmAbortDbTrans( m_pDb);
			bRestartTrans = TRUE;
		}
	}

	// Change the state tables value for this field/record template

	m_puiStateTbl[ uiFieldID] = 0;

	// Change the state of the dictionary item.

	if( RC_BAD( rc = flmChangeItemState( m_pDb, uiFieldID, uiNewState)))
	{
		goto Exit;
	}

	// Now restart the read transaction.
	
	if( bRestartTrans)
	{
		flmAssert( m_bInternalTrans);
		
		if( RC_BAD( rc = flmBeginDbTrans( m_pDb, FLM_READ_TRANS,
								0, FLM_DONT_POISON_CACHE)))
		{
			goto Exit;
		}
	}

Exit:

	return( rc);
}

/****************************************************************************
Desc:	Following a complete sweep of a database, this function will
		handle items that are still marked 'checking' or 'purge'.
****************************************************************************/
RCODE DbDict::finish( void)
{
	RCODE			rc = FERR_OK;
	FLMUINT		uiItem;

	// If we have an internal read transaction going then end it.
	
	if( m_bInternalTrans)
	{
		if( m_pDb->uiTransType != FLM_NO_TRANS)
		{
			(void)flmAbortDbTrans( m_pDb);
		}
	}

	// Loop through the state table changing: 'checking' fields to 'unused' and
	// deleting 'purged' fields

	for( uiItem = 1; uiItem < m_uiTblSize && RC_OK( rc); uiItem++)
	{
		if( m_puiStateTbl[ uiItem] == ITT_FLD_STATE_CHECKING)
		{
			// Change state to "unused"
			
			rc = flmChangeItemState( m_pDb, uiItem, ITT_FLD_STATE_UNUSED);
		}
		else if( m_puiStateTbl[ uiItem] == ITT_FLD_STATE_PURGE)
		{
			// Delete the 'purged' item

			if( RC_BAD( rc = FlmRecordDelete( (HFDB)m_pDb, FLM_DICT_CONTAINER, 
				uiItem, FLM_NO_TIMEOUT | FLM_AUTO_TRANS)))
			{
				goto Exit;
			}
		}
	}

Exit:

	if( m_bInternalTrans)
	{
		if( m_pDb->uiTransType != FLM_NO_TRANS)
		{
			(void)flmAbortDbTrans( m_pDb);
		}
	}

	return( rc);
}

/****************************************************************************
Desc: Change a field/record's defined state. Currently the only supported
		changes are:
			'checking' -> 'active'
			'checking' -> 'unused'
Ret:
****************************************************************************/
RCODE flmChangeItemState(
	FDB *				pDb,
	FLMUINT			uiItemId,
	FLMUINT			uiNewState)
{
	RCODE				rc = FERR_OK;
	FLMBOOL			bStartedTrans = FALSE;
	FlmRecord * 	pRecord = NULL;
	FlmRecord * 	pOldRecord = NULL;
	void *			pvField;

	// If needed, start a update transaction
	
	if( pDb->uiTransType == FLM_NO_TRANS)
	{
		if( RC_BAD( rc = flmBeginDbTrans( pDb, FLM_UPDATE_TRANS, 
			FLM_NO_TIMEOUT, FLM_DONT_POISON_CACHE)))
		{
			goto Exit;
		}
		
		bStartedTrans = TRUE;
	}

	// Now read the dictionary definition

	if( RC_BAD( rc = FlmRecordRetrieve( (HFDB)pDb, FLM_DICT_CONTAINER, 
		uiItemId, FO_EXACT, &pOldRecord, NULL)))
	{
		goto Exit;
	}

	if( (pRecord = pOldRecord->copy()) == NULL)
	{
		rc = RC_SET( FERR_MEM);
		goto Exit;
	}

	// Change the state to the correct state

	pvField = pRecord->find( pRecord->root(), FLM_STATE_TAG);
	flmAssert( pvField != NULL);

	if( RC_BAD( rc = pRecord->setNative( pvField, 
										(uiNewState == ITT_FLD_STATE_UNUSED)
												? "unused"
												: "active")))
	{
		goto Exit;
	}

	// Update the dictionary
	
	if( RC_BAD( rc = FlmRecordModify( (HFDB)pDb, FLM_DICT_CONTAINER, 
		pOldRecord->getID(), pRecord, 0)))
	{
		goto Exit;
	}

Exit:

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

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

	if( bStartedTrans)
	{
		if( RC_OK( rc))
		{
			rc = flmCommitDbTrans( pDb, 0, FALSE);
		}
		else
		{
			(void)flmAbortDbTrans( pDb);
		}
	}

	return( rc);
}
