Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(305)

Unified Diff: third_party/sqlite/src/src/recover.c

Issue 9110047: The complete work-in-progress SQLite recover virtual table. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/sqlite/src/src/pager.c ('k') | third_party/sqlite/src/src/shell.c » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/sqlite/src/src/recover.c
diff --git a/third_party/sqlite/src/src/recover.c b/third_party/sqlite/src/src/recover.c
new file mode 100644
index 0000000000000000000000000000000000000000..503aec84ad3c111e8492e712ada448505c7a915f
--- /dev/null
+++ b/third_party/sqlite/src/src/recover.c
@@ -0,0 +1,2116 @@
+/* TODO(shess): THIS MODULE IS STILL EXPERIMENTAL. DO NOT USE IT. */
+/* Implements a virtual table "recover" which can be used to recover
+ * data from a corrupt table. The table is walked manually, with
+ * corrupt items skipped. Additionally, any errors while reading will
+ * be skipped.
+ *
+ * Given a table with this definition:
+ *
+ * CREATE TABLE Stuff (
+ * name TEXT PRIMARY KEY,
+ * value TEXT NOT NULL
+ * );
+ *
+ * to recover the data from teh table, you could do something like:
+ *
+ * -- Attach another database, the original is not trustworthy.
+ * ATTACH DATABASE '/tmp/db.db' AS rdb;
+ * -- Create a new version of the table.
+ * CREATE TABLE rdb.Stuff (
+ * name TEXT PRIMARY KEY,
+ * value TEXT NOT NULL
+ * );
+ * -- This will read the original table's data.
+ * CREATE VIRTUAL TABLE temp.recover_Stuff using recover(
+ * main.Stuff,
+ * name TEXT STRICT NOT NULL, -- only real TEXT data allowed
+ * value TEXT STRICT NOT NULL
+ * );
+ * -- Corruption means the UNIQUE constraint may no longer hold for
+ * -- Stuff, so either OR REPLACE or OR IGNORE must be used.
+ * INSERT OR REPLACE INTO rdb.Stuff (rowid, name, value )
+ * SELECT rowid, name, value FROM temp.recover_Stuff;
+ * DROP TABLE temp.recover_Stuff;
+ * DETACH DATABASE rdb;
+ * -- Move db.db to replace original db in filesystem.
+ *
+ *
+ * Usage
+ *
+ * Given the goal of dealing with corruption, it would not be safe to
+ * create a recovery table in the database being recovered. So
+ * recovery tables must be created in the temp database. They are not
+ * appropriate to persist, in any case. [As a bonus, sqlite_master
+ * tables can be recovered. Perhaps more cute than useful, though.]
+ *
+ * The parameters are a specifier for the table to read, and a column
+ * definition for each bit of data stored in that table. The named
+ * table must be convertable to a root page number by reading the
+ * sqlite_master table. Bare table names are assumed to be in
+ * database 0 ("main"), other databases can be specified in db.table
+ * fashion.
+ *
+ * Column definitions are similar to BUT NOT THE SAME AS those
+ * provided to CREATE statements:
+ * column-def: column-name [type-name [STRICT] [NOT NULL]]
+ * type-name: (ANY|ROWID|INTEGER|FLOAT|NUMERIC|TEXT|BLOB)
+ *
+ * Only those exact type names are accepted, there is no type
+ * intuition. The only constraints accepted are STRICT (see below)
+ * and NOT NULL. Anything unexpected will cause the create to fail.
+ *
+ * ANY is a convenience to indicate that manifest typing is desired.
+ * It is equivalent to not specifying a type at all. The results for
+ * such columns will have the type of the data's storage. The exposed
+ * schema will contain no type for that column.
+ *
+ * ROWID is used for columns representing aliases to the rowid
+ * (INTEGER PRIMARY KEY, with or without AUTOINCREMENT), to make the
+ * concept explicit. Such columns are actually stored as NULL, so
+ * they cannot be simply ignored. The exposed schema will be INTEGER
+ * for that column.
+ *
+ * NOT NULL causes rows with a NULL in that column to be skipped. It
+ * also adds NOT NULL to the column in the exposed schema. If the
+ * table has ever had columns added using ALTER TABLE, then those
+ * columns implicitly contain NULL for rows which have not been
+ * updated. [Workaround using COALESCE() in your SELECT statement.]
+ *
+ * The created table is read-only, with no indices. Any SELECT will
+ * be a full-table scan, returning each valid row read from the
+ * storage of the backing table. The rowid will be the rowid of the
+ * row from the backing table. "Valid" means:
+ * - The cell metadata for the row is well-formed. Mainly this means that
+ * the cell header info describes a payload of the size indicated by
+ * the cell's payload size.
+ * - The cell does not run off the page.
+ * - The cell does not overlap any other cell on the page.
+ * - The cell contains doesn't contain too many columns.
+ * - The types of the serialized data match the indicated types (see below).
+ *
+ *
+ * Type affinity versus type storage.
+ *
+ * http://www.sqlite.org/datatype3.html describes SQLite's type
+ * affinity system. The system provides for automated coercion of
+ * types in certain cases, transparently enough that many developers
+ * do not realize that it is happening. Importantly, it implies that
+ * the raw data stored in the database may not have the obvious type.
+ *
+ * Differences between the stored data types and the expected data
+ * types may be a signal of corruption. This module makes some
+ * allowances for automatic coercion. It is important to be concious
+ * of the difference between the schema exposed by the module, and the
+ * data types read from storage. The following table describes how
+ * the module interprets things:
+ *
+ * type schema data STRICT
+ * ---- ------ ---- ------
+ * ANY <none> any any
+ * ROWID INTEGER n/a n/a
+ * INTEGER INTEGER integer integer
+ * FLOAT FLOAT integer or float float
+ * NUMERIC NUMERIC integer, float, or text integer or float
+ * TEXT TEXT text or blob text
+ * BLOB BLOB blob blob
+ *
+ * type is the type provided to the recover module, schema is the
+ * schema exposed by the module, data is the acceptable types of data
+ * decoded from storage, and STRICT is a modification of that.
+ *
+ * A very loose recovery system might use ANY for all columns, then
+ * use the appropriate sqlite3_column_*() calls to coerce to expected
+ * types. This doesn't provide much protection if a page from a
+ * different table with the same column count is linked into an
+ * inappropriate btree.
+ *
+ * A very tight recovery system might use STRICT to enforce typing on
+ * all columns, preferring to skip rows which are valid at the storage
+ * level but don't contain the right types. Note that FLOAT STRICT is
+ * almost certainly not appropriate, since integral values are
+ * transparently stored as integers, when that is more efficient.
+ *
+ * Another option is to use ANY for all columns and inspect each
+ * result manually (using sqlite3_column_*). This should only be
+ * necessary in cases where developers have used manifest typing (test
+ * to make sure before you decide that you aren't using manifest
+ * typing!).
+ *
+ *
+ * Caveats
+ *
+ * Leaf pages not referenced by interior nodes will not be found.
+ *
+ * Leaf pages referenced from interior nodes of other tables will not
+ * be resolved.
+ *
+ * Rows referencing invalid overflow pages will be skipped.
+ *
+ * SQlite rows have a header which describes how to interpret the rest
+ * of the payload. The header can be valid in cases where the rest of
+ * the record is actually corrupt (in the sense that the data is not
+ * the intended data). This can especially happen WRT overflow pages,
+ * as lack of atomic updates between pages is the primary form of
+ * corruption I have seen in the wild.
+ */
+/* Also provided are simple functions which can be used to explore a
+ * SQLite database file. hexdump(pageno) provides a hex/ascii dump of
+ * page #pageno, with all-0x00 lines elided. dumppage(pageno)
+ * attempts to provide a descriptive dump of all data on that page,
+ * while dumppage(pageno, "header") only dumps the header info.
+ *
+ * TODO(shess): These functions are incomplete!
+ */
+/* The implementation is via a series of nested cursors. The
+ * cursors are implemented with this pattern:
+ *
+ * // Creates the cursor using various initialization info.
+ * int cursorCreate(...);
+ *
+ * // Returns 1 if there is no more data, 0 otherwise.
+ * int cursorEOF(Cursor *pCursor);
+ *
+ * // Various accessors can be used if not at EOF.
+ *
+ * // Move to the next item.
+ * int cursorNext(Cursor *pCursor);
+ *
+ * // Destroy the memory associated with the cursor.
+ * void cursorDestroy(Cursor *pCursor);
+ *
+ * References in the following ar to sections at
+ * http://www.sqlite.org/fileformat2.html .
+ *
+ * RecoverLeafCursor iterates the records in a leaf table node
+ * described in section 1.5 "B-tree Pages".
+ *
+ * RecoverInteriorCursor iterates the child pages in an interior table
+ * node described in section 1.5 "B-tree Pages".
+ *
+ * RecoverCursor pulls these together to iterate the rows of a table,
+ * returning results via the SQLite virtual table interface.
+ */
+/* TODO(shess): Some unlinked leaf pages could potentially be found by
+ * looking at the pointer map pages to find pages which reference an
+ * interior node but which that node does not reference.
+ */
+/* TODO(shess): This code assumes UTF8 databases. Add handling for
+ * UTF16le and UTF16be.
+ */
+/* TODO(shess): The code doesn't monitor the freelist, so it can't
+ * enforce that cells don't overlap free areas.
+ */
+/* TODO(shess): The code is pretty lax WRT interior nodes. It only
+ * reads the 32-bit child page number from the cell, ignoring the
+ * rowid. This is probably fine, though.
+ */
+/* TODO(shess): Use auto_vacuum information to figure out which table
+ * a page is from.
+ */
+/* TODO(shess): Review to convert recursive routines into iterative as
+ * appropriate.
+ */
+/* TODO(shess): It might be useful to allow DEFAULT in types to
+ * specify what to do for NULL. Unfortunately, simply adding it to
+ * the exposed schema and using sqlite3_result_null() does not cause
+ * the default to be generate. Handling it ourselves seems hard,
+ * unfortunately.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Internal things that are used:
+ * u32, u64, i64 types.
+ * Btree, Pager, and DbPage structs.
+ * DbPage.pData, .pPager, and .pgno
+ * sqlite3 struct.
+ * sqlite3BtreePager() and sqlite3BtreeGetPageSize()
+ * sqlite3PagerAcquire() and sqlite3PagerUnref()
+ * getVarint32() and getVarint().
+ */
+#include "sqliteInt.h"
+
+/* For debugging. */
+#if 0
+#define FNENTRY() fprintf(stderr, "In %s\n", __FUNCTION__)
+#else
+#define FNENTRY()
+#endif
+
+/* Generic constants and helper functions. */
+
+static const unsigned char kTableLeafPage = 0x0D;
+static const unsigned char kTableInteriorPage = 0x05;
+
+/* Accepted types are specified by a mask. Conveniently, SQLite's
+ * data-storage types use small integers from 1 to 5...
+ */
+static const unsigned char kMaskNull = (1<<SQLITE_NULL);
+static const unsigned char kMaskInteger = (1<<SQLITE_INTEGER);
+static const unsigned char kMaskFloat = (1<<SQLITE_FLOAT);
+static const unsigned char kMaskBlob = (1<<SQLITE_BLOB);
+static const unsigned char kMaskText = (1<<SQLITE_TEXT);
+static const unsigned char kMaskRowid = 1;
+
+/* Helpers to decode fixed-size fields. */
+static unsigned decodeUnsigned16(const unsigned char *pData){
+ return (pData[0]<<8) + pData[1];
+}
+static unsigned decodeUnsigned32(const unsigned char *pData){
+ return (pData[0]<<24) + (pData[1]<<16) + (pData[2]<<8) + pData[3];
+}
+static int decodeInt8(const unsigned char *pData){
+ return (int)(*pData);
+}
+static int decodeInt16(const unsigned char *pData){
+ return (decodeInt8(pData)<<8) + pData[1];
+}
+static int decodeInt24(const unsigned char *pData){
+ return (decodeInt16(pData)<<8) + pData[2];
+}
+static int decodeInt32(const unsigned char *pData){
+ return (decodeInt24(pData)<<8) + pData[3];
+}
+static i64 decodeInt48(const unsigned char *pData){
+ return (((i64)decodeInt32(pData))<<16) + (pData[4]<<8) + pData[5];
+}
+static i64 decodeInt64(const unsigned char *pData){
+ return (decodeInt48(pData)<<16) + (pData[6]<<8) + pData[7];
+}
+static double decodeFloat64(const unsigned char *pData){
+ u64 x = decodeInt64(pData);
+ return *((double*)(&x));
+}
+
+/* Return 1 if c varints can be read from pData/nData. */
+static int checkVarints(const unsigned char *pData, unsigned nData,
+ unsigned n){
+ /* In the worst case the decoder takes all 8 bits of the 9th byte. */
+ if( nData>=9*n ){
+ return 1;
+ }
+
+ unsigned nCur = 0, nFound = 0;
+ unsigned i;
+ for( i=0; nFound<n && i<nData; ++i ){
+ nCur++;
+ if( nCur==9 || !(pData[i]&0x80) ){
+ nFound++;
+ nCur = 0;
+ }
+ }
+
+ return nFound==n;
+}
+static int checkVarint(const unsigned char *pData, unsigned nData){
+ /* In the worst case the decoder takes all 8 bits of the 9th byte. */
+ if( nData>=9 ){
+ return 1;
+ }
+
+ /* Look for a high-bit-clear byte in what's left. */
+ unsigned i;
+ for( i=0; i<nData; ++i ){
+ if( !(pData[i]&0x80) ){
+ return 1;
+ }
+ }
+
+ /* Cannot decode in the space given. */
+ return 0;
+}
+
+static const unsigned char *PageData(DbPage *pPage, unsigned iOffset){
+ assert( iOffset<=pPage->nPageSize );
+ return pPage->pData + iOffset;
+}
+static const unsigned char *PageHeader(DbPage *pPage){
+ if( pPage->pgno==1 ){
+ return PageData(pPage, 100);
+ }else{
+ return PageData(pPage, 0);
+ }
+}
+
+/* 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( !strcasecmp(db->aDb[i].zName, zName) ){
+ pBt = db->aDb[i].pBt;
+ break;
+ }
+ }
+ if( !pBt ){
+ return SQLITE_ERROR;
+ }
+
+ *pPager = sqlite3BtreePager(pBt);
+ *pnPageSize = sqlite3BtreeGetPageSize(pBt);
+ return SQLITE_OK;
+}
+
+/* iSerialType is a type read from a record header. See "2.1 Record Format".
+ * http://www.sqlite.org/fileformat2.html#record_format
+ */
+static u64 SerialTypeLength(u64 iSerialType){
+ switch( iSerialType ){
+ case 0 : return 0; /* NULL */
+ case 1 : return 1; /* Various integers. */
+ case 2 : return 2;
+ case 3 : return 3;
+ case 4 : return 4;
+ case 5 : return 6;
+ case 6 : return 8;
+ 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;
+ default : /* TEXT or BLOB. */
+ return (iSerialType>>1) - 6;
+ }
+ assert( !"UNKNOWN TYPE");
+ return 0;
+}
+/* True if iSerialType refers to a blob. */
+static int SerialTypeIsBlob(u64 iSerialType){
+ assert( iSerialType>=12 );
+ return (iSerialType%2)==0;
+}
+
+/* Returns true if the serialized type represented by iSerialType is
+ * compatible with the given type mask.
+ */
+static int SerialTypeIsCompatible(u64 iSerialType, unsigned char mask){
+ unsigned char check = 0;
+ switch( iSerialType ){
+ case 0 : check = kMaskNull; break;
+
+ case 1 : case 2 : case 3 : case 4 : case 5 : case 6 :
+ case 8 : case 9 : check = kMaskInteger; break;
+
+ case 7 : check = kMaskFloat; break;
+
+ case 10 : case 11 : assert( !"RESERVED TYPE"); break;
+
+ default :
+ check = SerialTypeIsBlob(iSerialType) ? kMaskBlob : kMaskText;
+ break;
+ }
+ return (mask&check)!=0;
+}
+
+/* Versions of strdup() with return values appropriate for
+ * sqlite3_free(). malloc.c has sqlite3DbStrDup()/NDup(), but those
+ * need sqlite3DbFree(), which seems intrusive.
+ */
+static char *sqlite3_strndup(const char *z, unsigned n){
+ if( z==NULL ){
+ return NULL;
+ }
+
+ char *zNew = sqlite3_malloc(n+1);
+ if( zNew!=NULL ){
+ memcpy(zNew, z, n);
+ zNew[n] = '\0';
+ }
+ return zNew;
+}
+static char *sqlite3_strdup(const char *z){
+ if( z==NULL ){
+ return NULL;
+ }
+ return sqlite3_strndup(z, strlen(z));
+}
+
+/* Fetch the page number of zTable in zDb from sqlite_master in zDb,
+ * and put it in *piRootPage.
+ */
+static int getRootPage(sqlite3 *db, const char *zDb, const char *zTable,
+ unsigned *piRootPage){
+ if( !strcmp(zTable, "sqlite_master") ){
+ *piRootPage = 1;
+ return SQLITE_OK;
+ }
+
+ char *zSql = sqlite3_mprintf("SELECT rootpage FROM %s.sqlite_master "
+ "WHERE type = 'table' AND tbl_name = %Q",
+ zDb, zTable);
+ if( !zSql ){
+ return SQLITE_NOMEM;
+ }
+
+ sqlite3_stmt *pStmt = 0;
+ int 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 ){
+ rc = SQLITE_CORRUPT;
+ }else if( rc==SQLITE_ROW ){
+ *piRootPage = sqlite3_column_int(pStmt, 0);
+
+ /* Require only one result. */
+ rc = sqlite3_step(pStmt);
+ if( rc==SQLITE_DONE ){
+ rc = SQLITE_OK;
+ }else if( rc==SQLITE_ROW ){
+ rc = SQLITE_CORRUPT;
+ }
+ }
+ sqlite3_finalize(pStmt);
+ return rc;
+}
+
+/* Cursor for iterating the table interior nodes. Each cell contains
+ * a page number and a rowid. The child page contains items left of
+ * the rowid (less than). The rightmost page of the subtree is stored
+ * in the header.
+ *
+ * interiorCursorDestroy - release all resources associated with the
+ * cursor and any parent cursors.
+ * interiorCursorCreate - create a cursor with the given parent and page.
+ * interiorCursorEOF - returns true if neither the cursor nor the
+ * parent cursors can return any more data.
+ * interiorCursorNextPage - fetch the next child page from the cursor.
+ *
+ * interiorCursorNextPage() returns the next child page at the
+ * cursor's level in the table tree. If the cursor runs out of cells,
+ * the parent is called to provide new nodes at the same level, until
+ * all levels are exhausted. Non-table pages at the cursor's depth
+ * are skipped, as are pages which would cause a loop, unexpected leaf
+ * table pages are returned to the caller, interior table pages are
+ * loaded and interated. Returns SQLITE_ROW if a child page is
+ * returned. SQLITE_DONE can be returned even if interiorCursorEOF()
+ * returned false, for instance if a parent cursor returns a final
+ * non-table page.
+ *
+ * Note that while interiorCursorNextPage() will refuse to follow
+ * loops, it does not keep track of pages returned for purposes of
+ * preventing duplication.
+ */
+typedef struct RecoverInteriorCursor RecoverInteriorCursor;
+struct RecoverInteriorCursor {
+ RecoverInteriorCursor *pParent; /* Parent node to this node. */
+ DbPage *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. */
+};
+
+static void interiorCursorDestroy(RecoverInteriorCursor *pCursor){
+ while( pCursor ){
+ RecoverInteriorCursor *p = pCursor;
+ pCursor = pCursor->pParent;
+
+ if( p->pPage ){
+ sqlite3PagerUnref(p->pPage);
+ p->pPage = NULL;
+ }
+
+ memset(p, 0xA5, sizeof(*p));
+ sqlite3_free(p);
+ }
+}
+
+/* Internal helper. Reset storage in preparation for iterating pPage. */
+static void interiorCursorSetPage(RecoverInteriorCursor *pCursor,
+ DbPage *pPage){
+ if( pCursor->pPage ){
+ sqlite3PagerUnref(pCursor->pPage);
+ pCursor->pPage = NULL;
+ }
+ pCursor->pPage = pPage;
+ pCursor->iChild = 0;
+
+ /* A child for each cell, plus one in the header. */
+ pCursor->nChildren = decodeUnsigned16(PageHeader(pPage) + 3) + 1;
+}
+
+static int interiorCursorCreate(RecoverInteriorCursor *pParent,
+ DbPage *pPage, int nPageSize,
+ RecoverInteriorCursor **ppCursor){
+ RecoverInteriorCursor *pCursor =
+ sqlite3_malloc(sizeof(RecoverInteriorCursor));
+ if( !pCursor ){
+ return SQLITE_NOMEM;
+ }
+
+ memset(pCursor, 0, sizeof(*pCursor));
+ pCursor->pParent = pParent;
+ pCursor->nPageSize = nPageSize;
+ interiorCursorSetPage(pCursor, pPage);
+ *ppCursor = pCursor;
+ return SQLITE_OK;
+}
+
+/* Internal helper. Return the child page number at iChild. */
+static unsigned interiorCursorChildPage(RecoverInteriorCursor *pCursor){
+ assert( pCursor->iChild<pCursor->nChildren );
+
+ /* Rightmost child is in the header. */
+ const unsigned char *pPageHeader = PageHeader(pCursor->pPage);
+ if( pCursor->iChild==pCursor->nChildren-1 ){
+ return decodeUnsigned32(pPageHeader + 8);
+ }
+
+ /* Each cell is a 4-byte integer page number and a varint rowid
+ * which is greater than the rowid of items in that sub-tree. This
+ * code ignores the latter constraint. Note that the offset is from
+ * the beginning of the page, not from the header.
+ */
+ const unsigned char *pOffsets = pPageHeader + 12;
+ unsigned offset = decodeUnsigned16(pOffsets + pCursor->iChild*2);
+ if( offset<=pCursor->nPageSize-4 ){
+ return decodeUnsigned32(PageData(pCursor->pPage, offset));
+ }
+
+ /* If the offset is broken, return an invalid page number. */
+ return 0;
+}
+
+static int interiorCursorEOF(RecoverInteriorCursor *pCursor){
+ while( pCursor ){
+ if( pCursor->iChild<pCursor->nChildren ){
+ return 0;
+ }
+ pCursor = pCursor->pParent;
+ }
+ return 1;
+}
+
+/* Internal helper. Used to detect if iPage would cause a loop. */
+static int interiorCursorPageInUse(RecoverInteriorCursor *pCursor,
+ unsigned iPage){
+ while( pCursor ){
+ if( pCursor->pPage->pgno==iPage ){
+ return 1;
+ }
+ pCursor = pCursor->pParent;
+ }
+ return 0;
+}
+
+/* NOTE(shess): This code may recurse up to the parent. The stack
+ * depth should be proportional to the log of the table size, so it
+ * has not seemed worthwhile to convert it to an iterative form.
+ */
+static int interiorCursorNextPage(RecoverInteriorCursor *pCursor,
+ DbPage **ppPage){
+ while( !interiorCursorEOF(pCursor) ){
+ /* This page has no more children. Get next page from parent. */
+ while( pCursor->iChild>=pCursor->nChildren ){
+ if( !pCursor->pParent ){
+ return SQLITE_DONE;
+ }
+
+ int rc = interiorCursorNextPage(pCursor->pParent, ppPage);
+ if( rc!=SQLITE_ROW ){
+ return rc;
+ }
+
+ const unsigned char *pPageHeader = PageHeader(*ppPage);
+ if( *pPageHeader==kTableLeafPage ){
+ return SQLITE_ROW;
+ }else if( *pPageHeader==kTableInteriorPage ){
+ interiorCursorSetPage(pCursor, *ppPage);
+ *ppPage = NULL;
+ }else{
+ sqlite3PagerUnref(*ppPage);
+ *ppPage = NULL;
+ }
+ }
+
+ /* Find a valid child page which isn't in use. */
+ while( pCursor->iChild<pCursor->nChildren ){
+ const unsigned iPage = interiorCursorChildPage(pCursor);
+ pCursor->iChild++;
+ if( interiorCursorPageInUse(pCursor, iPage) ){
+ fprintf(stderr, "Loop detected at %d\n", iPage);
+ }else{
+ int rc = sqlite3PagerAcquire(pCursor->pPage->pPager, iPage, ppPage, 0);
+ if( rc==SQLITE_OK ){
+ return SQLITE_ROW;
+ }
+ }
+ }
+ }
+ return SQLITE_DONE;
+}
+
+/* Structure for dealing with overflow pages. The row's main page
+ * stores an overflow page number after the local payload, with
+ * overflow pages forming a linked list forward from there.
+ *
+ * overflowDestroy - releases all resources associated with the structure.
+ * overflowMaybeCreate - create the overflow structure if it is needed
+ * to represent the given record. See function comment.
+ * overflowGetSegment - fetch a segment from the record, accounting
+ * for overflow pages. Segments which are not
+ * entirely contained with a page are constructed
+ * into a buffer which is returned. See function comment.
+ */
+/* TODO(shess): Since overflowMaybeCreate() can pass back a NULL, it
+ * might make sense to have overflowDestroy() and overflowGetSegment()
+ * accept a NULL and make appropriate decisions.
+ */
+typedef struct RecoverOverflow RecoverOverflow;
+struct RecoverOverflow {
+ RecoverOverflow *pNextOverflow;
+ DbPage *pPage;
+ unsigned nPageSize;
+};
+
+static void overflowDestroy(RecoverOverflow *pOverflow){
+ while( pOverflow ){
+ RecoverOverflow *p = pOverflow;
+ pOverflow = p->pNextOverflow;
+
+ if( p->pPage ){
+ sqlite3PagerUnref(p->pPage);
+ p->pPage = NULL;
+ }
+
+ memset(p, 0xA5, sizeof(*p));
+ sqlite3_free(p);
+ }
+}
+
+/* The target record paylod begins at iOffset on pPage. If nBytes can
+ * be satisfied entire in-page, then no overflow pages are needed and
+ * *pnLocalBytes is set to nBytes. Otherwise, *ppOverflow is set to
+ * the head of a list of overflow pages, and *pnLocalBytes is set to
+ * the number of bytes local to pPage.
+ */
+static int overflowMaybeCreate(DbPage *pPage, unsigned nPageSize,
+ unsigned iOffset, unsigned nBytes,
+ unsigned *pnLocalBytes,
+ RecoverOverflow **ppOverflow){
+ /* Calculations from section 1.5 of
+ * http://www.sqlite.org/fileformat2.html .
+ */
+ if( nBytes<=nPageSize-35 ){
+ *pnLocalBytes = nBytes;
+ *ppOverflow = NULL;
+ return SQLITE_OK;
+ }
+
+ unsigned m = ((nPageSize-12)*32/255)-23;
+ unsigned nLocalBytes = m + ((nBytes - m)%(nPageSize - 4));
+ if( nPageSize-35<nLocalBytes ){
+ nLocalBytes = nPageSize - 35;
+ }
+
+ /* Don't read off the end of the page. */
+ if( iOffset+nLocalBytes+4>nPageSize ){
+ return SQLITE_CORRUPT;
+ }
+
+ unsigned iNextPage = decodeUnsigned32(PageData(pPage, iOffset) + nLocalBytes);
+ int rc = SQLITE_OK;
+ nBytes -= nLocalBytes;
+
+ /* Ends of a linked list of overflow pages, and number of pages. */
+ RecoverOverflow *pFirstOverflow = NULL;
+ RecoverOverflow *pLastOverflow = NULL;
+ unsigned nPages = 0;
+
+ /* While there are more pages to read, and more pages are needed. */
+ while( iNextPage && nPages*(nPageSize-4)<nBytes ){
+ rc = sqlite3PagerAcquire(pPage->pPager, iNextPage, &pPage, 0);
+ if( rc!=SQLITE_OK ){
+ break;
+ }
+
+ RecoverOverflow *pOverflow = sqlite3_malloc(sizeof(RecoverOverflow));
+ if( !pOverflow ){
+ sqlite3PagerUnref(pPage);
+ rc = SQLITE_NOMEM;
+ break;
+ }
+ memset(pOverflow, 0, sizeof(*pOverflow));
+ pOverflow->pPage = pPage;
+ pOverflow->nPageSize = nPageSize;
+
+ if( !pFirstOverflow ){
+ pFirstOverflow = pOverflow;
+ }else{
+ pLastOverflow->pNextOverflow = pOverflow;
+ }
+ pLastOverflow = pOverflow;
+ nPages++;
+ iNextPage = decodeUnsigned32(pPage->pData);
+ }
+
+ /* If there were not enough pages, or too many, things are corrupt. */
+ if( rc==SQLITE_OK && (nPages*(nPageSize-4)<nBytes || iNextPage) ){
+ rc = SQLITE_CORRUPT;
+ }
+
+ if( rc==SQLITE_OK ){
+ *ppOverflow = pFirstOverflow;
+ *pnLocalBytes = nLocalBytes;
+ }else if( pFirstOverflow ){
+ overflowDestroy(pFirstOverflow);
+ }
+ return rc;
+}
+
+/* Gets a record segment, taking into account overflow pages. pPage
+ * is the initial database page which contains nBytes of row data at
+ * iOffset. iReqOffset and nReqBytes give the target range desired,
+ * offset from the beginning of the record (at iOffset). The segment
+ * is returned in *ppBase, possibly in an allocated buffer. If so,
+ * *pbFree is set true and *ppBase should eventually be freed using
+ * sqlite3_free().
+ *
+ * If the request can be satisfied from pPage, then pOverflow can
+ * safely be NULL.
+ */
+static int overflowGetSegment(DbPage *pPage, unsigned iOffset, unsigned nBytes,
+ RecoverOverflow *pOverflow,
+ unsigned iReqOffset, unsigned nReqBytes,
+ unsigned char **ppBase, int *pbFree){
+ /* Skip past initial pages. */
+ while( iReqOffset>=nBytes && pOverflow ){
+ iReqOffset -= nBytes;
+ pPage = pOverflow->pPage;
+ iOffset = 4;
+ nBytes = pOverflow->nPageSize - 4;
+ pOverflow = pOverflow->pNextOverflow;
+ }
+
+ /* If the requested data cannot be satisfied. */
+ if( iReqOffset+nReqBytes>iOffset+nBytes && !pOverflow ){
+ return SQLITE_ERROR;
+ }
+
+ /* If the requested segment is entirely within the local segment,
+ * return an internal pointer.
+ */
+ if( iReqOffset+nReqBytes<=nBytes ){
+ *ppBase = pPage->pData + iOffset + iReqOffset;
+ *pbFree = 0;
+ return SQLITE_OK;
+ }
+
+ /* Construct a buffer by copying pieces from multiple pages. */
+ unsigned char *pBase = sqlite3_malloc(nReqBytes);
+ if( !pBase ){
+ return SQLITE_NOMEM;
+ }
+ unsigned nBase = 0;
+ while( nBase<nReqBytes ){
+ unsigned nCopyBytes = nBytes - iReqOffset;
+ if( nReqBytes-nBase<nCopyBytes ){
+ nCopyBytes = nReqBytes - nBase;
+ }
+ memcpy(pBase + nBase, pPage->pData + iOffset + iReqOffset, nCopyBytes);
+ nBase += nCopyBytes;
+ if( pOverflow ){
+ iReqOffset = 0;
+ pPage = pOverflow->pPage;
+ iOffset = 4;
+ nBytes = pOverflow->nPageSize - 4;
+ pOverflow = pOverflow->pNextOverflow;
+ }else if( nBase<nReqBytes ){
+ /* More data is wanted, but no further overflow pages. */
+ sqlite3_free(pBase);
+ return SQLITE_ERROR;
+ }
+ }
+ assert( nBase==nReqBytes );
+ *ppBase = pBase;
+ *pbFree = 1;
+ return SQLITE_OK;
+}
+
+/* Primary structure for iterating the contents of a table.
+ *
+ * leafCursorDestroy - release all resources associated with the cursor.
+ * leafCursorCreate - create a cursor to iterate items from tree at
+ * the provided root page.
+ * leafCursorNextValidCell - get the cursor ready to return data from
+ * the next valid cell in the table.
+ * leafCursorCellRowid - get the current cell's rowid.
+ * leafCursorCellColumns - get current cell's column count.
+ * leafCursorCellColInfo - get type and data for a column in current cell.
+ *
+ * leafCursorNextValidCell skips cells which fail simple integrity
+ * checks, such as overlapping other cells, or being located at
+ * impossible offsets, or header data doesn't correctly describe
+ * payload data. Returns SQLITE_ROW if a valid cell is found,
+ * SQLITE_DONE if all pages in the tree were exhausted.
+ *
+ * leafCursorCellColInfo() accounts for overflow pages using
+ * overflowGetSegment().
+ */
+typedef struct RecoverLeafCursor RecoverLeafCursor;
+struct RecoverLeafCursor {
+ RecoverInteriorCursor *pParent; /* Parent node to this node. */
+ DbPage *pPage; /* Reference to leaf page. */
+ unsigned nPageSize; /* Size of left page. */
+ unsigned nCells; /* Number of cells on the page. */
+ unsigned iCell; /* Current cell. */
+
+ /* Info parsed from the current cell's data. pHeader is necessary
+ * because the header can extend into overflow, though it is usually
+ * in-page.
+ */
+ i64 iRowid; /* rowid parsed. */
+ unsigned nCols; /* how many data items in the cell. */
+ unsigned iPayloadOffset; /* offset to payload. */
+ unsigned nPayloadBytes; /* Size of payload. */
+ unsigned nLocalBytes; /* In-page portion of payload. */
+ unsigned nHeaderBytes; /* Size of payload header. */
+ unsigned char *pHeader; /* Header data for payload. */
+ int bFreeHeader; /* True if header needs to be freed. */
+ RecoverOverflow *pOverflow; /* Cell overflow info, if needed. */
+};
+
+/* Internal helper. Returns SQLITE_OK if the page is accepted (in
+ * which case the caller no longer owns it). pPage may be NULL after
+ * this call, in which case the passed page was either an interior
+ * page (in which case it adds a new parent cursor), or a non-table
+ * page.
+ */
+static int leafCursorLoadPage(RecoverLeafCursor *pCursor, DbPage *pPage){
+ if( pCursor->pPage ){
+ sqlite3PagerUnref(pCursor->pPage);
+ pCursor->pPage = NULL;
+ }
+
+ /* If the page is an unexpected interior node, inject a new stack
+ * layer and try again from there.
+ */
+ const unsigned char *pPageHeader = PageHeader(pPage);
+ if( pPageHeader[0]==kTableInteriorPage ){
+ RecoverInteriorCursor *pParent;
+ int rc = interiorCursorCreate(pCursor->pParent, pPage, pCursor->nPageSize,
+ &pParent);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ pCursor->pParent = pParent;
+ return SQLITE_OK;
+ }
+
+ /* If the page is not a leaf node, skip it. */
+ if( pPageHeader[0]!=kTableLeafPage ){
+ sqlite3PagerUnref(pPage);
+ return SQLITE_OK;
+ }
+
+ /* Take ownership of the page and start decoding. */
+ pCursor->pPage = pPage;
+ pCursor->iCell = 0;
+ pCursor->nCells = decodeUnsigned16(pPageHeader + 3);
+ return SQLITE_OK;
+}
+
+static int leafCursorNextPage(RecoverLeafCursor *pCursor){
+ if( !pCursor->pParent ){
+ return SQLITE_DONE;
+ }
+
+ /* Get the next page from the parent and load it, until one sticks
+ * or there are no more pages.
+ */
+ do {
+ DbPage *pNextPage;
+ int rc = interiorCursorNextPage(pCursor->pParent, &pNextPage);
+ if( rc!=SQLITE_ROW ){
+ assert( rc==SQLITE_DONE );
+ return rc;
+ }
+
+ rc = leafCursorLoadPage(pCursor, pNextPage);
+ if( rc!=SQLITE_OK ){
+ sqlite3PagerUnref(pNextPage);
+ return rc;
+ }
+ } while( !pCursor->pPage );
+
+ return SQLITE_ROW;
+}
+
+static void leafCursorDestroy(RecoverLeafCursor *pCursor){
+ if( pCursor->pHeader && pCursor->bFreeHeader ){
+ sqlite3_free(pCursor->pHeader);
+ pCursor->pHeader = NULL;
+ }
+
+ if( pCursor->pOverflow ){
+ overflowDestroy(pCursor->pOverflow);
+ pCursor->pOverflow = NULL;
+ }
+
+ if( pCursor->pParent ){
+ interiorCursorDestroy(pCursor->pParent);
+ pCursor->pParent = NULL;
+ }
+
+ if( pCursor->pPage ){
+ sqlite3PagerUnref(pCursor->pPage);
+ pCursor->pPage = NULL;
+ }
+
+ memset(pCursor, 0xA5, sizeof(*pCursor));
+ sqlite3_free(pCursor);
+}
+
+static int leafCursorCreate(Pager *pPager, int nPageSize,
+ int iRootPage, RecoverLeafCursor **ppCursor){
+ /* Start out with the root page. */
+ DbPage *pPage;
+ int rc = sqlite3PagerAcquire(pPager, iRootPage, &pPage, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ RecoverLeafCursor *pCursor = sqlite3_malloc(sizeof(RecoverLeafCursor));
+ if( !pCursor ){
+ sqlite3PagerUnref(pPage);
+ return SQLITE_NOMEM;
+ }
+ memset(pCursor, 0, sizeof(*pCursor));
+
+ pCursor->nPageSize = nPageSize;
+
+ rc = leafCursorLoadPage(pCursor, pPage);
+ if( rc!=SQLITE_OK ){
+ leafCursorDestroy(pCursor);
+ return rc;
+ }
+ if( !pCursor->pPage ){
+ rc = leafCursorNextPage(pCursor);
+ if( rc!=SQLITE_DONE && rc!=SQLITE_ROW ){
+ leafCursorDestroy(pCursor);
+ return rc;
+ }
+ }
+ /* TODO(shess): What should happen if leafCursorNextPage() returned
+ * SQLITE_DONE? It means there were no leaf pages, so probably the
+ * cursor should just go directly to EOF. Without having tested it,
+ * it looks like iCell == nCells == 0, so leafCursorNextValidCell()
+ * will return SQLITE_DONE. But leafCursorCellSetup() called from
+ * recoverOpen() will not go well.
+ */
+ *ppCursor = pCursor;
+ return SQLITE_OK;
+}
+
+static int ValidateError(){
+ return SQLITE_ERROR;
+}
+
+static int leafCursorCellSetup(RecoverLeafCursor *pCursor){
+ assert( pCursor->iCell<pCursor->nCells );
+
+ if( pCursor->pOverflow ){
+ overflowDestroy(pCursor->pOverflow);
+ pCursor->pOverflow = NULL;
+ }
+
+ /* Find the offset to the row. */
+ const unsigned char *pPageHeader = PageHeader(pCursor->pPage);
+ const unsigned char *pOffsets = pPageHeader + 8;
+ const unsigned iOffset = decodeUnsigned16(pOffsets + pCursor->iCell*2);
+ if( iOffset>=pCursor->nPageSize ){
+ return ValidateError();
+ }
+
+ const unsigned char *pCell = PageData(pCursor->pPage, iOffset);
+ const unsigned nCellMaxBytes = pCursor->nPageSize - iOffset;
+ unsigned l = 0;
+
+ /* B-tree leaf cells lead with varint payload size and varint rowid.
+ * Then there is the varint header size.
+ */
+ /* TODO(shess): The smallest page size is 512 bytes, which has an m
+ * of 39. Three varints need at most 27 bytes to encode. I think.
+ */
+ if( !checkVarints(pCell + l, nCellMaxBytes - l, 3) ){
+ return ValidateError();
+ }
+
+ u32 nPayloadBytes;
+ l += getVarint32(pCell + l, nPayloadBytes);
+ assert( iOffset+l<=pCursor->nPageSize );
+ pCursor->nPayloadBytes = nPayloadBytes;
+
+ u64 iRowid; /* Ignored */
+ l += getVarint(pCell + l, &iRowid);
+ assert( iOffset+l<=pCursor->nPageSize );
+ pCursor->iRowid = (i64)iRowid;
+
+ pCursor->iPayloadOffset = iOffset + l;
+
+ int rc = overflowMaybeCreate(pCursor->pPage, pCursor->nPageSize,
+ iOffset + l, nPayloadBytes,
+ &pCursor->nLocalBytes, &pCursor->pOverflow);
+ if( rc!=SQLITE_OK ){
+ return ValidateError();
+ }
+
+ /* Check that no other cell overlaps this cell. */
+ int i;
+ const unsigned iEndOffset = iOffset + l + pCursor->nLocalBytes;
+ for( i=0; i<pCursor->nCells; ++i ){
+ const unsigned iOtherOffset = decodeUnsigned16(pOffsets + i*2);
+ if( iOtherOffset>iOffset && iOtherOffset<iEndOffset ){
+ return ValidateError();
+ }
+ }
+
+ unsigned pl = 0;
+ u64 nHeaderBytes;
+ pl = getVarint(pCell + l, &nHeaderBytes);
+ assert( nHeaderBytes<=nPayloadBytes );
+ pCursor->nHeaderBytes = nHeaderBytes;
+
+ /* The header potentially could be large enough to overflow. */
+ rc = overflowGetSegment(pCursor->pPage, iOffset + l, pCursor->nLocalBytes,
+ pCursor->pOverflow, 0, nHeaderBytes,
+ &pCursor->pHeader, &pCursor->bFreeHeader);
+ if( rc!=SQLITE_OK ){
+ return ValidateError();
+ }
+
+ u64 nRecordBytes = 0;
+ unsigned nCols = 0;
+ while( pl<nHeaderBytes ){
+ if( !checkVarint(pCursor->pHeader + pl, nHeaderBytes - pl) ){
+ return ValidateError();
+ }
+ u64 iSerialType;
+ pl += getVarint(pCursor->pHeader + pl, &iSerialType);
+ if( iSerialType==10 || iSerialType==11 ){
+ return ValidateError();
+ }
+ nRecordBytes += SerialTypeLength(iSerialType);
+ nCols++;
+ }
+ pCursor->nCols = nCols;
+
+ /* Parsing the header used as many bytes as expected. */
+ if( pl!=nHeaderBytes ){
+ return ValidateError();
+ }
+
+ /* Calculated payload is size of expected payload. */
+ if( nHeaderBytes+nRecordBytes!=nPayloadBytes ){
+ return ValidateError();
+ }
+
+ return SQLITE_OK;
+}
+
+static i64 leafCursorCellRowid(RecoverLeafCursor *pCursor){
+ return pCursor->iRowid;
+}
+
+static unsigned leafCursorCellColumns(RecoverLeafCursor *pCursor){
+ return pCursor->nCols;
+}
+
+/* If ppBase is non-NULL, it and pbFree will be appropriately set
+ * using overflowGetSegment(). If *pbFree is set true, *ppBase must
+ * be freed using sqlite3_free().
+ *
+ * Pass NULL for ppBase to prevent retrieving the data segment.
+ */
+static int leafCursorCellColInfo(RecoverLeafCursor *pCursor,
+ unsigned iCol, u64 *piColType,
+ unsigned char **ppBase, int *pbFree){
+ /* Implicit NULL for columns past the end. This case happens when
+ * rows have not been updated since an ALTER TABLE added columns.
+ * It is more convenient to address here than in callers.
+ */
+ if( iCol>=pCursor->nCols ){
+ *piColType = 0;
+ if( ppBase ){
+ *ppBase = 0;
+ *pbFree = 0;
+ }
+ return SQLITE_OK;
+ }
+
+ /* Must be able to decode header size. */
+ const unsigned char *pHeader = pCursor->pHeader;
+ if( !checkVarint(pHeader, pCursor->nHeaderBytes) ){
+ return SQLITE_CORRUPT;
+ }
+
+ u64 nHeaderBytes;
+ unsigned l = getVarint(pHeader, &nHeaderBytes);
+ assert( nHeaderBytes==pCursor->nHeaderBytes );
+
+ u64 nRecordBytes = 0;
+ unsigned iColsSkipped = 0;
+ u64 iSerialType;
+ if( !checkVarint(pHeader + l, nHeaderBytes - l) ){
+ return SQLITE_CORRUPT;
+ }
+ l += getVarint(pHeader + l, &iSerialType);
+ while( iColsSkipped<iCol && l<nHeaderBytes ){
+ nRecordBytes += SerialTypeLength(iSerialType);
+ if( !checkVarint(pHeader + l, nHeaderBytes - l) ){
+ return SQLITE_CORRUPT;
+ }
+ l += getVarint(pHeader + l, &iSerialType);
+ iColsSkipped++;
+ }
+
+ /* Column's data extends past payload end. */
+ const unsigned nColBytes = SerialTypeLength(iSerialType);
+ if( nHeaderBytes+nRecordBytes+nColBytes>pCursor->nPayloadBytes ){
+ return SQLITE_CORRUPT;
+ }
+
+ *piColType = iSerialType;
+ if( ppBase ){
+ return overflowGetSegment(pCursor->pPage,
+ pCursor->iPayloadOffset + nHeaderBytes,
+ pCursor->nLocalBytes - nHeaderBytes,
+ pCursor->pOverflow,
+ nRecordBytes, nColBytes,
+ ppBase, pbFree);
+ }
+ return SQLITE_OK;
+}
+
+static int leafCursorNextValidCell(RecoverLeafCursor *pCursor){
+ while( 1 ){
+ /* Move to the next cell. */
+ pCursor->iCell++;
+
+ /* If the leaf is done, get the next leaf. */
+ if( pCursor->iCell>=pCursor->nCells ){
+ int rc = leafCursorNextPage(pCursor);
+ if( rc!=SQLITE_ROW ){
+ return rc;
+ }
+ }
+
+ /* If the cell is valid, indicate that a row is available. */
+ int rc = leafCursorCellSetup(pCursor);
+ if( rc==SQLITE_OK ){
+ return SQLITE_ROW;
+ }
+
+ /* Iterate until done or a valid row is found. */
+ fprintf(stderr, "Skipping invalid cell\n");
+ }
+ return SQLITE_ERROR;
+}
+
+typedef struct Recover Recover;
+struct Recover {
+ sqlite3_vtab base;
+ sqlite3 *db; /* Host database connection */
+ char *zDb; /* Database containing target table */
+ char *zTable; /* Target table */
+ int nCols; /* Number of columns in target table */
+ unsigned char *pTypes; /* Types of columns in target table */
+};
+
+/* Internal helper for deleting the module. */
+static void recoverRelease(Recover *pRecover){
+ sqlite3_free(pRecover->zDb);
+ sqlite3_free(pRecover->zTable);
+ sqlite3_free(pRecover->pTypes);
+ memset(pRecover, 0xA5, sizeof(*pRecover));
+ sqlite3_free(pRecover);
+}
+
+/* Helper function for initializing the module. Forward-declared so
+ * recoverCreate() and recoverConnect() can see it.
+ */
+static int recoverInit(
+ sqlite3 *, void *, int, const char *const*, sqlite3_vtab **, char **
+);
+
+static int recoverCreate(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ FNENTRY();
+ return recoverInit(db, pAux, argc, argv, ppVtab, pzErr);
+}
+
+/* This should never be called. */
+static int recoverConnect(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ FNENTRY();
+ return recoverInit(db, pAux, argc, argv, ppVtab, pzErr);
+}
+
+/* No indices supported. */
+static int recoverBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+ FNENTRY();
+ return SQLITE_OK;
+}
+
+/* Logically, this should never be called. */
+static int recoverDisconnect(sqlite3_vtab *pVtab){
+ FNENTRY();
+ recoverRelease((Recover*)pVtab);
+ return SQLITE_OK;
+}
+
+static int recoverDestroy(sqlite3_vtab *pVtab){
+ FNENTRY();
+ recoverRelease((Recover*)pVtab);
+ return SQLITE_OK;
+}
+
+typedef struct RecoverCursor RecoverCursor;
+struct RecoverCursor {
+ sqlite3_vtab_cursor base;
+ RecoverLeafCursor *pLeafCursor;
+ int bEOF;
+};
+
+static int recoverOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+ FNENTRY();
+
+ Recover *pRecover = (Recover*)pVTab;
+
+ unsigned iRootPage = 0;
+ int rc = getRootPage(pRecover->db, pRecover->zDb, pRecover->zTable,
+ &iRootPage);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ unsigned nPageSize;
+ Pager *pPager;
+ rc = GetPager(pRecover->db, pRecover->zDb, &pPager, &nPageSize);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ RecoverLeafCursor *pLeafCursor;
+ rc = leafCursorCreate(pPager, nPageSize, iRootPage, &pLeafCursor);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ RecoverCursor *pCursor = sqlite3_malloc(sizeof(RecoverCursor));
+ if( !pCursor ){
+ leafCursorDestroy(pLeafCursor);
+ return SQLITE_NOMEM;
+ }
+ memset(pCursor, 0, sizeof(*pCursor));
+ pCursor->base.pVtab = pVTab;
+ pCursor->pLeafCursor = pLeafCursor;
+
+ *ppCursor = (sqlite3_vtab_cursor*)pCursor;
+ return SQLITE_OK;
+}
+
+static int recoverClose(sqlite3_vtab_cursor *cur){
+ FNENTRY();
+ RecoverCursor *pCursor = (RecoverCursor*)cur;
+ if( pCursor->pLeafCursor ){
+ leafCursorDestroy(pCursor->pLeafCursor);
+ pCursor->pLeafCursor = NULL;
+ }
+ memset(pCursor, 0xA5, sizeof(*pCursor));
+ sqlite3_free(cur);
+ return SQLITE_OK;
+}
+
+/* Helpful place to set a breakpoint. */
+static int RecoverInvalidCell(){
+ return SQLITE_ERROR;
+}
+
+static int recoverValidateLeafCell(Recover *pRecover, RecoverCursor *pCursor){
+ /* If the row's storage has too many columns, skip it. */
+ if( leafCursorCellColumns(pCursor->pLeafCursor)>pRecover->nCols ){
+ return RecoverInvalidCell();
+ }
+
+ /* Skip rows with unexpected types. */
+ unsigned i;
+ for( i=0; i<pRecover->nCols; ++i ){
+ /* ROWID alias. */
+ if( (pRecover->pTypes[i]&kMaskRowid) ){
+ continue;
+ }
+
+ u64 iType;
+ int rc = leafCursorCellColInfo(pCursor->pLeafCursor, i, &iType, NULL, NULL);
+ assert( rc==SQLITE_OK );
+ if( rc!=SQLITE_OK || !SerialTypeIsCompatible(iType, pRecover->pTypes[i]) ){
+ return RecoverInvalidCell();
+ }
+ }
+
+ return SQLITE_OK;
+}
+
+static int recoverNext(sqlite3_vtab_cursor *pVtabCursor){
+ FNENTRY();
+ RecoverCursor *pCursor = (RecoverCursor*)pVtabCursor;
+ Recover *pRecover = (Recover*)pCursor->base.pVtab;
+ int rc;
+
+ /* Scan forward to the next cell with valid storage, then check that
+ * the stored data matches the schema.
+ */
+ while( (rc = leafCursorNextValidCell(pCursor->pLeafCursor))==SQLITE_ROW ){
+ if( recoverValidateLeafCell(pRecover, pCursor)==SQLITE_OK ){
+ return SQLITE_OK;
+ }
+ }
+
+ if( rc==SQLITE_DONE ){
+ pCursor->bEOF = 1;
+ return SQLITE_OK;
+ }
+
+ assert( rc!=SQLITE_OK );
+ return rc;
+}
+
+static int recoverFilter(
+ sqlite3_vtab_cursor *pVtabCursor,
+ int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv
+){
+ FNENTRY();
+ RecoverCursor *pCursor = (RecoverCursor*)pVtabCursor;
+ Recover *pRecover = (Recover*)pCursor->base.pVtab;
+
+ /* Load the first cell, and iterate forward if it's not valid. */
+ /* TODO(shess): What happens if no cells at all are valid? */
+ int rc = leafCursorCellSetup(pCursor->pLeafCursor);
+ if( rc!=SQLITE_OK || recoverValidateLeafCell(pRecover, pCursor)!=SQLITE_OK ){
+ return recoverNext(pVtabCursor);
+ }
+
+ return SQLITE_OK;
+}
+
+static int recoverEof(sqlite3_vtab_cursor *pVtabCursor){
+ FNENTRY();
+ RecoverCursor *pCursor = (RecoverCursor*)pVtabCursor;
+ return pCursor->bEOF;
+}
+
+static int recoverColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
+ FNENTRY();
+ RecoverCursor *pCursor = (RecoverCursor*)cur;
+ Recover *pRecover = (Recover*)pCursor->base.pVtab;
+
+ if( i>=pRecover->nCols ){
+ return SQLITE_ERROR;
+ }
+
+ /* ROWID alias. */
+ if( (pRecover->pTypes[i]&kMaskRowid) ){
+ sqlite3_result_int64(ctx, leafCursorCellRowid(pCursor->pLeafCursor));
+ return SQLITE_OK;
+ }
+
+ u64 iColType;
+ unsigned char *pColData = NULL;
+ int shouldFree = 0;
+ int rc = leafCursorCellColInfo(pCursor->pLeafCursor, i, &iColType,
+ &pColData, &shouldFree);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ if( !SerialTypeIsCompatible(iColType, pRecover->pTypes[i]) ){
+ if( shouldFree ){
+ sqlite3_free(pColData);
+ }
+ return SQLITE_ERROR;
+ }
+
+ switch( iColType ){
+ case 0 : sqlite3_result_null(ctx); break;
+ case 1 : sqlite3_result_int(ctx, decodeInt8(pColData)); break;
+ case 2 : sqlite3_result_int(ctx, decodeInt16(pColData)); break;
+ case 3 : sqlite3_result_int(ctx, decodeInt24(pColData)); break;
+ /* TODO(shess): Make sure that this is valid for large values. */
+ case 4 : sqlite3_result_int(ctx, decodeInt32(pColData)); break;
+ case 5 : sqlite3_result_int64(ctx, decodeInt48(pColData)); break;
+ case 6 : sqlite3_result_int64(ctx, decodeInt64(pColData)); break;
+ case 7 : sqlite3_result_double(ctx, decodeFloat64(pColData)); break;
+ case 8 : sqlite3_result_int(ctx, 0); break;
+ case 9 : sqlite3_result_int(ctx, 1); break;
+ case 10 : assert( iColType!=10 ); break;
+ case 11 : assert( iColType!=11 ); break;
+
+ default : {
+ /* If pColData was already allocated, arrange to pass ownership. */
+ sqlite3_destructor_type pFn = SQLITE_TRANSIENT;
+ if( shouldFree ){
+ pFn = sqlite3_free;
+ shouldFree = 0;
+ }
+
+ u64 l = SerialTypeLength(iColType);
+ if( SerialTypeIsBlob(iColType) ){
+ sqlite3_result_blob(ctx, pColData, l, pFn);
+ }else{
+ sqlite3_result_text(ctx, (const char*)pColData, l, pFn);
+ }
+ } break;
+ }
+ if( shouldFree ){
+ sqlite3_free(pColData);
+ }
+ return SQLITE_OK;
+}
+
+static int recoverRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){
+ FNENTRY();
+ RecoverCursor *pCursor = (RecoverCursor*)pVtabCursor;
+ *pRowid = leafCursorCellRowid(pCursor->pLeafCursor);
+ return SQLITE_OK;
+}
+
+static sqlite3_module recoverModule = {
+ 0, /* iVersion */
+ recoverCreate, /* xCreate - create a table */
+ recoverConnect, /* xConnect - connect to an existing table */
+ recoverBestIndex, /* xBestIndex - Determine search strategy */
+ recoverDisconnect, /* xDisconnect - Disconnect from a table */
+ recoverDestroy, /* xDestroy - Drop a table */
+ recoverOpen, /* xOpen - open a cursor */
+ recoverClose, /* xClose - close a cursor */
+ recoverFilter, /* xFilter - configure scan constraints */
+ recoverNext, /* xNext - advance a cursor */
+ recoverEof, /* xEof */
+ recoverColumn, /* xColumn - read data */
+ recoverRowid, /* xRowid - read data */
+ 0, /* xUpdate - write data */
+ 0, /* xBegin - begin transaction */
+ 0, /* xSync - sync transaction */
+ 0, /* xCommit - commit transaction */
+ 0, /* xRollback - rollback transaction */
+ 0, /* xFindFunction - function overloading */
+ 0, /* xRename - rename the table */
+};
+
+int recoverVtableInit(sqlite3 *db){
+ return sqlite3_create_module_v2(db, "recover", &recoverModule, NULL, 0);
+}
+
+/* This section of code is for parsing the create input and
+ * initializing the module.
+ */
+
+/* Find the next word in zText and place the endpoints in pzWord*.
+ * Returns true if the word is non-empty. "Word" is defined as
+ * alphanumeric plus '_' at this time.
+ */
+static int findWord(const char *zText,
+ const char **pzWordStart, const char **pzWordEnd){
+ while( isspace(*zText) ){
+ zText++;
+ }
+ *pzWordStart = zText;
+ while( isalnum(*zText) || *zText=='_' ){
+ zText++;
+ }
+ int r = zText>*pzWordStart; /* In case pzWordStart==pzWordEnd */
+ *pzWordEnd = zText;
+ return r;
+}
+
+/* Return true if the next word is zText, also setting *pzContinue to
+ * the character after the word.
+ */
+static int expectWord(const char *zText, const char *zWord,
+ const char **pzContinue){
+ const char *zWordStart, *zWordEnd;
+ if( findWord(zText, &zWordStart, &zWordEnd) &&
+ !strncasecmp(zWord, zWordStart, zWordEnd - zWordStart) ){
+ *pzContinue = zWordEnd;
+ return 1;
+ }
+ return 0;
+}
+
+/* Parse the name and type information out of parameter. In case of
+ * success, *pzNameStart/End contain the name of the column,
+ * *pzTypeStart/End contain the top-level type, and *pTypeMask has the
+ * type mask to use for the column.
+ */
+static int findNameAndType(const char *parameter,
+ const char **pzNameStart, const char **pzNameEnd,
+ const char **pzTypeStart, const char **pzTypeEnd,
+ unsigned char *pTypeMask){
+ if( !findWord(parameter, pzNameStart, pzNameEnd) ){
+ return SQLITE_MISUSE;
+ }
+
+ /* Manifest typing, accept any storage type. */
+ if( !findWord(*pzNameEnd, pzTypeStart, pzTypeEnd) ){
+ *pzTypeEnd = *pzTypeStart = "";
+ *pTypeMask = kMaskInteger | kMaskFloat | kMaskBlob | kMaskText | kMaskNull;
+ return SQLITE_OK;
+ }
+
+ /* strictMask is used for STRICT, strictMask|otherMask if STRICT is
+ * not supplied. zReplace provides an alternate type to expose to
+ * the caller.
+ */
+ struct {
+ const char *zName;
+ unsigned char strictMask;
+ unsigned char otherMask;
+ const char *zReplace;
+ } typeInfo[7] = {
+ { "ANY",
+ kMaskInteger | kMaskFloat | kMaskBlob | kMaskText | kMaskNull,
+ 0, "",
+ },
+ { "ROWID", kMaskInteger | kMaskRowid, 0, "INTEGER", },
+ { "INTEGER", kMaskInteger | kMaskNull, 0, NULL, },
+ { "FLOAT", kMaskFloat | kMaskNull, kMaskInteger, NULL, },
+ { "NUMERIC", kMaskInteger | kMaskFloat | kMaskNull, kMaskText, NULL, },
+ { "TEXT", kMaskText | kMaskNull, kMaskBlob, NULL, },
+ { "BLOB", kMaskBlob | kMaskNull, 0, NULL, },
+ };
+
+ unsigned i, nNameLen = *pzTypeEnd - *pzTypeStart;
+ for( i=0; i<ArraySize(typeInfo); ++i ){
+ if( !strncasecmp(typeInfo[i].zName, *pzTypeStart, nNameLen) ){
+ break;
+ }
+ }
+ if( i==ArraySize(typeInfo) ){
+ return SQLITE_MISUSE;
+ }
+
+ const char *zEnd = *pzTypeEnd;
+ int bStrict = 0;
+ if( expectWord(zEnd, "STRICT", &zEnd) ){
+ /* TODO(shess): Ick. But I don't want another single-purpose
+ * flag, either.
+ */
+ if( typeInfo[i].zReplace && !typeInfo[i].zReplace[0] ){
+ return SQLITE_MISUSE;
+ }
+ bStrict = 1;
+ }
+
+ int bNotNull = 0;
+ if( expectWord(zEnd, "NOT", &zEnd) ){
+ if( expectWord(zEnd, "NULL", &zEnd) ){
+ bNotNull = 1;
+ }else{
+ /* Anything other than NULL after NOT is an error. */
+ return SQLITE_MISUSE;
+ }
+ }
+
+ /* Anything else is an error. */
+ const char *zDummy;
+ if( findWord(zEnd, &zDummy, &zDummy) ){
+ return SQLITE_MISUSE;
+ }
+
+ *pTypeMask = typeInfo[i].strictMask;
+ if( !bStrict ){
+ *pTypeMask |= typeInfo[i].otherMask;
+ }
+ if( bNotNull ){
+ *pTypeMask &= ~kMaskNull;
+ }
+ if( typeInfo[i].zReplace ){
+ *pzTypeStart = typeInfo[i].zReplace;
+ *pzTypeEnd = *pzTypeStart + strlen(*pzTypeStart);
+ }
+ return SQLITE_OK;
+}
+
+/* Parse the arguments, placing type masks in *pTypes and the exposed
+ * schema in *pzCreateSql (for sqlite3_declare_vtab).
+ */
+static int ParseAndGenerateCreate(int nCols, const char *const *pCols,
+ char **pzCreateSql, unsigned char *pTypes){
+ char *zCreateSql = sqlite3_mprintf("CREATE TABLE x(");
+ if( !zCreateSql ){
+ return SQLITE_NOMEM;
+ }
+
+ unsigned i;
+ for( i=0; i<nCols; i++ ){
+ const char *zNameStart, *zNameEnd;
+ const char *zTypeStart, *zTypeEnd;
+ unsigned char iTypeMask;
+ int rc = findNameAndType(pCols[i],
+ &zNameStart, &zNameEnd,
+ &zTypeStart, &zTypeEnd,
+ &iTypeMask);
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(zCreateSql);
+ return rc;
+ }
+ pTypes[i] = iTypeMask;
+
+ const char *zNotNull = "";
+ if( !(iTypeMask&kMaskNull) ){
+ zNotNull = " NOT NULL";
+ }
+
+ /* Add name and type to the create statement. */
+ zCreateSql = sqlite3_mprintf("%z%.*s %.*s%s, ",
+ zCreateSql,
+ zNameEnd - zNameStart, zNameStart,
+ zTypeEnd - zTypeStart, zTypeStart,
+ zNotNull);
+ if( !zCreateSql ){
+ return SQLITE_NOMEM;
+ }
+ }
+
+ /* Strip trailing ", ". */
+ assert( nCols>0 );
+ zCreateSql[strlen(zCreateSql)-2] = '\0';
+
+ zCreateSql = sqlite3_mprintf("%z)", zCreateSql);
+ if( !zCreateSql ){
+ return SQLITE_NOMEM;
+ }
+
+ *pzCreateSql = zCreateSql;
+ return SQLITE_OK;
+}
+
+/* Helper function for initializing the module. */
+/* TODO(shess): Since connect isn't supported, could inline into
+ * recoverCreate().
+ */
+/* TODO(shess): Explore cases where it would make sense to set *pzErr. */
+static int recoverInit(
+ sqlite3 *db, /* Database connection */
+ void *pAux, /* unused */
+ int argc, const char *const*argv, /* Parameters to CREATE TABLE statement */
+ sqlite3_vtab **ppVtab, /* OUT: New virtual table */
+ char **pzErr /* OUT: Error message, if any */
+){
+ /* argv[0] module name
+ * argv[1] db name for virtual table
+ * argv[2] virtual table name
+ * argv[3] backing table name
+ * argv[4] columns
+ */
+ const int kTypeCol = 4;
+ int nCols = argc - kTypeCol;
+
+ /* Require to be in the temp database. */
+ if( strcasecmp(argv[1], "temp") ){
+ return SQLITE_MISUSE;
+ }
+
+ /* Need the backing table and at least one column. */
+ if( nCols<1 ){
+ return SQLITE_MISUSE;
+ }
+
+ Recover *pRecover = sqlite3_malloc(sizeof(Recover));
+ if( !pRecover ){
+ return SQLITE_NOMEM;
+ }
+ memset(pRecover, 0, sizeof(*pRecover));
+ pRecover->base.pModule = &recoverModule;
+ pRecover->db = db;
+
+ /* Parse out db.table, assuming main if no dot. */
+ char *dot = strchr(argv[3], '.');
+ if( dot && dot>argv[3] ){
+ pRecover->zDb = sqlite3_strndup(argv[3], dot - argv[3]);
+ pRecover->zTable = sqlite3_strdup(dot + 1);
+ }else{
+ pRecover->zDb = sqlite3_strdup(db->aDb[0].zName);
+ pRecover->zTable = sqlite3_strdup(argv[3]);
+ }
+
+ pRecover->nCols = nCols;
+ pRecover->pTypes = sqlite3_malloc(pRecover->nCols);
+ if( !pRecover->zDb || !pRecover->zTable || !pRecover->pTypes ){
+ recoverRelease(pRecover);
+ return SQLITE_NOMEM;
+ }
+
+ /* Require the backing table to exist. */
+ /* TODO(shess): Be more pedantic about the form of the descriptor
+ * string. This already fails for poorly-formed strings, simply
+ * because there won't be a root page, but it would make more sense
+ * to be explicit.
+ */
+ unsigned iRootPage;
+ int rc = getRootPage(pRecover->db, pRecover->zDb, pRecover->zTable,
+ &iRootPage);
+ if( rc!=SQLITE_OK ){
+ recoverRelease(pRecover);
+ return rc;
+ }
+
+ /* Parse the column definitions. */
+ char *zCreateSql;
+ rc = ParseAndGenerateCreate(nCols, argv + kTypeCol,
+ &zCreateSql, pRecover->pTypes);
+ if( rc!=SQLITE_OK ){
+ recoverRelease(pRecover);
+ return rc;
+ }
+
+ rc = sqlite3_declare_vtab(db, zCreateSql);
+ sqlite3_free(zCreateSql);
+ if( rc!=SQLITE_OK ){
+ recoverRelease(pRecover);
+ return rc;
+ }
+
+ *ppVtab = (sqlite3_vtab *)pRecover;
+ return SQLITE_OK;
+}
+
+/* Generate a line of hexdump-style output in b using min(16,n) bytes
+ * from pc. b must have room for at least 57 bytes (16*2 nibbles with
+ * 3 separator spaces, 16 ASCII chars with 3 separator spaces, 2
+ * spaces between sections, and a trailing nul).
+ */
+static void hexline(char *b, const unsigned char *pc, int n){
+ const char digits[16] = "0123456789ABCDEF";
+ int i;
+ /* 2*16 for hex, 16 for ASCII, 2*3 inline spaces, 2 spaces between
+ * sections.
+ */
+ memset(b, ' ', 3*16 + 8);
+ for( i=0; i<n && i<16; i++ ){
+ b[i*2 + i/4 ] = digits[(pc[i]>>4)&0xF];
+ b[i*2 + i/4 + 1] = digits[(pc[i]>>0)&0xF];
+ }
+ for( i=0; i<n && i<16; i++ ){
+ b[2*16 + 5 + i + i/4] = (isprint(pc[i]) ? pc[i] : '.');
+ }
+ b[3*16 + 8] = '\0';
+}
+
+/* Output nData bytes from pData to stderr, hexdump-style. Example:
+ * 0x0000:53514C69 74652066 6F726D61 74203300 SQLi te f orma t 3.
+ * 0x0010:04000101 00402020 00011362 000039C6 .... .@ ...b ..9.
+ *
+ * Consecutive lines consisting of entirely nul bytes are elided.
+ * Refuses to dump more than 4096 bytes.
+ */
+static void hexdump(const unsigned char *pData, unsigned nData){
+ unsigned iOffset = 0, bSkipped = 0;
+ assert( nData<=4096 );
+ if( nData>4096 ){
+ nData = 4096;
+ }
+ while( nData>0 ){
+ unsigned i, nCur = (nData<16 ? nData : 16);
+ for( i=0; i<nCur && !pData[iOffset+i]; i++ );
+ if( !iOffset || nCur==nData || i<16 ){
+ char buf[200];
+ hexline(buf, pData + iOffset, nCur);
+ fprintf(stderr, "0x%04x:%s\n", iOffset, buf);
+ bSkipped = 0;
+ }else if( !bSkipped ){
+ fprintf(stderr, "...\n");
+ bSkipped = 1;
+ }
+ iOffset += nCur;
+ nData -= nCur;
+ }
+}
+
+static int GetPageFromContext(sqlite3_context *ctx,
+ const char *zName, unsigned iPage,
+ DbPage **ppPage, unsigned *pnPageSize){
+ sqlite3 *db = (sqlite3 *)sqlite3_user_data(ctx);
+ Pager *pPager;
+ /* TODO(shess) This needs to acquire the shared lock before it can
+ * get the page.
+ */
+ int rc = GetPager(db, zName, &pPager, pnPageSize);
+ if( rc==SQLITE_OK ){
+ /* TODO(shess): This doesn't fail if the page doesn't exist. */
+ rc = sqlite3PagerAcquire(pPager, iPage, ppPage, 0);
+ }
+ return rc;
+}
+
+static void HexdumpFn(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){
+ DbPage *pPage;
+ unsigned nPageSize, iPage = sqlite3_value_int(aArg[0]);
+ int rc = GetPageFromContext(ctx, "main", iPage, &pPage, &nPageSize);
+ if( rc!=SQLITE_OK ){
+ sqlite3_result_error(ctx, "Error acquiring page", rc);
+ }else{
+ const unsigned char *pData = PageData(pPage, 0);
+ fprintf(stderr, "page %u size %u:\n", iPage, nPageSize);
+ hexdump(pData, nPageSize);
+ sqlite3PagerUnref(pPage);
+ sqlite3_result_text(ctx, "OK", -1, SQLITE_STATIC);
+ }
+}
+
+static void DumpFn(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){
+ /* TODO(shess): Consider options like:
+ * header - show only the page header info.
+ * cells - also show cell meta-info.
+ * records - also show summary record data.
+ * all - show complete record contents.
+ */
+ int headerOnly = 0;
+ if( nArg>1 ){
+ if( !strcasecmp((char*)sqlite3_value_text(aArg[1]), "header") ){
+ headerOnly = 1;
+ }
+ }else if( nArg==1 ){
+ headerOnly = 1;
+ }
+
+ DbPage *pPage;
+ unsigned nPageSize, iPage = sqlite3_value_int(aArg[0]);
+ int rc = GetPageFromContext(ctx, "main", iPage, &pPage, &nPageSize);
+ if( rc!=SQLITE_OK ){
+ sqlite3_result_error(ctx, "Error acquiring page", rc);
+ return;
+ }
+ fprintf(stderr, "page %u size %u:\n", iPage, nPageSize);
+
+ const unsigned char *pPageHeader = PageHeader(pPage);
+ if( *pPageHeader==0x02 || *pPageHeader==kTableInteriorPage || *pPageHeader==0x0A || *pPageHeader==kTableLeafPage ){
+ const int bTable = ((*pPageHeader)&5)==5;
+ const int bLeaf = ((*pPageHeader)&8)==8;
+ fprintf(stderr, "[0] 0x%02X - %s %s b-tree page.\n", *pPageHeader,
+ (bLeaf ? "Leaf" : "Interior"), (bTable ? "table" : "index"));
+ fprintf(stderr, "[1,2] %u - Offset to first freeblock.\n",
+ decodeUnsigned16(pPageHeader + 1));
+ fprintf(stderr, "[3,4] %u - Number of cells.\n",
+ decodeUnsigned16(pPageHeader + 3));
+ fprintf(stderr, "[5,6] %u - Offset to first byte of cell content area.\n",
+ decodeUnsigned16(pPageHeader + 5));
+ fprintf(stderr, "[7] %u - Fragmented free bytes.\n", pPageHeader[7]);
+ if( !bLeaf ){
+ fprintf(stderr, "[8-11] %u - Right-most child page.\n",
+ decodeUnsigned32(pPageHeader + 8));
+ }
+ if( !headerOnly ){
+ if( *pPageHeader==0x02 ){
+ const unsigned char *pOffsets = pPageHeader + 12;
+ unsigned iCell, nCells = decodeUnsigned16(pPageHeader + 3);
+ for( iCell=0; iCell<nCells; ++iCell ){
+ unsigned iOffset = decodeUnsigned16(pOffsets + iCell*2);
+ const unsigned char *pCell = PageData(pPage, iOffset);
+ unsigned iChildPage = decodeUnsigned32(pCell);
+ u64 nPayloadBytes;
+ getVarint(pCell + 4, &nPayloadBytes);
+ fprintf(stderr, "cell %u offset %u child %u payload %llu\n",
+ iCell, iOffset, iChildPage, nPayloadBytes);
+ /* TODO(shess): Partially print the payload. */
+ }
+ }else if( *pPageHeader==kTableInteriorPage ){
+ const unsigned char *pOffsets = pPageHeader + 12;
+ unsigned iCell, nCells = decodeUnsigned16(pPageHeader + 3);
+ for( iCell=0; iCell<nCells; ++iCell ){
+ unsigned iOffset = decodeUnsigned16(pOffsets + iCell*2);
+ const unsigned char *pCell = PageData(pPage, iOffset);
+ unsigned iChildPage = decodeUnsigned32(pCell);
+ u64 iRowid;
+ getVarint(pCell + 4, &iRowid);
+ fprintf(stderr, "cell %u offset %u child %u rowid %lld\n",
+ iCell, iOffset, iChildPage, iRowid);
+ }
+ }else if( *pPageHeader==kTableLeafPage || *pPageHeader==0x0A ){
+ const unsigned char *pOffsets = pPageHeader + 8;
+ unsigned iCell, nCells = decodeUnsigned16(pPageHeader + 3);
+ for( iCell=0; iCell<nCells; ++iCell ){
+ unsigned iOffset = decodeUnsigned16(pOffsets + iCell*2);
+
+ const unsigned char *pCell = PageData(pPage, iOffset);
+ int l = 0;
+
+ u64 nPayloadBytes;
+ l += getVarint(pCell + l, &nPayloadBytes);
+
+ u64 iRowid;
+ if( bTable ){
+ l += getVarint(pCell + l, &iRowid);
+ }
+
+ int pl = 0;
+ u64 nHeaderBytes;
+ pl += getVarint(pCell + l + pl, &nHeaderBytes);
+ if( bTable ){
+ fprintf(stderr,
+ "cell %u offset %u rowid %lld payload %llu header %llu\n",
+ iCell, iOffset, iRowid, nPayloadBytes, nHeaderBytes);
+ }else{
+ fprintf(stderr, "cell %u offset %u payload %llu header %llu\n",
+ iCell, iOffset, nPayloadBytes, nHeaderBytes);
+ }
+
+ RecoverOverflow *pOverflow = NULL;
+ unsigned nLocalBytes = nPayloadBytes;
+ int rc = overflowMaybeCreate(pPage, nPageSize,
+ iOffset + l, nPayloadBytes,
+ &nLocalBytes, &pOverflow);
+ if( rc!=SQLITE_OK ){
+ fprintf(stderr, "SQLITE IS NOT OK\n");
+ }
+
+ /* TODO(shess): Pull this inner loop out to a helper
+ * function to improve nesting.
+ */
+ u64 nRecordBytes = 0;
+ unsigned iCol = 0;
+ while( pl<nHeaderBytes ){
+ u64 iSerialType;
+ pl += getVarint(pCell + l + pl, &iSerialType);
+
+ unsigned nBytes = 200;
+ if( nBytes>nPayloadBytes - (nHeaderBytes + nRecordBytes) ){
+ nBytes = nPayloadBytes - (nHeaderBytes + nRecordBytes);
+ }
+ unsigned char *pRecord = NULL;
+ int bFree = 0;
+ int rc = overflowGetSegment(pPage, iOffset + l + nHeaderBytes,
+ nLocalBytes - nHeaderBytes, pOverflow,
+ nRecordBytes, nBytes,
+ &pRecord, &bFree);
+ if( rc!=SQLITE_OK ){
+ fprintf(stderr, "SQLITE IS NOT OK.\n");
+ }
+ switch( iSerialType ){
+ case 0 :
+ fprintf(stderr, " col[%u] NULL\n", iCol);
+ break;
+ case 1 :
+ fprintf(stderr, " col[%u] i8 %d\n", iCol, decodeInt8(pRecord));
+ break;
+ case 2 :
+ fprintf(stderr, " col[%u] i16 %d\n", iCol, decodeInt16(pRecord));
+ break;
+ case 3 :
+ fprintf(stderr, " col[%u] i24 %d\n", iCol, decodeInt24(pRecord));
+ break;
+ case 4 :
+ fprintf(stderr, " col[%u] i32 %d\n", iCol, decodeInt32(pRecord));
+ break;
+ case 5 :
+ fprintf(stderr, " col[%u] i48 %lld\n", iCol, decodeInt48(pRecord));
+ break;
+ case 6 :
+ fprintf(stderr, " col[%u] i64 %lld\n", iCol, decodeInt64(pRecord));
+ break;
+ case 7 :
+ fprintf(stderr, " col[%u] FLOAT %f\n", iCol, decodeFloat64(pRecord));
+ break;
+ case 8 :
+ fprintf(stderr, " col[%u] 0\n", iCol);
+ break;
+ case 9 :
+ fprintf(stderr, " col[%u] 1\n", iCol);
+ break;
+ case 10 : case 11 :
+ fprintf(stderr, " col[%u] RESERVED\n", iCol);
+ break;
+ default :
+ if( SerialTypeIsBlob(iSerialType) ){
+ char buf[200];
+ hexline(buf, pRecord, SerialTypeLength(iSerialType));
+ fprintf(stderr, " col[%u] blob[%llu] %s\n",
+ iCol, SerialTypeLength(iSerialType), buf);
+ }else{
+ char buf[200];
+ u64 nBytes = SerialTypeLength(iSerialType);
+ int i;
+ for( i=0; i<60 && i<nBytes; ++i ){
+ if( isprint(pRecord[i]) ){
+ buf[i] = pRecord[i];
+ }else{
+ buf[i] = '.';
+ }
+ }
+ if( i<nBytes ){
+ strcpy(buf + i, "...");
+ }else{
+ buf[i] = '\0';
+ }
+ fprintf(stderr, " col[%u] string[%llu] %s\n",
+ iCol, SerialTypeLength(iSerialType), buf);
+ /* TODO(shess): Support emitting the full record. */
+ }
+ break;
+ }
+ nRecordBytes += SerialTypeLength(iSerialType);
+ iCol++;
+ if( bFree ){
+ sqlite3_free(pRecord);
+ pRecord = NULL;
+ bFree = 0;
+ }
+ }
+ if( nRecordBytes+nHeaderBytes<nPayloadBytes ){
+ fprintf(stderr, " %llu unused payload bytes\n",
+ nPayloadBytes - nRecordBytes - nHeaderBytes);
+ }
+ if( pOverflow ){
+ overflowDestroy(pOverflow);
+ }
+ }
+ }
+ }
+ }else{
+ fprintf(stderr, "page %u size %u is unknown type:\n", iPage, nPageSize);
+ hexdump(PageData(pPage, 0), nPageSize);
+ }
+ sqlite3PagerUnref(pPage);
+ sqlite3_result_text(ctx, "OK", -1, SQLITE_STATIC);
+}
+
+int dumpFnsInit(sqlite3 *db){
+ int rc;
+ rc = sqlite3_create_function_v2(db, "hexdump", 1, SQLITE_ANY, db,
+ HexdumpFn, 0, 0, NULL);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ rc = sqlite3_create_function_v2(db, "dumppage", 1, SQLITE_ANY, db,
+ DumpFn, 0, 0, NULL);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ rc = sqlite3_create_function_v2(db, "dumppage", 2, SQLITE_ANY, db,
+ DumpFn, 0, 0, NULL);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ return SQLITE_OK;
+}
« no previous file with comments | « third_party/sqlite/src/src/pager.c ('k') | third_party/sqlite/src/src/shell.c » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698