//------------------------------------------------------------------------------ // Desc: Contains routines for starting a transaction. // // Tabs: 3 // // Copyright (c) 1991, 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$ //------------------------------------------------------------------------------ #include "flaimsys.h" /**************************************************************************** Desc: This routine unlinks an F_Db from a transaction's list of F_Dbs. ****************************************************************************/ void F_Db::unlinkFromTransList( FLMBOOL bCommitting) { flmAssert( m_pIxdFixups == NULL); if( m_eTransType != SFLM_NO_TRANS) { if (m_uiFlags & FDB_HAS_WRITE_LOCK) { // If this is a commit operation and we have a commit callback, // call the callback function before unlocking the DIB. if (bCommitting && m_pCommitClient) { m_pCommitClient->commit( this); } unlockExclusive(); } m_pDatabase->lockMutex(); if (m_pDict) { unlinkFromDict(); } // Unlink the transaction from the F_Database if it is a read transaction. if (m_eTransType == SFLM_READ_TRANS) { if (m_pNextReadTrans) { m_pNextReadTrans->m_pPrevReadTrans = m_pPrevReadTrans; } else if (!m_uiKilledTime) { m_pDatabase->m_pLastReadTrans = m_pPrevReadTrans; } if (m_pPrevReadTrans) { m_pPrevReadTrans->m_pNextReadTrans = m_pNextReadTrans; } else if (m_uiKilledTime) { m_pDatabase->m_pFirstKilledTrans = m_pNextReadTrans; } else { m_pDatabase->m_pFirstReadTrans = m_pNextReadTrans; } // Zero out so it will be zero for next transaction begin. m_uiKilledTime = 0; } else { // Reset to NULL or zero for next update transaction. m_pIxStartList = m_pIxStopList = NULL; flmAssert( !m_pIxdFixups); } m_pDatabase->unlockMutex(); m_eTransType = SFLM_NO_TRANS; m_uiFlags &= (~(FDB_UPDATED_DICTIONARY | FDB_DONT_KILL_TRANS | FDB_DONT_POISON_CACHE | FDB_SWEEP_SCHEDULED)); flmAssert( !m_uiDirtyRowCount); } } /**************************************************************************** Desc: This routine reads a database's dictionary. This is called only when we did not have a dictionary off of the F_Database object - which will be the first transaction after a database is opened. ****************************************************************************/ RCODE F_Db::readDictionary( void) { RCODE rc = NE_SFLM_OK; if (RC_BAD( rc = dictOpen())) { goto Exit; } m_pDatabase->lockMutex(); // At this point, we will not yet have opened the database for // general use, so there is no way that any other thread can have // created a dictionary yet. flmAssert( !m_pDatabase->m_pDictList); // Link the new local dictionary to its file structure. m_pDict->linkToDatabase( m_pDatabase); m_pDatabase->unlockMutex(); Exit: return( rc); } /**************************************************************************** Desc: This routine starts a transaction for the specified database. The transaction may be part of an overall larger transaction. ****************************************************************************/ RCODE F_Db::beginTrans( eDbTransType eTransType, FLMUINT uiMaxLockWait, FLMUINT uiFlags, SFLM_DB_HDR * pDbHdr) { RCODE rc = NE_SFLM_OK; SFLM_DB_HDR * pLastCommittedDbHdr; F_Rfl * pRfl = m_pDatabase->m_pRfl; FLMUINT uiRflToken = 0; FLMBOOL bMutexLocked = FALSE; // Should not be calling on a temporary database flmAssert( !m_pDatabase->m_bTempDb); // Check the state of the database engine if( RC_BAD( rc = checkState( __FILE__, __LINE__))) { goto Exit; } // Initialize a few things - as few as is necessary to avoid // unnecessary overhead. m_AbortRc = NE_SFLM_OK; pLastCommittedDbHdr = &m_pDatabase->m_lastCommittedDbHdr; m_bKrefSetup = FALSE; m_eTransType = eTransType; m_uiThreadId = (FLMUINT)f_threadId(); m_uiTransCount++; // Link the F_Db to the database's most current F_Dict structure, // if there is one. Also, if it is a read transaction, link the F_Db // into the list of read transactions off of the F_Database object. m_pDatabase->lockMutex(); bMutexLocked = TRUE; if (m_pDatabase->m_pDictList) { // Link the F_Db to the right F_Dict object linkToDict( m_pDatabase->m_pDictList); } // If it is a read transaction, link into the list of // read transactions off of the F_Database object. Until we // get the DB header transaction ID below, we set ui64CurrTransID // to zero and link this transaction in at the beginning of the // list. if (eTransType == SFLM_READ_TRANS) { getDbHdrInfo( pLastCommittedDbHdr); // Link in at the end of the transaction list. m_pNextReadTrans = NULL; if ((m_pPrevReadTrans = m_pDatabase->m_pLastReadTrans) != NULL) { // Make sure transaction IDs are always in ascending order. They // should be at this point. flmAssert( m_pDatabase->m_pLastReadTrans->m_ui64CurrTransID <= m_ui64CurrTransID); m_pDatabase->m_pLastReadTrans->m_pNextReadTrans = this; } else { m_pDatabase->m_pFirstReadTrans = this; } m_pDatabase->m_pLastReadTrans = this; m_uiInactiveTime = 0; if (uiFlags & SFLM_DONT_KILL_TRANS) { m_uiFlags |= FDB_DONT_KILL_TRANS; } else { m_uiFlags &= ~FDB_DONT_KILL_TRANS; } if (pDbHdr) { f_memcpy( pDbHdr, &m_pDatabase->m_lastCommittedDbHdr, sizeof( SFLM_DB_HDR)); } } m_pDatabase->unlockMutex(); bMutexLocked = FALSE; if (uiFlags & SFLM_DONT_POISON_CACHE) { m_uiFlags |= FDB_DONT_POISON_CACHE; } else { m_uiFlags &= ~FDB_DONT_POISON_CACHE; } // Put an exclusive lock on the database if we are not in a read // transaction. Read transactions require no lock. if (eTransType != SFLM_READ_TRANS) { // Set the m_bHadUpdOper to TRUE for all transactions to begin with. // Many calls to beginTrans are internal, and we WANT the // normal behavior at the end of the transaction when it is // committed or aborted. The only time this flag will be set // to FALSE is when the application starts the transaction as // opposed to an internal starting of the transaction. m_bHadUpdOper = TRUE; // Initialize the count of blocks changed to be 0 m_uiBlkChangeCnt = 0; if (RC_BAD( rc = lockExclusive( uiMaxLockWait))) { goto Exit; } // If there was a problem with the RFL volume, we must wait // for a checkpoint to be completed before continuing. // The checkpoint thread looks at this same flag and forces // a checkpoint. If it completes one successfully, it will // reset this flag. // Also, if the last forced checkpoint had a problem // (pFile->CheckpointRc != NE_SFLM_OK), we don't want to // start up a new update transaction until it is resolved. if( !pRfl->seeIfRflVolumeOk() || RC_BAD( m_pDatabase->m_CheckpointRc)) { rc = RC_SET( NE_SFLM_MUST_WAIT_CHECKPOINT); goto Exit; } // Set the first log block address to zero. m_pDatabase->m_uiFirstLogBlkAddress = 0; // Header must be read before opening roll forward log file to make // sure we have the most current log file and log options. f_memcpy( &m_pDatabase->m_uncommittedDbHdr, pLastCommittedDbHdr, sizeof( SFLM_DB_HDR)); getDbHdrInfo( pLastCommittedDbHdr); // Need to increment the current checkpoint for update transactions // so that it will be correct when we go to mark cache blocks. if (m_uiFlags & FDB_REPLAYING_RFL) { // During recovery we need to set the transaction ID to the // transaction ID that was logged. m_ui64CurrTransID = pRfl->getCurrTransID(); } else { m_ui64CurrTransID++; } // Link F_Db to the most current local dictionary, if there // is one. m_pDatabase->lockMutex(); if (m_pDatabase->m_pDictList != m_pDict && m_pDatabase->m_pDictList) { linkToDict( m_pDatabase->m_pDictList); } m_pDatabase->unlockMutex(); // Set the transaction EOF to the current file EOF m_uiTransEOF = m_uiLogicalEOF; // Put the transaction ID into the uncommitted log header. m_pDatabase->m_uncommittedDbHdr.ui64CurrTransID = m_ui64CurrTransID; if (pDbHdr) { f_memcpy( pDbHdr, &m_pDatabase->m_uncommittedDbHdr, sizeof( SFLM_DB_HDR)); } } // Set up to collect statistics. We only do this at transaction // begin and not on any other type of operation. So this is the // only time when an F_Db will sense that statistics have been // turned on or off. if (!gv_SFlmSysData.Stats.bCollectingStats) { m_pStats = NULL; m_pDbStats = NULL; } else { m_pStats = &m_Stats; // Statistics are being collected for the system. Therefore, // if we are not currently collecting statistics in the // session, start. If we were collecting statistics, but the // start time was earlier than the start time in the system // statistics structure, reset the statistics in the session. if (!m_Stats.bCollectingStats) { flmStatStart( &m_Stats); } else if (m_Stats.uiStartTime < gv_SFlmSysData.Stats.uiStartTime) { flmStatReset( &m_Stats, FALSE); } (void)flmStatGetDb( &m_Stats, m_pDatabase, 0, &m_pDbStats, NULL, NULL); m_pLFileStats = NULL; } if (m_pDbStats) { f_timeGetTimeStamp( &m_TransStartTime); } // If we do not have a dictionary, read it in from disk. // NOTE: This should only happen when we are first opening // the database. if (!m_pDict) { if (eTransType != SFLM_READ_TRANS) { pRfl->disableLogging( &uiRflToken); } if (RC_BAD( rc = readDictionary())) { goto Exit; } } Exit: if( bMutexLocked) { m_pDatabase->unlockMutex(); } if( uiRflToken) { pRfl->enableLogging( &uiRflToken); } if (eTransType != SFLM_READ_TRANS) { if (RC_OK( rc)) { rc = pRfl->logBeginTransaction( this); } #ifdef FLM_DBG_LOG flmDbgLogUpdate( m_pDatabase, m_ui64CurrTransID, 0, 0, rc, "TBeg"); #endif } if (eTransType == SFLM_UPDATE_TRANS && gv_SFlmSysData.EventHdrs [SFLM_EVENT_UPDATES].pEventCBList) { flmTransEventCallback( SFLM_EVENT_BEGIN_TRANS, this, rc, (FLMUINT)(RC_OK( rc) ? m_ui64CurrTransID : (FLMUINT64)0)); } if (RC_BAD( rc)) { // If there was an error, unlink the database from the transaction // structure as well as from the FDICT structure. Also dump any nodes // that are already in the cache. unlinkFromTransList( FALSE); if (m_pStats) { (void)flmStatUpdate( &m_Stats); } } return( rc); } /**************************************************************************** Desc: This routine starts a transaction for the specified database. It uses the transaction information in the passed in pDb. ****************************************************************************/ RCODE F_Db::beginTrans( F_Db * pDb) { RCODE rc = NE_SFLM_OK; FLMBOOL bMutexLocked = FALSE; // Should not be calling on a temporary database flmAssert( !m_pDatabase->m_bTempDb); // pDb better be running a read transaction. flmAssert( pDb->m_eTransType == SFLM_READ_TRANS); if( RC_BAD( rc = checkState( __FILE__, __LINE__))) { goto Exit; } // Initialize a few things - as few as is necessary to avoid // unnecessary overhead. m_AbortRc = NE_SFLM_OK; m_bKrefSetup = FALSE; m_eTransType = SFLM_READ_TRANS; m_uiThreadId = (FLMUINT)f_threadId(); m_uiTransCount++; // Link the F_Db to the database's most current F_Dict structure, // if there is one. Also, if it is a read transaction, link the F_Db // into the list of read transactions off of the F_Database object. m_pDatabase->lockMutex(); bMutexLocked = TRUE; // Link to the same dictionary as pDb. linkToDict( pDb->m_pDict); // If it is a read transaction, link into the list of // read transactions off of the F_Database object. Until we // get the DB header transaction ID below, we set ui64CurrTransID // to zero and link this transaction in at the beginning of the // list. getDbHdrInfo( pDb); // Link into the transaction list right after the point where // pDb is linked in. We need to keep transaction IDs in ascending // order. m_pPrevReadTrans = pDb; if ((m_pNextReadTrans = pDb->m_pNextReadTrans) != NULL) { m_pNextReadTrans->m_pPrevReadTrans = this; } else { m_pDatabase->m_pLastReadTrans = this; } pDb->m_pNextReadTrans = this; m_uiInactiveTime = 0; if (pDb->m_uiFlags & FDB_DONT_KILL_TRANS) { m_uiFlags |= FDB_DONT_KILL_TRANS; } else { m_uiFlags &= ~FDB_DONT_KILL_TRANS; } if (pDb->m_uiFlags & FDB_DONT_POISON_CACHE) { m_uiFlags |= FDB_DONT_POISON_CACHE; } else { m_uiFlags &= ~FDB_DONT_POISON_CACHE; } m_pDatabase->unlockMutex(); bMutexLocked = FALSE; // Set up to collect statistics. We only do this at transaction // begin and not on any other type of operation. So this is the // only time when an F_Db will sense that statistics have been // turned on or off. if (!gv_SFlmSysData.Stats.bCollectingStats) { m_pStats = NULL; m_pDbStats = NULL; } else { m_pStats = &m_Stats; // Statistics are being collected for the system. Therefore, // if we are not currently collecting statistics in the // session, start. If we were collecting statistics, but the // start time was earlier than the start time in the system // statistics structure, reset the statistics in the session. if (!m_Stats.bCollectingStats) { flmStatStart( &m_Stats); } else if (m_Stats.uiStartTime < gv_SFlmSysData.Stats.uiStartTime) { flmStatReset( &m_Stats, FALSE); } (void)flmStatGetDb( &m_Stats, m_pDatabase, 0, &m_pDbStats, NULL, NULL); m_pLFileStats = NULL; } if (m_pDbStats) { f_timeGetTimeStamp( &m_TransStartTime); } Exit: if( bMutexLocked) { m_pDatabase->unlockMutex(); } if (RC_BAD( rc)) { // If there was an error, unlink the database from the transaction // structure as well as from the FDICT structure. Also dump any nodes // that are already in the cache. unlinkFromTransList( FALSE); if (m_pStats) { (void)flmStatUpdate( &m_Stats); } } return( rc); } /**************************************************************************** Desc : Starts a transaction. ****************************************************************************/ RCODE F_Db::transBegin( eDbTransType eTransType, FLMUINT uiMaxLockWait, FLMUINT uiFlags, SFLM_DB_HDR * pDbHdr) { RCODE rc = NE_SFLM_OK; // Verify the transaction type. if (eTransType != SFLM_UPDATE_TRANS && eTransType != SFLM_READ_TRANS) { rc = RC_SET( NE_SFLM_ILLEGAL_TRANS_TYPE); goto Exit; } // Verify the transaction flags if ((uiFlags & SFLM_DONT_KILL_TRANS) && eTransType != SFLM_READ_TRANS) { rc = RC_SET( NE_SFLM_ILLEGAL_TRANS_TYPE); goto Exit; } // Can't start an update transaction on a database that // is locked in shared mode. if (eTransType == SFLM_UPDATE_TRANS && (m_uiFlags & FDB_FILE_LOCK_SHARED)) { rc = RC_SET( NE_SFLM_SHARED_LOCK); goto Exit; } // If the database is not running a transaction, start one. if (m_eTransType != SFLM_NO_TRANS) { // Cannot nest transactions. rc = RC_SET( NE_SFLM_TRANS_ACTIVE); goto Exit; } if (RC_BAD( rc = beginTrans( eTransType, uiMaxLockWait, uiFlags, pDbHdr))) { goto Exit; } m_bHadUpdOper = FALSE; Exit: return( rc); } /**************************************************************************** Desc : Starts a transaction. ****************************************************************************/ RCODE F_Db::transBegin( F_Db * pDb) { RCODE rc = NE_SFLM_OK; // Database cannot already be running a transaction. if (m_eTransType != SFLM_NO_TRANS) { // Cannot nest transactions. rc = RC_SET( NE_SFLM_TRANS_ACTIVE); goto Exit; } // Verify the transaction type. if (((F_Db *)pDb)->m_eTransType != SFLM_READ_TRANS) { rc = RC_SET( NE_SFLM_ILLEGAL_TRANS_TYPE); goto Exit; } if (RC_BAD( rc = beginTrans( (F_Db *)pDb))) { goto Exit; } m_bHadUpdOper = FALSE; Exit: return( rc); } /**************************************************************************** Desc : Obtains a a lock on the database. ****************************************************************************/ RCODE F_Db::dbLock( eLockType eLockType, FLMINT iPriority, FLMUINT uiTimeout) { RCODE rc = NE_SFLM_OK; // eLockType better be exclusive or shared if (eLockType != FLM_LOCK_EXCLUSIVE && eLockType != FLM_LOCK_SHARED) { rc = RC_SET( NE_SFLM_ILLEGAL_OP); goto Exit; } // Nesting of locks is not allowed - this test also keeps this call from // being executed inside an update transaction that implicitly acquired // the lock. if (m_uiFlags & (FDB_HAS_FILE_LOCK | FDB_FILE_LOCK_SHARED | FDB_FILE_LOCK_IMPLICIT)) { rc = RC_SET( NE_SFLM_ILLEGAL_OP); goto Exit; } if (RC_BAD( rc = checkState( __FILE__, __LINE__))) { goto Exit; } // Attempt to acquire the lock. if (RC_BAD( rc = m_pDatabase->m_pDatabaseLockObj->lock( m_hWaitSem, (FLMBOOL)((eLockType == FLM_LOCK_EXCLUSIVE) ? (FLMBOOL)TRUE : (FLMBOOL)FALSE), uiTimeout, iPriority, m_pDbStats ? &m_pDbStats->LockStats : NULL))) { goto Exit; } m_uiFlags |= FDB_HAS_FILE_LOCK; if (eLockType == FLM_LOCK_SHARED) { m_uiFlags |= FDB_FILE_LOCK_SHARED; } Exit: return( rc); } /**************************************************************************** Desc : Releases a lock on the database ****************************************************************************/ RCODE F_Db::dbUnlock( void) { RCODE rc = NE_SFLM_OK; // If we don't have an explicit lock, can't do the unlock. It is // also illegal to do the unlock during an update transaction. if (!(m_uiFlags & FDB_HAS_FILE_LOCK) || (m_uiFlags & FDB_FILE_LOCK_IMPLICIT) || (m_eTransType == SFLM_UPDATE_TRANS)) { rc = RC_SET( NE_SFLM_ILLEGAL_OP); goto Exit; } // Unlock the file. if (RC_BAD( rc = m_pDatabase->m_pDatabaseLockObj->unlock())) { goto Exit; } // Unset the flags that indicated the file was explicitly locked. m_uiFlags &= (~(FDB_HAS_FILE_LOCK | FDB_FILE_LOCK_SHARED)); Exit: if (RC_OK( rc)) { rc = checkState( __FILE__, __LINE__); } return( rc); } /**************************************************************************** Desc : Returns information about current and pending locks on the database. ****************************************************************************/ RCODE F_Db::getLockInfo( FLMINT iPriority, eLockType * peCurrLockType, FLMUINT * puiThreadId, FLMUINT * puiNumExclQueued, FLMUINT * puiNumSharedQueued, FLMUINT * puiPriorityCount) { RCODE rc = NE_SFLM_OK; if (RC_BAD( rc = checkState( __FILE__, __LINE__))) { goto Exit; } m_pDatabase->m_pDatabaseLockObj->getLockInfo( iPriority, peCurrLockType, puiThreadId, puiNumExclQueued, puiNumSharedQueued, puiPriorityCount); Exit: return( rc); } /**************************************************************************** Desc : Returns information about the lock held by the specified database handle. ****************************************************************************/ RCODE F_Db::getLockType( eLockType * peLockType, FLMBOOL * pbImplicit) { RCODE rc = NE_SFLM_OK; if (peLockType) { *peLockType = FLM_LOCK_NONE; } if (pbImplicit) { *pbImplicit = FALSE; } if (RC_BAD( rc = checkState( __FILE__, __LINE__))) { goto Exit; } if (m_uiFlags & FDB_HAS_FILE_LOCK) { if (peLockType) { if (m_uiFlags & FDB_FILE_LOCK_SHARED) { *peLockType = FLM_LOCK_SHARED; } else { *peLockType = FLM_LOCK_EXCLUSIVE; } } if (pbImplicit) { *pbImplicit = (m_uiFlags & FDB_FILE_LOCK_IMPLICIT) ? TRUE : FALSE; } } Exit: return( rc); } /**************************************************************************** Desc : Forces a checkpoint on the database. ****************************************************************************/ RCODE F_Db::doCheckpoint( FLMUINT uiTimeout) { RCODE rc = NE_SFLM_OK; if (RC_BAD( rc = checkState( __FILE__, __LINE__))) { goto Exit; } // Start an update transaction. Must not already be one going. if (m_eTransType != SFLM_NO_TRANS) { rc = RC_SET( NE_SFLM_TRANS_ACTIVE); goto Exit; } // If we get to this point, we need to start a transaction on the // database. if( RC_BAD( rc = beginTrans( SFLM_UPDATE_TRANS, uiTimeout))) { goto Exit; } // Commit the transaction, forcing it to be checkpointed. m_bHadUpdOper = FALSE; if (RC_BAD( rc = commitTrans( 0, TRUE))) { goto Exit; } Exit: return( rc); } /**************************************************************************** Desc: This routine locks a database for exclusive access. ****************************************************************************/ RCODE F_Db::lockExclusive( FLMUINT uiMaxLockWait) { RCODE rc = NE_SFLM_OK; FLMBOOL bGotFileLock = FALSE; flmAssert( !m_pDatabase->m_bTempDb); // There must NOT be a shared lock on the file. if (m_uiFlags & FDB_FILE_LOCK_SHARED) { rc = RC_SET( NE_SFLM_SHARED_LOCK); goto Exit; } // Must acquire an exclusive file lock first, if it hasn't been // acquired. if (!(m_uiFlags & FDB_HAS_FILE_LOCK)) { if (RC_BAD( rc = m_pDatabase->m_pDatabaseLockObj->lock( m_hWaitSem, TRUE, uiMaxLockWait, 0, m_pDbStats ? &m_pDbStats->LockStats : NULL))) { goto Exit; } bGotFileLock = TRUE; m_uiFlags |= (FDB_HAS_FILE_LOCK | FDB_FILE_LOCK_IMPLICIT); } if (RC_OK( rc = m_pDatabase->dbWriteLock( m_hWaitSem, m_pDbStats, FLM_NO_TIMEOUT))) { m_uiFlags |= FDB_HAS_WRITE_LOCK; } Exit: if (rc == NE_SFLM_DATABASE_LOCK_REQ_TIMEOUT) { if (bGotFileLock) { (void)m_pDatabase->m_pDatabaseLockObj->unlock(); m_uiFlags &= (~(FDB_HAS_FILE_LOCK | FDB_FILE_LOCK_IMPLICIT | FDB_HAS_WRITE_LOCK)); } if (m_eTransType != SFLM_NO_TRANS) { // Unlink the DB from the transaction. unlinkFromTransList( FALSE); } } else if (RC_BAD( rc)) { if (bGotFileLock) { (void)m_pDatabase->m_pDatabaseLockObj->unlock(); m_uiFlags &= (~(FDB_HAS_FILE_LOCK | FDB_FILE_LOCK_IMPLICIT | FDB_HAS_WRITE_LOCK)); } } return( rc); } /**************************************************************************** Desc: This routine unlocks a database that was previously locked using the lockExclusive routine. ****************************************************************************/ void F_Db::unlockExclusive( void) { flmAssert( !m_pDatabase->m_bTempDb); // If we have the write lock, unlock it first. flmAssert( m_uiFlags & FDB_HAS_WRITE_LOCK); m_pDatabase->dbWriteUnlock(); m_uiFlags &= ~FDB_HAS_WRITE_LOCK; // Give up the file lock, if it was acquired implicitly. if (m_uiFlags & FDB_FILE_LOCK_IMPLICIT) { (void)m_pDatabase->m_pDatabaseLockObj->unlock(); m_uiFlags &= (~(FDB_HAS_FILE_LOCK | FDB_FILE_LOCK_IMPLICIT)); } }