diff options
author | danielk1977 <danielk1977@noemail.net> | 2004-06-03 16:08:41 +0000 |
---|---|---|
committer | danielk1977 <danielk1977@noemail.net> | 2004-06-03 16:08:41 +0000 |
commit | 13adf8a0716f0fa05c42fcedcf9a210aefa22b08 (patch) | |
tree | 23b1459dfe656b13611cb31540df0f497c05ea18 | |
parent | ecb2a9644b4ddcc829fccb8ccda295fd38c2b9fb (diff) | |
download | sqlite-13adf8a0716f0fa05c42fcedcf9a210aefa22b08.tar.gz sqlite-13adf8a0716f0fa05c42fcedcf9a210aefa22b08.zip |
Untested updates to support atomic multi-file transactions (CVS 1526)
FossilOrigin-Name: d57e5252c8baaf615c2cd218a33356ea5d95a5e2
-rw-r--r-- | manifest | 40 | ||||
-rw-r--r-- | manifest.uuid | 2 | ||||
-rw-r--r-- | src/btree.c | 40 | ||||
-rw-r--r-- | src/btree.h | 5 | ||||
-rw-r--r-- | src/build.c | 4 | ||||
-rw-r--r-- | src/delete.c | 3 | ||||
-rw-r--r-- | src/os.h | 1 | ||||
-rw-r--r-- | src/os_unix.c | 73 | ||||
-rw-r--r-- | src/os_unix.h | 5 | ||||
-rw-r--r-- | src/pager.c | 291 | ||||
-rw-r--r-- | src/pager.h | 5 | ||||
-rw-r--r-- | src/pragma.c | 4 | ||||
-rw-r--r-- | src/test3.c | 4 | ||||
-rw-r--r-- | src/vacuum.c | 4 | ||||
-rw-r--r-- | src/vdbe.c | 7 | ||||
-rw-r--r-- | src/vdbeInt.h | 8 | ||||
-rw-r--r-- | src/vdbeaux.c | 180 |
17 files changed, 563 insertions, 113 deletions
@@ -1,5 +1,5 @@ -C Fix\sa\ssegfault\sin\ssqlite3OsLock()\s(CVS\s1525) -D 2004-06-02T06:30:16 +C Untested\supdates\sto\ssupport\satomic\smulti-file\stransactions\s(CVS\s1526) +D 2004-06-03T16:08:41 F Makefile.in ab7b0d5118e2da97bac66be8684a1034e3500f5a F Makefile.linux-gcc b86a99c493a5bfb402d1d9178dcdc4bd4b32f906 F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd @@ -25,11 +25,11 @@ F sqlite.def fc4f5734786fe4743cfe2aa98eb2da4b089edb5f F sqlite.pc.in 30552343140c53304c2a658c080fbe810cd09ca2 F src/attach.c c315c58cb16fd6e913b3bfa6412aedecb4567fa5 F src/auth.c 5c2f0bea4729c98c2be3b69d6b466fc51448fe79 -F src/btree.c bf725408c6ac656327b0f5546c7c5d1eb065d181 -F src/btree.h 1e2beb41b4b4a4fc41da67cb4692614938066f2f -F src/build.c 13985630bf6b3db4c7e7d4c9476bb94495b7d5fa +F src/btree.c 3cf513520d5b6fe54cc4c5fb44ce5c6231f1a535 +F src/btree.h 7b682341772eb1199de3f9c29ce5e34f96836d17 +F src/build.c e12e602f06e37a0fbcb49af17cba68ad85e101b6 F src/date.c 8e6fa3173386fb29fdef012ee08a853c1e9908b2 -F src/delete.c 72f8febf6170cda830f509c8f9dffbed3df3596c +F src/delete.c b30f08250c9ed53a25a13c7c04599c1e8753992d F src/encode.c a876af473d1d636faa3dca51c7571f2e007eea37 F src/expr.c 5145de7d25a4b960a4afdb754a9e88b60cce0405 F src/func.c 3b87e2e8b9aaa3a6d36b2c9616e7f404be38a667 @@ -39,18 +39,18 @@ F src/insert.c 4268d9e3959cc845ea243fb4ec7507269404dad9 F src/legacy.c ad23746f15f67e34577621b1875f639c94839e1f F src/main.c d2a7632f459e9c270bb6e313e10916fc840f4a6e F src/md5.c 4302e84ae516c616bb079c4e6d038c0addb33481 -F src/os.h 833b9639720d1602d9bfa4ca69c615ec2bfe625a +F src/os.h cc2fd62b2e8e11103701913871908ff77532af58 F src/os_common.h 744286a27de55c52f1b18921e8d17abbf7fafc0f F src/os_mac.c b823874690615ace0dd520d3ad1fe8bfd864b7e0 F src/os_mac.h 51d2445f47e182ed32d3bd6937f81070c6fd9bd4 -F src/os_unix.c 36682ee5e93c2fdad63ff44f7e10ac7d4a39f746 -F src/os_unix.h 426e1480f0847a7f8ba22aa9ac5115520875610b +F src/os_unix.c 3175540f8d1c820dab7a470c50875c221c3a98cd +F src/os_unix.h 7999f2246c6347707e98f7078871ea8ca605df3f F src/os_win.c 92b51a38437b98d8aa3ac05b57c71e1d1092e5be F src/os_win.h 5d41af24caaef6c13a2d8e2399caa1c57d45c84d -F src/pager.c 048872f1ccd27e4c17d77098eb6e86990a7a9b88 -F src/pager.h 78a00ac280899bcba1a89dc51585dcae6b7b3253 +F src/pager.c 1619b6a0338cefa3b4d8be54afbbe1c46e85e64e +F src/pager.h ade5bee4a0771adf82180fd702f170cb0031d811 F src/parse.y 27c1ce09f9d309be91f9e537df2fb00892990af4 -F src/pragma.c 7f432dee3c94460638df1e5fffeb59a560943d13 +F src/pragma.c 1b58d852b84b36a8b84e2245dd29b63c377414ec F src/printf.c ef750e8e2398ca7e8b58be991075f08c6a7f0e53 F src/random.c eff68e3f257e05e81eae6c4d50a51eb88beb4ff3 F src/select.c 0297717eb7331604687c2e29c147d3a311359df1 @@ -61,7 +61,7 @@ F src/table.c af14284fa36c8d41f6829e3f2819dce07d3e2de2 F src/tclsqlite.c 3db6b868bd844bfb71720c8e573f4c9b0d536bd5 F src/test1.c 4a3cc1b628a29f24c0a43227a035d0f2a96eb634 F src/test2.c 6195a1ca2c8d0d2d93644e86da3289b403486872 -F src/test3.c 86117b74ec7353d76f5cd85c144c7cda23a7e11b +F src/test3.c b3f331bda440ae21b6cab5171f28ddb402001f26 F src/test4.c caf675e443460ec76b04d78e1688986c17c82cec F src/test5.c e731274b902eaad09b195cfbac06768dfefba93e F src/tokenize.c 183c5d7da11affab5d70d903d33409c8c0ce6c5b @@ -69,12 +69,12 @@ F src/trigger.c 04b2c310d0d056b213609cab6df5fff03d5eaf88 F src/update.c 259f06e7b22c684b2d3dda54a18185892d6e9573 F src/utf.c c8be20ecdcb10659e23c43e35d835460e964d248 F src/util.c d3d2f62ec94160db3cb2b092267405ba99122152 -F src/vacuum.c c91acc316127411980982938d050b299d42b81ef -F src/vdbe.c 4ce596ee57b663d4c428bee5c40f51094525acd9 +F src/vacuum.c b921eb778842592e1fb48a9d4cef7e861103878f +F src/vdbe.c 2cf1376f23e2f8ddd1a55143ea9e0e289095504d F src/vdbe.h e73f890e0f2a6c42b183d7d6937947930fe4fdeb -F src/vdbeInt.h f19df2246cf61f4dc27b13c0f9c278a379a935ee +F src/vdbeInt.h 9f5df0a21474be02fe870cbb0a414d09b66eb31a F src/vdbeapi.c 77d2e681a992ef189032cd9c1b7bf922f01ebe3e -F src/vdbeaux.c 5842109cc9fa76bd454305861c5936b370b9458a +F src/vdbeaux.c 55c6d501175edb35cd84430302bbbde8dad4b752 F src/vdbemem.c 5d029d83bc60eaf9c45837fcbc0b03348ec95d7a F src/where.c 444a7c3a8b1eb7bba072e489af628555d21d92a4 F test/all.test 569a92a8ee88f5300c057cc4a8f50fbbc69a3242 @@ -214,7 +214,7 @@ F www/support.tcl 1801397edd271cc39a2aadd54e701184b5181248 F www/tclsqlite.tcl 19191cf2a1010eaeff74c51d83fd5f5a4d899075 F www/vdbe.tcl 59288db1ac5c0616296b26dce071c36cb611dfe9 F www/whentouse.tcl a8335bce47cc2fddb07f19052cb0cb4d9129a8e4 -P 165d69a04cca719dec2b042117f848f153721a1d -R e34294435e363d8121bbbf40938d6dd7 +P 51348b82c4d5801091537b80059d770410774905 +R 0e06f4b108dd74f5024eb81017752495 U danielk1977 -Z cf403c13a566790d4729e792131db901 +Z 4a23c8e1ebb05080684ee0992bb72463 diff --git a/manifest.uuid b/manifest.uuid index e388ab026..91ad00296 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -51348b82c4d5801091537b80059d770410774905
\ No newline at end of file +d57e5252c8baaf615c2cd218a33356ea5d95a5e2
\ No newline at end of file diff --git a/src/btree.c b/src/btree.c index 6059899c3..07ebaa9d5 100644 --- a/src/btree.c +++ b/src/btree.c @@ -9,7 +9,7 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** $Id: btree.c,v 1.155 2004/06/02 01:22:02 drh Exp $ +** $Id: btree.c,v 1.156 2004/06/03 16:08:41 danielk1977 Exp $ ** ** This file implements a external (disk-based) database using BTrees. ** For a detailed discussion of BTrees, refer to @@ -1200,8 +1200,13 @@ static int newDatabase(Btree *pBt){ ** sqlite3BtreeInsert() ** sqlite3BtreeDelete() ** sqlite3BtreeUpdateMeta() +** +** If wrflag is true, then nMaster specifies the maximum length of +** a master journal file name supplied later via sqlite3BtreeSync(). +** This is so that appropriate space can be allocated in the journal file +** when it is created.. */ -int sqlite3BtreeBeginTrans(Btree *pBt, int wrflag){ +int sqlite3BtreeBeginTrans(Btree *pBt, int wrflag, int nMaster){ int rc = SQLITE_OK; /* If the btree is already in a write-transaction, or it @@ -1221,7 +1226,7 @@ int sqlite3BtreeBeginTrans(Btree *pBt, int wrflag){ } if( rc==SQLITE_OK && wrflag ){ - rc = sqlite3pager_begin(pBt->pPage1->aData); + rc = sqlite3pager_begin(pBt->pPage1->aData, 0); if( rc==SQLITE_OK ){ rc = newDatabase(pBt); } @@ -2073,7 +2078,7 @@ int sqlite3BtreeMoveto(BtCursor *pCur, const void *pKey, i64 nKey, int *pRes){ upr = pPage->nCell-1; pageIntegrity(pPage); while( lwr<=upr ){ - const void *pCellKey; + void *pCellKey; i64 nCellKey; pCur->idx = (lwr+upr)/2; pCur->info.nSize = 0; @@ -2088,13 +2093,13 @@ int sqlite3BtreeMoveto(BtCursor *pCur, const void *pKey, i64 nKey, int *pRes){ } }else{ int available; - pCellKey = fetchPayload(pCur, &available, 0); + pCellKey = (void *)fetchPayload(pCur, &available, 0); if( available>=nCellKey ){ c = pCur->xCompare(pCur->pArg, nCellKey, pCellKey, nKey, pKey); }else{ pCellKey = sqliteMallocRaw( nCellKey ); if( pCellKey==0 ) return SQLITE_NOMEM; - rc = sqlite3BtreeKey(pCur, 0, nCellKey, pCellKey); + rc = sqlite3BtreeKey(pCur, 0, nCellKey, (void *)pCellKey); c = pCur->xCompare(pCur->pArg, nCellKey, pCellKey, nKey, pKey); sqliteFree(pCellKey); if( rc ) return rc; @@ -4220,3 +4225,26 @@ int sqlite3BtreeIsInTrans(Btree *pBt){ int sqlite3BtreeIsInStmt(Btree *pBt){ return (pBt && pBt->inStmt); } + +/* +** This call is a no-op if no write-transaction is currently active on pBt. +** +** Otherwise, sync the database file for the btree pBt. zMaster points to +** the name of a master journal file that should be written into the +** individual journal file, or is NULL, indicating no master journal file +** (single database transaction). +** +** When this is called, the master journal should already have been +** created, populated with this journal pointer and synced to disk. +** +** Once this is routine has returned, the only thing required to commit +** the write-transaction for this database file is to delete the journal. +*/ +int sqlite3BtreeSync(Btree *pBt, const char *zMaster){ + if( pBt->inTrans==TRANS_WRITE ){ + return sqlite3pager_sync(pBt->pPager, zMaster); + } + return SQLITE_OK; +} + + diff --git a/src/btree.h b/src/btree.h index 931cca990..472fd9052 100644 --- a/src/btree.h +++ b/src/btree.h @@ -13,7 +13,7 @@ ** subsystem. See comments in the source code for a detailed description ** of what each interface routine does. ** -** @(#) $Id: btree.h,v 1.51 2004/05/31 10:01:35 danielk1977 Exp $ +** @(#) $Id: btree.h,v 1.52 2004/06/03 16:08:41 danielk1977 Exp $ */ #ifndef _BTREE_H_ #define _BTREE_H_ @@ -41,7 +41,7 @@ int sqlite3BtreeOpen(const char *zFilename, Btree **, int nCache, int flags); int sqlite3BtreeClose(Btree*); int sqlite3BtreeSetCacheSize(Btree*,int); int sqlite3BtreeSetSafetyLevel(Btree*,int); -int sqlite3BtreeBeginTrans(Btree*,int); +int sqlite3BtreeBeginTrans(Btree*,int,int); int sqlite3BtreeCommit(Btree*); int sqlite3BtreeRollback(Btree*); int sqlite3BtreeBeginStmt(Btree*); @@ -50,6 +50,7 @@ int sqlite3BtreeRollbackStmt(Btree*); int sqlite3BtreeCreateTable(Btree*, int*, int flags); int sqlite3BtreeIsInTrans(Btree*); int sqlite3BtreeIsInStmt(Btree*); +int sqlite3BtreeSync(Btree*, const char *zMaster); const char *sqlite3BtreeGetFilename(Btree *); int sqlite3BtreeCopyFile(Btree *, Btree *); diff --git a/src/build.c b/src/build.c index 48cc1f596..30f8d04b9 100644 --- a/src/build.c +++ b/src/build.c @@ -23,7 +23,7 @@ ** ROLLBACK ** PRAGMA ** -** $Id: build.c,v 1.205 2004/05/31 18:51:58 drh Exp $ +** $Id: build.c,v 1.206 2004/06/03 16:08:41 danielk1977 Exp $ */ #include "sqliteInt.h" #include <ctype.h> @@ -556,7 +556,7 @@ void sqlite3StartTable( return; } if( db->flags & !db->autoCommit ){ - rc = sqlite3BtreeBeginTrans(db->aDb[1].pBt, 1); + rc = sqlite3BtreeBeginTrans(db->aDb[1].pBt, 1, 0); if( rc!=SQLITE_OK ){ sqlite3ErrorMsg(pParse, "unable to get a write lock on " "the temporary database file"); diff --git a/src/delete.c b/src/delete.c index bd77b59bc..82bb8575b 100644 --- a/src/delete.c +++ b/src/delete.c @@ -12,7 +12,7 @@ ** This file contains C code routines that are called by the parser ** to handle DELETE FROM statements. ** -** $Id: delete.c,v 1.71 2004/05/28 16:00:22 drh Exp $ +** $Id: delete.c,v 1.72 2004/06/03 16:08:41 danielk1977 Exp $ */ #include "sqliteInt.h" @@ -377,7 +377,6 @@ void sqlite3GenerateRowIndexDelete( Index *pIdx; for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ - int j; if( aIdxUsed!=0 && aIdxUsed[i-1]==0 ) continue; sqlite3GenerateIndexKey(v, pIdx, iCur); sqlite3VdbeAddOp(v, OP_IdxDelete, iCur+i, 0); @@ -114,5 +114,6 @@ void sqlite3OsEnterMutex(void); void sqlite3OsLeaveMutex(void); char *sqlite3OsFullPathname(const char*); int sqlite3OsLock(OsFile*, int); +int sqlite3OsCheckWriteLock(OsFile *id); #endif /* _SQLITE_OS_H_ */ diff --git a/src/os_unix.c b/src/os_unix.c index d8dca0215..9ea456970 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -61,6 +61,10 @@ */ #include "os_common.h" +#if defined(THREADSAFE) && defined(__linux__) +#define getpid pthread_self +#endif + /* ** Here is the dirt on POSIX advisory locks: ANSI STD 1003.1 (1996) ** section 6.5.2.2 lines 483 through 490 specify that when a process @@ -170,7 +174,7 @@ struct lockKey { */ struct lockInfo { struct lockKey key; /* The lookup key */ - int cnt; /* 0: unlocked. -1: write lock. 1...: read lock. */ + int cnt; /* Number of locks held */ int locktype; /* One of SHARED_LOCK, RESERVED_LOCK etc. */ int nRef; /* Number of pointers to this structure */ }; @@ -355,7 +359,7 @@ int sqlite3OsOpenReadWrite( close(id->fd); return SQLITE_NOMEM; } - id->locked = 0; + id->locktype = 0; TRACE3("OPEN %-3d %s\n", id->fd, zFilename); OpenCounter(+1); return SQLITE_OK; @@ -395,7 +399,7 @@ int sqlite3OsOpenExclusive(const char *zFilename, OsFile *id, int delFlag){ unlink(zFilename); return SQLITE_NOMEM; } - id->locked = 0; + id->locktype = 0; if( delFlag ){ unlink(zFilename); } @@ -425,7 +429,7 @@ int sqlite3OsOpenReadOnly(const char *zFilename, OsFile *id){ close(id->fd); return SQLITE_NOMEM; } - id->locked = 0; + id->locktype = 0; TRACE3("OPEN-RO %-3d %s\n", id->fd, zFilename); OpenCounter(+1); return SQLITE_OK; @@ -662,6 +666,41 @@ int sqlite3OsWriteLock(OsFile *id){ } /* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, return +** non-zero, otherwise zero. +*/ +int sqlite3OsCheckWriteLock(OsFile *id){ + int r = 0; + + sqlite3OsEnterMutex(); + + /* Check if a thread in this process holds such a lock */ + if( id->pLock->locktype>SHARED_LOCK ){ + r = 1; + } + + /* Otherwise see if some other process holds it. Just check the whole + ** file for write-locks, rather than any specific bytes. + */ + if( !r ){ + struct flock lock; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + lock.l_type = F_RDLCK; + fcntl(id->fd, F_GETLK, &lock); + if( lock.l_type!=F_UNLCK ){ + r = 1; + } + } + + sqlite3OsLeaveMutex(); + + return r; +} + +/* ** Lock the file with the lock specified by parameter locktype - one ** of the following: ** @@ -677,17 +716,17 @@ int sqlite3OsLock(OsFile *id, int locktype){ int s; /* It is an error to request any kind of lock before a shared lock */ - if( locktype>SHARED_LOCK && id->locked==0 ){ + if( locktype>SHARED_LOCK && id->locktype==0 ){ rc = sqlite3OsLock(id, SHARED_LOCK); if( rc!=SQLITE_OK ) return rc; } - assert( locktype==SHARED_LOCK || id->locked!=0 ); + assert( locktype==SHARED_LOCK || id->locktype!=0 ); /* If there is already a lock of this type or more restrictive on the ** OsFile, do nothing. Don't use the end_lock: exit path, as ** sqlite3OsEnterMutex() hasn't been called yet. */ - if( id->locked>=locktype ){ + if( id->locktype>=locktype ){ return SQLITE_OK; } @@ -696,7 +735,7 @@ int sqlite3OsLock(OsFile *id, int locktype){ /* If some thread using this PID has a lock via a different OsFile* ** handle that precludes the requested lock, return BUSY. */ - if( (id->locked!=pLock->locktype && + if( (id->locktype!=pLock->locktype && (pLock->locktype>RESERVED_LOCK || locktype!=SHARED_LOCK)) || (locktype>RESERVED_LOCK && pLock->cnt>1) ){ @@ -711,15 +750,15 @@ int sqlite3OsLock(OsFile *id, int locktype){ if( locktype==SHARED_LOCK && (pLock->locktype==SHARED_LOCK || pLock->locktype==RESERVED_LOCK) ){ assert( locktype==SHARED_LOCK ); - assert( id->locked==0 ); + assert( id->locktype==0 ); assert( pLock->cnt>0 ); - id->locked = SHARED_LOCK; + id->locktype = SHARED_LOCK; pLock->cnt++; id->pOpen->nLock++; goto end_lock; } - lock.l_len = 0L; + lock.l_len = 1L; lock.l_whence = SEEK_SET; /* If control gets to this point, then actually go ahead and make @@ -749,7 +788,7 @@ int sqlite3OsLock(OsFile *id, int locktype){ if( s ){ rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY; }else{ - id->locked = SHARED_LOCK; + id->locktype = SHARED_LOCK; id->pOpen->nLock++; pLock->cnt = 1; } @@ -758,7 +797,7 @@ int sqlite3OsLock(OsFile *id, int locktype){ ** assumed that there is a SHARED or greater lock on the file ** already. */ - assert( 0!=id->locked ); + assert( 0!=id->locktype ); lock.l_type = F_WRLCK; switch( locktype ){ case RESERVED_LOCK: @@ -780,7 +819,7 @@ int sqlite3OsLock(OsFile *id, int locktype){ } if( rc==SQLITE_OK ){ - id->locked = locktype; + id->locktype = locktype; pLock->locktype = locktype; assert( pLock->locktype==RESERVED_LOCK || pLock->cnt==1 ); } @@ -798,8 +837,8 @@ end_lock: */ int sqlite3OsUnlock(OsFile *id){ int rc; - if( !id->locked ) return SQLITE_OK; - id->locked = 0; + if( !id->locktype ) return SQLITE_OK; + id->locktype = 0; sqlite3OsEnterMutex(); assert( id->pLock->cnt!=0 ); if( id->pLock->cnt>1 ){ @@ -840,7 +879,7 @@ int sqlite3OsUnlock(OsFile *id){ } } sqlite3OsLeaveMutex(); - id->locked = 0; + id->locktype = 0; return rc; } diff --git a/src/os_unix.h b/src/os_unix.h index 9dab7375c..e9a016f33 100644 --- a/src/os_unix.h +++ b/src/os_unix.h @@ -56,13 +56,16 @@ ** of an open file handle. It is defined differently for each architecture. ** ** This is the definition for Unix. +** +** OsFile.locktype takes one of the values SHARED_LOCK, RESERVED_LOCK, +** PENDING_LOCK or EXCLUSIVE_LOCK. */ typedef struct OsFile OsFile; struct OsFile { struct openCnt *pOpen; /* Info about all open fd's on this inode */ struct lockInfo *pLock; /* Info about locks on this inode */ int fd; /* The file descriptor */ - int locked; /* True if this instance holds the lock */ + int locktype; /* The type of lock held on this fd */ int dirfd; /* File descriptor for the directory */ }; diff --git a/src/pager.c b/src/pager.c index d2b84b97b..7068df786 100644 --- a/src/pager.c +++ b/src/pager.c @@ -18,7 +18,7 @@ ** file simultaneously, or one process from reading the database while ** another is writing. ** -** @(#) $Id: pager.c,v 1.109 2004/05/31 08:26:49 danielk1977 Exp $ +** @(#) $Id: pager.c,v 1.110 2004/06/03 16:08:42 danielk1977 Exp $ */ #include "os.h" /* Must be first to enable large file support */ #include "sqliteInt.h" @@ -212,6 +212,7 @@ struct Pager { PgHdr *pAll; /* List of all pages */ PgHdr *pStmt; /* List of pages in the statement subjournal */ PgHdr *aHash[N_PG_HASH]; /* Hash table to map page number of PgHdr */ + int nMaster; /* Number of bytes to reserve for master j.p */ }; /* @@ -299,11 +300,18 @@ int journal_format = 3; ** to which journal format is being used. The following macros figure out ** the sizes based on format numbers. */ +/* #define JOURNAL_HDR_SZ(X) \ (sizeof(aJournalMagic1) + sizeof(Pgno) + ((X)>=3)*2*sizeof(u32)) +*/ +#define JOURNAL_HDR_SZ(pPager, X) (\ + sizeof(aJournalMagic1) + \ + sizeof(Pgno) + \ + ((X)>=3?3*sizeof(u32)+(pPager)->nMaster:0) ) #define JOURNAL_PG_SZ(X) \ (SQLITE_PAGE_SIZE + sizeof(Pgno) + ((X)>=3)*sizeof(u32)) + /* ** Enable reference count tracking here: */ @@ -602,6 +610,104 @@ static int pager_playback_one_page(Pager *pPager, OsFile *jfd, int format){ } /* +** Parameter zMaster is the name of a master journal file. A single journal +** file that referred to the master journal file has just been rolled back. +** This routine checks if it is possible to delete the master journal file, +** and does so if it is. +*/ +static int pager_delmaster(const char *zMaster){ + int rc; + int master_open = 0; + OsFile master; + char *zMasterJournal = 0; /* Contents of master journal file */ + off_t nMasterJournal; /* Size of master journal file */ + + /* Open the master journal file exclusively in case some other process + ** is running this routine also. Not that it makes too much difference. + */ + rc = sqlite3OsOpenExclusive(zMaster, &master, 0); + if( rc!=SQLITE_OK ) goto delmaster_out; + master_open = 1; + + rc = sqlite3OsFileSize(&master, &nMasterJournal); + if( rc!=SQLITE_OK ) goto delmaster_out; + + if( nMasterJournal>0 ){ + char *zDb; + zMasterJournal = (char *)sqliteMalloc(nMasterJournal); + if( !zMasterJournal ){ + rc = SQLITE_NOMEM; + goto delmaster_out; + } + rc = sqlite3OsRead(&master, zMasterJournal, nMasterJournal); + if( rc!=SQLITE_OK ) goto delmaster_out; + + zDb = zMasterJournal; + while( (zDb-zMasterJournal)<nMasterJournal ){ + char *zJournal = 0; + sqlite3SetString(&zJournal, zDb, "-journal", 0); + if( !zJournal ){ + rc = SQLITE_NOMEM; + goto delmaster_out; + } + if( sqlite3OsFileExists(zJournal) ){ + /* One of the journals pointed to by the master journal exists. + ** Open it and check if it points at the master journal. If + ** so, return without deleting the master journal file. + */ + OsFile journal; + int nMaster; + + rc = sqlite3OsOpenReadOnly(zJournal, &journal); + if( rc!=SQLITE_OK ){ + sqlite3OsClose(&journal); + sqliteFree(zJournal); + goto delmaster_out; + } + sqlite3OsClose(&journal); + + /* Seek to the point in the journal where the master journal name + ** is stored. Read the master journal name into memory obtained + ** from malloc. + */ + rc = sqlite3OsSeek(&journal, sizeof(aJournalMagic3)+2*sizeof(u32)); + if( rc!=SQLITE_OK ) goto delmaster_out; + rc = read32bits(3, &journal, (u32 *)&nMaster); + if( rc!=SQLITE_OK ) goto delmaster_out; + if( nMaster>0 && nMaster==strlen(zMaster)+1 ){ + char *zMasterPtr = (char *)sqliteMalloc(nMaster); + if( !zMasterPtr ){ + rc = SQLITE_NOMEM; + } + rc = sqlite3OsRead(&journal, zMasterPtr, nMaster); + if( rc!=SQLITE_OK ){ + sqliteFree(zMasterPtr); + goto delmaster_out; + } + if( 0==strncmp(zMasterPtr, zMaster, nMaster) ){ + /* We have a match. Do not delete the master journal file. */ + sqliteFree(zMasterPtr); + goto delmaster_out; + } + } + } + zDb += (strlen(zDb)+1); + } + } + + sqlite3OsDelete(zMaster); + +delmaster_out: + if( zMasterJournal ){ + sqliteFree(zMasterJournal); + } + if( master_open ){ + sqlite3OsClose(&master); + } + return rc; +} + +/* ** Playback the journal and thus restore the database file to ** the state it was in before we started making changes. ** @@ -661,6 +767,7 @@ static int pager_playback(Pager *pPager, int useJournalSize){ int format; /* Format of the journal file. */ unsigned char aMagic[sizeof(aJournalMagic1)]; int rc; + char *zMaster = 0; /* Name of master journal file if any */ /* Figure out how many records are in the journal. Abort early if ** the journal is empty. @@ -701,7 +808,7 @@ static int pager_playback(Pager *pPager, int useJournalSize){ goto end_playback; } if( format>=JOURNAL_FORMAT_3 ){ - if( szJ < sizeof(aMagic) + 3*sizeof(u32) ){ + if( szJ < sizeof(aMagic) + 4*sizeof(u32) ){ /* Ignore the journal if it is too small to contain a complete ** header. We already did this test once above, but at the prior ** test, we did not know the journal format and so we had to assume @@ -715,11 +822,28 @@ static int pager_playback(Pager *pPager, int useJournalSize){ rc = read32bits(format, &pPager->jfd, &pPager->cksumInit); if( rc ) goto end_playback; if( nRec==0xffffffff || useJournalSize ){ - nRec = (szJ - JOURNAL_HDR_SZ(3))/JOURNAL_PG_SZ(3); + nRec = (szJ - JOURNAL_HDR_SZ(pPager, 3))/JOURNAL_PG_SZ(3); + } + + /* Check if a master journal file is specified. If one is specified, + ** only proceed with the playback if it still exists. + */ + rc = read32bits(format, &pPager->jfd, &pPager->nMaster); + if( rc ) goto end_playback; + if( pPager->nMaster>0 ){ + zMaster = sqliteMalloc(pPager->nMaster); + if( !zMaster ){ + rc = SQLITE_NOMEM; + goto end_playback; + } + rc = sqlite3OsRead(&pPager->jfd, zMaster, pPager->nMaster); + if( rc!=SQLITE_OK || (strlen(zMaster) && !sqlite3OsFileExists(zMaster)) ){ + goto end_playback; + } } }else{ - nRec = (szJ - JOURNAL_HDR_SZ(2))/JOURNAL_PG_SZ(2); - assert( nRec*JOURNAL_PG_SZ(2)+JOURNAL_HDR_SZ(2)==szJ ); + nRec = (szJ - JOURNAL_HDR_SZ(pPager, 2))/JOURNAL_PG_SZ(2); + assert( nRec*JOURNAL_PG_SZ(2)+JOURNAL_HDR_SZ(pPager, 2)==szJ ); } rc = read32bits(format, &pPager->jfd, &mxPg); if( rc!=SQLITE_OK ){ @@ -772,7 +896,21 @@ static int pager_playback(Pager *pPager, int useJournalSize){ } end_playback: + if( zMaster ){ + /* If there was a master journal and this routine will return true, + ** see if it is possible to delete the master journal. If errors + ** occur during this process, ignore them. + */ + if( rc==SQLITE_OK ){ + pager_delmaster(zMaster); + } + sqliteFree(zMaster); + } if( rc!=SQLITE_OK ){ + /* FIX ME: We shouldn't delete the journal if an error occured during + ** rollback. It may have been a transient error and the rollback may + ** succeed next time it is attempted. + */ pager_unwritelock(pPager); pPager->errMask |= PAGER_ERR_CORRUPT; rc = SQLITE_CORRUPT; @@ -1064,7 +1202,7 @@ int sqlite3pager_pagecount(Pager *pPager){ /* ** Forward declaration */ -static int syncJournal(Pager*); +static int syncJournal(Pager*, const char*); /* @@ -1156,7 +1294,7 @@ int sqlite3pager_truncate(Pager *pPager, Pgno nPage){ memoryTruncate(pPager); return SQLITE_OK; } - syncJournal(pPager); + syncJournal(pPager, 0); rc = sqlite3OsTruncate(&pPager->fd, SQLITE_PAGE_SIZE*(off_t)nPage); if( rc==SQLITE_OK ){ pPager->dbSize = nPage; @@ -1302,14 +1440,14 @@ int sqlite3pager_ref(void *pData){ ** This routine clears the needSync field of every page current held in ** memory. */ -static int syncJournal(Pager *pPager){ +static int syncJournal(Pager *pPager, const char *zMaster){ PgHdr *pPg; int rc = SQLITE_OK; /* Sync the journal before modifying the main database ** (assuming there is a journal and it needs to be synced.) */ - if( pPager->needSync ){ + if( pPager->needSync || zMaster ){ if( !pPager->tempFile ){ assert( pPager->journalOpen ); /* assert( !pPager->noSync ); // noSync might be set if synchronous @@ -1320,7 +1458,7 @@ static int syncJournal(Pager *pPager){ ** with the nRec computed from the size of the journal file. */ off_t hdrSz, pgSz, jSz; - hdrSz = JOURNAL_HDR_SZ(journal_format); + hdrSz = JOURNAL_HDR_SZ(pPager, journal_format); pgSz = JOURNAL_PG_SZ(journal_format); rc = sqlite3OsFileSize(&pPager->jfd, &jSz); if( rc!=0 ) return rc; @@ -1338,7 +1476,17 @@ static int syncJournal(Pager *pPager){ sqlite3OsSeek(&pPager->jfd, sizeof(aJournalMagic1)); rc = write32bits(&pPager->jfd, pPager->nRec); if( rc ) return rc; - szJ = JOURNAL_HDR_SZ(journal_format) + + + /* Write the name of the master journal file if one is specified */ + if( zMaster ){ + assert( strlen(zMaster)<pPager->nMaster ); + rc = sqlite3OsSeek(&pPager->jfd, sizeof(aJournalMagic3) + 3*4); + if( rc ) return rc; + rc = sqlite3OsWrite(&pPager->jfd, zMaster, strlen(zMaster)+1); + if( rc ) return rc; + } + + szJ = JOURNAL_HDR_SZ(pPager, journal_format) + pPager->nRec*JOURNAL_PG_SZ(journal_format); sqlite3OsSeek(&pPager->jfd, szJ); } @@ -1451,7 +1599,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){ return pager_errcode(pPager); } - /* If this is the first page accessed, then get a read lock + /* If this is the first page accessed, then get a SHARED lock ** on the database file. */ if( pPager->nRef==0 && !pPager->memDb ){ @@ -1461,14 +1609,17 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){ } pPager->state = SQLITE_READLOCK; - /* If a journal file exists, try to play it back. + /* If a journal file exists, and there is no RESERVED lock on the + ** database file, then it either needs to be played back or deleted. */ - if( pPager->useJournal && sqlite3OsFileExists(pPager->zJournal) ){ + if( pPager->useJournal && + sqlite3OsFileExists(pPager->zJournal) && + !sqlite3OsCheckWriteLock(&pPager->fd) + ){ int rc; - /* Get a write lock on the database - */ - rc = sqlite3OsWriteLock(&pPager->fd); + /* Get an EXCLUSIVE lock on the database file. */ + rc = sqlite3OsLock(&pPager->fd, EXCLUSIVE_LOCK); if( rc!=SQLITE_OK ){ if( sqlite3OsUnlock(&pPager->fd)!=SQLITE_OK ){ /* This should never happen! */ @@ -1545,7 +1696,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){ ** it can't be helped. */ if( pPg==0 ){ - int rc = syncJournal(pPager); + int rc = syncJournal(pPager, 0); if( rc!=0 ){ sqlite3pager_rollback(pPager); return SQLITE_IOERR; @@ -1764,6 +1915,14 @@ static int pager_open_journal(Pager *pPager){ } pPager->origDbSize = pPager->dbSize; if( journal_format==JOURNAL_FORMAT_3 ){ + /* Create the header for a format 3 journal: + ** - 8 bytes: Magic identifying journal format 3. + ** - 4 bytes: Number of records in journal, or -1 no-sync mode is on. + ** - 4 bytes: Magic used for page checksums. + ** - 4 bytes: Number of bytes reserved for master journal ptr (nMaster) + ** - nMaster bytes: Space for a master journal pointer. + ** - 4 bytes: Initial database page count. + */ rc = sqlite3OsWrite(&pPager->jfd, aJournalMagic3, sizeof(aJournalMagic3)); if( rc==SQLITE_OK ){ rc = write32bits(&pPager->jfd, pPager->noSync ? 0xffffffff : 0); @@ -1772,6 +1931,22 @@ static int pager_open_journal(Pager *pPager){ sqlite3Randomness(sizeof(pPager->cksumInit), &pPager->cksumInit); rc = write32bits(&pPager->jfd, pPager->cksumInit); } + if( rc==SQLITE_OK ){ + rc = write32bits(&pPager->jfd, pPager->nMaster); + } + + /* Unless the size reserved for the master-journal pointer is 0, set + ** the first byte of the master journal pointer to 0x00. Either way, + ** this is interpreted as 'no master journal' in the event of a + ** rollback after a crash. + */ + if( rc==SQLITE_OK && pPager->nMaster>0 ){ + rc = sqlite3OsWrite(&pPager->jfd, "", 1); + } + if( rc==SQLITE_OK ){ + rc = sqlite3OsSeek(&pPager->jfd, + sizeof(aJournalMagic3) + 3*4 + pPager->nMaster); + } }else if( journal_format==JOURNAL_FORMAT_2 ){ rc = sqlite3OsWrite(&pPager->jfd, aJournalMagic2, sizeof(aJournalMagic2)); }else{ @@ -1802,22 +1977,26 @@ static int pager_open_journal(Pager *pPager){ ** * sqlite3pager_close() is called. ** * sqlite3pager_unref() is called to on every outstanding page. ** -** The parameter to this routine is a pointer to any open page of the -** database file. Nothing changes about the page - it is used merely -** to acquire a pointer to the Pager structure and as proof that there -** is already a read-lock on the database. +** The first parameter to this routine is a pointer to any open page of the +** database file. Nothing changes about the page - it is used merely to +** acquire a pointer to the Pager structure and as proof that there is +** already a read-lock on the database. ** -** A journal file is opened if this is not a temporary file. For -** temporary files, the opening of the journal file is deferred until -** there is an actual need to write to the journal. +** The second parameter indicates how much space in bytes to reserve for a +** master journal file-name at the start of the journal when it is created. +** +** A journal file is opened if this is not a temporary file. For temporary +** files, the opening of the journal file is deferred until there is an +** actual need to write to the journal. ** ** If the database is already write-locked, this routine is a no-op. */ -int sqlite3pager_begin(void *pData){ +int sqlite3pager_begin(void *pData, int nMaster){ PgHdr *pPg = DATA_TO_PGHDR(pData); Pager *pPager = pPg->pPager; int rc = SQLITE_OK; assert( pPg->nRef>0 ); + assert( nMaster>=0 ); assert( pPager->state!=SQLITE_UNLOCK ); if( pPager->state==SQLITE_READLOCK ){ assert( pPager->aInJournal==0 ); @@ -1829,6 +2008,7 @@ int sqlite3pager_begin(void *pData){ if( rc!=SQLITE_OK ){ return rc; } + pPager->nMaster = nMaster; pPager->state = SQLITE_WRITELOCK; pPager->dirtyFile = 0; TRACE1("TRANSACTION\n"); @@ -1888,7 +2068,7 @@ int sqlite3pager_write(void *pData){ ** create it if it does not. */ assert( pPager->state!=SQLITE_UNLOCK ); - rc = sqlite3pager_begin(pData); + rc = sqlite3pager_begin(pData, 0); if( rc!=SQLITE_OK ){ return rc; } @@ -1917,7 +2097,7 @@ int sqlite3pager_write(void *pData){ memcpy(pHist->pOrig, PGHDR_TO_DATA(pPg), pPager->pageSize); } pPg->inJournal = 1; - }else { + }else{ if( journal_format>=JOURNAL_FORMAT_3 ){ u32 cksum = pager_cksum(pPager, pPg->pgno, pData); saved = *(u32*)PGHDR_TO_EXTRA(pPg); @@ -2155,6 +2335,7 @@ int sqlite3pager_commit(Pager *pPager){ pPager->state = SQLITE_READLOCK; return SQLITE_OK; } +#if 0 if( pPager->dirtyFile==0 ){ /* Exit early (without doing the time-consuming sqlite3OsSync() calls) ** if there have been no changes to the database file. */ @@ -2164,7 +2345,7 @@ int sqlite3pager_commit(Pager *pPager){ return rc; } assert( pPager->journalOpen ); - rc = syncJournal(pPager); + rc = syncJournal(pPager, 0); if( rc!=SQLITE_OK ){ goto commit_abort; } @@ -2175,6 +2356,10 @@ int sqlite3pager_commit(Pager *pPager){ goto commit_abort; } } +#endif + rc = sqlite3pager_sync(pPager, 0); + if( rc!=SQLITE_OK ) goto commit_abort; + rc = pager_unwritelock(pPager); pPager->dbSize = -1; return rc; @@ -2310,10 +2495,11 @@ int sqlite3pager_stmt_begin(Pager *pPager){ rc = sqlite3OsFileSize(&pPager->jfd, &pPager->stmtJSize); if( rc ) goto stmt_begin_failed; assert( pPager->stmtJSize == - pPager->nRec*JOURNAL_PG_SZ(journal_format)+JOURNAL_HDR_SZ(journal_format) ); + pPager->nRec*JOURNAL_PG_SZ(journal_format) + + JOURNAL_HDR_SZ(pPager, journal_format) ); #endif pPager->stmtJSize = pPager->nRec*JOURNAL_PG_SZ(journal_format) - + JOURNAL_HDR_SZ(journal_format); + + JOURNAL_HDR_SZ(pPager, journal_format); pPager->stmtSize = pPager->dbSize; if( !pPager->stmtOpen ){ rc = sqlite3pager_opentemp(zTemp, &pPager->stfd); @@ -2414,6 +2600,49 @@ void sqlite3pager_set_codec( pPager->pCodecArg = pCodecArg; } +/* +** Sync the database file for the pager pPager. zMaster points to the name +** of a master journal file that should be written into the individual +** journal file. zMaster may be NULL, which is interpreted as no master +** journal (a single database transaction). +** +** This routine ensures that the journal is synced, all dirty pages written +** to the database file and the database file synced. The only thing that +** remains to commit the transaction is to delete the journal file (or +** master journal file if specified). +** +** Note that if zMaster==NULL, this does not overwrite a previous value +** passed to an sqlite3pager_sync() call. +*/ +int sqlite3pager_sync(Pager *pPager, const char *zMaster){ + int rc = SQLITE_OK; + + /* If this is an in-memory db, or no pages have been written to, this + ** function is a no-op. + */ + if( !pPager->memDb && pPager->dirtyFile ){ + PgHdr *pPg; + assert( pPager->journalOpen ); + + /* Sync the journal file */ + rc = syncJournal(pPager, zMaster); + if( rc!=SQLITE_OK ) goto sync_exit; + + /* Write all dirty pages to the database file */ + pPg = pager_get_all_dirty_pages(pPager); + rc = pager_write_pagelist(pPg); + if( rc!=SQLITE_OK ) goto sync_exit; + + /* If any pages were actually written, sync the database file */ + if( pPg && !pPager->noSync ){ + rc = sqlite3OsSync(&pPager->fd); + } + } + +sync_exit: + return rc; +} + #ifdef SQLITE_TEST /* ** Print a listing of all referenced pages and their ref count. diff --git a/src/pager.h b/src/pager.h index 1aa70c715..132989d76 100644 --- a/src/pager.h +++ b/src/pager.h @@ -13,7 +13,7 @@ ** subsystem. The page cache subsystem reads and writes a file a page ** at a time and provides a journal for rollback. ** -** @(#) $Id: pager.h,v 1.29 2004/05/14 01:58:13 drh Exp $ +** @(#) $Id: pager.h,v 1.30 2004/06/03 16:08:42 danielk1977 Exp $ */ /* @@ -84,8 +84,9 @@ int sqlite3pager_iswriteable(void*); int sqlite3pager_overwrite(Pager *pPager, Pgno pgno, void*); int sqlite3pager_pagecount(Pager*); int sqlite3pager_truncate(Pager*,Pgno); -int sqlite3pager_begin(void*); +int sqlite3pager_begin(void*,int); int sqlite3pager_commit(Pager*); +int sqlite3pager_sync(Pager*,const char *zMaster); int sqlite3pager_rollback(Pager*); int sqlite3pager_isreadonly(Pager*); int sqlite3pager_stmt_begin(Pager*); diff --git a/src/pragma.c b/src/pragma.c index d80005b41..8df5cd381 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -11,7 +11,7 @@ ************************************************************************* ** This file contains code used to implement the PRAGMA command. ** -** $Id: pragma.c,v 1.35 2004/05/31 08:26:49 danielk1977 Exp $ +** $Id: pragma.c,v 1.36 2004/06/03 16:08:42 danielk1977 Exp $ */ #include "sqliteInt.h" #include <ctype.h> @@ -632,7 +632,7 @@ void sqlite3Pragma(Parse *pParse, Token *pLeft, Token *pRight, int minusFlag){ loopTop = sqlite3VdbeAddOp(v, OP_Rewind, 1, 0); sqlite3VdbeAddOp(v, OP_MemIncr, 1, 0); for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ - int k, jmp2; + int jmp2; static VdbeOpList idxErr[] = { { OP_MemIncr, 0, 0, 0}, { OP_String8, 0, 0, "rowid "}, diff --git a/src/test3.c b/src/test3.c index da90cfaac..a5e5f92a2 100644 --- a/src/test3.c +++ b/src/test3.c @@ -13,7 +13,7 @@ ** is not included in the SQLite library. It is used for automated ** testing of the SQLite library. ** -** $Id: test3.c,v 1.41 2004/05/31 10:01:35 danielk1977 Exp $ +** $Id: test3.c,v 1.42 2004/06/03 16:08:42 danielk1977 Exp $ */ #include "sqliteInt.h" #include "pager.h" @@ -129,7 +129,7 @@ static int btree_begin_transaction( return TCL_ERROR; } if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR; - rc = sqlite3BtreeBeginTrans(pBt, 1); + rc = sqlite3BtreeBeginTrans(pBt, 1, 0); if( rc!=SQLITE_OK ){ Tcl_AppendResult(interp, errorName(rc), 0); return TCL_ERROR; diff --git a/src/vacuum.c b/src/vacuum.c index d0fca6ea4..3ebcd66f0 100644 --- a/src/vacuum.c +++ b/src/vacuum.c @@ -14,7 +14,7 @@ ** Most of the code in this file may be omitted by defining the ** SQLITE_OMIT_VACUUM macro. ** -** $Id: vacuum.c,v 1.20 2004/05/31 10:01:35 danielk1977 Exp $ +** $Id: vacuum.c,v 1.21 2004/06/03 16:08:42 danielk1977 Exp $ */ #include "sqliteInt.h" #include "os.h" @@ -196,7 +196,7 @@ int sqlite3RunVacuum(char **pzErrMsg, sqlite *db){ u32 meta; assert( 0==sqlite3BtreeIsInTrans(pMain) ); - rc = sqlite3BtreeBeginTrans(db->aDb[0].pBt, 1); + rc = sqlite3BtreeBeginTrans(db->aDb[0].pBt, 1, 0); if( rc!=SQLITE_OK ) goto end_of_vacuum; /* Copy Btree meta values 3 and 4. These correspond to SQL layer meta diff --git a/src/vdbe.c b/src/vdbe.c index bff8480e8..491f107b7 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -43,7 +43,7 @@ ** in this file for details. If in doubt, do not deviate from existing ** commenting and indentation practices when changing or adding code. ** -** $Id: vdbe.c,v 1.355 2004/06/02 01:22:02 drh Exp $ +** $Id: vdbe.c,v 1.356 2004/06/03 16:08:42 danielk1977 Exp $ */ #include "sqliteInt.h" #include "os.h" @@ -2289,7 +2289,8 @@ case OP_Transaction: { pBt = db->aDb[i].pBt; while( pBt && busy ){ - rc = sqlite3BtreeBeginTrans(db->aDb[i].pBt, pOp->p2); + int nMaster = strlen(sqlite3BtreeGetFilename(db->aDb[0].pBt))+11; + rc = sqlite3BtreeBeginTrans(db->aDb[i].pBt, pOp->p2, nMaster); switch( rc ){ case SQLITE_BUSY: { if( db->xBusyCallback==0 ){ @@ -2553,7 +2554,7 @@ case OP_OpenTemp: { rc = sqlite3BtreeFactory(db, 0, 1, TEMP_PAGES, &pCx->pBt); if( rc==SQLITE_OK ){ - rc = sqlite3BtreeBeginTrans(pCx->pBt, 1); + rc = sqlite3BtreeBeginTrans(pCx->pBt, 1, 0); } if( rc==SQLITE_OK ){ /* If a transient index is required, create it by calling diff --git a/src/vdbeInt.h b/src/vdbeInt.h index e52dc51e6..40c8fa8ac 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -24,10 +24,10 @@ #define intToKey(X) (X) /* -** The makefile scans this source file and creates the following -** array of string constants which are the names of all VDBE opcodes. -** This array is defined in a separate source code file named opcode.c -** which is automatically generated by the makefile. +** The makefile scans the vdbe.c source file and creates the following +** array of string constants which are the names of all VDBE opcodes. This +** array is defined in a separate source code file named opcode.c which is +** automatically generated by the makefile. */ extern char *sqlite3OpcodeNames[]; diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 0920ae28b..4c343946c 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -895,6 +895,150 @@ int sqlite3VdbeSetColName(Vdbe *p, int idx, const char *zName, int N){ return rc; } +/* +** A read or write transaction may or may not be active on database handle +** db. If a transaction is active, commit it. If there is a +** write-transaction spanning more than one database file, this routine +** takes care of the master journal trickery. +*/ +static int vdbeCommit(sqlite *db){ + int i; + int nTrans = 0; /* Number of databases with an active write-transaction */ + int rc = SQLITE_OK; + int needXcommit = 0; + + for(i=0; i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt && sqlite3BtreeIsInTrans(pBt) ){ + needXcommit = 1; + if( i!=1 ) nTrans++; + } + } + + /* If there are any write-transactions at all, invoke the commit hook */ + if( needXcommit && db->xCommitCallback ){ + if( db->xCommitCallback(db->pCommitArg) ){ + return SQLITE_CONSTRAINT; + } + } + + /* The simple case - if less than two databases have write-transactions + ** active, there is no need for the master-journal. + */ + if( nTrans<2 ){ + for(i=0; i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt ){ + int rc2 = sqlite3BtreeCommit(db->aDb[i].pBt); + if( rc==SQLITE_OK ) rc = rc2; + } + } + } + + /* The complex case - There is a multi-file write-transaction active. + ** This requires a master journal file to ensure the transaction is + ** committed atomicly. + */ + else{ + char *zMaster = 0; /* File-name for the master journal */ + char const *zMainFile = sqlite3BtreeGetFilename(db->aDb[0].pBt); + OsFile master; + + /* Select a master journal file name */ + do { + int random; + if( zMaster ){ + sqliteFree(zMaster); + } + sqlite3Randomness(sizeof(random), &random); + zMaster = sqlite3_mprintf("%s%d", zMainFile, random); + if( !zMaster ){ + return SQLITE_NOMEM; + } + }while( sqlite3OsFileExists(zMaster) ); + + /* Open the master journal. */ + rc = sqlite3OsOpenExclusive(zMaster, &master, 0); + if( rc!=SQLITE_OK ){ + sqliteFree(zMaster); + return rc; + } + + /* Write the name of each database file in the transaction into the new + ** master journal file. If an error occurs at this point close + ** and delete the master journal file. All the individual journal files + ** still have 'null' as the master journal pointer, so they will roll + ** back independantly if a failure occurs. + */ + for(i=0; i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt && sqlite3BtreeIsInTrans(pBt) ){ + char const *zFile = sqlite3BtreeGetFilename(pBt); + rc = sqlite3OsWrite(&master, zFile, strlen(zFile)); + if( rc!=SQLITE_OK ){ + sqlite3OsClose(&master); + sqlite3OsDelete(zMaster); + sqliteFree(zMaster); + return rc; + } + rc = sqlite3OsWrite(&master, "\0", 1); + if( rc!=SQLITE_OK ){ + sqlite3OsClose(&master); + sqlite3OsDelete(zMaster); + sqliteFree(zMaster); + return rc; + } + } + } + + /* Sync the master journal file */ + rc = sqlite3OsSync(&master); + sqlite3OsClose(&master); + + /* Sync all the db files involved in the transaction. The same call + ** sets the master journal pointer in each individual journal. If + ** an error occurs here, do not delete the master journal file. + ** + ** If the error occurs during the first call to sqlite3BtreeSync(), + ** then there is a chance that the master journal file will be + ** orphaned. But we cannot delete it, in case the master journal + ** file name was written into the journal file before the failure + ** occured. + */ + for(i=0; i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt && sqlite3BtreeIsInTrans(pBt) ){ + rc = sqlite3BtreeSync(pBt, zMaster); + if( rc!=SQLITE_OK ){ + sqliteFree(zMaster); + return rc; + } + } + } + sqliteFree(zMaster); + zMaster = 0; + + /* Delete the master journal file. This commits the transaction. */ + rc = sqlite3OsDelete(zMaster); + assert( rc==SQLITE_OK ); + + /* All files and directories have already been synced, so the following + ** calls to sqlite3BtreeCommit() are only closing files and deleting + ** journals. If something goes wrong while this is happening we don't + ** really care. The integrity of the transaction is already guarenteed, + ** but some stray 'cold' journals may be lying around. Returning an + ** error code won't help matters. + */ + for(i=0; i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt ){ + sqlite3BtreeCommit(pBt); + } + } + } + return SQLITE_OK; +} + /* ** This routine checks that the sqlite3.activeVdbeCnt count variable ** matches the number of vdbe's in the list sqlite3.pVdbe that are @@ -932,7 +1076,6 @@ int sqlite3VdbeReset(Vdbe *p, char **pzErrMsg){ sqlite *db = p->db; int i; int (*xFunc)(Btree *pBt) = 0; /* Function to call on each btree backend */ - int needXcommit = 0; if( p->magic!=VDBE_MAGIC_RUN && p->magic!=VDBE_MAGIC_HALT ){ sqlite3SetString(pzErrMsg, sqlite3ErrStr(SQLITE_MISUSE), (char*)0); @@ -956,14 +1099,24 @@ int sqlite3VdbeReset(Vdbe *p, char **pzErrMsg){ } Cleanup(p); - /* Figure out which function to call on the btree backends that - ** have active transactions. + /* What is done now depends on the exit status of the vdbe, the value of + ** the sqlite.autoCommit flag and whether or not there are any other + ** queries in progress. A transaction or statement transaction may need + ** to be committed or rolled back on each open database file. */ checkActiveVdbeCnt(db); if( db->autoCommit && db->activeVdbeCnt==1 ){ if( p->rc==SQLITE_OK || p->errorAction==OE_Fail ){ - xFunc = sqlite3BtreeCommit; - needXcommit = 1; + /* The auto-commit flag is true, there are no other active queries + ** using this handle and the vdbe program was successful or hit an + ** 'OR FAIL' constraint. This means a commit is required, which is + ** handled a little differently from the other options. + */ + p->rc = vdbeCommit(db); + if( p->rc!=SQLITE_OK ){ + sqlite3Error(p->db, p->rc, 0); + xFunc = sqlite3BtreeRollback; + } }else{ xFunc = sqlite3BtreeRollback; } @@ -978,19 +1131,14 @@ int sqlite3VdbeReset(Vdbe *p, char **pzErrMsg){ } } - for(i=0; xFunc && i<db->nDb; i++){ + /* If xFunc is not NULL, then it is one of sqlite3BtreeRollback, + ** sqlite3BtreeRollbackStmt or sqlite3BtreeCommitStmt. Call it once on + ** each backend. If an error occurs and the return code is still + ** SQLITE_OK, set the return code to the new error value. + */ + for(i=0; xFunc && i<db->nDb; i++){ int rc; Btree *pBt = db->aDb[i].pBt; - if( sqlite3BtreeIsInTrans(pBt) ){ - if( db->xCommitCallback && needXcommit ){ - if( db->xCommitCallback(db->pCommitArg)!=0 ){ - p->rc = SQLITE_CONSTRAINT; - sqlite3Error(db, SQLITE_CONSTRAINT, 0); - xFunc = sqlite3BtreeRollback; - } - needXcommit = 0; - } - } if( pBt ){ rc = xFunc(pBt); if( p->rc==SQLITE_OK ) p->rc = rc; |