From: dan Date: Wed, 19 Aug 2015 20:27:05 +0000 (+0000) Subject: When committing an unlocked transaction, relocate newly allocated database pages... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7b3d71e9cc82ae2b7aad42234875b53b935b21af;p=thirdparty%2Fsqlite.git When committing an unlocked transaction, relocate newly allocated database pages within the file to avoid conflicting with committed transactions. There are lots of things still to fix in this code. FossilOrigin-Name: 3bbc31d515ba9fc920c5cbc7059d3eb1ba9c7f8e --- diff --git a/manifest b/manifest index 7ca1c00b94..92f5317a3d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Handle\swrites\sto\sauto-vacuum\sdatabases\swithin\sUNLOCKED\stransactions\sin\sthe\ssame\sway\sas\sfor\snon-UNLOCKED\stransactions. -D 2015-08-15T18:16:46.463 +C When\scommitting\san\sunlocked\stransaction,\srelocate\snewly\sallocated\sdatabase\spages\swithin\sthe\sfile\sto\savoid\sconflicting\swith\scommitted\stransactions.\sThere\sare\slots\sof\sthings\sstill\sto\sfix\sin\sthis\scode. +D 2015-08-19T20:27:05.840 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 4de3ef40c8b3b75c0c55ff4242a43c8ce1ad90ee F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -270,9 +270,9 @@ F src/auth.c b56c78ebe40a2110fd361379f7e8162d23f92240 F src/backup.c 4d9134dc988a87838c06056c89c0e8c4700a0452 F src/bitvec.c d1f21d7d91690747881f03940584f4cc548c9d3d F src/btmutex.c 45a968cc85afed9b5e6cf55bf1f42f8d18107f79 -F src/btree.c c1bbc83539de1e749aa36ce9c283f95b50543bcb -F src/btree.h 40bd41ef0b71d6f7502725dc159fa0d6bebd8bb3 -F src/btreeInt.h 2ad754dd4528baa8d0946a593cc373b890bf859e +F src/btree.c a00a7c4809d642bf6c63979e73face9fed53dc66 +F src/btree.h 00d4cdb747c4172a5566faf037116985dbbc377e +F src/btreeInt.h 9f77bac260d81cc74ecd96cccf73a8c5af1688c4 F src/build.c 28c15c43eefc0066ff64040526ff649c32fe5523 F src/callback.c 7b44ce59674338ad48b0e84e7b72f935ea4f68b0 F src/complete.c addcd8160b081131005d5bc2d34adf20c1c5c92f @@ -315,8 +315,8 @@ F src/os_setup.h c9d4553b5aaa6f73391448b265b89bed0b890faa F src/os_unix.c 388c023582b17890f10c980b30ec1922b471753b F src/os_win.c 40b3af7a47eb1107d0d69e592bec345a3b7b798a F src/os_win.h eb7a47aa17b26b77eb97e4823f20a00b8bda12ca -F src/pager.c 18aa67541bd2c7d7963f180669ea4e48cd5246f4 -F src/pager.h 41938212a5d80a7068883bb29ea6ac0d4ef3a6d8 +F src/pager.c fe82e368600278cfd35024c20b888e945236f675 +F src/pager.h 3fc23bca2e7d606ca4fbdd923713cdcdcec48297 F src/parse.y 199145cd982587d70fa3db2f9b11f7ab118f0acd F src/pcache.c cde06aa50962595e412d497e22fd2e07878ba1f0 F src/pcache.h 9968603796240cdf83da7e7bef76edf90619cea9 @@ -395,15 +395,15 @@ F src/vdbe.c fd6832408d9621ad347a65b01a05380fe766232f F src/vdbe.h 7a75045d879118b9d3af7e8b3c108f2f27c51473 F src/vdbeInt.h 8b54e01ad0463590e7cffabce0bc36da9ee4f816 F src/vdbeapi.c adabbd66eb2e3a10f3998485ee0be7e326d06ee4 -F src/vdbeaux.c 24a03ccbadc8d4cfecf25b6399db95851b10dd21 +F src/vdbeaux.c 27feae106f3f4db298e65d37a9da4962aaff724b F src/vdbeblob.c 4f2e8e075d238392df98c5e03a64342465b03f90 F src/vdbemem.c ae38a0d35ae71cf604381a887c170466ba518090 F src/vdbesort.c f5009e7a35e3065635d8918b9a31f498a499976b F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0 F src/vtab.c 082b35a25a26e3d36f365ca8cd73c1922532f05e F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb -F src/wal.c 4497b466fee8ed56c31672bb35741dd0321ec999 -F src/wal.h cd7ea52439d56571d549b7d4eacdeed58d841385 +F src/wal.c 8bb1130dc1876e1c1c3fa082a5b0ec6437feb8e8 +F src/wal.h 5aaed8ca6cad1406088042ff8767951a6a3c7b56 F src/walker.c c253b95b4ee44b21c406e2a1052636c31ea27804 F src/where.c 909eba3b6db984eb2adfbca9de2c237ee7056adb F src/whereInt.h 5f87e3c4b0551747d119730dfebddd3c54f04047 @@ -1215,7 +1215,8 @@ F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a F test/unique.test 93f8b2ef5ea51b9495f8d6493429b1fd0f465264 F test/unique2.test 41e7f83c6827605991160a31380148a9fc5f1339 F test/unixexcl.test cd6c765f75e50e8e2c2ba763149e5d340ea19825 -F test/unlocked.test 644a84d7f032ca029d490f0b0bc8fb9f86c7a1a5 +F test/unlocked.test d143a871bd874311d4e5c98ce458218ca6b579fd +F test/unlocked2.test fc4c730a44c3650250467ecacca6ddb969f64405 F test/unordered.test ca7adce0419e4ca0c50f039885e76ed2c531eda8 F test/update.test 6c68446b8a0a33d522a7c72b320934596a2d7d32 F test/uri.test 23662b7b61958b0f0e47082de7d06341ccf85d5b @@ -1367,7 +1368,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 0b9718426e44df092850c5d095ce1b84a1e567cf -R ac1d3857424ab06ed70d671704c3737d +P de1ea450db33b140b11af5b801ea6a15875e774e +R db05422c8fb37e882a7e6cc4bd7c8ffb U dan -Z 12da9addf6141c212c3c7cdffe33d767 +Z cccfd407eb244cf1cd3b5cab72e65fda diff --git a/manifest.uuid b/manifest.uuid index 6a4d41072d..7d70f3c411 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -de1ea450db33b140b11af5b801ea6a15875e774e \ No newline at end of file +3bbc31d515ba9fc920c5cbc7059d3eb1ba9c7f8e \ No newline at end of file diff --git a/src/btree.c b/src/btree.c index 7f9054f309..cc97fd8623 100644 --- a/src/btree.c +++ b/src/btree.c @@ -439,6 +439,152 @@ static void downgradeAllSharedCacheTableLocks(Btree *p){ #endif /* SQLITE_OMIT_SHARED_CACHE */ +/* +** The following structure stores the in-memory pointer map used for newly +** allocated pages in UNLOCKED transactions. Such pages are always allocated +** in a contiguous block (from the end of the file) starting with page +** BtreePtrmap.iFirst. +** +** The page number for the parent page iFirst is stored in aPtr[0]. For +** (iFirst+1), aPtr[1]. A zero value indicates that the page has not +** been allocated. +** +** +*/ + +typedef struct RollbackEntry RollbackEntry; +typedef struct PtrmapEntry PtrmapEntry; +struct PtrmapEntry { + Pgno parent; + u8 eType; +}; +struct RollbackEntry { + Pgno pgno; + Pgno parent; + u8 eType; +}; + +struct BtreePtrmap { + Pgno iFirst; /* First new page number aPtr[0] */ + + int nPtrAlloc; /* Allocated size of aPtr[] array */ + PtrmapEntry *aPtr; /* Array of parent page numbers */ + + int nSvpt; /* Used size of aSvpt[] array */ + int nSvptAlloc; /* Allocated size of aSvpt[] */ + int *aSvpt; /* First aRollback[] entry for savepoint i */ + + int nRollback; /* Used size of aRollback[] array */ + int nRollbackAlloc; /* Allocated size of aRollback[] array */ + RollbackEntry *aRollback; /* Array of rollback entries */ +}; + +static int btreePtrmapStore( + BtreePtrmap *pMap, + Pgno pgno, + u8 eType, + Pgno parent +){ + if( pgno>=pMap->iFirst ){ + int iEntry = pgno - pMap->iFirst; + + /* Grow the aPtr[] array if required */ + if( iEntry>=pMap->nPtrAlloc ){ + int nNew = pMap->nPtrAlloc ? pMap->nPtrAlloc*2 : 16; + PtrmapEntry *aNew = (PtrmapEntry*)sqlite3_realloc( + pMap->aPtr, nNew*sizeof(PtrmapEntry) + ); + if( aNew==0 ){ + return SQLITE_NOMEM; + }else{ + int nByte = (nNew-pMap->nPtrAlloc)*sizeof(PtrmapEntry); + memset(&aNew[pMap->nPtrAlloc], 0, nByte); + pMap->aPtr = aNew; + pMap->nPtrAlloc = nNew; + } + } + + /* Add an entry to the rollback log if required */ + if( pMap->nSvpt>0 && pMap->aPtr[iEntry].parent ){ + if( pMap->nRollback>=pMap->nRollbackAlloc ){ + int nNew = pMap->nRollback ? pMap->nRollback*2 : 16; + RollbackEntry *aNew = (RollbackEntry*)sqlite3_realloc( + pMap->aRollback, nNew*sizeof(RollbackEntry) + ); + if( aNew==0 ){ + return SQLITE_NOMEM; + }else{ + pMap->aRollback = aNew; + pMap->nRollbackAlloc = nNew; + } + } + + pMap->aRollback[pMap->nRollback].pgno = pgno; + pMap->aRollback[pMap->nRollback].parent = pMap->aPtr[iEntry].parent; + pMap->aRollback[pMap->nRollback].eType = pMap->aPtr[iEntry].eType; + } + + /* Update the aPtr[] array */ + pMap->aPtr[iEntry].parent = parent; + pMap->aPtr[iEntry].eType = eType; + } + + return SQLITE_OK; +} + +/* +** Open savepoint iSavepoint, if it is not already open. +*/ +static int btreePtrmapBegin(BtreePtrmap *pMap, int nSvpt){ + if( nSvptnSvpt ){ + int i; + if( nSvpt>=pMap->nSvptAlloc ){ + int nNew = pMap->nSvptAlloc ? pMap->nSvptAlloc*2 : 16; + int *aNew = sqlite3_realloc(pMap->aSvpt, sizeof(int) * nNew); + if( aNew==0 ){ + return SQLITE_NOMEM; + }else{ + pMap->aSvpt = aNew; + pMap->nSvptAlloc = nNew; + } + } + + for(i=pMap->nSvpt; iaSvpt[i] = pMap->nRollback; + } + pMap->nSvpt = nSvpt; + } + + return SQLITE_OK; +} + +/* +** Rollback (if op==SAVEPOINT_ROLLBACK) or release (if op==SAVEPOINT_RELEASE) +** savepoint iSvpt. +*/ +static void btreePtrmapEnd(BtreePtrmap *pMap, int op, int iSvpt){ + assert( op==SAVEPOINT_ROLLBACK || op==SAVEPOINT_RELEASE ); + assert( iSvpt>=0 || (iSvpt==-1 && op==SAVEPOINT_ROLLBACK) ); + if( iSvpt<0 ){ + pMap->nSvpt = 0; + pMap->nRollback = 0; + memset(pMap->aPtr, 0, sizeof(Pgno) * pMap->nPtrAlloc); + }else if( iSvptnSvpt ){ + if( op==SAVEPOINT_ROLLBACK ){ + int ii; + for(ii=pMap->nRollback-1; ii>=pMap->aSvpt[iSvpt]; ii--){ + RollbackEntry *p = &pMap->aRollback[ii]; + PtrmapEntry *pEntry = &pMap->aPtr[p->pgno - pMap->iFirst]; + pEntry->parent = p->parent; + pEntry->eType = p->eType; + } + } + pMap->nSvpt = iSvpt + (op==SAVEPOINT_ROLLBACK); + pMap->nRollback = pMap->aSvpt[iSvpt]; + } +} + + static void releasePage(MemPage *pPage); /* Forward reference */ /* @@ -878,42 +1024,44 @@ static void ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent, int *pRC){ int offset; /* Offset in pointer map page */ int rc; /* Return code from subfunctions */ + assert( sqlite3_mutex_held(pBt->mutex) ); if( *pRC ) return; - assert( sqlite3_mutex_held(pBt->mutex) ); - /* The master-journal page number must never be used as a pointer map page */ - assert( 0==PTRMAP_ISPAGE(pBt, PENDING_BYTE_PAGE(pBt)) ); + if( pBt->pMap ){ + *pRC = btreePtrmapStore(pBt->pMap, key, eType, parent); + }else{ + /* The master-journal page number must never be used as a ptr map page */ + assert( 0==PTRMAP_ISPAGE(pBt, PENDING_BYTE_PAGE(pBt)) ); - assert( pBt->autoVacuum ); - if( key==0 ){ - *pRC = SQLITE_CORRUPT_BKPT; - return; - } - iPtrmap = PTRMAP_PAGENO(pBt, key); - rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage); - if( rc!=SQLITE_OK ){ - *pRC = rc; - return; - } - offset = PTRMAP_PTROFFSET(iPtrmap, key); - if( offset<0 ){ - *pRC = SQLITE_CORRUPT_BKPT; - goto ptrmap_exit; - } - assert( offset <= (int)pBt->usableSize-5 ); - pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage); + assert( pBt->autoVacuum ); + if( key==0 ){ + *pRC = SQLITE_CORRUPT_BKPT; + return; + } + iPtrmap = PTRMAP_PAGENO(pBt, key); + rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage); + if( rc!=SQLITE_OK ){ + *pRC = rc; + return; + } + offset = PTRMAP_PTROFFSET(iPtrmap, key); + if( offset<0 ){ + *pRC = SQLITE_CORRUPT_BKPT; + }else{ + assert( offset <= (int)pBt->usableSize-5 ); + pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage); - if( eType!=pPtrmap[offset] || get4byte(&pPtrmap[offset+1])!=parent ){ - TRACE(("PTRMAP_UPDATE: %d->(%d,%d)\n", key, eType, parent)); - *pRC= rc = sqlite3PagerWrite(pDbPage); - if( rc==SQLITE_OK ){ - pPtrmap[offset] = eType; - put4byte(&pPtrmap[offset+1], parent); + if( eType!=pPtrmap[offset] || get4byte(&pPtrmap[offset+1])!=parent ){ + TRACE(("PTRMAP_UPDATE: %d->(%d,%d)\n", key, eType, parent)); + *pRC= rc = sqlite3PagerWrite(pDbPage); + if( rc==SQLITE_OK ){ + pPtrmap[offset] = eType; + put4byte(&pPtrmap[offset+1], parent); + } + } } + sqlite3PagerUnref(pDbPage); } - -ptrmap_exit: - sqlite3PagerUnref(pDbPage); } /* @@ -1924,6 +2072,50 @@ u32 sqlite3BtreeLastPage(Btree *p){ return btreePagecount(p->pBt); } +#ifdef SQLITE_ENABLE_UNLOCKED +/* +** This function is called before allocating or freeing a b-tree page. If +** the current transaction is UNLOCKED, it allocates the BtreePtrmap +** structure and zeroes the nFree/iTrunk fields in the database header +** on page 1. +*/ +static int allocatePtrmap(BtShared *pBt){ + int rc = SQLITE_OK; + if( pBt->pMap==0 && sqlite3PagerIsUnlocked(pBt->pPager) ){ + /* If this is an unlocked transaction, set the header values + ** identifying the size of the free-list and the page number + ** of the first trunk page to zero. */ + BtreePtrmap *pMap = sqlite3_malloc(sizeof(BtreePtrmap)); + if( pMap==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(&pBt->pPage1->aData[32], 0, sizeof(u32)*2); + memset(pMap, 0, sizeof(BtreePtrmap)); + pMap->iFirst = btreePagecount(pBt) + 1; + pBt->pMap = pMap; + } + } + return rc; +} + +/* +** Free a pointer-map allocated by allocatePtrmap. +*/ +static void deletePtrmap(BtShared *pBt){ + BtreePtrmap *pMap = pBt->pMap; + if( pMap ){ + sqlite3_free(pMap->aRollback); + sqlite3_free(pMap->aPtr); + sqlite3_free(pMap->aSvpt); + sqlite3_free(pMap); + pBt->pMap = 0; + } +} +#else +#define allocatePtrmap(x) SQLITE_OK +#define deletePtrmap(x) +#endif + /* ** Get a page from the pager and initialize it. ** @@ -3149,6 +3341,9 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag){ if( rc==SQLITE_OK ){ rc = newDatabase(pBt); } + if( rc==SQLITE_OK ){ + rc = allocatePtrmap(pBt); + } } } @@ -3205,7 +3400,11 @@ trans_begun: ** open savepoints. If the second parameter is greater than 0 and ** the sub-journal is not already open, then it will be opened here. */ - rc = sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint); + int nSavepoint = p->db->nSavepoint; + rc = sqlite3PagerOpenSavepoint(pBt->pPager, nSavepoint); + if( pBt->pMap && rc==SQLITE_OK && nSavepoint ){ + rc = btreePtrmapBegin(pBt->pMap, nSavepoint); + } } btreeIntegrity(p); @@ -3642,6 +3841,92 @@ static int autoVacuumCommit(BtShared *pBt){ # define setChildPtrmaps(x) SQLITE_OK #endif +/* +** The b-tree handle passed as the only argument is about to commit an +** UNLOCKED transaction. At this point it is guaranteed that this is +** possible - the wal WRITER lock is held and it is known that there are +** no conflicts with committed transactions. +*/ +static int btreeFixUnlocked(Btree *p){ + BtShared *pBt = p->pBt; + MemPage *pPage1 = pBt->pPage1; + u8 *p1 = pPage1->aData; + Pager *pPager = pBt->pPager; + int rc = SQLITE_OK; + + /* If page 1 of the database is not writable, then no pages were allocated + ** or freed by this transaction. In this case no special handling is + ** required. Otherwise, if page 1 is dirty, proceed. */ + BtreePtrmap *pMap = pBt->pMap; + Pgno iTrunk = get4byte(&p1[32]); + Pgno nPage = btreePagecount(pBt); + Pgno nOrig = pMap->iFirst-1; + u32 nFree = get4byte(&p1[36]); + + assert( sqlite3PagerIsUnlocked(pPager) ); + assert( pBt->pMap ); + rc = sqlite3PagerUpgradeSnapshot(pPager, pPage1->pDbPage); + assert( p1==pPage1->aData ); + + if( rc==SQLITE_OK ){ + Pgno nHPage = get4byte(&p1[28]); + Pgno nFinal = nHPage; + + if( sqlite3PagerIswriteable(pPage1->pDbPage) ){ + Pgno iHTrunk = get4byte(&p1[32]); + u32 nHFree = get4byte(&p1[36]); + + /* Attach the head database free list to the end of the current + ** transactions free-list (if any). */ + if( iTrunk!=0 ){ + put4byte(&p1[36], nHFree + nFree); + put4byte(&p1[32], iTrunk); + while( iTrunk ){ + DbPage *pTrunk = sqlite3PagerLookup(pPager, iTrunk); + iTrunk = get4byte((u8*)pTrunk->pData); + if( iTrunk==0 ){ + put4byte((u8*)pTrunk->pData, iHTrunk); + } + sqlite3PagerUnref(pTrunk); + }; + } + + if( nHPageiFirst through + ** nPage (inclusive) at the end of the database file. Meanwhile, + ** other transactions have allocated (iFirst..nHPage). So move + ** pages (iFirst..MIN(nPage,nHPage)) to (MAX(nPage,nHPage)+1). + */ + Pgno iLast = MIN(nPage, nHPage); /* Last page to move */ + Pgno iPg; + + nFinal = MAX(nPage, nHPage); /* Final size of database */ + for(iPg=pMap->iFirst; iPg<=iLast && rc==SQLITE_OK; iPg++){ + MemPage *pPg = 0; + Pgno iNew; /* New page number for pPg */ + PtrmapEntry *pEntry; /* Pointer map entry for page iPg */ + + btreeGetPage(pBt, iPg, &pPg, 0); + assert( sqlite3PagerIswriteable(pPg->pDbPage) ); + assert( sqlite3PagerPageRefcount(pPg->pDbPage)==1 ); + pEntry = &pMap->aPtr[iPg - pMap->iFirst]; + iNew = ++nFinal; + + rc = relocatePage(pBt, pPg, pEntry->eType, pEntry->parent, iNew, 1); + releasePageNotNull(pPg); + } + + put4byte(&p1[28], nFinal); + } + } + sqlite3PagerSetDbsize(pPager, nFinal); + } + + return rc; +} + /* ** This routine does the first phase of a two-phase commit. This routine ** causes a rollback journal to be created (if it does not already exist) @@ -3678,10 +3963,8 @@ int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){ /* Figure out if this is a commit of an UNLOCKED transaction that ** requires a snapshot upgrade. If so, skip any auto-vacuum ** processing. */ - if( pBt->autoVacuum && ( - 0==pBt->db->bUnlocked - || 0==sqlite3PagerCommitRequiresUpgrade(pBt->pPager) - )){ + if( pBt->autoVacuum ){ + assert( sqlite3PagerIsUnlocked(pBt->pPager)==0 ); rc = autoVacuumCommit(pBt); if( rc!=SQLITE_OK ){ sqlite3BtreeLeave(p); @@ -3692,6 +3975,9 @@ int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){ sqlite3PagerTruncateImage(pBt->pPager, pBt->nPage); } #endif + if( rc==SQLITE_OK && sqlite3PagerIsUnlocked(pBt->pPager) ){ + rc = btreeFixUnlocked(p); + } if( rc==SQLITE_OK ){ rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zMaster, 0); } @@ -3737,6 +4023,8 @@ static void btreeEndTransaction(Btree *p){ unlockBtreeIfUnused(pBt); } + /* If this was an UNLOCKED transaction, delete the pBt->pMap object */ + deletePtrmap(pBt); btreeIntegrity(p); } @@ -3962,6 +4250,9 @@ int sqlite3BtreeBeginStmt(Btree *p, int iStatement){ ** such savepoints while the statement transaction savepoint is active. */ rc = sqlite3PagerOpenSavepoint(pBt->pPager, iStatement); + if( rc==SQLITE_OK && pBt->pMap ){ + rc = btreePtrmapBegin(pBt->pMap, iStatement); + } sqlite3BtreeLeave(p); return rc; } @@ -3985,6 +4276,7 @@ int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){ assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK ); assert( iSavepoint>=0 || (iSavepoint==-1 && op==SAVEPOINT_ROLLBACK) ); sqlite3BtreeEnter(p); + if( pBt->pMap ) btreePtrmapEnd(pBt->pMap, op, iSavepoint); rc = sqlite3PagerSavepoint(pBt->pPager, op, iSavepoint); if( rc==SQLITE_OK ){ if( iSavepoint<0 && (pBt->btsFlags & BTS_INITIALLY_EMPTY)!=0 ){ @@ -5493,6 +5785,16 @@ static int allocateBtreePage( if( n>=mxPage ){ return SQLITE_CORRUPT_BKPT; } + + /* Ensure page 1 is writable. This function will either change the number + ** of pages in the free-list or the size of the database file. Since both + ** of these operations involve modifying page 1 header fields, page 1 + ** will definitely be written by this transaction. If this is an UNLOCKED + ** transaction, ensure the BtreePtrmap structure has been allocated. */ + assert( eMode!=BTALLOC_EXACT || sqlite3PagerIsUnlocked(pBt->pPager)==0 ); + rc = sqlite3PagerWrite(pPage1->pDbPage); + if( rc ) return rc; + if( n>0 ){ /* There are pages on the freelist. Reuse one of those pages. */ Pgno iTrunk; @@ -5523,8 +5825,6 @@ static int allocateBtreePage( /* Decrement the free-list count by 1. Set iTrunk to the index of the ** first free-list trunk page. iPrevTrunk is initially 1. */ - rc = sqlite3PagerWrite(pPage1->pDbPage); - if( rc ) return rc; put4byte(&pPage1->aData[36], n-1); /* The code within this loop is run only once if the 'searchList' variable @@ -5830,7 +6130,7 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ /* If the database supports auto-vacuum, write an entry in the pointer-map ** to indicate that the page is free. */ - if( ISAUTOVACUUM ){ + if( REQUIRE_PTRMAP ){ ptrmapPut(pBt, iPage, PTRMAP_FREEPAGE, 0, &rc); if( rc ) goto freepage_out; } @@ -6116,7 +6416,7 @@ static int fillInCell( } #endif rc = allocateBtreePage(pBt, &pOvfl, &pgnoOvfl, pgnoOvfl, 0); -#ifndef SQLITE_OMIT_AUTOVACUUM + /* If the database supports auto-vacuum, and the second or subsequent ** overflow page is being allocated, add an entry to the pointer-map ** for that page now. @@ -6127,14 +6427,13 @@ static int fillInCell( ** may misinterpret the uninitialized values and delete the ** wrong pages from the database. */ - if( pBt->autoVacuum && rc==SQLITE_OK ){ + if( REQUIRE_PTRMAP && rc==SQLITE_OK ){ u8 eType = (pgnoPtrmap?PTRMAP_OVERFLOW2:PTRMAP_OVERFLOW1); ptrmapPut(pBt, pgnoOvfl, eType, pgnoPtrmap, &rc); if( rc ){ releasePage(pOvfl); } } -#endif if( rc ){ releasePage(pToRelease); return rc; @@ -6301,6 +6600,7 @@ static void insertCell( assert( j==0 || pPage->aiOvfl[j-1]<(u16)i ); /* Overflows in sorted order */ assert( j==0 || i==pPage->aiOvfl[j-1]+1 ); /* Overflows are sequential */ }else{ + BtShared *pBt = pPage->pBt; int rc = sqlite3PagerWrite(pPage->pDbPage); if( rc!=SQLITE_OK ){ *pRC = rc; @@ -6315,7 +6615,7 @@ static void insertCell( ** if it returns successfully */ assert( idx >= 0 ); assert( idx >= pPage->cellOffset+2*pPage->nCell+2 || CORRUPT_DB ); - assert( idx+sz <= (int)pPage->pBt->usableSize ); + assert( idx+sz <= (int)pBt->usableSize ); pPage->nFree -= (u16)(2 + sz); memcpy(&data[idx], pCell, sz); if( iChild ){ @@ -6328,14 +6628,12 @@ static void insertCell( /* increment the cell count */ if( (++data[pPage->hdrOffset+4])==0 ) data[pPage->hdrOffset+3]++; assert( get2byte(&data[pPage->hdrOffset+3])==pPage->nCell ); -#ifndef SQLITE_OMIT_AUTOVACUUM - if( pPage->pBt->autoVacuum ){ + if( REQUIRE_PTRMAP ){ /* The cell may contain a pointer to an overflow page. If so, write ** the entry for the overflow page into the pointer map. */ ptrmapPutOvflPtr(pPage, pCell, pRC); } -#endif } } @@ -6740,7 +7038,7 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ ** be marked as dirty. Returning an error code will cause a ** rollback, undoing any changes made to the parent page. */ - if( ISAUTOVACUUM ){ + if( REQUIRE_PTRMAP ){ ptrmapPut(pBt, pgnoNew, PTRMAP_BTREE, pParent->pgno, &rc); if( szCell>pNew->minLocal ){ ptrmapPutOvflPtr(pNew, pCell, &rc); @@ -6875,7 +7173,7 @@ static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){ /* If this is an auto-vacuum database, update the pointer-map entries ** for any b-tree or overflow pages that pTo now contains the pointers to. */ - if( ISAUTOVACUUM ){ + if( REQUIRE_PTRMAP ){ *pRC = setChildPtrmaps(pTo); } } @@ -7345,7 +7643,7 @@ static int balance_nonroot( cntOld[i] = b.nCell; /* Set the pointer-map entry for the new sibling page. */ - if( ISAUTOVACUUM ){ + if( REQUIRE_PTRMAP ){ ptrmapPut(pBt, pNew->pgno, PTRMAP_BTREE, pParent->pgno, &rc); if( rc!=SQLITE_OK ){ goto balance_cleanup; @@ -7439,7 +7737,7 @@ static int balance_nonroot( ** updated. This happens below, after the sibling pages have been ** populated, not here. */ - if( ISAUTOVACUUM ){ + if( REQUIRE_PTRMAP ){ MemPage *pNew = apNew[0]; u8 *aOld = pNew->aData; int cntOldNext = pNew->nCell + pNew->nOverflow; @@ -7624,7 +7922,7 @@ static int balance_nonroot( ); copyNodeContent(apNew[0], pParent, &rc); freePage(apNew[0], &rc); - }else if( ISAUTOVACUUM && !leafCorrection ){ + }else if( REQUIRE_PTRMAP && !leafCorrection ){ /* Fix the pointer map entries associated with the right-child of each ** sibling page. All other pointer map entries have already been taken ** care of. */ @@ -7710,7 +8008,7 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){ if( rc==SQLITE_OK ){ rc = allocateBtreePage(pBt,&pChild,&pgnoChild,pRoot->pgno,0); copyNodeContent(pRoot, pChild, &rc); - if( ISAUTOVACUUM ){ + if( REQUIRE_PTRMAP ){ ptrmapPut(pBt, pgnoChild, PTRMAP_BTREE, pRoot->pgno, &rc); } } @@ -9590,3 +9888,13 @@ int sqlite3BtreeIsReadonly(Btree *p){ ** Return the size of the header added to each page by this module. */ int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); } + +int sqlite3BtreeExclusiveLock(Btree *p){ + int rc; + BtShared *pBt = p->pBt; + assert( p->inTrans==TRANS_WRITE && pBt->pPage1 ); + sqlite3BtreeEnter(p); + rc = sqlite3PagerExclusiveLock(pBt->pPager, pBt->pPage1->pDbPage); + sqlite3BtreeLeave(p); + return rc; +} diff --git a/src/btree.h b/src/btree.h index 50dc8ca290..8f611b7a9b 100644 --- a/src/btree.h +++ b/src/btree.h @@ -215,6 +215,8 @@ int sqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask); int sqlite3BtreeIsReadonly(Btree *pBt); int sqlite3HeaderSizeBtree(void); +int sqlite3BtreeExclusiveLock(Btree *pBt); + #ifndef NDEBUG int sqlite3BtreeCursorIsValid(BtCursor*); #endif diff --git a/src/btreeInt.h b/src/btreeInt.h index cbf6c99847..67b1da6e3f 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -232,6 +232,7 @@ typedef struct MemPage MemPage; typedef struct BtLock BtLock; typedef struct CellInfo CellInfo; +typedef struct BtreePtrmap BtreePtrmap; /* ** This is a magic string that appears at the beginning of every @@ -447,6 +448,9 @@ struct BtShared { Btree *pWriter; /* Btree with currently open write transaction */ #endif u8 *pTmpSpace; /* Temp space sufficient to hold a single cell */ +#ifdef SQLITE_ENABLE_UNLOCKED + BtreePtrmap *pMap; +#endif }; /* @@ -659,6 +663,12 @@ struct BtCursor { #define ISAUTOVACUUM 0 #endif +#ifdef SQLITE_ENABLE_UNLOCKED +# define REQUIRE_PTRMAP (ISAUTOVACUUM || pBt->pMap) +#else +# define REQUIRE_PTRMAP ISAUTOVACUUM +#endif + /* ** This structure is passed around through all the sanity checking routines diff --git a/src/pager.c b/src/pager.c index 5107edda36..efbea0ab8e 100644 --- a/src/pager.c +++ b/src/pager.c @@ -657,7 +657,9 @@ struct Pager { u32 cksumInit; /* Quasi-random value added to every checksum */ u32 nSubRec; /* Number of records written to sub-journal */ Bitvec *pInJournal; /* One bit for each page in the database file */ +#ifdef SQLITE_ENABLE_UNLOCKED Bitvec *pAllRead; /* Pages read within current UNLOCKED trans. */ +#endif sqlite3_file *fd; /* File descriptor for database */ sqlite3_file *jfd; /* File descriptor for main journal */ sqlite3_file *sjfd; /* File descriptor for sub-journal */ @@ -4091,7 +4093,7 @@ static int syncJournal(Pager *pPager, int newHdr){ assert( assert_pager_state(pPager) ); assert( !pagerUseWal(pPager) ); - rc = sqlite3PagerExclusiveLock(pPager); + rc = sqlite3PagerExclusiveLock(pPager, 0); if( rc!=SQLITE_OK ) return rc; if( !pPager->noSync ){ @@ -5929,11 +5931,9 @@ int sqlite3PagerWrite(PgHdr *pPg){ ** to sqlite3PagerWrite(). In other words, return TRUE if it is ok ** to change the content of the page. */ -#ifndef NDEBUG int sqlite3PagerIswriteable(DbPage *pPg){ return pPg->flags & PGHDR_WRITEABLE; } -#endif /* ** A call to this routine tells the pager that it is not necessary to @@ -6091,7 +6091,7 @@ int sqlite3PagerSync(Pager *pPager, const char *zMaster){ ** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is ** returned. */ -int sqlite3PagerExclusiveLock(Pager *pPager){ +int sqlite3PagerExclusiveLock(Pager *pPager, PgHdr *pPage1){ int rc = SQLITE_OK; assert( pPager->eState==PAGER_WRITER_CACHEMOD || pPager->eState==PAGER_WRITER_DBMOD @@ -6108,7 +6108,7 @@ int sqlite3PagerExclusiveLock(Pager *pPager){ ** non-zero. */ do { /* rc = sqlite3WalBeginWriteTransaction(pWal); */ - rc = sqlite3WalLockForCommit(pPager->pWal, pPager->pAllRead); + rc = sqlite3WalLockForCommit(pPager->pWal, pPage1, pPager->pAllRead); }while( rc==SQLITE_BUSY && pPager->xBusyHandler(pPager->pBusyHandlerArg) ); @@ -6117,6 +6117,24 @@ int sqlite3PagerExclusiveLock(Pager *pPager){ return rc; } +int sqlite3PagerUpgradeSnapshot(Pager *pPager, DbPage *pPage1){ + int rc; + u32 iFrame = 0; + + assert( pPager->pWal && pPager->pAllRead ); + sqlite3WalUpgradeSnapshot(pPager->pWal); + rc = sqlite3WalFindFrame(pPager->pWal, 1, &iFrame); + if( rc==SQLITE_OK ){ + rc = readDbPage(pPage1, iFrame); + } + + return rc; +} + +void sqlite3PagerSetDbsize(Pager *pPager, Pgno nFinal){ + pPager->dbSize = nFinal; +} + /* ** If this is a WAL mode connection and the WRITER lock is currently held, ** relinquish it. @@ -6131,14 +6149,11 @@ void sqlite3PagerDropExclusiveLock(Pager *pPager){ ** Return true if this is a WAL database and snapshot upgrade is required ** before the current transaction can be committed. */ -int sqlite3PagerCommitRequiresUpgrade(Pager *pPager){ - int res = 0; - if( pagerUseWal(pPager) ){ - res = sqlite3WalCommitRequiresUpgrade(pPager->pWal); - } - return res; +int sqlite3PagerIsUnlocked(Pager *pPager){ + return pPager->pAllRead!=0; } + /* ** 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 diff --git a/src/pager.h b/src/pager.h index 3c0b9a1a71..92e5546282 100644 --- a/src/pager.h +++ b/src/pager.h @@ -145,12 +145,13 @@ int sqlite3PagerMovepage(Pager*,DbPage*,Pgno,int); int sqlite3PagerPageRefcount(DbPage*); void *sqlite3PagerGetData(DbPage *); void *sqlite3PagerGetExtra(DbPage *); +int sqlite3PagerIsDirty(DbPage*); /* Functions used to manage pager transactions and savepoints. */ void sqlite3PagerPagecount(Pager*, int*); int sqlite3PagerBegin(Pager*, int exFlag, int); int sqlite3PagerCommitPhaseOne(Pager*,const char *zMaster, int); -int sqlite3PagerExclusiveLock(Pager*); +int sqlite3PagerExclusiveLock(Pager*, DbPage *pPage1); int sqlite3PagerSync(Pager *pPager, const char *zMaster); int sqlite3PagerCommitPhaseTwo(Pager*); int sqlite3PagerRollback(Pager*); @@ -159,7 +160,7 @@ int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint); int sqlite3PagerSharedLock(Pager *pPager); void sqlite3PagerDropExclusiveLock(Pager*); -int sqlite3PagerCommitRequiresUpgrade(Pager*); +int sqlite3PagerIsUnlocked(Pager*); #ifndef SQLITE_OMIT_WAL int sqlite3PagerCheckpoint(Pager *pPager, int, int*, int*); @@ -196,6 +197,10 @@ void sqlite3PagerTruncateImage(Pager*,Pgno); void sqlite3PagerRekey(DbPage*, Pgno, u16); +int sqlite3PagerIswriteable(DbPage*); +int sqlite3PagerUpgradeSnapshot(Pager *pPager, DbPage*); +void sqlite3PagerSetDbsize(Pager *pPager, Pgno); + #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL) void *sqlite3PagerCodec(DbPage *); #endif @@ -203,7 +208,6 @@ void *sqlite3PagerCodec(DbPage *); /* Functions to support testing and debugging. */ #if !defined(NDEBUG) || defined(SQLITE_TEST) Pgno sqlite3PagerPagenumber(DbPage*); - int sqlite3PagerIswriteable(DbPage*); #endif #ifdef SQLITE_TEST int *sqlite3PagerStats(Pager*); diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 93a7187ab9..f5f9d0fbdd 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -2020,9 +2020,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ if( sqlite3BtreeIsInTrans(pBt) ){ needXcommit = 1; if( i!=1 ) nTrans++; - sqlite3BtreeEnter(pBt); - rc = sqlite3PagerExclusiveLock(sqlite3BtreePager(pBt)); - sqlite3BtreeLeave(pBt); + rc = sqlite3BtreeExclusiveLock(pBt); } } diff --git a/src/wal.c b/src/wal.c index 5fbdae6a1a..809ce2b109 100644 --- a/src/wal.c +++ b/src/wal.c @@ -2573,7 +2573,8 @@ int sqlite3WalBeginWriteTransaction(Wal *pWal){ ** transaction. It may be assumed that no frames have been written to ** the wal file. */ -int sqlite3WalLockForCommit(Wal *pWal, Bitvec *pAllRead){ +int sqlite3WalLockForCommit(Wal *pWal, PgHdr *pPage1, Bitvec *pAllRead){ + Pager *pPager = pPage1->pPager; int rc = walWriteLock(pWal); /* If the database has been modified since this transaction was started, @@ -2589,6 +2590,9 @@ int sqlite3WalLockForCommit(Wal *pWal, Bitvec *pAllRead){ if( rc==SQLITE_OK ){ volatile WalIndexHdr *pHead; /* Head of the wal file */ pHead = walIndexHdr(pWal); + + /* TODO: Check header checksum is good here. */ + if( memcmp(&pWal->hdr, (void*)pHead, sizeof(WalIndexHdr))!=0 ){ /* TODO: Is this safe? Because it holds the WRITER lock this thread ** has exclusive access to the live header, but might it be corrupt? @@ -2609,12 +2613,33 @@ int sqlite3WalLockForCommit(Wal *pWal, Bitvec *pAllRead){ if( iMin<1 ) iMin = 1; if( iMax>pHead->mxFrame ) iMax = pHead->mxFrame; for(i=iMin; i<=iMax; i++){ - if( sqlite3BitvecTestNotNull(pAllRead, aPgno[i]) ){ + PgHdr *pPg; + if( aPgno[i]==1 ){ + /* Check that the schema cookie has not been modified. If + ** it has not, the commit can proceed. */ + u8 aNew[4]; + u8 *aOld = &((u8*)pPage1->pData)[40]; + int sz; + i64 iOffset; + sz = pWal->hdr.szPage; + sz = (sz&0xfe00) + ((sz&0x0001)<<16); + iOffset = walFrameOffset(i+iZero, sz) + WAL_FRAME_HDRSIZE + 40; + rc = sqlite3OsRead(pWal->pWalFd, aNew, sizeof(aNew), iOffset); + if( rc==SQLITE_OK && memcmp(aOld, aNew, sizeof(aNew)) ){ + rc = SQLITE_BUSY_SNAPSHOT; + } + }else if( sqlite3BitvecTestNotNull(pAllRead, aPgno[i]) ){ sqlite3_log(SQLITE_OK, "cannot commit UNLOCKED transaction (conflict at page %d)", (int)aPgno[i] ); rc = SQLITE_BUSY_SNAPSHOT; + }else if( (pPg = sqlite3PagerLookup(pPager, aPgno[i])) ){ + if( sqlite3PagerIswriteable(pPg)==0 ){ + sqlite3PcacheDrop(pPg); + }else{ + sqlite3PagerUnref(pPg); + } } } } @@ -2626,6 +2651,11 @@ int sqlite3WalLockForCommit(Wal *pWal, Bitvec *pAllRead){ return rc; } +void sqlite3WalUpgradeSnapshot(Wal *pWal){ + assert( pWal->writeLock ); + memcpy(&pWal->hdr, (void*)walIndexHdr(pWal), sizeof(WalIndexHdr)); +} + /* ** This function is only ever called while committing an UNLOCKED ** transaction, after the caller has already obtained the WRITER lock @@ -2890,7 +2920,6 @@ int sqlite3WalFrames( int szFrame; /* The size of a single frame */ i64 iOffset; /* Next byte to write in WAL file */ WalWriter w; /* The writer */ - int bUpgrade = 0; /* True if commit requires snapshot upgrade */ assert( pList ); assert( pWal->writeLock ); @@ -2906,22 +2935,6 @@ int sqlite3WalFrames( } #endif - if( isCommit ){ - volatile WalIndexHdr *pHead = walIndexHdr(pWal); - if( pHead->mxFrame>pWal->hdr.mxFrame ){ - if( memcmp((void*)&pHead[0], (void*)&pHead[1], sizeof(WalIndexHdr))!=0 ){ - /* TODO: Deal with this case. It's quite possible, but fiddly. */ - return SQLITE_CORRUPT_BKPT; - } - memcpy(&pWal->hdr, (void*)pHead, sizeof(WalIndexHdr)); - if( nTruncatehdr.nPage ){ - /* Do not truncate the database file in this case */ - nTruncate = pWal->hdr.nPage; - } - bUpgrade = 1; - } - } - /* See if it is possible to write these frames into the start of the ** log file, instead of appending to it at pWal->hdr.mxFrame. */ @@ -3070,14 +3083,6 @@ int sqlite3WalFrames( } } - if( rc==SQLITE_OK && bUpgrade ){ - /* If this commit required a snapshot upgrade, the pager cache is - ** not currently consistent with the head of the wal file. Zeroing - ** Wal.hdr here forces the next transaction to reset the cache - ** before beginning to read the db. */ - memset(&pWal->hdr, 0, sizeof(WalIndexHdr)); - } - WALTRACE(("WAL%p: frame write %s\n", pWal, rc ? "failed" : "ok")); return rc; } diff --git a/src/wal.h b/src/wal.h index fe85a52cc0..5b4ca15943 100644 --- a/src/wal.h +++ b/src/wal.h @@ -127,8 +127,9 @@ int sqlite3WalExclusiveMode(Wal *pWal, int op); int sqlite3WalHeapMemory(Wal *pWal); /* Return true if the WRITER lock is held. False otherwise. */ -int sqlite3WalLockForCommit(Wal *pWal, Bitvec *pRead); +int sqlite3WalLockForCommit(Wal *pWal, PgHdr *pPg, Bitvec *pRead); int sqlite3WalCommitRequiresUpgrade(Wal *pWal); +void sqlite3WalUpgradeSnapshot(Wal *pWal); #ifdef SQLITE_ENABLE_ZIPVFS /* If the WAL file is not empty, return the number of bytes of content diff --git a/test/unlocked.test b/test/unlocked.test index 23eb5a5064..c743cc6328 100644 --- a/test/unlocked.test +++ b/test/unlocked.test @@ -399,10 +399,9 @@ do_multiclient_test tn { do_test 2.$tn.7.3 { list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db] - } {1 {database is locked} SQLITE_BUSY_SNAPSHOT} - sql1 ROLLBACK - + } {0 {} SQLITE_OK} + do_test 2.$tn.7.4 { sql3 { PRAGMA integrity_check } } ok } diff --git a/test/unlocked2.test b/test/unlocked2.test new file mode 100644 index 0000000000..f808a84e7b --- /dev/null +++ b/test/unlocked2.test @@ -0,0 +1,101 @@ +# 2015 July 26 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +set ::testprefix unlocked2 + + +do_multiclient_test tn { + + do_test 1.$tn.1 { + sql1 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x); + CREATE TABLE t2(y); + } + } {wal} + + # Test that an UNLOCKED transaction that allocates/frees no pages does + # not conflict with a transaction that does allocate pages. + do_test 1.$tn.2 { + sql1 { + BEGIN UNLOCKED; + INSERT INTO t1 VALUES(4); + } + sql2 { + INSERT INTO t2 VALUES(randomblob(1500)); + } + sql1 { + COMMIT; + } + } {} + + # But that an UNLOCKED transaction does conflict with a transaction + # that modifies the db schema. + do_test 1.$tn.3 { + sql1 { + BEGIN UNLOCKED; + INSERT INTO t1 VALUES(5); + } + sql2 { + CREATE TABLE t3(z); + } + list [catch { sql1 COMMIT } msg] $msg + } {1 {database is locked}} + + # Test that an UNLOCKED transaction that allocates at least one page + # does not conflict with a transaction that allocates no pages. + do_test 1.$tn.4 { + sql1 { + ROLLBACK; + BEGIN UNLOCKED; + INSERT INTO t1 VALUES(randomblob(1500)); + } + sql2 { + INSERT INTO t2 VALUES(8); + } + sql1 { + COMMIT; + } + } {} + + do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok} +} + +do_multiclient_test tn { + do_test 2.$tn.1 { + sql1 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x UNIQUE); + CREATE TABLE t2(y UNIQUE); + } + } {wal} + + do_test 2.$tn.2 { + sql1 { + BEGIN UNLOCKED; + INSERT INTO t1 VALUES(randomblob(1500)); + } + sql2 { + INSERT INTO t2 VALUES(randomblob(1500)); + } + sql1 { + COMMIT; + } + } {} + + do_test 2.$tn.3 { sql3 { PRAGMA integrity_check } } {ok} +} + +finish_test