Index: src/bitvec.c ================================================================== --- src/bitvec.c +++ src/bitvec.c @@ -290,10 +290,67 @@ ** was created. */ u32 sqlite3BitvecSize(Bitvec *p){ return p->iSize; } + +int bitvecAppendArrayElem(int *pnAlloc, int *pnElem, u32 **paElem, u32 iNew){ + if( *pnElem==*pnAlloc ){ + int nNew = *pnAlloc ? (*pnAlloc)*2 : 128; + u32 *aNew; + aNew = sqlite3_realloc(*paElem, nNew*sizeof(u32)); + if( aNew==0 ){ + sqlite3_free(*paElem); + *paElem = 0; + return SQLITE_NOMEM; + } + *paElem = aNew; + *pnAlloc = nNew; + } + + (*paElem)[(*pnElem)++] = iNew; + return SQLITE_OK; +} + +#ifdef SQLITE_ENABLE_TRANSACTION_PAGES +int bitvecToArray(Bitvec *p, int iOff, int *pnAlloc, int *pnElem, u32 **paElem){ + int rc = SQLITE_OK; + int i; + if( p->iDivisor ){ + for(i=0; rc==SQLITE_OK && iu.apSub[i] ){ + int iOff2 = iOff + i*p->iDivisor; + rc = bitvecToArray(p->u.apSub[i], iOff2, pnAlloc, pnElem, paElem); + } + } + }else{ + if( p->iSize<=BITVEC_NBIT ){ + for(i=0; rc==SQLITE_OK && iu.aBitmap[i/BITVEC_SZELEM] & (1<<(i&(BITVEC_SZELEM-1))) ){ + rc = bitvecAppendArrayElem(pnAlloc, pnElem, paElem, i+iOff); + } + } + }else{ + for(i=0; rc==SQLITE_OK && iu.aHash[i]; + if( iVal ){ + rc = bitvecAppendArrayElem(pnAlloc, pnElem, paElem, iVal-1+iOff); + } + } + } + } + + return rc; +} + +int sqlite3BitvecToArray(Bitvec *p, int *pnElem, u32 **paElem){ + int nAlloc = 0; + *pnElem = 0; + *paElem = 0; + return bitvecToArray(p, 1, &nAlloc, pnElem, paElem); +} +#endif #ifndef SQLITE_UNTESTABLE /* ** Let V[] be an array of unsigned characters sufficient to hold ** up to N bits. Let I be an integer between 0 and N. 0<=InCell<1 || (*ppPage)->intKey!=pCur->curIntKey) ){ rc = SQLITE_CORRUPT_BKPT; releasePage(*ppPage); goto getAndInitPage_error; } + +#ifdef SQLITE_ENABLE_TRANSACTION_PAGES + if( pBt->inTransaction==TRANS_WRITE + && pgno<=sqlite3BitvecSize(pBt->pBtRead) + ){ + rc = sqlite3BitvecSet(pBt->pBtRead, pgno); + if( rc!=SQLITE_OK ){ + releasePage(*ppPage); + goto getAndInitPage_error; + } + } +#endif + return SQLITE_OK; getAndInitPage_error: if( pCur ) pCur->iPage--; testcase( pgno==0 ); @@ -3104,10 +3117,31 @@ rc = newDatabase(p->pBt); sqlite3BtreeLeave(p); return rc; } +#ifdef SQLITE_ENABLE_TRANSACTION_PAGES +/* +** If the b-tree is not currently in a write transaction, free the various +** resources allocated for the sqlite3_transaction_pages() functionality. +*/ +static void freeTransactionPagesBitvec(BtShared *pBt){ + if( pBt->inTransaction!=TRANS_WRITE ){ + sqlite3BitvecDestroy(pBt->pBtRead); + sqlite3BitvecDestroy(pBt->pBtWrite); + sqlite3BitvecDestroy(pBt->pBtAlloc); + pBt->pBtAlloc = pBt->pBtRead = pBt->pBtWrite = 0; + sqlite3_free(pBt->aiRead); + sqlite3_free(pBt->aiWrite); + pBt->aiRead = pBt->aiWrite = 0; + pBt->nRead = pBt->nWrite = 0; + } +} +#else +# define freeTransactionPagesBitvec(x) +#endif + /* ** Attempt to start a new transaction. A write-transaction ** is started if the second argument is nonzero, otherwise a read- ** transaction. If the second argument is 2 or more and exclusive ** transaction is started, meaning that no other process is allowed @@ -3188,10 +3222,24 @@ rc = SQLITE_LOCKED_SHAREDCACHE; goto trans_begun; } } #endif + +#ifdef SQLITE_ENABLE_TRANSACTION_PAGES + if( wrflag ){ + assert( pBt->pBtRead==0 && pBt->pBtWrite==0 && pBt->pBtAlloc==0 ); + assert( rc==SQLITE_OK ); + pBt->pBtRead = sqlite3BitvecCreate(pBt->nPage); + pBt->pBtWrite = sqlite3BitvecCreate(pBt->nPage); + pBt->pBtAlloc = sqlite3BitvecCreate(pBt->nPage); + if( pBt->pBtRead==0 || pBt->pBtWrite==0 || pBt->pBtAlloc==0 ){ + rc = SQLITE_NOMEM; + goto trans_begun; + } + } +#endif /* Any read-only or read-write transaction implies a read-lock on ** page 1. So if some other shared-cache client already has a write-lock ** on page 1, the transaction cannot be opened. */ rc = querySharedCacheTableLock(p, MASTER_ROOT, READ_LOCK); @@ -3264,20 +3312,20 @@ } } } } - 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. */ rc = sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint); } + freeTransactionPagesBitvec(pBt); btreeIntegrity(p); sqlite3BtreeLeave(p); return rc; } @@ -3788,10 +3836,11 @@ ** pager if this call closed the only read or write transaction. */ p->inTrans = TRANS_NONE; unlockBtreeIfUnused(pBt); } + freeTransactionPagesBitvec(pBt); btreeIntegrity(p); } /* ** Commit the transaction currently in progress. @@ -4414,10 +4463,28 @@ /* Copy data from page to buffer (a read operation) */ memcpy(pBuf, pPayload, nByte); } return SQLITE_OK; } + +/* +** Call PagerWrite() on pager page pDbPage. And, if the page is currently +** in the pBtRead bit vector, add it to pBtWrite as well. +*/ +static int pagerWrite(BtShared *pBt, DbPage *pDbPage){ + Pgno pgno = sqlite3PagerPagenumber(pDbPage); + int rc = SQLITE_OK; +#ifdef SQLITE_ENABLE_TRANSACTION_PAGES + if( sqlite3BitvecTestNotNull(pBt->pBtRead, pgno) ){ + rc = sqlite3BitvecSet(pBt->pBtWrite, pgno); + } +#endif + if( rc==SQLITE_OK ){ + rc = sqlite3PagerWrite(pDbPage); + } + return rc; +} /* ** This function is used to read or overwrite payload information ** for the entry that the pCur cursor is pointing to. The eOp ** argument is interpreted as follows: @@ -4491,11 +4558,18 @@ if( offsetinfo.nLocal ){ int a = amt; if( a+offset>pCur->info.nLocal ){ a = pCur->info.nLocal - offset; } - rc = copyPayload(&aPayload[offset], pBuf, a, (eOp & 0x01), pPage->pDbPage); +#ifdef SQLITE_ENABLE_TRANSACTION_PAGES + if( eOp & 0x01 ){ + rc = pagerWrite(pBt, pPage->pDbPage); + } +#endif + if( rc==SQLITE_OK ){ + rc = copyPayload(&aPayload[offset], pBuf, a, (eOp&0x01), pPage->pDbPage); + } offset = 0; pBuf += a; amt -= a; }else{ offset -= pCur->info.nLocal; @@ -5827,10 +5901,20 @@ } TRACE(("ALLOCATE: %d from end of file\n", *pPgno)); } assert( *pPgno!=PENDING_BYTE_PAGE(pBt) ); + +#ifdef SQLITE_ENABLE_TRANSACTION_PAGES + if( rc==SQLITE_OK && *pPgnopBtAlloc) ){ + rc = sqlite3BitvecSet(pBt->pBtAlloc, *pPgno); + if( rc!=SQLITE_OK ){ + releasePage(*ppPage); + *ppPage = 0; + } + } +#endif end_allocate_page: releasePage(pTrunk); releasePage(pPrevTrunk); assert( rc!=SQLITE_OK || sqlite3PagerPageRefcount((*ppPage)->pDbPage)<=1 ); @@ -6283,10 +6367,11 @@ memmove(ptr, ptr+2, 2*(pPage->nCell - idx)); put2byte(&data[hdr+3], pPage->nCell); pPage->nFree += 2; } } + /* ** Insert a new cell on pPage at cell index "i". pCell points to the ** content of the cell. ** @@ -6349,11 +6434,11 @@ ** 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); + int rc = pagerWrite(pPage->pBt, pPage->pDbPage); if( rc!=SQLITE_OK ){ *pRC = rc; return; } assert( sqlite3PagerIswriteable(pPage->pDbPage) ); @@ -7383,11 +7468,11 @@ for(i=0; ipDbPage); + rc = pagerWrite(pBt, pNew->pDbPage); nNew++; if( rc ) goto balance_cleanup; }else{ assert( i>0 ); rc = allocateBtreePage(pBt, &pNew, &pgno, (bBulk ? 1 : pgno), 0); @@ -7753,11 +7838,11 @@ /* Make pRoot, the root page of the b-tree, writable. Allocate a new ** 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); + rc = pagerWrite(pBt, pRoot->pDbPage); if( rc==SQLITE_OK ){ rc = allocateBtreePage(pBt,&pChild,&pgnoChild,pRoot->pgno,0); copyNodeContent(pRoot, pChild, &rc); if( ISAUTOVACUUM ){ ptrmapPut(pBt, pgnoChild, PTRMAP_BTREE, pRoot->pgno, &rc); @@ -7835,11 +7920,11 @@ break; }else{ MemPage * const pParent = pCur->apPage[iPage-1]; int const iIdx = pCur->aiIdx[iPage-1]; - rc = sqlite3PagerWrite(pParent->pDbPage); + rc = pagerWrite(pParent->pBt, pParent->pDbPage); if( rc==SQLITE_OK ){ #ifndef SQLITE_OMIT_QUICKBALANCE if( pPage->intKeyLeaf && pPage->nOverflow==1 && pPage->aiOvfl[0]==pPage->nCell @@ -8056,11 +8141,11 @@ assert( szNew <= MX_CELL_SIZE(pBt) ); idx = pCur->aiIdx[pCur->iPage]; if( loc==0 ){ CellInfo info; assert( idxnCell ); - rc = sqlite3PagerWrite(pPage->pDbPage); + rc = pagerWrite(pBt, pPage->pDbPage); if( rc ){ goto end_insert; } oldCell = findCell(pPage, idx); if( !pPage->leaf ){ @@ -8236,11 +8321,11 @@ } /* Make the page containing the entry to be deleted writable. Then free any ** overflow pages associated with the entry and finally remove the cell ** itself from within the page. */ - rc = sqlite3PagerWrite(pPage->pDbPage); + rc = pagerWrite(pBt, pPage->pDbPage); if( rc ) return rc; rc = clearCell(pPage, pCell, &info); dropCell(pPage, iCellIdx, info.nSize, &rc); if( rc ) return rc; @@ -8259,11 +8344,11 @@ if( pCell<&pLeaf->aData[4] ) return SQLITE_CORRUPT_BKPT; nCell = pLeaf->xCellSize(pLeaf, pCell); assert( MX_CELL_SIZE(pBt) >= nCell ); pTmp = pBt->pTmpSpace; assert( pTmp!=0 ); - rc = sqlite3PagerWrite(pLeaf->pDbPage); + rc = pagerWrite(pBt, pLeaf->pDbPage); if( rc==SQLITE_OK ){ insertCell(pPage, iCellIdx, pCell-4, nCell+4, pTmp, n, &rc); } dropCell(pLeaf, pLeaf->nCell-1, nCell, &rc); if( rc ) return rc; @@ -8522,11 +8607,11 @@ testcase( !pPage->intKey ); *pnChange += pPage->nCell; } if( freePageFlag ){ freePage(pPage, &rc); - }else if( (rc = sqlite3PagerWrite(pPage->pDbPage))==0 ){ + }else if( (rc = pagerWrite(pBt, pPage->pDbPage))==0 ){ zeroPage(pPage, pPage->aData[hdr] | PTF_LEAF); } cleardatabasepage_out: pPage->bBusy = 0; @@ -9724,10 +9809,39 @@ /* ** Return the size of the header added to each page by this module. */ int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); } +#ifdef SQLITE_ENABLE_TRANSACTION_PAGES +int sqlite3BtreeTransactionPages( + Btree *pBtree, /* Btree handle */ + int *pnRead, u32 **paiRead, /* OUT: Pages read */ + int *pnWrite, u32 **paiWrite /* OUT: Pages written */ +){ + int rc = SQLITE_OK; + BtShared *pBt = pBtree->pBt; + sqlite3BtreeEnter(pBtree); + sqlite3_free(pBt->aiRead); + sqlite3_free(pBt->aiWrite); + pBt->nRead = pBt->nWrite = 0; + pBt->aiRead = pBt->aiWrite = 0; + if( pBtree->inTrans==TRANS_WRITE ){ + assert( pBt->inTransaction==TRANS_WRITE ); + rc = sqlite3BitvecToArray(pBt->pBtRead, &pBt->nRead, &pBt->aiRead); + if( rc==SQLITE_OK ){ + rc = sqlite3BitvecToArray(pBt->pBtWrite, &pBt->nWrite, &pBt->aiWrite); + } + } + *pnRead = pBt->nRead; + *paiRead = pBt->aiRead; + *pnWrite = pBt->nWrite; + *paiWrite = pBt->aiWrite; + sqlite3BtreeLeave(pBtree); + return rc; +} +#endif /* SQLITE_ENABLE_TRANSACTION_PAGES */ + #if !defined(SQLITE_OMIT_SHARED_CACHE) /* ** Return true if the Btree passed as the only argument is sharable. */ int sqlite3BtreeSharable(Btree *p){ Index: src/btree.h ================================================================== --- src/btree.h +++ src/btree.h @@ -363,8 +363,12 @@ # define sqlite3BtreeHoldsMutex(X) 1 # define sqlite3BtreeHoldsAllMutexes(X) 1 # define sqlite3SchemaMutexHeld(X,Y,Z) 1 #endif + +#ifdef SQLITE_ENABLE_TRANSACTION_PAGES + int sqlite3BtreeTransactionPages(Btree*, int*, u32**, int*, u32**); +#endif #endif /* SQLITE_BTREE_H */ Index: src/btreeInt.h ================================================================== --- src/btreeInt.h +++ src/btreeInt.h @@ -438,10 +438,20 @@ 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_TRANSACTION_PAGES + Bitvec *pBtRead; /* Btree pages read during current write transaction */ + Bitvec *pBtWrite; /* Btree pages written during current transaction */ + Bitvec *pBtAlloc; /* Btree pages allocated during current transaction */ + int nRead; /* Number of entries in aiRead[] array */ + u32 *aiRead; /* Array returned to sqlite3_transaction_pages() */ + int nWrite; /* Number of entries in aiWrite[] array */ + u32 *aiWrite; /* Array returned to sqlite3_transaction_pages() */ +#endif }; /* ** Allowed values for BtShared.btsFlags */ Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -4075,6 +4075,35 @@ ** Free a snapshot handle obtained from sqlite3_snapshot_get(). */ void sqlite3_snapshot_free(sqlite3_snapshot *pSnapshot){ sqlite3_free(pSnapshot); } + +#ifdef SQLITE_ENABLE_TRANSACTION_PAGES +/* +** Return the pages read and written by the current write transaction. +*/ +int sqlite3_transaction_pages( + sqlite3 *db, const char *zDbName, + int *pnRead, unsigned int **paRead, + int *pnWrite, unsigned int **paWrite +){ + Btree *pBt; /* Btree to query */ + int rc; /* Return code */ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + + sqlite3_mutex_enter(db->mutex); + pBt = sqlite3DbNameToBtree(db, zDbName); + if( pBt==0 ) return SQLITE_ERROR; + rc = sqlite3BtreeTransactionPages(pBt, pnRead, paRead, pnWrite, paWrite); + sqlite3_mutex_leave(db->mutex); + + return rc; +} +#endif /* SQLITE_ENABLE_TRANSACTION_PAGES */ + #endif /* SQLITE_ENABLE_SNAPSHOT */ Index: src/pager.c ================================================================== --- src/pager.c +++ src/pager.c @@ -4111,11 +4111,11 @@ sqlite3_free(pPager); return SQLITE_OK; } -#if !defined(NDEBUG) || defined(SQLITE_TEST) +#if !defined(NDEBUG) || defined(SQLITE_TEST) || defined(SQLITE_ENABLE_TRANSACTION_PAGES) /* ** Return the page number for page pPg. */ Pgno sqlite3PagerPagenumber(DbPage *pPg){ return pPg->pgno; Index: src/pager.h ================================================================== --- src/pager.h +++ src/pager.h @@ -218,12 +218,14 @@ #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) +#if !defined(NDEBUG) || defined(SQLITE_TEST) || defined(SQLITE_ENABLE_TRANSACTION_PAGES) Pgno sqlite3PagerPagenumber(DbPage*); +#endif +#if !defined(NDEBUG) || defined(SQLITE_TEST) int sqlite3PagerIswriteable(DbPage*); #endif #ifdef SQLITE_TEST int *sqlite3PagerStats(Pager*); void sqlite3PagerRefdump(Pager*); Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -8445,10 +8445,57 @@ ** database. ** ** SQLITE_OK is returned if successful, or an SQLite error code otherwise. */ SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); + + +/* +** THIS API IS A HACK ONLY. +** +** Return the page numbers of all b-tree pages read from or written to +** database zDb ("main", "temp" etc.) belonging to handle db since the +** current write transaction was started. A page is only reported on if: +** +** + It is a b-tree page, not an overflow, free or pointer-map page, and +** +** + it was a b-tree page when the transaction was started (i.e. is not a +** b-tree page created by reusing free page or extending the database +** file). +** +** If successful, this function returns SQLITE_OK and sets the four output +** variables as follows: +** +** + (*paRead) is set to point to an array containing the page numbers +** of all pages read since the current write transaction was opened. +** (*pnRead) is set to the number of elements in this array. +** +** + (*paWrite) is set to point to an array containing the page numbers +** of all pages written since the current write transaction was opened. +** (*paRead) is set to the number of elements in this array. +** +** The array references are valid until the next call to this API function +** on the same database or until the database is DETACHed from the database +** handle. +** +** If this function is called when there is no write transaction opened +** on the specified database, all four output parameters are set to 0. +** +** If an error occurs, an SQLite error code is returned (e.g. SQLITE_NOMEM) +** and the final values of the four output parameters are undefined. +** +** This function is only enabled if SQLITE_ENABLE_TRANSACTION_PAGES is +** defined during compilation. +*/ +SQLITE_EXPERIMENTAL int sqlite3_transaction_pages( + sqlite3 *db, const char *zDb, + int *pnRead, unsigned int **paRead, + int *pnWrite, unsigned int **paWrite +); + + + /* ** Undo the hack that converts floating point types to integer for ** builds on processors without floating point support. */ Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -3628,10 +3628,14 @@ void sqlite3BitvecDestroy(Bitvec*); u32 sqlite3BitvecSize(Bitvec*); #ifndef SQLITE_UNTESTABLE int sqlite3BitvecBuiltinTest(int,int*); #endif + +#ifdef SQLITE_ENABLE_TRANSACTION_PAGES + int sqlite3BitvecToArray(Bitvec *p, int *pnElem, u32 **paElem); +#endif RowSet *sqlite3RowSetInit(sqlite3*, void*, unsigned int); void sqlite3RowSetClear(RowSet*); void sqlite3RowSetInsert(RowSet*, i64); int sqlite3RowSetTest(RowSet*, int iBatch, i64); Index: src/test1.c ================================================================== --- src/test1.c +++ src/test1.c @@ -2548,10 +2548,59 @@ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); return TCL_OK; } +/* +** Usage: sqlite3_transaction_pages DB FILENAME +*/ +#ifdef SQLITE_ENABLE_TRANSACTION_PAGES +static int SQLITE_TCLAPI test_transaction_pages( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + const char *zDb; + sqlite3 *db; + int rc; + + int nRead; + int nWrite; + unsigned int *aiRead; + unsigned int *aiWrite; + + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB FILE"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + zDb = (const char*)Tcl_GetString(objv[2]); + + rc = sqlite3_transaction_pages(db, zDb, &nRead, &aiRead, &nWrite, &aiWrite); + if( rc==SQLITE_OK ){ + Tcl_Obj *pList = Tcl_NewObj(); + Tcl_Obj *p = Tcl_NewObj(); + int i; + for(i=0; i