summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorshess <shess@chromium.org>2016-02-26 14:54:04 -0800
committerCommit bot <commit-bot@chromium.org>2016-02-26 22:54:58 +0000
commitee29289c53effb24d9d653afeed75d99c82df0fc (patch)
treec4e8343e7b7366ee6e2f1cbc7724c2ed748eacc0
parent11d7af3d7ca3d032ec52ce8d56cc6fdaa69063cf (diff)
downloadchromium_src-ee29289c53effb24d9d653afeed75d99c82df0fc.zip
chromium_src-ee29289c53effb24d9d653afeed75d99c82df0fc.tar.gz
chromium_src-ee29289c53effb24d9d653afeed75d99c82df0fc.tar.bz2
[sqlite] Pull out recent recover.c changes into new patch.
Pull the changes to make recover.c work without SQLite internals into the recover patch. The changes are not quite complete, but other changes being made to third_party/sqlite/ run the risk of losing the in-progress patches. After change, google_generate_amalgamation.sh generated identical sqlite3.c output. BUG=584407 TBR=rch@chromium.org Review URL: https://codereview.chromium.org/1742763003 Cr-Commit-Position: refs/heads/master@{#378013}
-rw-r--r--third_party/sqlite/patches/0005-Virtual-table-supporting-recovery-of-corrupted-datab.patch359
1 files changed, 245 insertions, 114 deletions
diff --git a/third_party/sqlite/patches/0005-Virtual-table-supporting-recovery-of-corrupted-datab.patch b/third_party/sqlite/patches/0005-Virtual-table-supporting-recovery-of-corrupted-datab.patch
index 35a29b9..7d925c4 100644
--- a/third_party/sqlite/patches/0005-Virtual-table-supporting-recovery-of-corrupted-datab.patch
+++ b/third_party/sqlite/patches/0005-Virtual-table-supporting-recovery-of-corrupted-datab.patch
@@ -1,7 +1,7 @@
-From 29b8ad9263dbc700c002e9044c7c712a566a26d7 Mon Sep 17 00:00:00 2001
+From 31dd2fd1cdeba87223f6a46cc19bcea9eb8b2f38 Mon Sep 17 00:00:00 2001
From: Scott Hess <shess@chromium.org>
Date: Sat, 20 Jul 2013 11:42:21 -0700
-Subject: [PATCH 05/10] Virtual table supporting recovery of corrupted
+Subject: [PATCH 05/13] Virtual table supporting recovery of corrupted
databases.
"recover" implements a virtual table which uses the SQLite pager layer
@@ -19,14 +19,14 @@ third_party/sqlite/src/src/{recover,recover-alt}.c .
third_party/sqlite/src/Makefile.linux-gcc | 4 +
third_party/sqlite/src/main.mk | 5 +-
third_party/sqlite/src/src/main.c | 8 +
- third_party/sqlite/src/src/recover.c | 2167 ++++++++++++++++++++++++++++
+ third_party/sqlite/src/src/recover.c | 2281 ++++++++++++++++++++++++++++
third_party/sqlite/src/src/sqlite.h.in | 16 +
- third_party/sqlite/src/test/recover.test | 147 ++
+ third_party/sqlite/src/test/recover.test | 164 ++
third_party/sqlite/src/test/recover0.test | 532 +++++++
third_party/sqlite/src/test/recover1.test | 429 ++++++
third_party/sqlite/src/test/recover2.test | 157 ++
third_party/sqlite/src/tool/mksqlite3c.tcl | 2 +
- 11 files changed, 3467 insertions(+), 1 deletion(-)
+ 11 files changed, 3598 insertions(+), 1 deletion(-)
create mode 100644 third_party/sqlite/src/src/recover.c
create mode 100644 third_party/sqlite/src/test/recover.test
create mode 100644 third_party/sqlite/src/test/recover0.test
@@ -111,10 +111,10 @@ index 3be7c77..301808c 100644
rc = sqlite3IcuInit(db);
diff --git a/third_party/sqlite/src/src/recover.c b/third_party/sqlite/src/src/recover.c
new file mode 100644
-index 0000000..8e3929d
+index 0000000..5ff6f78
--- /dev/null
+++ b/third_party/sqlite/src/src/recover.c
-@@ -0,0 +1,2167 @@
+@@ -0,0 +1,2281 @@
+/*
+** 2012 Jan 11
+**
@@ -354,6 +354,15 @@ index 0000000..8e3929d
+static const unsigned char kTableLeafPage = 0x0D;
+static const unsigned char kTableInteriorPage = 0x05;
+
++/* From section 1.2. */
++static const unsigned kiHeaderPageSizeOffset = 16;
++static const unsigned kiHeaderReservedSizeOffset = 20;
++static const unsigned kiHeaderEncodingOffset = 56;
++/* TODO(shess) |static const unsigned| fails creating the header in GetPager()
++** because |knHeaderSize| isn't |constexpr|. But this isn't C++, either.
++*/
++enum { knHeaderSize = 100};
++
+/* From section 1.5. */
+static const unsigned kiPageTypeOffset = 0;
+static const unsigned kiPageFreeBlockOffset = 1;
@@ -487,9 +496,104 @@ index 0000000..8e3929d
+ return ascii_strncasecmp(s1, s2, strlen(s1)+1);
+}
+
++/* Provide access to the pages of a SQLite database in a way similar to SQLite's
++** Pager.
++*/
++typedef struct RecoverPager RecoverPager;
++struct RecoverPager {
++ sqlite3_file *pSqliteFile; /* Reference to database's file handle */
++ u32 nPageSize; /* Size of pages in pSqliteFile */
++};
++
++static void pagerDestroy(RecoverPager *pPager){
++ pPager->pSqliteFile->pMethods->xUnlock(pPager->pSqliteFile, SQLITE_LOCK_NONE);
++ memset(pPager, 0xA5, sizeof(*pPager));
++ sqlite3_free(pPager);
++}
++
++/* pSqliteFile should already have a SHARED lock. */
++static int pagerCreate(sqlite3_file *pSqliteFile, u32 nPageSize,
++ RecoverPager **ppPager){
++ RecoverPager *pPager = sqlite3_malloc(sizeof(RecoverPager));
++ if( !pPager ){
++ return SQLITE_NOMEM;
++ }
++
++ memset(pPager, 0, sizeof(*pPager));
++ pPager->pSqliteFile = pSqliteFile;
++ pPager->nPageSize = nPageSize;
++ *ppPager = pPager;
++ return SQLITE_OK;
++}
++
++/* Matches DbPage (aka PgHdr) from SQLite internals. */
++/* TODO(shess): SQLite by default allocates page metadata in a single allocation
++** such that the page's data and metadata are contiguous, see pcache1AllocPage
++** in pcache1.c. I believe this was intended to reduce malloc churn. It means
++** that Chromium's automated tooling would be unlikely to see page-buffer
++** overruns. I believe that this code is safe, but for now replicate SQLite's
++** approach with kExcessSpace.
++*/
++const int kExcessSpace = 128;
++typedef struct RecoverPage RecoverPage;
++struct RecoverPage {
++ Pgno pgno; /* Page number for this page */
++ void *pData; /* Page data for pgno */
++ RecoverPager *pPager; /* The pager this page is part of */
++};
++
++static void pageDestroy(RecoverPage *pPage){
++ sqlite3_free(pPage->pData);
++ memset(pPage, 0xA5, sizeof(*pPage));
++ sqlite3_free(pPage);
++}
++
++static int pageCreate(RecoverPager *pPager, u32 pgno, RecoverPage **ppPage){
++ RecoverPage *pPage = sqlite3_malloc(sizeof(RecoverPage));
++ if( !pPage ){
++ return SQLITE_NOMEM;
++ }
++
++ memset(pPage, 0, sizeof(*pPage));
++ pPage->pPager = pPager;
++ pPage->pgno = pgno;
++ pPage->pData = sqlite3_malloc(pPager->nPageSize + kExcessSpace);
++ if( pPage->pData==NULL ){
++ pageDestroy(pPage);
++ return SQLITE_NOMEM;
++ }
++ memset((u8 *)pPage->pData + pPager->nPageSize, 0, kExcessSpace);
++
++ *ppPage = pPage;
++ return SQLITE_OK;
++}
++
++static int pagerGetPage(RecoverPager *pPager, u32 iPage, RecoverPage **ppPage) {
++ sqlite3_int64 iOfst;
++ sqlite3_file *pFile = pPager->pSqliteFile;
++ RecoverPage *pPage;
++ int rc = pageCreate(pPager, iPage, &pPage);
++ if( rc!=SQLITE_OK ){
++ return rc;
++ }
++
++ /* xRead() can return SQLITE_IOERR_SHORT_READ, which should be treated as
++ ** SQLITE_OK plus an EOF indicator. The excess space is zero-filled.
++ */
++ iOfst = ((sqlite3_int64)iPage - 1) * pPager->nPageSize;
++ rc = pFile->pMethods->xRead(pFile, pPage->pData, pPager->nPageSize, iOfst);
++ if( rc!=SQLITE_OK && rc!=SQLITE_IOERR_SHORT_READ ){
++ pageDestroy(pPage);
++ return rc;
++ }
++
++ *ppPage = pPage;
++ return SQLITE_OK;
++}
++
+/* For some reason I kept making mistakes with offset calculations. */
-+static const unsigned char *PageData(DbPage *pPage, unsigned iOffset){
-+ assert( iOffset<=pPage->nPageSize );
++static const unsigned char *PageData(RecoverPage *pPage, unsigned iOffset){
++ assert( iOffset<=pPage->pPager->nPageSize );
+ return (unsigned char *)pPage->pData + iOffset;
+}
+
@@ -498,10 +602,9 @@ index 0000000..8e3929d
+ * the offsets in the page's header information are relative to the
+ * beginning of the page, NOT the end of the page header.
+ */
-+static const unsigned char *PageHeader(DbPage *pPage){
++static const unsigned char *PageHeader(RecoverPage *pPage){
+ if( pPage->pgno==1 ){
-+ const unsigned nDatabaseHeader = 100;
-+ return PageData(pPage, nDatabaseHeader);
++ return PageData(pPage, knHeaderSize);
+ }else{
+ return PageData(pPage, 0);
+ }
@@ -509,22 +612,74 @@ index 0000000..8e3929d
+
+/* Helper to fetch the pager and page size for the named database. */
+static int GetPager(sqlite3 *db, const char *zName,
-+ Pager **pPager, unsigned *pnPageSize){
-+ Btree *pBt = NULL;
-+ int i;
-+ for( i=0; i<db->nDb; ++i ){
-+ if( ascii_strcasecmp(db->aDb[i].zName, zName)==0 ){
-+ pBt = db->aDb[i].pBt;
-+ break;
++ RecoverPager **ppPager, unsigned *pnPageSize,
++ int *piEncoding){
++ int rc, iEncoding;
++ unsigned nPageSize, nReservedSize;
++ unsigned char header[knHeaderSize];
++ sqlite3_file *pFile = NULL;
++ RecoverPager *pPager;
++
++ rc = sqlite3_file_control(db, zName, SQLITE_FCNTL_FILE_POINTER, &pFile);
++ if( rc!=SQLITE_OK ) {
++ return rc;
++ } else if( pFile==NULL ){
++ /* The documentation for sqlite3PagerFile() indicates it can return NULL if
++ ** the file has not yet been opened. That should not be possible here...
++ */
++ return SQLITE_MISUSE;
++ }
++
++ /* Get a shared lock to make sure the on-disk version of the file is truth. */
++ rc = pFile->pMethods->xLock(pFile, SQLITE_LOCK_SHARED);
++ if( rc != SQLITE_OK ){
++ return rc;
++ }
++
++ /* Read the Initial header information. In case of SQLITE_IOERR_SHORT_READ,
++ ** the header is incomplete, which means no data could be recovered anyhow.
++ */
++ rc = pFile->pMethods->xRead(pFile, header, sizeof(header), 0);
++ if( rc != SQLITE_OK ){
++ pFile->pMethods->xUnlock(pFile, SQLITE_LOCK_NONE);
++ if( rc==SQLITE_IOERR_SHORT_READ ){
++ return SQLITE_CORRUPT;
+ }
++ return rc;
+ }
-+ if( !pBt ){
-+ return SQLITE_ERROR;
++
++ /* Page size must be a power of two between 512 and 32768 inclusive. */
++ nPageSize = decodeUnsigned16(header + kiHeaderPageSizeOffset);
++ if( (nPageSize&(nPageSize-1)) || nPageSize>32768 || nPageSize<512 ){
++ pFile->pMethods->xUnlock(pFile, SQLITE_LOCK_NONE);
++ return rc;
++ }
++
++ /* Space reserved a the end of the page for extensions. Usually 0. */
++ nReservedSize = header[kiHeaderReservedSizeOffset];
++
++ /* 1 for UTF-8, 2 for UTF-16le, 3 for UTF-16be. */
++ iEncoding = decodeUnsigned32(header + kiHeaderEncodingOffset);
++ if( iEncoding==3 ){
++ *piEncoding = SQLITE_UTF16BE;
++ } else if( iEncoding==2 ){
++ *piEncoding = SQLITE_UTF16LE;
++ } else if( iEncoding==1 ){
++ *piEncoding = SQLITE_UTF8;
++ } else {
++ /* This case should not be possible. */
++ *piEncoding = SQLITE_UTF8;
++ }
++
++ rc = pagerCreate(pFile, nPageSize, &pPager);
++ if( rc!=SQLITE_OK ){
++ pFile->pMethods->xUnlock(pFile, SQLITE_LOCK_NONE);
++ return rc;
+ }
+
-+ *pPager = sqlite3BtreePager(pBt);
-+ *pnPageSize =
-+ sqlite3BtreeGetPageSize(pBt) - sqlite3BtreeGetOptimalReserve(pBt);
++ *ppPager = pPager;
++ *pnPageSize = nPageSize - nReservedSize;
++ *piEncoding = iEncoding;
+ return SQLITE_OK;
+}
+
@@ -548,7 +703,7 @@ index 0000000..8e3929d
+ case 7 : return 8; /* 64-bit float. */
+ case 8 : return 0; /* Constant 0. */
+ case 9 : return 0; /* Constant 1. */
-+ case 10 : case 11 : assert( !"RESERVED TYPE"); return 0;
++ case 10 : case 11 : assert( "RESERVED TYPE"==NULL ); return 0;
+ }
+ return (u32)((iSerialType>>1) - 6);
+}
@@ -574,8 +729,8 @@ index 0000000..8e3929d
+ case 7 : return (mask&MASK_FLOAT)!=0;
+ case 8 : return (mask&MASK_INTEGER)!=0;
+ case 9 : return (mask&MASK_INTEGER)!=0;
-+ case 10 : assert( !"RESERVED TYPE"); return 0;
-+ case 11 : assert( !"RESERVED TYPE"); return 0;
++ case 10 : assert( "RESERVED TYPE"==NULL ); return 0;
++ case 11 : assert( "RESERVED TYPE"==NULL ); return 0;
+ }
+ return (mask&(SerialTypeIsBlob(iSerialType) ? MASK_BLOB : MASK_TEXT));
+}
@@ -651,57 +806,6 @@ index 0000000..8e3929d
+ return rc;
+}
+
-+static int getEncoding(sqlite3 *db, const char *zDb, int* piEncoding){
-+ sqlite3_stmt *pStmt;
-+ int rc;
-+ char *zSql = sqlite3_mprintf("PRAGMA %s.encoding", zDb);
-+ if( !zSql ){
-+ return SQLITE_NOMEM;
-+ }
-+
-+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
-+ sqlite3_free(zSql);
-+ if( rc!=SQLITE_OK ){
-+ return rc;
-+ }
-+
-+ /* Require a result. */
-+ rc = sqlite3_step(pStmt);
-+ if( rc==SQLITE_DONE ){
-+ /* This case should not be possible. */
-+ rc = SQLITE_CORRUPT;
-+ }else if( rc==SQLITE_ROW ){
-+ if( sqlite3_column_type(pStmt, 0)==SQLITE_TEXT ){
-+ const char* z = (const char *)sqlite3_column_text(pStmt, 0);
-+ /* These strings match the literals in pragma.c. */
-+ if( !strcmp(z, "UTF-16le") ){
-+ *piEncoding = SQLITE_UTF16LE;
-+ }else if( !strcmp(z, "UTF-16be") ){
-+ *piEncoding = SQLITE_UTF16BE;
-+ }else if( !strcmp(z, "UTF-8") ){
-+ *piEncoding = SQLITE_UTF8;
-+ }else{
-+ /* This case should not be possible. */
-+ *piEncoding = SQLITE_UTF8;
-+ }
-+ }else{
-+ /* This case should not be possible. */
-+ *piEncoding = SQLITE_UTF8;
-+ }
-+
-+ /* Require only one result. */
-+ rc = sqlite3_step(pStmt);
-+ if( rc==SQLITE_DONE ){
-+ rc = SQLITE_OK;
-+ }else if( rc==SQLITE_ROW ){
-+ /* This case should not be possible. */
-+ rc = SQLITE_CORRUPT;
-+ }
-+ }
-+ sqlite3_finalize(pStmt);
-+ return rc;
-+}
-+
+/* Cursor for iterating interior nodes. Interior page cells contain a
+ * child page number and a rowid. The child page contains items left
+ * of the rowid (less than). The rightmost page of the subtree is
@@ -737,7 +841,7 @@ index 0000000..8e3929d
+typedef struct RecoverInteriorCursor RecoverInteriorCursor;
+struct RecoverInteriorCursor {
+ RecoverInteriorCursor *pParent; /* Parent node to this node. */
-+ DbPage *pPage; /* Reference to leaf page. */
++ RecoverPage *pPage; /* Reference to leaf page. */
+ unsigned nPageSize; /* Size of page. */
+ unsigned nChildren; /* Number of children on the page. */
+ unsigned iChild; /* Index of next child to return. */
@@ -750,7 +854,7 @@ index 0000000..8e3929d
+ pCursor = pCursor->pParent;
+
+ if( p->pPage ){
-+ sqlite3PagerUnref(p->pPage);
++ pageDestroy(p->pPage);
+ p->pPage = NULL;
+ }
+
@@ -761,13 +865,13 @@ index 0000000..8e3929d
+
+/* Internal helper. Reset storage in preparation for iterating pPage. */
+static void interiorCursorSetPage(RecoverInteriorCursor *pCursor,
-+ DbPage *pPage){
++ RecoverPage *pPage){
+ const unsigned knMinCellLength = 2 + 4 + 1;
+ unsigned nMaxChildren;
+ assert( PageHeader(pPage)[kiPageTypeOffset]==kTableInteriorPage );
+
+ if( pCursor->pPage ){
-+ sqlite3PagerUnref(pCursor->pPage);
++ pageDestroy(pCursor->pPage);
+ pCursor->pPage = NULL;
+ }
+ pCursor->pPage = pPage;
@@ -798,7 +902,7 @@ index 0000000..8e3929d
+}
+
+static int interiorCursorCreate(RecoverInteriorCursor *pParent,
-+ DbPage *pPage, int nPageSize,
++ RecoverPage *pPage, int nPageSize,
+ RecoverInteriorCursor **ppCursor){
+ RecoverInteriorCursor *pCursor =
+ sqlite3_malloc(sizeof(RecoverInteriorCursor));
@@ -883,7 +987,7 @@ index 0000000..8e3929d
+ * reverse the list during traversal.
+ */
+static int interiorCursorNextPage(RecoverInteriorCursor **ppCursor,
-+ DbPage **ppPage){
++ RecoverPage **ppPage){
+ RecoverInteriorCursor *pCursor = *ppCursor;
+ while( 1 ){
+ int rc;
@@ -896,7 +1000,7 @@ index 0000000..8e3929d
+ if( interiorCursorPageInUse(pCursor, iPage) ){
+ fprintf(stderr, "Loop detected at %d\n", iPage);
+ }else{
-+ int rc = sqlite3PagerGet(pCursor->pPage->pPager, iPage, ppPage, 0);
++ int rc = pagerGetPage(pCursor->pPage->pPager, iPage, ppPage);
+ if( rc==SQLITE_OK ){
+ return SQLITE_ROW;
+ }
@@ -950,7 +1054,7 @@ index 0000000..8e3929d
+typedef struct RecoverOverflow RecoverOverflow;
+struct RecoverOverflow {
+ RecoverOverflow *pNextOverflow;
-+ DbPage *pPage;
++ RecoverPage *pPage;
+ unsigned nPageSize;
+};
+
@@ -960,7 +1064,7 @@ index 0000000..8e3929d
+ pOverflow = p->pNextOverflow;
+
+ if( p->pPage ){
-+ sqlite3PagerUnref(p->pPage);
++ pageDestroy(p->pPage);
+ p->pPage = NULL;
+ }
+
@@ -987,7 +1091,7 @@ index 0000000..8e3929d
+ * overflowGetSegment() will do the right thing regardless of whether
+ * those values are set to be in-page or not.
+ */
-+static int overflowMaybeCreate(DbPage *pPage, unsigned nPageSize,
++static int overflowMaybeCreate(RecoverPage *pPage, unsigned nPageSize,
+ unsigned iRecordOffset, unsigned nRecordBytes,
+ unsigned *pnLocalRecordBytes,
+ RecoverOverflow **ppOverflow){
@@ -1040,14 +1144,14 @@ index 0000000..8e3929d
+ while( iNextPage && nBytes<nRecordBytes ){
+ RecoverOverflow *pOverflow; /* New overflow page for the list. */
+
-+ rc = sqlite3PagerGet(pPage->pPager, iNextPage, &pPage, 0);
++ rc = pagerGetPage(pPage->pPager, iNextPage, &pPage);
+ if( rc!=SQLITE_OK ){
+ break;
+ }
+
+ pOverflow = sqlite3_malloc(sizeof(RecoverOverflow));
+ if( !pOverflow ){
-+ sqlite3PagerUnref(pPage);
++ pageDestroy(pPage);
+ rc = SQLITE_NOMEM;
+ break;
+ }
@@ -1111,7 +1215,7 @@ index 0000000..8e3929d
+ * and overflow pages consistently by adjusting the values
+ * appropriately.
+ */
-+static int overflowGetSegment(DbPage *pPage, unsigned iRecordOffset,
++static int overflowGetSegment(RecoverPage *pPage, unsigned iRecordOffset,
+ unsigned nLocalRecordBytes,
+ RecoverOverflow *pOverflow,
+ unsigned iRequestOffset, unsigned nRequestBytes,
@@ -1218,7 +1322,8 @@ index 0000000..8e3929d
+typedef struct RecoverLeafCursor RecoverLeafCursor;
+struct RecoverLeafCursor {
+ RecoverInteriorCursor *pParent; /* Parent node to this node. */
-+ DbPage *pPage; /* Reference to leaf page. */
++ RecoverPager *pPager; /* Page provider. */
++ RecoverPage *pPage; /* Current leaf page. */
+ unsigned nPageSize; /* Size of pPage. */
+ unsigned nCells; /* Number of cells in pPage. */
+ unsigned iCell; /* Current cell. */
@@ -1253,12 +1358,13 @@ index 0000000..8e3929d
+ * If SQLITE_OK is returned, the caller no longer owns pPage,
+ * otherwise the caller is responsible for discarding it.
+ */
-+static int leafCursorLoadPage(RecoverLeafCursor *pCursor, DbPage *pPage){
++static int leafCursorLoadPage(RecoverLeafCursor *pCursor, RecoverPage *pPage){
+ const unsigned char *pPageHeader; /* Header of *pPage */
++ unsigned nCells; /* Number of cells in the page */
+
+ /* Release the current page. */
+ if( pCursor->pPage ){
-+ sqlite3PagerUnref(pCursor->pPage);
++ pageDestroy(pCursor->pPage);
+ pCursor->pPage = NULL;
+ pCursor->iCell = pCursor->nCells = 0;
+ }
@@ -1280,14 +1386,21 @@ index 0000000..8e3929d
+
+ /* Not a leaf page, skip it. */
+ if( pPageHeader[kiPageTypeOffset]!=kTableLeafPage ){
-+ sqlite3PagerUnref(pPage);
++ pageDestroy(pPage);
++ return SQLITE_OK;
++ }
++
++ /* Leaf contains no data, skip it. Empty tables, for instance. */
++ nCells = decodeUnsigned16(pPageHeader + kiPageCellCountOffset);;
++ if( nCells<1 ){
++ pageDestroy(pPage);
+ return SQLITE_OK;
+ }
+
+ /* Take ownership of the page and start decoding. */
+ pCursor->pPage = pPage;
+ pCursor->iCell = 0;
-+ pCursor->nCells = decodeUnsigned16(pPageHeader + kiPageCellCountOffset);
++ pCursor->nCells = nCells;
+ return SQLITE_OK;
+}
+
@@ -1302,7 +1415,7 @@ index 0000000..8e3929d
+
+ /* Repeatedly load the parent's next child page until a leaf is found. */
+ do {
-+ DbPage *pNextPage;
++ RecoverPage *pNextPage;
+ int rc = interiorCursorNextPage(&pCursor->pParent, &pNextPage);
+ if( rc!=SQLITE_ROW ){
+ assert( rc==SQLITE_DONE );
@@ -1311,7 +1424,7 @@ index 0000000..8e3929d
+
+ rc = leafCursorLoadPage(pCursor, pNextPage);
+ if( rc!=SQLITE_OK ){
-+ sqlite3PagerUnref(pNextPage);
++ pageDestroy(pNextPage);
+ return rc;
+ }
+ } while( !pCursor->pPage );
@@ -1341,10 +1454,15 @@ index 0000000..8e3929d
+ }
+
+ if( pCursor->pPage ){
-+ sqlite3PagerUnref(pCursor->pPage);
++ pageDestroy(pCursor->pPage);
+ pCursor->pPage = NULL;
+ }
+
++ if( pCursor->pPager ){
++ pagerDestroy(pCursor->pPager);
++ pCursor->pPager = NULL;
++ }
++
+ memset(pCursor, 0xA5, sizeof(*pCursor));
+ sqlite3_free(pCursor);
+}
@@ -1362,30 +1480,31 @@ index 0000000..8e3929d
+ * - pPage is a valid interior page who's leaves contain no valid cells.
+ * - pPage is not a valid leaf or interior page.
+ */
-+static int leafCursorCreate(Pager *pPager, unsigned nPageSize,
++static int leafCursorCreate(RecoverPager *pPager, unsigned nPageSize,
+ u32 iRootPage, RecoverLeafCursor **ppCursor){
-+ DbPage *pPage; /* Reference to page at iRootPage. */
++ RecoverPage *pPage; /* Reference to page at iRootPage. */
+ RecoverLeafCursor *pCursor; /* Leaf cursor being constructed. */
+ int rc;
+
+ /* Start out with the root page. */
-+ rc = sqlite3PagerGet(pPager, iRootPage, &pPage, 0);
++ rc = pagerGetPage(pPager, iRootPage, &pPage);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ pCursor = sqlite3_malloc(sizeof(RecoverLeafCursor));
+ if( !pCursor ){
-+ sqlite3PagerUnref(pPage);
++ pageDestroy(pPage);
+ return SQLITE_NOMEM;
+ }
+ memset(pCursor, 0, sizeof(*pCursor));
+
+ pCursor->nPageSize = nPageSize;
++ pCursor->pPager = pPager;
+
+ rc = leafCursorLoadPage(pCursor, pPage);
+ if( rc!=SQLITE_OK ){
-+ sqlite3PagerUnref(pPage);
++ pageDestroy(pPage);
+ leafCursorDestroy(pCursor);
+ return rc;
+ }
@@ -1728,7 +1847,7 @@ index 0000000..8e3929d
+ u32 iRootPage; /* Root page of the backing table. */
+ int iEncoding; /* UTF encoding for backing database. */
+ unsigned nPageSize; /* Size of pages in backing database. */
-+ Pager *pPager; /* Backing database pager. */
++ RecoverPager *pPager; /* Backing database pager. */
+ RecoverLeafCursor *pLeafCursor; /* Cursor to read table's leaf pages. */
+ RecoverCursor *pCursor; /* Cursor to read rows from leaves. */
+ int rc;
@@ -1742,19 +1861,14 @@ index 0000000..8e3929d
+ return rc;
+ }
+
-+ iEncoding = 0;
-+ rc = getEncoding(pRecover->db, pRecover->zDb, &iEncoding);
-+ if( rc!=SQLITE_OK ){
-+ return rc;
-+ }
-+
-+ rc = GetPager(pRecover->db, pRecover->zDb, &pPager, &nPageSize);
++ rc = GetPager(pRecover->db, pRecover->zDb, &pPager, &nPageSize, &iEncoding);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ rc = leafCursorCreate(pPager, nPageSize, iRootPage, &pLeafCursor);
+ if( rc!=SQLITE_OK ){
++ pagerDestroy(pPager);
+ return rc;
+ }
+
@@ -2311,10 +2425,10 @@ index e5673fd..6829bcb 100644
** DO NOT EXTEND THE USE OF THIS.
diff --git a/third_party/sqlite/src/test/recover.test b/third_party/sqlite/src/test/recover.test
new file mode 100644
-index 0000000..b5aa182
+index 0000000..bfb7888
--- /dev/null
+++ b/third_party/sqlite/src/test/recover.test
-@@ -0,0 +1,147 @@
+@@ -0,0 +1,164 @@
+# 2012 January 11 {}
+#
+# The author disclaims copyright to this source code. In place of
@@ -2427,6 +2541,23 @@ index 0000000..b5aa182
+ execsql {SELECT t, n FROM leaf_recover ORDER BY rowid}
+} {{Leaf-node-generating line 0} 0 {Leaf-node-generating line 1} 1 {Leaf-node-generating line 2} 2 {Leaf-node-generating line 3} 3 {Leaf-node-generating line 4} 4 {Leaf-node-generating line 5} 5 {Leaf-node-generating line 6} 6 {Leaf-node-generating line 7} 7 {Leaf-node-generating line 8} 8 {Leaf-node-generating line 9} 9}
+
++# Empty table gives empty results.
++do_test recover-leaf-2.0 {
++ db close
++ sqlite3 db test.db
++ generate "empty" "Leaf-node-generating line " 0
++
++ db eval {
++ DROP TABLE IF EXISTS temp.leaf_recover;
++ CREATE VIRTUAL TABLE temp.leaf_recover USING recover(
++ empty,
++ t TEXT,
++ n INTEGER
++ );
++ }
++ execsql {SELECT t, n FROM leaf_recover ORDER BY rowid}
++} {}
++
+# Single level of interior node.
+do_test recover-interior-1.0 {
+ db close