/**************************************************************************** Desc: This routine aborts an active transaction for a particular database. If the database is open via a server, a message is sent to the server to abort the transaction. Otherwise, the transaction is rolled back locally. ****************************************************************************/ RCODE F_Db::abortTrans( FLMBOOL bOkToLogAbort) { RCODE rc = NE_XFLM_OK; eDbTransType eSaveTransType; XFLM_DB_HDR * pLastCommittedDbHdr; XFLM_DB_HDR * pUncommittedDbHdr; FLMBOOL bDumpedCache = FALSE; FLMBOOL bKeepAbortedTrans; FLMUINT64 ui64TransId; F_Rfl * pRfl = m_pDatabase->m_pRfl; RCODE tmpRc; // Should never be calling on a temporary database. flmAssert( !m_pDatabase->m_bTempDb); // Get transaction type if (m_eTransType == XFLM_NO_TRANS) { goto Exit; // Will return SUCCESS. } // No recovery required if it is a read transaction. if (m_eTransType == XFLM_READ_TRANS) { if (m_bKrefSetup) { // krefCntrlFree could be called w/o checking bKrefSetup because // it checks the flag, but it is more optimal to check the // flag before making the call because most of the time it will // be false. krefCntrlFree(); } goto Unlink_From_Trans; } #ifdef FLM_DBG_LOG flmDbgLogUpdate( m_pDatabase, m_ui64CurrTransID, 0, 0, NE_XFLM_OK, "TAbrt"); #endif // Disable DB header writes pRfl->clearDbHdrs(); // End any pending input operations m_pDatabase->endPendingInput(); // Clear the document list m_pDatabase->m_DocumentList.clearNodes(); // If the transaction had no update operations, restore it // to its pre-transaction state - make it appear that no // transaction ever happened. pLastCommittedDbHdr = &m_pDatabase->m_lastCommittedDbHdr; pUncommittedDbHdr = &m_pDatabase->m_uncommittedDbHdr; ui64TransId = m_ui64CurrTransID; // Free up all keys associated with this database. This is done even // if we didn't have any update operations because the KREF may // have been initialized by key generation operations performed // by cursors, etc. krefCntrlFree(); if (m_bHadUpdOper) { // Dump any start and stop indexing stubs that should be aborted. indexingAfterAbort(); // Log the abort record to the rfl file, or throw away the logged // records altogether, depending on the LOG_KEEP_ABORTED_TRANS_IN_RFL // flag. If the RFL volume is bad, we will not attempt to keep this // transaction in the RFL. if (!pRfl->seeIfRflVolumeOk()) { bKeepAbortedTrans = FALSE; } else { bKeepAbortedTrans = (pUncommittedDbHdr->ui8RflKeepAbortedTrans) ? TRUE : FALSE; } } else { bKeepAbortedTrans = FALSE; } // Log an abort transaction record to the roll-forward log or // throw away the entire transaction, depending on the // bKeepAbortedTrans flag. // If the transaction is being "dumped" because of a failed commit, // don't log anything to the RFL. if (bOkToLogAbort) { #ifdef FLM_DEBUG if( pRfl->isLoggingEnabled()) { flmAssert( m_ui64CurrTransID == pRfl->getCurrTransID()); } #endif if (RC_BAD( rc = pRfl->logEndTransaction( this, RFL_TRNS_ABORT_PACKET, !bKeepAbortedTrans))) { goto Exit1; } } #ifdef FLM_DEBUG else { // If bOkToLogAbort is FALSE, this always means that either a // commit failed while trying to log an end transaction packet or a // commit packet was logged and the transaction commit subsequently // failed for some other reason. In either case, the RFL should be // in a good state, with its current transaction ID reset to 0. If // not, either bOkToLogAbort is being used incorrectly by the caller // or there is a bug in the RFL logic. flmAssert( pRfl->getCurrTransID() == 0); } #endif // If there were no operations in the transaction, restore // everything as if the transaction never happened. // Even empty transactions can have modified nodes to clean up // so we need to call this no matter what. m_pDatabase->freeModifiedNodes( this, m_ui64CurrTransID - 1); if (!m_bHadUpdOper) { // Pretend we dumped cache - shouldn't be any to worry about at // this point. bDumpedCache = TRUE; goto Exit1; } // Dump ALL modified cache blocks associated with the DB. // NOTE: This needs to be done BEFORE the call to flmGetDbHdrInfo // below, because that call will change pDb->m_ui64CurrTransID, // and that value is used by freeModifiedNodes. m_pDatabase->freeModifiedBlocks( m_ui64CurrTransID); bDumpedCache = TRUE; // Reset the Db header from the last committed DB header in pFile. getDbHdrInfo( pLastCommittedDbHdr); if (RC_BAD( rc = physRollback( (FLMUINT)pUncommittedDbHdr->ui32RblEOF, m_pDatabase->m_uiFirstLogBlkAddress, FALSE, 0))) { goto Exit1; } m_pDatabase->lockMutex(); // Put the new transaction ID into the log header even though // we are not committing. We want to keep the transaction IDs // incrementing even though we aborted. pLastCommittedDbHdr->ui64CurrTransID = ui64TransId; // Preserve where we are at in the roll-forward log. Even though // the transaction aborted, we may have kept it in the RFL instead of // throw it away. pLastCommittedDbHdr->ui32RflCurrFileNum = pUncommittedDbHdr->ui32RflCurrFileNum; pLastCommittedDbHdr->ui32RflLastTransOffset = pUncommittedDbHdr->ui32RflLastTransOffset; f_memcpy( pLastCommittedDbHdr->ucLastTransRflSerialNum, pUncommittedDbHdr->ucLastTransRflSerialNum, XFLM_SERIAL_NUM_SIZE); f_memcpy( pLastCommittedDbHdr->ucNextRflSerialNum, pUncommittedDbHdr->ucNextRflSerialNum, XFLM_SERIAL_NUM_SIZE); // The following items tell us where we are at in the roll-back log. // During a transaction we may log blocks for the checkpoint or for // read transactions. So, even though we are aborting this transaction, // there may be other things in the roll-back log that we don't want // to lose. These items should not be reset until we do a checkpoint, // which is when we know it is safe to throw away the entire roll-back log. pLastCommittedDbHdr->ui32RblEOF = pUncommittedDbHdr->ui32RblEOF; pLastCommittedDbHdr->ui32RblFirstCPBlkAddr = pUncommittedDbHdr->ui32RblFirstCPBlkAddr; m_pDatabase->unlockMutex(); pRfl->commitDbHdrs( pLastCommittedDbHdr, &m_pDatabase->m_checkpointDbHdr); Exit1: // Dump cache, if not done above. if (!bDumpedCache) { m_pDatabase->freeModifiedBlocks( m_ui64CurrTransID); m_pDatabase->freeModifiedNodes( this, m_ui64CurrTransID - 1); bDumpedCache = TRUE; } // Throw away IXD_FIXUPs if (m_pIxdFixups) { IXD_FIXUP * pIxdFixup; IXD_FIXUP * pDeleteIxdFixup; pIxdFixup = m_pIxdFixups; while (pIxdFixup) { pDeleteIxdFixup = pIxdFixup; pIxdFixup = pIxdFixup->pNext; f_free( &pDeleteIxdFixup); } m_pIxdFixups = NULL; } if (m_eTransType == XFLM_UPDATE_TRANS && gv_XFlmSysData.EventHdrs[ XFLM_EVENT_UPDATES].pEventCBList) { flmTransEventCallback( XFLM_EVENT_ABORT_TRANS, this, rc, ui64TransId); } Unlink_From_Trans: eSaveTransType = m_eTransType; if (m_uiFlags & FDB_HAS_WRITE_LOCK) { if (RC_BAD( tmpRc = pRfl->completeTransWrites( this, FALSE, FALSE))) { if (RC_OK( rc)) { rc = tmpRc; } } } if (eSaveTransType == XFLM_UPDATE_TRANS) { // Before unlocking, restore collection information. if (m_uiFlags & FDB_UPDATED_DICTIONARY) { m_pDatabase->lockMutex(); flmAssert( m_pDict); unlinkFromDict(); if (m_pDatabase->m_pDictList) { // Link the F_Db to the right F_Dict object so it will // fixup the correct F_COLLECTION structures. linkToDict( m_pDatabase->m_pDictList); } m_pDatabase->unlockMutex(); } if (m_pDict) { F_COLLECTION * pCollection; FLMUINT uiLfNum; #ifdef FLM_DEBUG IXD * pIxd; FLMUINT uiRootBlk; #endif // Only need to do collections. Nothing from the LFH of // an index is stored in memory except for the root block // address, and whenever that is changed, we get a new // dictionary. Since the new dictionary will be discarded // in that case, there is nothing to restore for an index. uiLfNum = 0; while ((pCollection = m_pDict->getNextCollection( uiLfNum, TRUE)) != NULL) { #ifdef FLM_DEBUG uiRootBlk = pCollection->lfInfo.uiRootBlk; #endif if (RC_BAD( tmpRc = m_pDatabase->lFileRead( this, &pCollection->lfInfo, pCollection))) { if (RC_OK( rc)) { rc = tmpRc; } } #ifdef FLM_DEBUG else { // Make sure root block did not change - should not // have because root block changes are done by creating // a new dictionary, and we have already discarded // any new dictionary. Hence, root block address should // be the same in memory as it is no disk. flmAssert( uiRootBlk == pCollection->lfInfo.uiRootBlk); } #endif uiLfNum = pCollection->lfInfo.uiLfNum; } // Do indexes in debug mode to make sure uiRootBlk is correct #ifdef FLM_DEBUG uiLfNum = 0; while ((pIxd = m_pDict->getNextIndex( uiLfNum, TRUE)) != NULL) { uiRootBlk = pIxd->lfInfo.uiRootBlk; if (RC_BAD( tmpRc = m_pDatabase->lFileRead( this, &pIxd->lfInfo, NULL))) { if (RC_OK( rc)) { rc = tmpRc; } } else { // Make sure root block did not change - should not // have because root block changes are done by creating // a new dictionary, and we have already discarded // any new dictionary. Hence, root block address should // be the same in memory as it is no disk. flmAssert( uiRootBlk == pIxd->lfInfo.uiRootBlk); } uiLfNum = pIxd->lfInfo.uiLfNum; } #endif } } // Unlink the database from the transaction list. unlinkFromTransList( FALSE); if (m_pDbStats) { FLMUINT64 ui64ElapMilli = 0; flmAddElapTime( &m_TransStartTime, &ui64ElapMilli); m_pDbStats->bHaveStats = TRUE; if (eSaveTransType == XFLM_READ_TRANS) { m_pDbStats->ReadTransStats.AbortedTrans.ui64Count++; m_pDbStats->ReadTransStats.AbortedTrans.ui64ElapMilli += ui64ElapMilli; } else { m_pDbStats->UpdateTransStats.AbortedTrans.ui64Count++; m_pDbStats->UpdateTransStats.AbortedTrans.ui64ElapMilli += ui64ElapMilli; } } if (m_pStats) { (void)flmStatUpdate( &m_Stats); } Exit: m_AbortRc = NE_XFLM_OK; return( rc); }
/**************************************************************************** Desc : Deletes a record from a container. Notes: If an index definition record or a container definition record is deleted from the dictionary container, the index B-TREE container or container B-TREE will be deleted automatically when the transaction commits. Field definition records can only be deleted from the dictionary using this routine if the field is not in use. For more information on deletion of field definitions, see the Dictionary Syntax document. ****************************************************************************/ FLMEXP RCODE FLMAPI FlmRecordDelete( HFDB hDb, FLMUINT uiContainer, FLMUINT uiDrn, FLMUINT uiAutoTrans ) { RCODE rc = FERR_OK; LFILE * pLFile; FDB * pDb = (FDB *)hDb; FLMBOOL bStartedAutoTrans; FlmRecord * pOldRecord; FlmRecord ** ppOldRecord; DB_STATS * pDbStats = NULL; F_TMSTAMP StartTime; if( uiContainer == FLM_TRACKER_CONTAINER) { rc = RC_SET( FERR_ILLEGAL_OP); goto Exit; } if (IsInCSMode( hDb)) { fdbInitCS( pDb); rc = flmDoUpdateCS( pDb, FCS_OP_RECORD_DELETE, uiContainer, &uiDrn, NULL, uiAutoTrans); goto ExitCS; } bStartedAutoTrans = FALSE; pOldRecord = NULL; ppOldRecord = NULL; if( RC_BAD( rc = fdbInit( pDb, FLM_UPDATE_TRANS, FDB_TRANS_GOING_OK, uiAutoTrans, &bStartedAutoTrans))) { goto Exit; } if( (pDbStats = pDb->pDbStats) != NULL) { f_timeGetTimeStamp( &StartTime); } if( !uiDrn || uiDrn == (FLMUINT) DRN_LAST_MARKER) { rc = RC_SET( FERR_BAD_DRN); goto Exit; } if( RC_BAD(rc = fdictGetContainer( pDb->pDict, uiContainer, &pLFile))) { goto Exit; } if( gv_FlmSysData.UpdateEvents.pEventCBList) { // Do not have flmDeleteRecord fetch the old version of the record // unless an event callback is registered. ppOldRecord = &pOldRecord; } // NOTE: pLFile should NOT be used after this call, because flmDeleteRecord // could actually change its position in memory due to field changes. if (RC_BAD( rc = flmDeleteRecord( pDb, pLFile, uiDrn, ppOldRecord, FALSE))) { goto Exit; } Exit: if( RC_OK( rc)) { rc = pDb->pFile->pRfl->logUpdate( uiContainer, uiDrn, uiAutoTrans, NULL, NULL); } if( pDbStats) { flmAddElapTime( &StartTime, &pDbStats->RecordDeletes.ui64ElapMilli); pDbStats->RecordDeletes.ui64Count++; pDbStats->bHaveStats = TRUE; } if( gv_FlmSysData.UpdateEvents.pEventCBList) { flmUpdEventCallback( pDb, F_EVENT_DELETE_RECORD, hDb, rc, uiDrn, uiContainer, NULL, pOldRecord); } #ifdef FLM_DBG_LOG flmDbgLogUpdate( pDb->pFile->uiFFileId, pDb->LogHdr.uiCurrTransID, uiContainer, uiDrn, rc, "RDel"); #endif // If started an automatic transaction, end it. if( bStartedAutoTrans) { rc = flmEndAutoTrans( pDb, rc); } if( pOldRecord) { pOldRecord->Release(); pOldRecord = NULL; } ExitCS: flmExit( FLM_RECORD_DELETE, pDb, rc); return( rc); }
/**************************************************************************** Desc : Modifies a record within a container. Notes: If an index definition record is modified in the dictionary container, the index B-TREE will be deleted and rebuilt automatically when the transaction commits. If an index definition record is changed into a field definition record or a container definition record, the index will be automatically deleted when the transaction commits. When a non-unique index is changed to a unique index, or the fields in a unique index are changed, FLAIM needs to verify that each key in the proposed index is indeed a unique key. However, this verification does NOT occur until the dictionary transaction commits and the index is actually rebuilt. If FLAIM discovers that the keys in the proposed index are not unique, the transaction commit will fail and return FERR_NOT_UNIQUE. If a container definition record is changed into a field definition record, the container will be deleted automatically when the transaction commits. If a container definition record is changed into an index definition record, the container will be automatically deleted and the index will be automatically built when the transaction commits. Only the name and state of field definition records can be modified. Changing a field type or changing a field definition record into an index definition record is not allowed. For information on changing the state of a field, see the Dictionary Syntax document. ****************************************************************************/ FLMEXP RCODE FLMAPI FlmRecordModify( HFDB hDb, FLMUINT uiContainer, FLMUINT uiDrn, FlmRecord * pRecord, FLMUINT uiAutoTrans ) { RCODE rc = FERR_OK; RCODE rc1; FDB * pDb = (FDB *)hDb; FlmRecord * pOldRecord = NULL; LFILE * pLFile; FLMBOOL bStartedAutoTrans = FALSE; FLMBOOL bProcessedKeys = FALSE; FLMBOOL bLogCompleteIndexSet = FALSE; FLMBOOL bHadUniqueKeys; DB_STATS * pDbStats = NULL; F_TMSTAMP StartTime; if( uiContainer == FLM_TRACKER_CONTAINER) { rc = RC_SET( FERR_ILLEGAL_OP); goto Exit; } if (IsInCSMode( hDb)) { fdbInitCS( pDb); rc = flmDoUpdateCS( pDb, FCS_OP_RECORD_MODIFY, uiContainer, &uiDrn, pRecord, uiAutoTrans); goto ExitCS; } if( RC_BAD( rc = fdbInit( pDb, FLM_UPDATE_TRANS, FDB_TRANS_GOING_OK, uiAutoTrans, &bStartedAutoTrans))) { goto Exit; } if( pDb->uiFlags & FDB_COMMITTING_TRANS) { flmAssert( 0); rc = RC_SET( FERR_ILLEGAL_TRANS_OP); goto Exit; } if ((pDbStats = pDb->pDbStats) != NULL) { f_timeGetTimeStamp( &StartTime); } // Make sure we have a valid record if( !pRecord) { rc = RC_SET( FERR_INVALID_PARM); goto Exit; } // We cannot modify a record that is marked read-only. if( pRecord->isReadOnly()) { flmAssert( 0); rc = RC_SET( FERR_ILLEGAL_OP); goto Exit; } if( !uiDrn || (uiDrn == (FLMUINT) DRN_LAST_MARKER)) { rc = RC_SET( FERR_BAD_DRN); goto Exit; } if( RC_BAD( rc = fdictGetContainer( pDb->pDict, uiContainer, &pLFile))) { goto Exit; } if( RC_BAD( rc = KrefCntrlCheck( pDb))) { goto Exit; } // DICTIONARY RECORD MODIFY if( uiContainer == FLM_LOCAL_DICT_CONTAINER) { if( RC_BAD( rc = flmRcaRetrieveRec( pDb, NULL, uiContainer, uiDrn, FALSE, NULL, NULL, &pOldRecord))) { // NOTE: Deliberately not reading in to cache if not found. if (rc != FERR_NOT_FOUND) { goto Exit; } if( RC_BAD( rc = FSReadRecord( pDb, pLFile, uiDrn, &pOldRecord, NULL, NULL))) { goto Exit; } } // Sanity check -- make sure that the new and old records point at // different objects. flmAssert( pRecord != pOldRecord); if( RC_BAD( rc = flmLFileDictUpdate( pDb, &pLFile, &uiDrn, pRecord, pOldRecord, (uiAutoTrans & FLM_DO_IN_BACKGROUND) ? TRUE : FALSE, (uiAutoTrans & FLM_SUSPENDED) ? TRUE : FALSE, &bLogCompleteIndexSet))) { goto Exit; } pRecord->setID( uiDrn); pRecord->setContainerID( uiContainer); if( RC_BAD( rc = flmRcaInsertRec( pDb, pLFile, uiDrn, pRecord))) { goto Exit; } goto Exit; } // First read the old record, and delete it's keys. // To do this we need to be able to read any purged fields and delete any // keys they build. if( RC_BAD( rc = flmRcaRetrieveRec( pDb, NULL, uiContainer, uiDrn, FALSE, NULL, NULL, &pOldRecord))) { // NOTE: Deliberately not reading in to cache if not found. if (rc != FERR_NOT_FOUND) { goto Exit; } if( RC_BAD( rc = FSReadRecord( pDb, pLFile, uiDrn, &pOldRecord, NULL, NULL))) { goto Exit; } } // Sanity check -- make sure that the new and old records point at // different objects. flmAssert( pRecord != pOldRecord); bProcessedKeys = TRUE; bHadUniqueKeys = FALSE; if( RC_BAD( rc = flmProcessRecFlds( pDb, NULL, uiContainer, uiDrn, pOldRecord, KREF_DEL_KEYS | KREF_IN_MODIFY, TRUE, // Purged Field OK &bHadUniqueKeys))) { goto Exit; } if( RC_BAD( rc = flmProcessRecFlds( pDb, NULL, uiContainer, uiDrn, pRecord, KREF_ADD_KEYS | KREF_IN_MODIFY, FALSE, &bHadUniqueKeys))) { goto Exit; } // NOTE: The LFile table may have changed locations if the dictionary // was update because of a change in a field state if( RC_BAD( rc = fdictGetContainer( pDb->pDict, uiContainer, &pLFile))) { goto Exit; } if( RC_BAD( rc = FSRecUpdate( pDb, pLFile, pRecord, uiDrn, REC_UPD_MODIFY))) { goto Exit; } // Finish up the keys adding the unique keys to the indexes. if( RC_BAD( rc = KYProcessDupKeys( pDb, bHadUniqueKeys))) { // Undo the record that was modified - replace with original record. if( RC_BAD( rc1 = FSRecUpdate( pDb, pLFile, pOldRecord, uiDrn, REC_UPD_MODIFY))) { rc = (rc == FERR_NOT_UNIQUE) ? rc1 : rc; } goto Exit; } // Insert record into cache pRecord->setID( uiDrn); pRecord->setContainerID( uiContainer); if( RC_BAD( rc = flmRcaInsertRec( pDb, pLFile, uiDrn, pRecord))) { if ( rc != FERR_MEM) { flmAssert( 0); } // Undo the record that was modified - replace with original record. FSRecUpdate( pDb, pLFile, pOldRecord, uiDrn, REC_UPD_MODIFY); goto Exit; } // Don't make this call until we are sure of success - because we want to // be able to back things out of KREF table. KYFinishCurrentRecord( pDb); Exit: if( RC_BAD( rc) && bProcessedKeys) { KYAbortCurrentRecord( pDb); } // Add the BLOB entries to the blob list. rc = FB_OperationEnd( pDb, rc); if( RC_OK( rc)) { if( RC_OK( rc = pDb->pFile->pRfl->logUpdate( uiContainer, uiDrn, uiAutoTrans, pOldRecord, pRecord)) && bLogCompleteIndexSet && pDb->pFile->FileHdr.uiVersionNum <= FLM_FILE_FORMAT_VER_4_51) { // Log the fact that we indexed everything so the redo will also // index all data records in the container. rc = pDb->pFile->pRfl->logIndexSet( uiDrn, 0, 1, 0xFFFFFFFF); } } if( pDbStats) { flmAddElapTime( &StartTime, &pDbStats->RecordModifies.ui64ElapMilli); pDbStats->RecordModifies.ui64Count++; pDbStats->bHaveStats = TRUE; } if( gv_FlmSysData.UpdateEvents.pEventCBList) { flmUpdEventCallback( pDb, F_EVENT_MODIFY_RECORD, hDb, rc, uiDrn, uiContainer, pRecord, pOldRecord); } #ifdef FLM_DBG_LOG flmDbgLogUpdate( pDb->pFile->uiFFileId, pDb->LogHdr.uiCurrTransID, uiContainer, uiDrn, rc, "RMod"); #endif // If started an automatic transaction, end it. if( bStartedAutoTrans) { rc = flmEndAutoTrans( pDb, rc); } if( pOldRecord) { pOldRecord->Release(); pOldRecord = NULL; } ExitCS: flmExit( FLM_RECORD_MODIFY, pDb, rc); return( rc); }
/**************************************************************************** Desc : Adds a record to a container. Notes: If an index definition record is added to the dictionary container, the index will be built automatically when the transaction commits. When a unique index is added to the database, FLAIM needs to verify that each key in the proposed index is indeed a unique key. However, this verification does NOT occur until the dictionary transaction commits and the index is actually built. If FLAIM discovers that the keys in an index are not unique, the transaction commit will fail and return an error. ****************************************************************************/ FLMEXP RCODE FLMAPI FlmRecordAdd( HFDB hDb, FLMUINT uiContainer, FLMUINT * puiDrn, FlmRecord * pRecord, FLMUINT uiAutoTrans) { RCODE rc = FERR_OK; FLMUINT uiDrn = 0; FDB * pDb = (FDB *)hDb; LFILE * pLFile; FLMBOOL bStartedAutoTrans = FALSE; FLMBOOL bLogCompleteIndexSet = FALSE; DB_STATS * pDbStats = NULL; F_TMSTAMP StartTime; if( puiDrn) { uiDrn = *puiDrn; } if( uiContainer == FLM_TRACKER_CONTAINER) { rc = RC_SET( FERR_ILLEGAL_OP); goto Exit; } if (IsInCSMode( hDb)) { fdbInitCS( pDb); rc = flmDoUpdateCS( pDb, FCS_OP_RECORD_ADD, uiContainer, &uiDrn, pRecord, uiAutoTrans); goto ExitCS; } if( RC_BAD( rc = fdbInit( (FDB *)hDb, FLM_UPDATE_TRANS, FDB_TRANS_GOING_OK, uiAutoTrans, &bStartedAutoTrans))) { goto Exit; } if( (pDbStats = pDb->pDbStats) != NULL) { f_timeGetTimeStamp( &StartTime); } // Make sure we have a valid record if( !pRecord) { rc = RC_SET( FERR_INVALID_PARM); goto Exit; } // We cannot add a record that is marked read-only, because it // is probably already in the cache under a different record ID. if( pRecord->isReadOnly()) { flmAssert( 0); rc = RC_SET( FERR_ILLEGAL_OP); goto Exit; } if( RC_BAD( rc = fdictGetContainer( pDb->pDict, uiContainer, &pLFile))) { goto Exit; } rc = flmAddRecord( pDb, pLFile, &uiDrn, pRecord, FALSE, (uiAutoTrans & FLM_DO_IN_BACKGROUND) ? TRUE : FALSE, (uiAutoTrans & FLM_SUSPENDED) ? TRUE : FALSE, (FLMBOOL)((uiAutoTrans & FLM_DONT_INSERT_IN_CACHE) ? FALSE : TRUE), &bLogCompleteIndexSet); Exit: rc = FB_OperationEnd( pDb, rc); if( RC_OK( rc)) { if( RC_OK( rc = pDb->pFile->pRfl->logUpdate( uiContainer, uiDrn, uiAutoTrans, NULL, pRecord)) && bLogCompleteIndexSet && pDb->pFile->FileHdr.uiVersionNum <= FLM_FILE_FORMAT_VER_4_51) { // Log the fact that we indexed everything so the redo will also // index all data records in the container. rc = pDb->pFile->pRfl->logIndexSet( uiDrn, 0, 1, 0xFFFFFFFF); } } if( pDbStats) { flmAddElapTime( &StartTime, &pDbStats->RecordAdds.ui64ElapMilli); pDbStats->RecordAdds.ui64Count++; pDbStats->bHaveStats = TRUE; } if( gv_FlmSysData.UpdateEvents.pEventCBList) { flmUpdEventCallback( pDb, F_EVENT_ADD_RECORD, hDb, rc, uiDrn, uiContainer, pRecord, NULL); } // If started an automatic transaction end it. if( bStartedAutoTrans) { rc = flmEndAutoTrans( pDb, rc); } ExitCS: if( puiDrn) { *puiDrn = uiDrn; } flmExit( FLM_RECORD_ADD, pDb, rc); return( rc); }
/**************************************************************************** Desc: This routine aborts an active transaction for a particular database. If the database is open via a server, a message is sent to the server to abort the transaction. Otherwise, the transaction is rolled back locally. ****************************************************************************/ RCODE flmAbortDbTrans( FDB * pDb, FLMBOOL bOkToLogAbort) { RCODE rc = FERR_OK; FFILE * pFile = pDb->pFile; FLMUINT uiTransType; FLMBYTE * pucLastCommittedLogHdr; FLMBYTE * pucUncommittedLogHdr; FLMBOOL bDumpedCache = FALSE; DB_STATS * pDbStats = pDb->pDbStats; FLMBOOL bKeepAbortedTrans; FLMUINT uiTransId; FLMBOOL bInvisibleTrans; // Get transaction type if ((uiTransType = pDb->uiTransType) == FLM_NO_TRANS) { goto Exit; } // No recovery required if it is a read transaction. if (uiTransType == FLM_READ_TRANS) { if( pDb->KrefCntrl.bKrefSetup) { // KrefCntrlFree could be called w/o checking bKrefSetup because // it checks the flag, but it is more optimal to check the // flag before making the call because most of the time it will // be false. KrefCntrlFree( pDb); } goto Unlink_From_Trans; } #ifdef FLM_DBG_LOG flmDbgLogUpdate( pFile->uiFFileId, pDb->LogHdr.uiCurrTransID, 0, 0, FERR_OK, "TAbrt"); #endif pFile->pRfl->clearLogHdrs(); // If the transaction had no update operations, restore it // to its pre-transaction state - make it appear that no // transaction ever happened. pucLastCommittedLogHdr = &pFile->ucLastCommittedLogHdr [0]; pucUncommittedLogHdr = &pFile->ucUncommittedLogHdr [0]; uiTransId = pDb->LogHdr.uiCurrTransID; // Free up all keys associated with this database. This is done even // if we didn't have any update operations because the KREF may // have been initialized by key generation operations performed // by cursors, etc. KrefCntrlFree( pDb); // Free any index counts we may have allocated. FSFreeIxCounts( pDb); if (pDb->bHadUpdOper) { // Dump any BLOB structures that should be aborted. FBListAfterAbort( pDb); // Dump any start and stop indexing stubs that should be aborted. flmIndexingAfterAbort( pDb); // Log the abort record to the rfl file, or throw away the logged // records altogether, depending on the LOG_KEEP_ABORTED_TRANS_IN_RFL // flag. If the RFL volume is bad, we will not attempt to keep this // transaction in the RFL. if (!pFile->pRfl->seeIfRflVolumeOk()) { bKeepAbortedTrans = FALSE; } else { bKeepAbortedTrans = (pucUncommittedLogHdr [LOG_KEEP_ABORTED_TRANS_IN_RFL]) ? TRUE : FALSE; } } else { bKeepAbortedTrans = FALSE; } // Log an abort transaction record to the roll-forward log or // throw away the entire transaction, depending on the // bKeepAbortedTrans flag. // If the transaction is being "dumped" because of a failed commit, // don't log anything to the RFL. if( bOkToLogAbort) { flmAssert( pDb->LogHdr.uiCurrTransID == pFile->pRfl->getCurrTransID()); if (RC_BAD( rc = pFile->pRfl->logEndTransaction( RFL_TRNS_ABORT_PACKET, !bKeepAbortedTrans))) { goto Exit1; } } #ifdef FLM_DEBUG else { // If bOkToLogAbort is FALSE, this always means that either a // commit failed while trying to log an end transaction packet or a // commit packet was logged and the transaction commit subsequently // failed for some other reason. In either case, the RFL should be // in a good state, with its current transaction ID reset to 0. If // not, either bOkToLogAbort is being used incorrectly by the caller // or there is a bug in the RFL logic. flmAssert( pFile->pRfl->getCurrTransID() == 0); } #endif // If there were no operations in the transaction, restore // everything as if the transaction never happened. if (!pDb->bHadUpdOper) { f_mutexLock( gv_FlmSysData.hShareMutex); pFile->uiUpdateTransID = 0; f_mutexUnlock( gv_FlmSysData.hShareMutex); // Pretend we dumped cache - shouldn't be any to worry about at // this point. bDumpedCache = TRUE; goto Exit1; } // Dump ALL modified cache blocks associated with the DB. // NOTE: This needs to be done BEFORE the call to flmGetLogHdrInfo // below, because that call will change pDb->LogHdr.uiCurrTransID, // and that value is used by flmRcaAbortTrans. ScaFreeModifiedBlocks( pDb); flmRcaAbortTrans( pDb); bDumpedCache = TRUE; // Reset the LogHdr from the last committed log header in pFile. flmGetLogHdrInfo( pucLastCommittedLogHdr, &pDb->LogHdr); if (RC_BAD( rc = flmPhysRollback( pDb, (FLMUINT)FB2UD( &pucUncommittedLogHdr [LOG_ROLLBACK_EOF]), pFile->uiFirstLogBlkAddress, FALSE, 0))) { goto Exit1; } f_mutexLock( gv_FlmSysData.hShareMutex); // Put the new transaction ID into the log header even though // we are not committing. We want to keep the transaction IDs // incrementing even though we aborted. UD2FBA( (FLMUINT32)uiTransId, &pucLastCommittedLogHdr [LOG_CURR_TRANS_ID]); // Preserve where we are at in the roll-forward log. Even though // the transaction aborted, we may have kept it in the RFL instead of // throw it away. f_memcpy( &pucLastCommittedLogHdr [LOG_RFL_FILE_NUM], &pucUncommittedLogHdr [LOG_RFL_FILE_NUM], 4); f_memcpy( &pucLastCommittedLogHdr [LOG_RFL_LAST_TRANS_OFFSET], &pucUncommittedLogHdr [LOG_RFL_LAST_TRANS_OFFSET], 4); f_memcpy( &pucLastCommittedLogHdr [LOG_LAST_TRANS_RFL_SERIAL_NUM], &pucUncommittedLogHdr [LOG_LAST_TRANS_RFL_SERIAL_NUM], F_SERIAL_NUM_SIZE); f_memcpy( &pucLastCommittedLogHdr [LOG_RFL_NEXT_SERIAL_NUM], &pucUncommittedLogHdr [LOG_RFL_NEXT_SERIAL_NUM], F_SERIAL_NUM_SIZE); // The following items tell us where we are at in the roll-back log. // During a transaction we may log blocks for the checkpoint or for // read transactions. So, even though we are aborting this transaction, // there may be other things in the roll-back log that we don't want // to lose. These items should not be reset until we do a checkpoint, // which is when we know it is safe to throw away the entire roll-back log. f_memcpy( &pucLastCommittedLogHdr [LOG_ROLLBACK_EOF], &pucUncommittedLogHdr [LOG_ROLLBACK_EOF], 4); f_memcpy( &pucLastCommittedLogHdr [LOG_PL_FIRST_CP_BLOCK_ADDR], &pucUncommittedLogHdr [LOG_PL_FIRST_CP_BLOCK_ADDR], 4); f_mutexUnlock( gv_FlmSysData.hShareMutex); pFile->pRfl->commitLogHdrs( pucLastCommittedLogHdr, pFile->ucCheckpointLogHdr); Exit1: // Dump cache, if not done above. if (!bDumpedCache) { ScaFreeModifiedBlocks( pDb); flmRcaAbortTrans( pDb); bDumpedCache = TRUE; } // Throw away IXD_FIXUPs if (pDb->pIxdFixups) { IXD_FIXUP * pIxdFixup; IXD_FIXUP * pDeleteIxdFixup; pIxdFixup = pDb->pIxdFixups; while (pIxdFixup) { pDeleteIxdFixup = pIxdFixup; pIxdFixup = pIxdFixup->pNext; f_free( &pDeleteIxdFixup); } pDb->pIxdFixups = NULL; } if (uiTransType != FLM_READ_TRANS && gv_FlmSysData.UpdateEvents.pEventCBList) { flmTransEventCallback( F_EVENT_ABORT_TRANS, (HFDB)pDb, rc, uiTransId); } Unlink_From_Trans: bInvisibleTrans = (pDb->uiFlags & FDB_INVISIBLE_TRANS) ? TRUE : FALSE; if (pDb->uiFlags & FDB_HAS_WRITE_LOCK) { RCODE tmpRc; if (RC_BAD( tmpRc = pFile->pRfl->completeTransWrites( pDb, FALSE, FALSE))) { if (RC_OK( rc)) { rc = tmpRc; } } } // Unlink the database from the transaction // structure as well as from the FLDICT structure. flmUnlinkDbFromTrans( pDb, FALSE); if (pDbStats) { FLMUINT64 ui64ElapMilli = 0; flmAddElapTime( &pDb->TransStartTime, &ui64ElapMilli); pDbStats->bHaveStats = TRUE; if (uiTransType == FLM_READ_TRANS) { pDbStats->ReadTransStats.AbortedTrans.ui64Count++; pDbStats->ReadTransStats.AbortedTrans.ui64ElapMilli += ui64ElapMilli; if (bInvisibleTrans) { pDbStats->ReadTransStats.InvisibleTrans.ui64Count++; pDbStats->ReadTransStats.InvisibleTrans.ui64ElapMilli += ui64ElapMilli; } } else { pDbStats->UpdateTransStats.AbortedTrans.ui64Count++; pDbStats->UpdateTransStats.AbortedTrans.ui64ElapMilli += ui64ElapMilli; } } if (pDb->pStats) { (void)flmStatUpdate( &gv_FlmSysData.Stats, &pDb->Stats); } Exit: return( rc); }
/**************************************************************************** Desc: This routine commits an active transaction for a particular database. If the database is open via a server, a message is sent to the server to commit the transaction. Otherwise, the transaction is committed locally. ****************************************************************************/ RCODE flmCommitDbTrans( FDB * pDb, FLMUINT uiNewLogicalEOF, FLMBOOL bForceCheckpoint, FLMBOOL * pbEmpty) { RCODE rc = FERR_OK; FLMBYTE * pucUncommittedLogHdr; FFILE * pFile = pDb->pFile; FLMUINT uiCPFileNum = 0; FLMUINT uiCPOffset = 0; FLMUINT uiTransId = 0; FLMBOOL bTransEndLogged; FLMBOOL bForceCloseOnError = FALSE; FLMBOOL bOkToLogAbort = TRUE; DB_STATS * pDbStats = pDb->pDbStats; FLMUINT uiTransType; FLMBOOL bInvisibleTrans = FALSE; FLMBOOL bIndexAfterCommit = FALSE; pDb->uiFlags |= FDB_COMMITTING_TRANS; // See if we even have a transaction going. if ((uiTransType = pDb->uiTransType) == FLM_NO_TRANS) { goto Exit; // Will return FERR_OK. } // See if we have a transaction going which should be aborted. if (flmCheckBadTrans( pDb)) { rc = RC_SET( FERR_ABORT_TRANS); goto Exit; } // If we are in a read transaction we can skip most of the stuff // below because no updates would have occurred. This will help // improve performance. if (uiTransType == FLM_READ_TRANS) { if( pDb->KrefCntrl.bKrefSetup) { // KrefCntrlFree could be called w/o checking bKrefSetup because // it checks the flag, but it is more optimal to check the // flag before making the call because most of the time it will // be false. KrefCntrlFree( pDb); } goto Exit1; } // At this point, we know we have an update transaction. pFile->pRfl->clearLogHdrs(); #ifdef FLM_DBG_LOG flmDbgLogUpdate( pFile->uiFFileId, pDb->LogHdr.uiCurrTransID, 0, 0, FERR_OK, "TCmit"); #endif uiTransId = pDb->LogHdr.uiCurrTransID; // If the transaction had no update operations, restore it // to its pre-transaction state - make it appear that no // transaction ever happened. if (!pDb->bHadUpdOper) { bOkToLogAbort = FALSE; rc = pFile->pRfl->logEndTransaction( RFL_TRNS_COMMIT_PACKET, TRUE); // Even though we didn't have any update operations, there may have // been operations during the transaction (i.e., query operations) // that initialized the KREF in order to generate keys. KrefCntrlFree( pDb); // Restore everything as if the transaction never happened. f_mutexLock( gv_FlmSysData.hShareMutex); pFile->uiUpdateTransID = 0; f_mutexUnlock( gv_FlmSysData.hShareMutex); if (pbEmpty) { *pbEmpty = TRUE; } goto Exit1; } // Log commit record to roll-forward log bOkToLogAbort = FALSE; if (RC_BAD( rc = pFile->pRfl->logEndTransaction( RFL_TRNS_COMMIT_PACKET, FALSE, &bTransEndLogged))) { goto Exit1; } bForceCloseOnError = TRUE; // Commit any keys in the KREF buffers. if (RC_BAD( rc = KYKeysCommit( pDb, TRUE))) { flmLogError( rc, "calling KYKeysCommit from flmCommitDbTrans"); goto Exit1; } if (RC_BAD( rc = FSCommitIxCounts( pDb))) { flmLogError( rc, "calling FSCommitIxCounts from flmCommitDbTrans"); goto Exit1; } // Reinitialize the log header. If the local dictionary was updated // during the transaction, increment the local dictionary ID so that // other concurrent users will know that it has been modified and // that they need to re-read it into memory. // If we are in recovery mode, see if we need to force // a checkpoint with what we have so far. We force a // checkpoint on one of two conditions: // 1. If it appears that we have a buildup of dirty cache // blocks. We force a checkpoint on this condition // because it will be more efficient than replacing // cache blocks one at a time. // We check for this condition by looking to see if // our LRU block is not used and it is dirty. That is // a pretty good indicator that we have a buildup // of dirty cache blocks. // 2. We are at the end of the roll-forward log. We // want to force a checkpoint here to complete the // recovery phase. if ( pDb->uiFlags & FDB_REPLAYING_RFL) { // If we are in the middle of upgrading, and are forcing // a checkpoint, use the file number and offset that were // set in the FDB. if ((pDb->uiFlags & FDB_UPGRADING) && bForceCheckpoint) { uiCPFileNum = pDb->uiUpgradeCPFileNum; uiCPOffset = pDb->uiUpgradeCPOffset; } else { SCACHE * pTmpSCache; F_Rfl * pRfl = pFile->pRfl; f_mutexLock( gv_FlmSysData.hShareMutex); pTmpSCache = gv_FlmSysData.SCacheMgr.pLRUCache; // Test for buildup of dirty cache blocks. if( (pTmpSCache && !pTmpSCache->uiUseCount && (pTmpSCache->ui16Flags & (CA_DIRTY | CA_LOG_FOR_CP | CA_WRITE_TO_LOG))) || pRfl->atEndOfLog() || bForceCheckpoint) { bForceCheckpoint = TRUE; uiCPFileNum = pRfl->getCurrFileNum(); uiCPOffset = pRfl->getCurrReadOffset(); } f_mutexUnlock( gv_FlmSysData.hShareMutex); } } // Move information collected in the pDb->LogHdr into the // uncommitted log header. Other things that need to be // set have already been set in the uncommitted log header // at various places in the code. // Mutex does not have to be locked while we do this because // the update transaction is the only one that ever accesses // the uncommitted log header buffer. pucUncommittedLogHdr = &pFile->ucUncommittedLogHdr [0]; // Set the new logical EOF if passed in. if( uiNewLogicalEOF) { pDb->LogHdr.uiLogicalEOF = uiNewLogicalEOF; } UD2FBA( (FLMUINT32)pDb->LogHdr.uiLogicalEOF, &pucUncommittedLogHdr [LOG_LOGICAL_EOF]); // Increment the commit counter. flmIncrUint( &pucUncommittedLogHdr [LOG_COMMIT_COUNT], 1); // Set the last committed transaction ID if( (bTransEndLogged || (pDb->uiFlags & FDB_REPLAYING_COMMIT)) && pDb->pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_31) { UD2FBA( (FLMUINT32)uiTransId, &pucUncommittedLogHdr [LOG_LAST_RFL_COMMIT_ID]); } // Write the header pFile->pRfl->commitLogHdrs( pucUncommittedLogHdr, pFile->ucCheckpointLogHdr); // Commit any record cache. flmRcaCommitTrans( pDb); // Push the IXD_FIXUP values back into the IXD if (pDb->pIxdFixups) { IXD_FIXUP * pIxdFixup; IXD_FIXUP * pDeleteIxdFixup; IXD * pIxd; pIxdFixup = pDb->pIxdFixups; while (pIxdFixup) { if( RC_BAD( fdictGetIndex( pDb->pDict, pDb->pFile->bInLimitedMode, pIxdFixup->uiIndexNum, NULL, &pIxd, TRUE))) { flmAssert( 0); pIxd = NULL; } if( pIxd) { pIxd->uiLastContainerIndexed = pIxdFixup->uiLastContainerIndexed; pIxd->uiLastDrnIndexed = pIxdFixup->uiLastDrnIndexed; } pDeleteIxdFixup = pIxdFixup; pIxdFixup = pIxdFixup->pNext; f_free( &pDeleteIxdFixup); } pDb->pIxdFixups = NULL; } // Set the update transaction ID back to zero only // AFTER we know the transaction has safely committed. f_mutexLock( gv_FlmSysData.hShareMutex); f_memcpy( pFile->ucLastCommittedLogHdr, pucUncommittedLogHdr, LOG_HEADER_SIZE); pFile->uiUpdateTransID = 0; ScaReleaseLogBlocks( pFile); if (pDb->uiFlags & FDB_UPDATED_DICTIONARY) { // Link the new local dictionary to its file. // Since the new local dictionary will be linked at the head // of the list of FDICT structures, see if the FDICT currently // at the head of the list is unused and can be unlinked. if ((pFile->pDictList) && (!pFile->pDictList->uiUseCount)) { flmUnlinkDict( pFile->pDictList); } flmLinkDictToFile( pFile, pDb->pDict); } f_mutexUnlock( gv_FlmSysData.hShareMutex); Exit1: // If the local dictionary was updated during this transaction, // link the new local dictionary structures to their file - or free // them if there was an error. if (pDb->uiFlags & FDB_UPDATED_DICTIONARY) { if( RC_BAD( rc) && pDb->pDict) { // Unlink the FDB from the FDICT. - Shouldn't have // to lock semaphore, because the DICT is NOT linked // to the FFILE. flmAssert( pDb->pDict->pFile == NULL); flmUnlinkFdbFromDict( pDb); } } if (RC_BAD( rc)) { // Since we failed to commit, do an abort. We are purposely not // checking the return code from flmAbortDbTrans because we already // have an error return code. If we attempted to log the transaction // to the RFL and failed, we don't want to try to log an abort packet. // The RFL code has already reset the log back to the starting point // of the transaction, thereby discarding all operations. pDb->uiFlags &= ~FDB_COMMITTING_TRANS; (void)flmAbortDbTrans( pDb, bOkToLogAbort); uiTransType = FLM_NO_TRANS; // Do we need to force all handles to close? if( bForceCloseOnError) { // Since the commit packet has already been logged to the RFL, // we must have failed when trying to write the log header. The // database is in a bad state and must be closed. // Set the "must close" flag on all FDBs linked to the FFILE // and set the FFILE's "must close" flag. This will cause any // subsequent operations on the database to fail until all // handles have been closed. flmSetMustCloseFlags( pFile, rc, FALSE); } } else { bInvisibleTrans = (pDb->uiFlags & FDB_INVISIBLE_TRANS) ? TRUE : FALSE; if (uiTransType == FLM_UPDATE_TRANS) { if (gv_FlmSysData.UpdateEvents.pEventCBList) { flmTransEventCallback( F_EVENT_COMMIT_TRANS, (HFDB)pDb, rc, uiTransId); } // Do the BLOB and indexing work before we unlock the db. FBListAfterCommit( pDb); if (pDb->pIxStopList || pDb->pIxStartList) { // Must not call flmIndexingAfterCommit until after // completeTransWrites. Otherwise, there is a potential // deadlock condition where flmIndexingAfterCommit is // waiting on an indexing thread to quit, but that // thread is waiting to be signaled by this thread that // writes are completed. However, flmIndexingAfterCommit // also must only be called while the database is still // locked. If we were to leave the database locked for // every call to completeTransWrites, however, we would // lose the group commit capability. Hence, we opt to // only lose it when there are actual indexing operations // to start or stop - which should be very few transactions. // That is what the bIndexAfterCommit flag is for. bIndexAfterCommit = TRUE; } } } // Unlock the database, if the update transaction is still going. // NOTE: We check uiTransType because it may have been reset // to FLM_NO_TRANS up above if flmAbortDbTrans was called. if (uiTransType == FLM_UPDATE_TRANS) { if (RC_BAD( rc)) { // SHOULD NEVER HAPPEN - because it would have been taken // care of above - flmAbortDbTrans would have been called and // uiTransType would no longer be FLM_UPDATE_TRANS. flmAssert( 0); (void)pFile->pRfl->completeTransWrites( pDb, FALSE, TRUE); } else if( !bForceCheckpoint) { if( bIndexAfterCommit) { rc = pFile->pRfl->completeTransWrites( pDb, TRUE, FALSE); flmIndexingAfterCommit( pDb); flmUnlinkDbFromTrans( pDb, TRUE); } else { rc = pFile->pRfl->completeTransWrites( pDb, TRUE, TRUE); } } else { // Do checkpoint, if forcing. Before doing the checkpoint // we have to make sure the roll-forward log writes // complete. We don't want to unlock the DB while the // writes are happening in this case - thus, the FALSE // parameter to completeTransWrites. if (RC_OK( rc = pFile->pRfl->completeTransWrites( pDb, TRUE, FALSE))) { bForceCloseOnError = FALSE; rc = ScaDoCheckpoint( pDbStats, pDb->pSFileHdl, pFile, (pDb->uiFlags & FDB_DO_TRUNCATE) ? TRUE : FALSE, TRUE, CP_TIME_INTERVAL_REASON, uiCPFileNum, uiCPOffset); } if (bIndexAfterCommit) { flmIndexingAfterCommit( pDb); } flmUnlinkDbFromTrans( pDb, TRUE); } if (RC_BAD( rc) && bForceCloseOnError) { // Since the commit packet has already been logged to the RFL, // we must have failed when trying to write the log header. The // database is in a bad state and must be closed. // Set the "must close" flag on all FDBs linked to the FFILE // and set the FFILE's "must close" flag. This will cause any // subsequent operations on the database to fail until all // handles have been closed. flmSetMustCloseFlags( pFile, rc, FALSE); } } else { // Unlink the database from the transaction // structure as well as from the FDICT structure. flmUnlinkDbFromTrans( pDb, FALSE); } if (pDbStats && uiTransType != FLM_NO_TRANS) { FLMUINT64 ui64ElapMilli = 0; flmAddElapTime( &pDb->TransStartTime, &ui64ElapMilli); pDbStats->bHaveStats = TRUE; if (uiTransType == FLM_READ_TRANS) { pDbStats->ReadTransStats.CommittedTrans.ui64Count++; pDbStats->ReadTransStats.CommittedTrans.ui64ElapMilli += ui64ElapMilli; if (bInvisibleTrans) { pDbStats->ReadTransStats.InvisibleTrans.ui64Count++; pDbStats->ReadTransStats.InvisibleTrans.ui64ElapMilli += ui64ElapMilli; } } else { pDbStats->UpdateTransStats.CommittedTrans.ui64Count++; pDbStats->UpdateTransStats.CommittedTrans.ui64ElapMilli += ui64ElapMilli; } } // Update stats if (pDb->pStats) { (void)flmStatUpdate( &gv_FlmSysData.Stats, &pDb->Stats); } Exit: pDb->uiFlags &= ~FDB_COMMITTING_TRANS; return( rc); }