Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | 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. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | begin-concurrent |
Files: | files | file ages | folders |
SHA1: |
3bbc31d515ba9fc920c5cbc7059d3eb1 |
User & Date: | dan 2015-08-19 20:27:05.840 |
Context
2015-08-20
| ||
20:25 | Fix a problem causing corruption when an UNLOCKED transaction is rolled back. (check-in: 7c36147846 user: dan tags: begin-concurrent) | |
2015-08-19
| ||
20:27 | 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. (check-in: 3bbc31d515 user: dan tags: begin-concurrent) | |
2015-08-15
| ||
18:16 | Handle writes to auto-vacuum databases within UNLOCKED transactions in the same way as for non-UNLOCKED transactions. (check-in: de1ea450db user: dan tags: begin-concurrent) | |
Changes
Changes to src/btree.c.
︙ | ︙ | |||
434 435 436 437 438 439 440 441 442 443 444 445 446 447 | assert( pLock->eLock==READ_LOCK || pLock->pBtree==p ); pLock->eLock = READ_LOCK; } } } #endif /* SQLITE_OMIT_SHARED_CACHE */ static void releasePage(MemPage *pPage); /* Forward reference */ /* ***** This routine is used inside of assert() only **** ** ** Verify that the cursor holds the mutex on its BtShared | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 | assert( pLock->eLock==READ_LOCK || pLock->pBtree==p ); pLock->eLock = READ_LOCK; } } } #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( nSvpt<pMap->nSvpt ){ 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; i<nSvpt; i++){ pMap->aSvpt[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( iSvpt<pMap->nSvpt ){ 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 */ /* ***** This routine is used inside of assert() only **** ** ** Verify that the cursor holds the mutex on its BtShared |
︙ | ︙ | |||
874 875 876 877 878 879 880 881 882 | static void ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent, int *pRC){ DbPage *pDbPage; /* The pointer map page */ u8 *pPtrmap; /* The pointer map data */ Pgno iPtrmap; /* The pointer map page number */ int offset; /* Offset in pointer map page */ int rc; /* Return code from subfunctions */ if( *pRC ) return; | > | > > | | | | | | | | | | | | | | | | < | | | | | | | | | | | | < | > | 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 | static void ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent, int *pRC){ DbPage *pDbPage; /* The pointer map page */ u8 *pPtrmap; /* The pointer map data */ Pgno iPtrmap; /* The pointer map page number */ int offset; /* Offset in pointer map page */ int rc; /* Return code from subfunctions */ assert( sqlite3_mutex_held(pBt->mutex) ); if( *pRC ) return; 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; }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); } } } sqlite3PagerUnref(pDbPage); } } /* ** Read an entry from the pointer map. ** ** This routine retrieves the pointer map entry for page 'key', writing ** the type and parent page number to *pEType and *pPgno respectively. |
︙ | ︙ | |||
1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 | return pBt->nPage; } u32 sqlite3BtreeLastPage(Btree *p){ assert( sqlite3BtreeHoldsMutex(p) ); assert( ((p->pBt->nPage)&0x8000000)==0 ); return btreePagecount(p->pBt); } /* ** Get a page from the pager and initialize it. ** ** If pCur!=0 then the page is being fetched as part of a moveToChild() ** call. Do additional sanity checking on the page in this case. ** And if the fetch fails, this routine must decrement pCur->iPage. | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 | return pBt->nPage; } u32 sqlite3BtreeLastPage(Btree *p){ assert( sqlite3BtreeHoldsMutex(p) ); assert( ((p->pBt->nPage)&0x8000000)==0 ); 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. ** ** If pCur!=0 then the page is being fetched as part of a moveToChild() ** call. Do additional sanity checking on the page in this case. ** And if the fetch fails, this routine must decrement pCur->iPage. |
︙ | ︙ | |||
3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 | int exFlag = (p->db->bUnlocked && !ISAUTOVACUUM) ? -1 : (wrflag>1); int bSubjInMem = sqlite3TempInMemory(p->db); assert( p->db->bUnlocked==0 || wrflag==1 ); rc = sqlite3PagerBegin(pBt->pPager, exFlag, bSubjInMem); if( rc==SQLITE_OK ){ rc = newDatabase(pBt); } } } if( rc!=SQLITE_OK ){ unlockBtreeIfUnused(pBt); } }while( (rc&0xFF)==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE && | > > > | 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 | int exFlag = (p->db->bUnlocked && !ISAUTOVACUUM) ? -1 : (wrflag>1); int bSubjInMem = sqlite3TempInMemory(p->db); assert( p->db->bUnlocked==0 || wrflag==1 ); rc = sqlite3PagerBegin(pBt->pPager, exFlag, bSubjInMem); if( rc==SQLITE_OK ){ rc = newDatabase(pBt); } if( rc==SQLITE_OK ){ rc = allocatePtrmap(pBt); } } } if( rc!=SQLITE_OK ){ unlockBtreeIfUnused(pBt); } }while( (rc&0xFF)==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE && |
︙ | ︙ | |||
3201 3202 3203 3204 3205 3206 3207 | trans_begun: if( rc==SQLITE_OK && wrflag ){ /* This call makes sure that the pager has the correct number of ** open savepoints. If the second parameter is greater than 0 and ** the sub-journal is not already open, then it will be opened here. */ | > | > > > | 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 | trans_begun: if( rc==SQLITE_OK && wrflag ){ /* This call makes sure that the pager has the correct number of ** open savepoints. If the second parameter is greater than 0 and ** the sub-journal is not already open, then it will be opened here. */ int nSavepoint = p->db->nSavepoint; rc = sqlite3PagerOpenSavepoint(pBt->pPager, nSavepoint); if( pBt->pMap && rc==SQLITE_OK && nSavepoint ){ rc = btreePtrmapBegin(pBt->pMap, nSavepoint); } } btreeIntegrity(p); sqlite3BtreeLeave(p); return rc; } |
︙ | ︙ | |||
3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 | assert( nRef>=sqlite3PagerRefcount(pPager) ); return rc; } #else /* ifndef SQLITE_OMIT_AUTOVACUUM */ # define setChildPtrmaps(x) SQLITE_OK #endif /* ** 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) ** and populated with enough information so that if a power loss occurs ** the database can be restored to its original state by playing back ** the journal. Then the contents of the journal are flushed out to | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 | assert( nRef>=sqlite3PagerRefcount(pPager) ); return rc; } #else /* ifndef SQLITE_OMIT_AUTOVACUUM */ # 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( nHPage<nOrig ){ rc = SQLITE_CORRUPT_BKPT; }else{ /* The current transaction allocated pages pMap->iFirst 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) ** and populated with enough information so that if a power loss occurs ** the database can be restored to its original state by playing back ** the journal. Then the contents of the journal are flushed out to |
︙ | ︙ | |||
3674 3675 3676 3677 3678 3679 3680 | BtShared *pBt = p->pBt; sqlite3BtreeEnter(p); #ifndef SQLITE_OMIT_AUTOVACUUM /* Figure out if this is a commit of an UNLOCKED transaction that ** requires a snapshot upgrade. If so, skip any auto-vacuum ** processing. */ | | < | < > > > | 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 | BtShared *pBt = p->pBt; sqlite3BtreeEnter(p); #ifndef SQLITE_OMIT_AUTOVACUUM /* 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 ){ assert( sqlite3PagerIsUnlocked(pBt->pPager)==0 ); rc = autoVacuumCommit(pBt); if( rc!=SQLITE_OK ){ sqlite3BtreeLeave(p); return rc; } } if( pBt->bDoTruncate ){ 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); } sqlite3BtreeLeave(p); } return rc; } |
︙ | ︙ | |||
3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 | /* Set the current transaction state to TRANS_NONE and unlock the ** pager if this call closed the only read or write transaction. */ p->inTrans = TRANS_NONE; unlockBtreeIfUnused(pBt); } btreeIntegrity(p); } /* ** Commit the transaction currently in progress. ** ** This routine implements the second phase of a 2-phase commit. The | > > | 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 | /* Set the current transaction state to TRANS_NONE and unlock the ** pager if this call closed the only read or write transaction. */ p->inTrans = TRANS_NONE; unlockBtreeIfUnused(pBt); } /* If this was an UNLOCKED transaction, delete the pBt->pMap object */ deletePtrmap(pBt); btreeIntegrity(p); } /* ** Commit the transaction currently in progress. ** ** This routine implements the second phase of a 2-phase commit. The |
︙ | ︙ | |||
3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 | assert( pBt->inTransaction==TRANS_WRITE ); /* At the pager level, a statement transaction is a savepoint with ** an index greater than all savepoints created explicitly using ** SQL statements. It is illegal to open, release or rollback any ** such savepoints while the statement transaction savepoint is active. */ rc = sqlite3PagerOpenSavepoint(pBt->pPager, iStatement); sqlite3BtreeLeave(p); return rc; } /* ** The second argument to this function, op, is always SAVEPOINT_ROLLBACK ** or SAVEPOINT_RELEASE. This function either releases or rolls back the | > > > | 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 | assert( pBt->inTransaction==TRANS_WRITE ); /* At the pager level, a statement transaction is a savepoint with ** an index greater than all savepoints created explicitly using ** SQL statements. It is illegal to open, release or rollback any ** 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; } /* ** The second argument to this function, op, is always SAVEPOINT_ROLLBACK ** or SAVEPOINT_RELEASE. This function either releases or rolls back the |
︙ | ︙ | |||
3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 | int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){ int rc = SQLITE_OK; if( p && p->inTrans==TRANS_WRITE ){ BtShared *pBt = p->pBt; assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK ); assert( iSavepoint>=0 || (iSavepoint==-1 && op==SAVEPOINT_ROLLBACK) ); sqlite3BtreeEnter(p); rc = sqlite3PagerSavepoint(pBt->pPager, op, iSavepoint); if( rc==SQLITE_OK ){ if( iSavepoint<0 && (pBt->btsFlags & BTS_INITIALLY_EMPTY)!=0 ){ pBt->nPage = 0; } rc = newDatabase(pBt); pBt->nPage = get4byte(28 + pBt->pPage1->aData); | > | 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 | int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){ int rc = SQLITE_OK; if( p && p->inTrans==TRANS_WRITE ){ BtShared *pBt = p->pBt; 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 ){ pBt->nPage = 0; } rc = newDatabase(pBt); pBt->nPage = get4byte(28 + pBt->pPage1->aData); |
︙ | ︙ | |||
5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 | /* EVIDENCE-OF: R-05119-02637 The 4-byte big-endian integer at offset 36 ** stores stores the total number of pages on the freelist. */ n = get4byte(&pPage1->aData[36]); testcase( n==mxPage-1 ); if( n>=mxPage ){ return SQLITE_CORRUPT_BKPT; } if( n>0 ){ /* There are pages on the freelist. Reuse one of those pages. */ Pgno iTrunk; u8 searchList = 0; /* If the free-list must be searched for 'nearby' */ u32 nSearch = 0; /* Count of the number of search attempts */ /* If eMode==BTALLOC_EXACT and a query of the pointer-map | > > > > > > > > > > | 5781 5782 5783 5784 5785 5786 5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 | /* EVIDENCE-OF: R-05119-02637 The 4-byte big-endian integer at offset 36 ** stores stores the total number of pages on the freelist. */ n = get4byte(&pPage1->aData[36]); testcase( n==mxPage-1 ); 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; u8 searchList = 0; /* If the free-list must be searched for 'nearby' */ u32 nSearch = 0; /* Count of the number of search attempts */ /* If eMode==BTALLOC_EXACT and a query of the pointer-map |
︙ | ︙ | |||
5519 5520 5521 5522 5523 5524 5525 | searchList = 1; } #endif /* Decrement the free-list count by 1. Set iTrunk to the index of the ** first free-list trunk page. iPrevTrunk is initially 1. */ | < < | 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 | searchList = 1; } #endif /* Decrement the free-list count by 1. Set iTrunk to the index of the ** first free-list trunk page. iPrevTrunk is initially 1. */ put4byte(&pPage1->aData[36], n-1); /* The code within this loop is run only once if the 'searchList' variable ** is not true. Otherwise, it runs once for each trunk-page on the ** free-list until the page 'nearby' is located (eMode==BTALLOC_EXACT) ** or until a page less than 'nearby' is located (eMode==BTALLOC_LT) */ |
︙ | ︙ | |||
5826 5827 5828 5829 5830 5831 5832 | } memset(pPage->aData, 0, pPage->pBt->pageSize); } /* If the database supports auto-vacuum, write an entry in the pointer-map ** to indicate that the page is free. */ | | | 6126 6127 6128 6129 6130 6131 6132 6133 6134 6135 6136 6137 6138 6139 6140 | } memset(pPage->aData, 0, pPage->pBt->pageSize); } /* If the database supports auto-vacuum, write an entry in the pointer-map ** to indicate that the page is free. */ if( REQUIRE_PTRMAP ){ ptrmapPut(pBt, iPage, PTRMAP_FREEPAGE, 0, &rc); if( rc ) goto freepage_out; } /* Now manipulate the actual database free-list structure. There are two ** possibilities. If the free-list is currently empty, or if the first ** trunk page in the free-list is full, then this page will become a |
︙ | ︙ | |||
6112 6113 6114 6115 6116 6117 6118 | pgnoOvfl++; } while( PTRMAP_ISPAGE(pBt, pgnoOvfl) || pgnoOvfl==PENDING_BYTE_PAGE(pBt) ); } #endif rc = allocateBtreePage(pBt, &pOvfl, &pgnoOvfl, pgnoOvfl, 0); | | | < | 6412 6413 6414 6415 6416 6417 6418 6419 6420 6421 6422 6423 6424 6425 6426 6427 6428 6429 6430 6431 6432 6433 6434 6435 6436 6437 6438 6439 6440 6441 6442 6443 | pgnoOvfl++; } while( PTRMAP_ISPAGE(pBt, pgnoOvfl) || pgnoOvfl==PENDING_BYTE_PAGE(pBt) ); } #endif rc = allocateBtreePage(pBt, &pOvfl, &pgnoOvfl, pgnoOvfl, 0); /* 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. ** ** If this is the first overflow page, then write a partial entry ** to the pointer-map. If we write nothing to this pointer-map slot, ** then the optimistic overflow chain processing in clearCell() ** may misinterpret the uninitialized values and delete the ** wrong pages from the database. */ if( REQUIRE_PTRMAP && rc==SQLITE_OK ){ u8 eType = (pgnoPtrmap?PTRMAP_OVERFLOW2:PTRMAP_OVERFLOW1); ptrmapPut(pBt, pgnoOvfl, eType, pgnoPtrmap, &rc); if( rc ){ releasePage(pOvfl); } } if( rc ){ releasePage(pToRelease); return rc; } /* If pToRelease is not zero than pPrior points into the data area ** of pToRelease. Make sure pToRelease is still writeable. */ |
︙ | ︙ | |||
6297 6298 6299 6300 6301 6302 6303 6304 6305 6306 6307 6308 6309 6310 6311 6312 6313 6314 6315 6316 6317 | ** sorted order. This invariants arise because multiple overflows can ** only occur when inserting divider cells into the parent page during ** balancing, and the dividers are adjacent and sorted. */ 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{ int rc = sqlite3PagerWrite(pPage->pDbPage); if( rc!=SQLITE_OK ){ *pRC = rc; return; } assert( sqlite3PagerIswriteable(pPage->pDbPage) ); data = pPage->aData; assert( &data[pPage->cellOffset]==pPage->aCellIdx ); rc = allocateSpace(pPage, sz, &idx); if( rc ){ *pRC = rc; return; } /* The allocateSpace() routine guarantees the following properties ** if it returns successfully */ assert( idx >= 0 ); assert( idx >= pPage->cellOffset+2*pPage->nCell+2 || CORRUPT_DB ); | > | < | < | 6596 6597 6598 6599 6600 6601 6602 6603 6604 6605 6606 6607 6608 6609 6610 6611 6612 6613 6614 6615 6616 6617 6618 6619 6620 6621 6622 6623 6624 6625 6626 6627 6628 6629 6630 6631 6632 6633 6634 6635 6636 6637 6638 6639 6640 6641 6642 6643 | ** sorted order. This invariants arise because multiple overflows can ** only occur when inserting divider cells into the parent page during ** balancing, and the dividers are adjacent and sorted. */ 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; return; } assert( sqlite3PagerIswriteable(pPage->pDbPage) ); data = pPage->aData; assert( &data[pPage->cellOffset]==pPage->aCellIdx ); rc = allocateSpace(pPage, sz, &idx); if( rc ){ *pRC = rc; return; } /* The allocateSpace() routine guarantees the following properties ** if it returns successfully */ assert( idx >= 0 ); assert( idx >= pPage->cellOffset+2*pPage->nCell+2 || CORRUPT_DB ); assert( idx+sz <= (int)pBt->usableSize ); pPage->nFree -= (u16)(2 + sz); memcpy(&data[idx], pCell, sz); if( iChild ){ put4byte(&data[idx], iChild); } pIns = pPage->aCellIdx + i*2; memmove(pIns+2, pIns, 2*(pPage->nCell - i)); put2byte(pIns, idx); pPage->nCell++; /* increment the cell count */ if( (++data[pPage->hdrOffset+4])==0 ) data[pPage->hdrOffset+3]++; assert( get2byte(&data[pPage->hdrOffset+3])==pPage->nCell ); 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); } } } /* ** A CellArray object contains a cache of pointers and sizes for a ** consecutive sequence of cells that might be held multiple pages. */ |
︙ | ︙ | |||
6736 6737 6738 6739 6740 6741 6742 | ** cell on the page to an overflow page. If either of these ** operations fails, the return code is set, but the contents ** of the parent page are still manipulated by thh code below. ** That is Ok, at this point the parent page is guaranteed to ** be marked as dirty. Returning an error code will cause a ** rollback, undoing any changes made to the parent page. */ | | | 7034 7035 7036 7037 7038 7039 7040 7041 7042 7043 7044 7045 7046 7047 7048 | ** cell on the page to an overflow page. If either of these ** operations fails, the return code is set, but the contents ** of the parent page are still manipulated by thh code below. ** That is Ok, at this point the parent page is guaranteed to ** be marked as dirty. Returning an error code will cause a ** rollback, undoing any changes made to the parent page. */ if( REQUIRE_PTRMAP ){ ptrmapPut(pBt, pgnoNew, PTRMAP_BTREE, pParent->pgno, &rc); if( szCell>pNew->minLocal ){ ptrmapPutOvflPtr(pNew, pCell, &rc); } } /* Create a divider cell to insert into pParent. The divider cell |
︙ | ︙ | |||
6871 6872 6873 6874 6875 6876 6877 | *pRC = rc; return; } /* 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. */ | | | 7169 7170 7171 7172 7173 7174 7175 7176 7177 7178 7179 7180 7181 7182 7183 | *pRC = rc; return; } /* 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( REQUIRE_PTRMAP ){ *pRC = setChildPtrmaps(pTo); } } } /* ** This routine redistributes cells on the iParentIdx'th child of pParent |
︙ | ︙ | |||
7341 7342 7343 7344 7345 7346 7347 | if( rc ) goto balance_cleanup; zeroPage(pNew, pageFlags); apNew[i] = pNew; nNew++; cntOld[i] = b.nCell; /* Set the pointer-map entry for the new sibling page. */ | | | 7639 7640 7641 7642 7643 7644 7645 7646 7647 7648 7649 7650 7651 7652 7653 | if( rc ) goto balance_cleanup; zeroPage(pNew, pageFlags); apNew[i] = pNew; nNew++; cntOld[i] = b.nCell; /* Set the pointer-map entry for the new sibling page. */ if( REQUIRE_PTRMAP ){ ptrmapPut(pBt, pNew->pgno, PTRMAP_BTREE, pParent->pgno, &rc); if( rc!=SQLITE_OK ){ goto balance_cleanup; } } } } |
︙ | ︙ | |||
7435 7436 7437 7438 7439 7440 7441 | ** with the cell. ** ** If the sibling pages are not leaves, then the pointer map entry ** associated with the right-child of each sibling may also need to be ** updated. This happens below, after the sibling pages have been ** populated, not here. */ | | | 7733 7734 7735 7736 7737 7738 7739 7740 7741 7742 7743 7744 7745 7746 7747 | ** with the cell. ** ** If the sibling pages are not leaves, then the pointer map entry ** associated with the right-child of each sibling may also need to be ** updated. This happens below, after the sibling pages have been ** populated, not here. */ if( REQUIRE_PTRMAP ){ MemPage *pNew = apNew[0]; u8 *aOld = pNew->aData; int cntOldNext = pNew->nCell + pNew->nOverflow; int usableSize = pBt->usableSize; int iNew = 0; int iOld = 0; |
︙ | ︙ | |||
7620 7621 7622 7623 7624 7625 7626 | testcase( rc!=SQLITE_OK ); assert( apNew[0]->nFree == (get2byte(&apNew[0]->aData[5])-apNew[0]->cellOffset-apNew[0]->nCell*2) || rc!=SQLITE_OK ); copyNodeContent(apNew[0], pParent, &rc); freePage(apNew[0], &rc); | | | 7918 7919 7920 7921 7922 7923 7924 7925 7926 7927 7928 7929 7930 7931 7932 | testcase( rc!=SQLITE_OK ); assert( apNew[0]->nFree == (get2byte(&apNew[0]->aData[5])-apNew[0]->cellOffset-apNew[0]->nCell*2) || rc!=SQLITE_OK ); copyNodeContent(apNew[0], pParent, &rc); freePage(apNew[0], &rc); }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. */ for(i=0; i<nNew; i++){ u32 key = get4byte(&apNew[i]->aData[8]); ptrmapPut(pBt, key, PTRMAP_BTREE, apNew[i]->pgno, &rc); } |
︙ | ︙ | |||
7706 7707 7708 7709 7710 7711 7712 | ** page that will become the new right-child of pPage. Copy the contents ** of the node stored on pRoot into the new child page. */ rc = sqlite3PagerWrite(pRoot->pDbPage); if( rc==SQLITE_OK ){ rc = allocateBtreePage(pBt,&pChild,&pgnoChild,pRoot->pgno,0); copyNodeContent(pRoot, pChild, &rc); | | | 8004 8005 8006 8007 8008 8009 8010 8011 8012 8013 8014 8015 8016 8017 8018 | ** page that will become the new right-child of pPage. Copy the contents ** of the node stored on pRoot into the new child page. */ rc = sqlite3PagerWrite(pRoot->pDbPage); if( rc==SQLITE_OK ){ rc = allocateBtreePage(pBt,&pChild,&pgnoChild,pRoot->pgno,0); copyNodeContent(pRoot, pChild, &rc); if( REQUIRE_PTRMAP ){ ptrmapPut(pBt, pgnoChild, PTRMAP_BTREE, pRoot->pgno, &rc); } } if( rc ){ *ppChild = 0; releasePage(pChild); return rc; |
︙ | ︙ | |||
9586 9587 9588 9589 9590 9591 9592 | return (p->pBt->btsFlags & BTS_READ_ONLY)!=0; } /* ** Return the size of the header added to each page by this module. */ int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); } | > > > > > > > > > > | 9884 9885 9886 9887 9888 9889 9890 9891 9892 9893 9894 9895 9896 9897 9898 9899 9900 | return (p->pBt->btsFlags & BTS_READ_ONLY)!=0; } /* ** 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; } |
Changes to src/btree.h.
︙ | ︙ | |||
210 211 212 213 214 215 216 217 218 219 220 221 222 223 | int sqlite3BtreeSetVersion(Btree *pBt, int iVersion); void sqlite3BtreeCursorHints(BtCursor *, unsigned int mask); #ifdef SQLITE_DEBUG int sqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask); #endif int sqlite3BtreeIsReadonly(Btree *pBt); int sqlite3HeaderSizeBtree(void); #ifndef NDEBUG int sqlite3BtreeCursorIsValid(BtCursor*); #endif #ifndef SQLITE_OMIT_BTREECOUNT int sqlite3BtreeCount(BtCursor *, i64 *); | > > | 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | int sqlite3BtreeSetVersion(Btree *pBt, int iVersion); void sqlite3BtreeCursorHints(BtCursor *, unsigned int mask); #ifdef SQLITE_DEBUG int sqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask); #endif int sqlite3BtreeIsReadonly(Btree *pBt); int sqlite3HeaderSizeBtree(void); int sqlite3BtreeExclusiveLock(Btree *pBt); #ifndef NDEBUG int sqlite3BtreeCursorIsValid(BtCursor*); #endif #ifndef SQLITE_OMIT_BTREECOUNT int sqlite3BtreeCount(BtCursor *, i64 *); |
︙ | ︙ |
Changes to src/btreeInt.h.
︙ | ︙ | |||
228 229 230 231 232 233 234 235 236 237 238 239 240 241 | */ #define MX_CELL(pBt) ((pBt->pageSize-8)/6) /* Forward declarations */ typedef struct MemPage MemPage; typedef struct BtLock BtLock; typedef struct CellInfo CellInfo; /* ** This is a magic string that appears at the beginning of every ** SQLite database in order to identify the file as a real database. ** ** You can change this value at compile-time by specifying a ** -DSQLITE_FILE_HEADER="..." on the compiler command-line. The | > | 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | */ #define MX_CELL(pBt) ((pBt->pageSize-8)/6) /* Forward declarations */ 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 ** SQLite database in order to identify the file as a real database. ** ** You can change this value at compile-time by specifying a ** -DSQLITE_FILE_HEADER="..." on the compiler command-line. The |
︙ | ︙ | |||
443 444 445 446 447 448 449 450 451 452 453 454 455 456 | #ifndef SQLITE_OMIT_SHARED_CACHE int nRef; /* Number of references to this structure */ BtShared *pNext; /* Next on a list of sharable BtShared structs */ BtLock *pLock; /* List of locks held on this shared-btree struct */ Btree *pWriter; /* Btree with currently open write transaction */ #endif u8 *pTmpSpace; /* Temp space sufficient to hold a single cell */ }; /* ** Allowed values for BtShared.btsFlags */ #define BTS_READ_ONLY 0x0001 /* Underlying file is readonly */ #define BTS_PAGESIZE_FIXED 0x0002 /* Page size can no longer be changed */ | > > > | 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 | #ifndef SQLITE_OMIT_SHARED_CACHE int nRef; /* Number of references to this structure */ BtShared *pNext; /* Next on a list of sharable BtShared structs */ BtLock *pLock; /* List of locks held on this shared-btree struct */ 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 }; /* ** Allowed values for BtShared.btsFlags */ #define BTS_READ_ONLY 0x0001 /* Underlying file is readonly */ #define BTS_PAGESIZE_FIXED 0x0002 /* Page size can no longer be changed */ |
︙ | ︙ | |||
654 655 656 657 658 659 660 661 662 663 664 665 666 667 | ** So, this macro is defined instead. */ #ifndef SQLITE_OMIT_AUTOVACUUM #define ISAUTOVACUUM (pBt->autoVacuum) #else #define ISAUTOVACUUM 0 #endif /* ** This structure is passed around through all the sanity checking routines ** in order to keep track of some global state information. ** ** The aRef[] array is allocated so that there is 1 bit for each page in | > > > > > > | 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 | ** So, this macro is defined instead. */ #ifndef SQLITE_OMIT_AUTOVACUUM #define ISAUTOVACUUM (pBt->autoVacuum) #else #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 ** in order to keep track of some global state information. ** ** The aRef[] array is allocated so that there is 1 bit for each page in |
︙ | ︙ |
Changes to src/pager.c.
︙ | ︙ | |||
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 | Pgno dbFileSize; /* Number of pages in the database file */ Pgno dbHintSize; /* Value passed to FCNTL_SIZE_HINT call */ int errCode; /* One of several kinds of errors */ int nRec; /* Pages journalled since last j-header written */ 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 */ Bitvec *pAllRead; /* Pages read within current UNLOCKED trans. */ sqlite3_file *fd; /* File descriptor for database */ sqlite3_file *jfd; /* File descriptor for main journal */ sqlite3_file *sjfd; /* File descriptor for sub-journal */ i64 journalOff; /* Current write offset in the journal file */ i64 journalHdr; /* Byte offset to previous journal header */ sqlite3_backup *pBackup; /* Pointer to list of ongoing backup processes */ PagerSavepoint *aSavepoint; /* Array of active savepoints */ | > > | 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 | Pgno dbFileSize; /* Number of pages in the database file */ Pgno dbHintSize; /* Value passed to FCNTL_SIZE_HINT call */ int errCode; /* One of several kinds of errors */ int nRec; /* Pages journalled since last j-header written */ 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 */ i64 journalOff; /* Current write offset in the journal file */ i64 journalHdr; /* Byte offset to previous journal header */ sqlite3_backup *pBackup; /* Pointer to list of ongoing backup processes */ PagerSavepoint *aSavepoint; /* Array of active savepoints */ |
︙ | ︙ | |||
4087 4088 4089 4090 4091 4092 4093 | assert( pPager->eState==PAGER_WRITER_CACHEMOD || pPager->eState==PAGER_WRITER_DBMOD ); assert( assert_pager_state(pPager) ); assert( !pagerUseWal(pPager) ); | | | 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 | assert( pPager->eState==PAGER_WRITER_CACHEMOD || pPager->eState==PAGER_WRITER_DBMOD ); assert( assert_pager_state(pPager) ); assert( !pagerUseWal(pPager) ); rc = sqlite3PagerExclusiveLock(pPager, 0); if( rc!=SQLITE_OK ) return rc; if( !pPager->noSync ){ assert( !pPager->tempFile ); if( isOpen(pPager->jfd) && pPager->journalMode!=PAGER_JOURNALMODE_MEMORY ){ const int iDc = sqlite3OsDeviceCharacteristics(pPager->fd); assert( isOpen(pPager->jfd) ); |
︙ | ︙ | |||
5925 5926 5927 5928 5929 5930 5931 | } /* ** Return TRUE if the page given in the argument was previously passed ** to sqlite3PagerWrite(). In other words, return TRUE if it is ok ** to change the content of the page. */ | < < | 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 | } /* ** Return TRUE if the page given in the argument was previously passed ** to sqlite3PagerWrite(). In other words, return TRUE if it is ok ** to change the content of the page. */ int sqlite3PagerIswriteable(DbPage *pPg){ return pPg->flags & PGHDR_WRITEABLE; } /* ** A call to this routine tells the pager that it is not necessary to ** write the information on page pPg back to the disk, even though ** that page might be marked as dirty. This happens, for example, when ** the page has been added as a leaf of the freelist and so its ** content no longer matters. |
︙ | ︙ | |||
6087 6088 6089 6090 6091 6092 6093 | ** the database file, an attempt is made to obtain one. ** ** If the EXCLUSIVE lock is already held or the attempt to obtain it is ** successful, or the connection is in WAL mode, SQLITE_OK is returned. ** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is ** returned. */ | | | > > > > > > > > > > > > > > > > > > | < | < | < | | 6087 6088 6089 6090 6091 6092 6093 6094 6095 6096 6097 6098 6099 6100 6101 6102 6103 6104 6105 6106 6107 6108 6109 6110 6111 6112 6113 6114 6115 6116 6117 6118 6119 6120 6121 6122 6123 6124 6125 6126 6127 6128 6129 6130 6131 6132 6133 6134 6135 6136 6137 6138 6139 6140 6141 6142 6143 6144 6145 6146 6147 6148 6149 6150 6151 6152 6153 6154 6155 6156 6157 6158 6159 6160 6161 6162 | ** the database file, an attempt is made to obtain one. ** ** If the EXCLUSIVE lock is already held or the attempt to obtain it is ** successful, or the connection is in WAL mode, SQLITE_OK is returned. ** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is ** returned. */ int sqlite3PagerExclusiveLock(Pager *pPager, PgHdr *pPage1){ int rc = SQLITE_OK; assert( pPager->eState==PAGER_WRITER_CACHEMOD || pPager->eState==PAGER_WRITER_DBMOD || pPager->eState==PAGER_WRITER_LOCKED ); assert( assert_pager_state(pPager) ); if( 0==pagerUseWal(pPager) ){ rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK); }else{ if( pPager->pAllRead ){ /* This is an UNLOCKED transaction. Attempt to lock the wal database ** here. If SQLITE_BUSY (but not SQLITE_BUSY_SNAPSHOT) is returned, ** invoke the busy-handler and try again for as long as it returns ** non-zero. */ do { /* rc = sqlite3WalBeginWriteTransaction(pWal); */ rc = sqlite3WalLockForCommit(pPager->pWal, pPage1, pPager->pAllRead); }while( rc==SQLITE_BUSY && pPager->xBusyHandler(pPager->pBusyHandlerArg) ); } } 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. */ void sqlite3PagerDropExclusiveLock(Pager *pPager){ if( pagerUseWal(pPager) ){ sqlite3WalEndWriteTransaction(pPager->pWal); } } /* ** Return true if this is a WAL database and snapshot upgrade is required ** before the current transaction can be committed. */ 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 ** journal file. zMaster may be NULL, which is interpreted as no master ** journal (a single database transaction). ** |
︙ | ︙ |
Changes to src/pager.h.
︙ | ︙ | |||
141 142 143 144 145 146 147 148 149 150 151 152 | /* Operations on page references. */ int sqlite3PagerWrite(DbPage*); void sqlite3PagerDontWrite(DbPage*); int sqlite3PagerMovepage(Pager*,DbPage*,Pgno,int); int sqlite3PagerPageRefcount(DbPage*); void *sqlite3PagerGetData(DbPage *); void *sqlite3PagerGetExtra(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); | > | | | 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | /* Operations on page references. */ int sqlite3PagerWrite(DbPage*); void sqlite3PagerDontWrite(DbPage*); 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*, DbPage *pPage1); int sqlite3PagerSync(Pager *pPager, const char *zMaster); int sqlite3PagerCommitPhaseTwo(Pager*); int sqlite3PagerRollback(Pager*); int sqlite3PagerOpenSavepoint(Pager *pPager, int n); int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint); int sqlite3PagerSharedLock(Pager *pPager); void sqlite3PagerDropExclusiveLock(Pager*); int sqlite3PagerIsUnlocked(Pager*); #ifndef SQLITE_OMIT_WAL int sqlite3PagerCheckpoint(Pager *pPager, int, int*, int*); int sqlite3PagerWalSupported(Pager *pPager); int sqlite3PagerWalCallback(Pager *pPager); int sqlite3PagerOpenWal(Pager *pPager, int *pisOpen); int sqlite3PagerCloseWal(Pager *pPager); |
︙ | ︙ | |||
192 193 194 195 196 197 198 199 200 201 202 203 204 205 | int sqlite3SectorSize(sqlite3_file *); /* Functions used to truncate the database file. */ void sqlite3PagerTruncateImage(Pager*,Pgno); void sqlite3PagerRekey(DbPage*, Pgno, u16); #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL) void *sqlite3PagerCodec(DbPage *); #endif /* Functions to support testing and debugging. */ #if !defined(NDEBUG) || defined(SQLITE_TEST) Pgno sqlite3PagerPagenumber(DbPage*); | > > > > < | 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | int sqlite3SectorSize(sqlite3_file *); /* Functions used to truncate the database file. */ 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 /* Functions to support testing and debugging. */ #if !defined(NDEBUG) || defined(SQLITE_TEST) Pgno sqlite3PagerPagenumber(DbPage*); #endif #ifdef SQLITE_TEST int *sqlite3PagerStats(Pager*); void sqlite3PagerRefdump(Pager*); void disable_simulated_io_errors(void); void enable_simulated_io_errors(void); #else # define disable_simulated_io_errors() # define enable_simulated_io_errors() #endif #endif /* _PAGER_H_ */ |
Changes to src/vdbeaux.c.
︙ | ︙ | |||
2016 2017 2018 2019 2020 2021 2022 | ** file is required for an atomic commit. */ for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ Btree *pBt = db->aDb[i].pBt; if( sqlite3BtreeIsInTrans(pBt) ){ needXcommit = 1; if( i!=1 ) nTrans++; | | < < | 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 | ** file is required for an atomic commit. */ for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ Btree *pBt = db->aDb[i].pBt; if( sqlite3BtreeIsInTrans(pBt) ){ needXcommit = 1; if( i!=1 ) nTrans++; rc = sqlite3BtreeExclusiveLock(pBt); } } if( db->bUnlocked && (rc & 0xFF)==SQLITE_BUSY ){ /* An SQLITE_BUSY or SQLITE_BUSY_SNAPSHOT was encountered while ** attempting to take the WRITER lock on a wal file. Release the ** WRITER locks on all wal files and return early. */ |
︙ | ︙ |
Changes to src/wal.c.
︙ | ︙ | |||
2569 2570 2571 2572 2573 2574 2575 | /* ** TODO: Combine some code with BeginWriteTransaction() ** ** This function is only ever called when committing a "BEGIN UNLOCKED" ** transaction. It may be assumed that no frames have been written to ** the wal file. */ | | > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > | 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 | /* ** TODO: Combine some code with BeginWriteTransaction() ** ** This function is only ever called when committing a "BEGIN UNLOCKED" ** transaction. It may be assumed that no frames have been written to ** the wal file. */ 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, ** check if it is still possible to commit. The transaction can be ** committed if: ** ** a) None of the pages in pList have been modified since the ** transaction opened, and ** ** b) The database schema cookie has not been modified since the ** transaction was started. */ 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? ** This code should check that the wal-index-header is Ok, and return ** SQLITE_BUSY_SNAPSHOT if it is not. */ int iHash; int iLastHash = walFramePage(pHead->mxFrame); for(iHash=walFramePage(pWal->hdr.mxFrame+1); iHash<=iLastHash; iHash++){ volatile ht_slot *aHash; volatile u32 *aPgno; u32 iZero; rc = walHashGet(pWal, iHash, &aHash, &aPgno, &iZero); if( rc==SQLITE_OK ){ int i; int iMin = (pWal->hdr.mxFrame+1 - iZero); int iMax = (iHash==0) ? HASHTABLE_NPAGE_ONE : HASHTABLE_NPAGE; if( iMin<1 ) iMin = 1; if( iMax>pHead->mxFrame ) iMax = pHead->mxFrame; for(i=iMin; i<=iMax; 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); } } } } if( rc!=SQLITE_OK ) break; } } } 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 ** (by calling the sqlite3WalLockForCommit() routine). This function ** returns true if the transaction was prepared against a database ** snapshot older than the current head of the wal file. |
︙ | ︙ | |||
2886 2887 2888 2889 2890 2891 2892 | u32 iFrame; /* Next frame address */ PgHdr *p; /* Iterator to run through pList with. */ PgHdr *pLast = 0; /* Last frame in list */ int nExtra = 0; /* Number of extra copies of last page */ int szFrame; /* The size of a single frame */ i64 iOffset; /* Next byte to write in WAL file */ WalWriter w; /* The writer */ | < < < < < < < < < < < < < < < < < | 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 | u32 iFrame; /* Next frame address */ PgHdr *p; /* Iterator to run through pList with. */ PgHdr *pLast = 0; /* Last frame in list */ int nExtra = 0; /* Number of extra copies of last page */ int szFrame; /* The size of a single frame */ i64 iOffset; /* Next byte to write in WAL file */ WalWriter w; /* The writer */ assert( pList ); assert( pWal->writeLock ); /* If this frame set completes a transaction, then nTruncate>0. If ** nTruncate==0 then this frame set does not complete the transaction. */ assert( (isCommit!=0)==(nTruncate!=0) ); #if defined(SQLITE_TEST) && defined(SQLITE_DEBUG) { int cnt; for(cnt=0, p=pList; p; p=p->pDirty, cnt++){} WALTRACE(("WAL%p: frame write begin. %d frames. mxFrame=%d. %s\n", pWal, cnt, pWal->hdr.mxFrame, isCommit ? "Commit" : "Spill")); } #endif /* 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. */ if( SQLITE_OK!=(rc = walRestartLog(pWal)) ){ return rc; } |
︙ | ︙ | |||
3066 3067 3068 3069 3070 3071 3072 | /* If this is a commit, update the wal-index header too. */ if( isCommit ){ walIndexWriteHdr(pWal); pWal->iCallback = iFrame; } } | < < < < < < < < | 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 | /* If this is a commit, update the wal-index header too. */ if( isCommit ){ walIndexWriteHdr(pWal); pWal->iCallback = iFrame; } } WALTRACE(("WAL%p: frame write %s\n", pWal, rc ? "failed" : "ok")); return rc; } /* ** This routine is called to implement sqlite3_wal_checkpoint() and ** related interfaces. |
︙ | ︙ |
Changes to src/wal.h.
︙ | ︙ | |||
123 124 125 126 127 128 129 | /* Return true if the argument is non-NULL and the WAL module is using ** heap-memory for the wal-index. Otherwise, if the argument is NULL or the ** WAL module is using shared-memory, return false. */ int sqlite3WalHeapMemory(Wal *pWal); /* Return true if the WRITER lock is held. False otherwise. */ | | > | 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | /* Return true if the argument is non-NULL and the WAL module is using ** heap-memory for the wal-index. Otherwise, if the argument is NULL or the ** WAL module is using shared-memory, return false. */ int sqlite3WalHeapMemory(Wal *pWal); /* Return true if the WRITER lock is held. False otherwise. */ 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 ** stored in each frame (i.e. the db page-size when the WAL was created). */ int sqlite3WalFramesize(Wal *pWal); #endif |
︙ | ︙ |
Changes to test/unlocked.test.
︙ | ︙ | |||
395 396 397 398 399 400 401 | VALUES(1,1) UNION ALL SELECT a+1,b+1 FROM src WHERE a<10000 ) INSERT INTO t2 SELECT * FROM src; } } {} do_test 2.$tn.7.3 { list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db] | | < | | 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 | VALUES(1,1) UNION ALL SELECT a+1,b+1 FROM src WHERE a<10000 ) INSERT INTO t2 SELECT * FROM src; } } {} do_test 2.$tn.7.3 { list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db] } {0 {} SQLITE_OK} do_test 2.$tn.7.4 { sql3 { PRAGMA integrity_check } } ok } finish_test |
Added test/unlocked2.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 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 |