From: dan Date: Sat, 27 Apr 2019 18:47:03 +0000 (+0000) Subject: Add the "--lost-and-found" option to the ".recover" command. For setting the name... X-Git-Tag: version-3.29.0~176^2~2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=42ebb01e9fd94c94d4f90b0a59b46194aea28400;p=thirdparty%2Fsqlite.git Add the "--lost-and-found" option to the ".recover" command. For setting the name of the orphaned rows table. FossilOrigin-Name: 67bb88e24c74d02ae0c4ac6ff2f873f6b0035ccefe5cccfc71c5686cbc76b4c3 --- diff --git a/ext/misc/dbdata.c b/ext/misc/dbdata.c index a27f19dc7a..786369ce0a 100644 --- a/ext/misc/dbdata.c +++ b/ext/misc/dbdata.c @@ -10,10 +10,14 @@ ** ****************************************************************************** ** -** This file contains an implementation of the eponymous "sqlite_dbdata" -** virtual table. sqlite_dbdata is used to extract data directly from a -** database b-tree page and its associated overflow pages, bypassing the b-tree -** layer. The table schema is equivalent to: +** This file contains an implementation of two eponymous virtual tables, +** "sqlite_dbdata" and "sqlite_dbptr". Both modules require that the +** "sqlite_dbpage" eponymous virtual table be available. +** +** SQLITE_DBDATA: +** sqlite_dbdata is used to extract data directly from a database b-tree +** page and its associated overflow pages, bypassing the b-tree layer. +** The table schema is equivalent to: ** ** CREATE TABLE sqlite_dbdata( ** pgno INTEGER, @@ -23,23 +27,25 @@ ** schema TEXT HIDDEN ** ); ** -** IMPORTANT: THE VIRTUAL TABLE SCHEMA ABOVE IS SUBJECT TO CHANGE. IN THE -** FUTURE NEW NON-HIDDEN COLUMNS MAY BE ADDED BETWEEN "value" AND "schema". +** IMPORTANT: THE VIRTUAL TABLE SCHEMA ABOVE IS SUBJECT TO CHANGE. IN THE +** FUTURE NEW NON-HIDDEN COLUMNS MAY BE ADDED BETWEEN "value" AND +** "schema". ** -** Each page of the database is inspected. If it cannot be interpreted as a -** b-tree page, or if it is a b-tree page containing 0 entries, the -** sqlite_dbdata table contains no rows for that page. Otherwise, the table -** contains one row for each field in the record associated with each -** cell on the page. For intkey b-trees, the key value is stored in field -1. +** Each page of the database is inspected. If it cannot be interpreted as +** a b-tree page, or if it is a b-tree page containing 0 entries, the +** sqlite_dbdata table contains no rows for that page. Otherwise, the +** table contains one row for each field in the record associated with +** each cell on the page. For intkey b-trees, the key value is stored in +** field -1. ** -** For example, for the database: +** For example, for the database: ** ** CREATE TABLE t1(a, b); -- root page is page 2 ** INSERT INTO t1(rowid, a, b) VALUES(5, 'v', 'five'); ** INSERT INTO t1(rowid, a, b) VALUES(10, 'x', 'ten'); ** -** the sqlite_dbdata table contains, as well as from entries related to -** page 1, content equivalent to: +** the sqlite_dbdata table contains, as well as from entries related to +** page 1, content equivalent to: ** ** INSERT INTO sqlite_dbdata(pgno, cell, field, value) VALUES ** (2, 0, -1, 5 ), @@ -49,19 +55,21 @@ ** (2, 1, 0, 'x' ), ** (2, 1, 1, 'ten' ); ** -** If database corruption is encountered, this module does not report an -** error. Instead, it attempts to extract as much data as possible and -** ignores the corruption. -** -** This module requires that the "sqlite_dbpage" eponymous virtual table be -** available. +** If database corruption is encountered, this module does not report an +** error. Instead, it attempts to extract as much data as possible and +** ignores the corruption. ** +** SQLITE_DBPTR: +** The sqlite_dbptr table has the following schema: ** ** CREATE TABLE sqlite_dbptr( ** pgno INTEGER, ** child INTEGER, ** schema TEXT HIDDEN ** ); +** +** It contains one entry for each b-tree pointer between a parent and +** child page in the database. */ #if !defined(SQLITEINT_H) #include "sqlite3ext.h" @@ -77,8 +85,7 @@ SQLITE_EXTENSION_INIT1 typedef struct DbdataTable DbdataTable; typedef struct DbdataCursor DbdataCursor; - -/* A cursor for the sqlite_dbdata table */ +/* Cursor object */ struct DbdataCursor { sqlite3_vtab_cursor base; /* Base class. Must be first */ sqlite3_stmt *pStmt; /* For fetching database pages */ @@ -103,7 +110,7 @@ struct DbdataCursor { sqlite3_int64 iIntkey; /* Integer key value */ }; -/* The sqlite_dbdata table */ +/* Table object */ struct DbdataTable { sqlite3_vtab base; /* Base class. Must be first */ sqlite3 *db; /* The database connection */ @@ -111,16 +118,12 @@ struct DbdataTable { int bPtr; /* True for sqlite3_dbptr table */ }; +/* Column and schema definitions for sqlite_dbdata */ #define DBDATA_COLUMN_PGNO 0 #define DBDATA_COLUMN_CELL 1 #define DBDATA_COLUMN_FIELD 2 #define DBDATA_COLUMN_VALUE 3 #define DBDATA_COLUMN_SCHEMA 4 - -#define DBPTR_COLUMN_PGNO 0 -#define DBPTR_COLUMN_CHILD 1 -#define DBPTR_COLUMN_SCHEMA 2 - #define DBDATA_SCHEMA \ "CREATE TABLE x(" \ " pgno INTEGER," \ @@ -130,6 +133,10 @@ struct DbdataTable { " schema TEXT HIDDEN" \ ")" +/* Column and schema definitions for sqlite_dbptr */ +#define DBPTR_COLUMN_PGNO 0 +#define DBPTR_COLUMN_CHILD 1 +#define DBPTR_COLUMN_SCHEMA 2 #define DBPTR_SCHEMA \ "CREATE TABLE x(" \ " pgno INTEGER," \ @@ -138,7 +145,8 @@ struct DbdataTable { ")" /* -** Connect to the sqlite_dbdata virtual table. +** Connect to an sqlite_dbdata (pAux==0) or sqlite_dbptr (pAux!=0) virtual +** table. */ static int dbdataConnect( sqlite3 *db, @@ -166,7 +174,7 @@ static int dbdataConnect( } /* -** Disconnect from or destroy a dbdata virtual table. +** Disconnect from or destroy a sqlite_dbdata or sqlite_dbptr virtual table. */ static int dbdataDisconnect(sqlite3_vtab *pVtab){ DbdataTable *pTab = (DbdataTable*)pVtab; @@ -178,7 +186,6 @@ static int dbdataDisconnect(sqlite3_vtab *pVtab){ } /* -** ** This function interprets two types of constraints: ** ** schema=? @@ -239,7 +246,7 @@ static int dbdataBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdx){ } /* -** Open a new dbdata cursor. +** Open a new sqlite_dbdata or sqlite_dbptr cursor. */ static int dbdataOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ DbdataCursor *pCsr; @@ -256,6 +263,10 @@ static int dbdataOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ return SQLITE_OK; } +/* +** Restore a cursor object to the state it was in when first allocated +** by dbdataOpen(). +*/ static void dbdataResetCursor(DbdataCursor *pCsr){ DbdataTable *pTab = (DbdataTable*)(pCsr->base.pVtab); if( pTab->pStmt==0 ){ @@ -271,7 +282,7 @@ static void dbdataResetCursor(DbdataCursor *pCsr){ } /* -** Close a dbdata cursor. +** Close an sqlite_dbdata or sqlite_dbptr cursor. */ static int dbdataClose(sqlite3_vtab_cursor *pCursor){ DbdataCursor *pCsr = (DbdataCursor*)pCursor; @@ -280,8 +291,9 @@ static int dbdataClose(sqlite3_vtab_cursor *pCursor){ return SQLITE_OK; } - -/* Decode big-endian integers */ +/* +** Utility methods to decode 16 and 32-bit big-endian unsigned integers. +*/ static unsigned int get_uint16(unsigned char *a){ return (a[0]<<8)|a[1]; } @@ -289,11 +301,21 @@ static unsigned int get_uint32(unsigned char *a){ return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|a[3]; } +/* +** Load page pgno from the database via the sqlite_dbpage virtual table. +** If successful, set (*ppPage) to point to a buffer containing the page +** data, (*pnPage) to the size of that buffer in bytes and return +** SQLITE_OK. In this case it is the responsibility of the caller to +** eventually free the buffer using sqlite3_free(). +** +** Or, if an error occurs, set both (*ppPage) and (*pnPage) to 0 and +** return an SQLite error code. +*/ static int dbdataLoadPage( - DbdataCursor *pCsr, - u32 pgno, - u8 **ppPage, - int *pnPage + DbdataCursor *pCsr, /* Cursor object */ + u32 pgno, /* Page number of page to load */ + u8 **ppPage, /* OUT: pointer to page buffer */ + int *pnPage /* OUT: Size of (*ppPage) in bytes */ ){ int rc2; int rc = SQLITE_OK; @@ -338,6 +360,10 @@ static int dbdataGetVarint(const u8 *z, sqlite3_int64 *pVal){ return 9; } +/* +** Return the number of bytes of space used by an SQLite value of type +** eType. +*/ static int dbdataValueBytes(int eType){ switch( eType ){ case 0: case 8: case 9: @@ -361,6 +387,10 @@ static int dbdataValueBytes(int eType){ } } +/* +** Load a value of type eType from buffer pData and use it to set the +** result of context object pCtx. +*/ static void dbdataValue(sqlite3_context *pCtx, int eType, u8 *pData){ switch( eType ){ case 0: @@ -411,7 +441,7 @@ static void dbdataValue(sqlite3_context *pCtx, int eType, u8 *pData){ /* -** Move a dbdata cursor to the next entry in the file. +** Move an sqlite_dbdata or sqlite_dbptr cursor to the next entry. */ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ DbdataCursor *pCsr = (DbdataCursor*)pCursor; @@ -575,14 +605,20 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ return SQLITE_OK; } -/* We have reached EOF if previous sqlite3_step() returned -** anything other than SQLITE_ROW; +/* +** Return true if the cursor is at EOF. */ static int dbdataEof(sqlite3_vtab_cursor *pCursor){ DbdataCursor *pCsr = (DbdataCursor*)pCursor; return pCsr->aPage==0; } +/* +** Determine the size in pages of database zSchema (where zSchema is +** "main", "temp" or the name of an attached database) and set +** pCsr->szDb accordingly. If successful, return SQLITE_OK. Otherwise, +** an SQLite error code. +*/ static int dbdataDbsize(DbdataCursor *pCsr, const char *zSchema){ DbdataTable *pTab = (DbdataTable*)pCsr->base.pVtab; char *zSql = 0; @@ -601,7 +637,8 @@ static int dbdataDbsize(DbdataCursor *pCsr, const char *zSchema){ return rc; } -/* Position a cursor back to the beginning. +/* +** xFilter method for sqlite_dbdata and sqlite_dbptr. */ static int dbdataFilter( sqlite3_vtab_cursor *pCursor, @@ -648,7 +685,9 @@ static int dbdataFilter( return rc; } -/* Return a column for the sqlite_dbdata table */ +/* +** Return a column for the sqlite_dbdata or sqlite_dbptr table. +*/ static int dbdataColumn( sqlite3_vtab_cursor *pCursor, sqlite3_context *ctx, @@ -699,7 +738,9 @@ static int dbdataColumn( return SQLITE_OK; } -/* Return the ROWID for the sqlite_dbdata table */ +/* +** Return the rowid for an sqlite_dbdata or sqlite_dptr table. +*/ static int dbdataRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ DbdataCursor *pCsr = (DbdataCursor*)pCursor; *pRowid = pCsr->iRowid; diff --git a/manifest b/manifest index 94b2e05ed0..2f2f86a8fc 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\sproblem\sin\sthe\s.recover\scommand\swith\srecovering\sWITHOUT\sROWID\stables\swhere\sthe\sPK\scolumns\sare\snot\sthe\sleftmost\sin\sthe\sCREATE\sTABLE\sstatement. -D 2019-04-27T15:35:45.828 +C Add\sthe\s"--lost-and-found"\soption\sto\sthe\s".recover"\scommand.\sFor\ssetting\sthe\sname\sof\sthe\sorphaned\srows\stable. +D 2019-04-27T18:47:03.466 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -284,7 +284,7 @@ F ext/misc/closure.c dbfd8543b2a017ae6b1a5843986b22ddf99ff126ec9634a2f4047cd14c8 F ext/misc/completion.c cec672d40604075bb341a7f11ac48393efdcd90a979269b8fe7977ea62d0547f F ext/misc/compress.c dd4f8a6d0baccff3c694757db5b430f3bbd821d8686d1fc24df55cf9f035b189 F ext/misc/csv.c 7f047aeb68f5802e7ce6639292095d622a488bb43526ed04810e0649faa71ceb -F ext/misc/dbdata.c b7547f43906f9296e43be807ca78e2ef3f335ca26d6e91d178df30cd2fd46572 +F ext/misc/dbdata.c fe978dad2df13dd4b377b5d38f4883282801b18711220a229d0fd266a5deab26 F ext/misc/dbdump.c baf6e37447c9d6968417b1cd34cbedb0b0ab3f91b5329501d8a8d5be3287c336 F ext/misc/eval.c 4b4757592d00fd32e44c7a067e6a0e4839c81a4d57abc4131ee7806d1be3104e F ext/misc/explain.c d5c12962d79913ef774b297006872af1fccda388f61a11d37758f9179a09551f @@ -520,7 +520,7 @@ F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384 F src/resolve.c 567888ee3faec14dae06519b4306201771058364a37560186a3e0e755ebc4cb8 F src/rowset.c d977b011993aaea002cab3e0bb2ce50cf346000dff94e944d547b989f4b1fe93 F src/select.c b7304d2f491c11a03a7fbdf34bc218282ac54052377809d4dc3b4b1e7f4bfc93 -F src/shell.c.in 3701177f3821330c8eb2af96f60123245cf42273abdae472bcb96bb120dcba8f +F src/shell.c.in 51f027f6b48fab39e0745915d979747880b7c35827798535726ac44366a291f1 F src/sqlite.h.in 38390767acc1914d58930e03149595ee4710afa4e3c43ab6c3a8aea3f1a6b8cd F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 9ecc93b8493bd20c0c07d52e2ac0ed8bab9b549c7f7955b59869597b650dd8b5 @@ -1226,7 +1226,7 @@ F test/randexpr1.tcl 40dec52119ed3a2b8b2a773bce24b63a3a746459 F test/randexpr1.test eda062a97e60f9c38ae8d806b03b0ddf23d796df F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736 F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8 -F test/recover.test bfeb5ab4574f9a264b3893ce0e41f04c2052b72b174e5dd9d847b6e7b8f4d15c +F test/recover.test 52609c8cc24e72d3d8a20fb8bc32ba2ce8ca2093a7f4573bd4f2969f78f6d2b4 F test/regexp1.test 497ea812f264d12b6198d6e50a76be4a1973a9d8 F test/regexp2.test 40e894223b3d6672655481493f1be12012f2b33c F test/reindex.test 44edd3966b474468b823d481eafef0c305022254 @@ -1821,7 +1821,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 7221f6e33ed6a5a96ec61e25f2a1f70b84aae66e503d897eb7b7ff1aec42355d -R e71b5bc56d773e53b87820c68d2be836 +P 91df4b8e0386105d01614921e8410994b621404a3d46ec4af8687b8743c52d52 +R a9542f620433fc5d37a56caa32ea75c3 U dan -Z d098c243e647f537ccde95cbf714168f +Z 07c2469e7eeced685ce45289494bc94e diff --git a/manifest.uuid b/manifest.uuid index 6b5834e3bb..e6d0f63ea7 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -91df4b8e0386105d01614921e8410994b621404a3d46ec4af8687b8743c52d52 \ No newline at end of file +67bb88e24c74d02ae0c4ac6ff2f873f6b0035ccefe5cccfc71c5686cbc76b4c3 \ No newline at end of file diff --git a/src/shell.c.in b/src/shell.c.in index f906205bff..8c967a83d8 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -1104,7 +1104,6 @@ struct ShellState { #define SHFLG_Newlines 0x00000010 /* .dump --newline flag */ #define SHFLG_CountChanges 0x00000020 /* .changes setting */ #define SHFLG_Echo 0x00000040 /* .echo or --echo setting */ -#define SHFLG_Recover 0x00000080 /* .dump is --recover */ /* ** Macros for testing and setting shellFlgs @@ -3577,6 +3576,7 @@ static const char *(azHelp[]) = { ".prompt MAIN CONTINUE Replace the standard prompts", ".quit Exit this program", ".read FILE Read input from FILE", + ".recover Recover as much data as possible from corrupt db.", ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE", ".save FILE Write in-memory database into FILE", ".scanstats on|off Turn sqlite3_stmt_scanstatus() metrics on or off", @@ -6153,6 +6153,12 @@ end_ar_command: **********************************************************************************/ #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */ +/* +** If (*pRc) is not SQLITE_OK when this function is called, it is a no-op. +** Otherwise, the SQL statement or statements in zSql are executed using +** database connection db and the error code written to *pRc before +** this function returns. +*/ static void shellExec(sqlite3 *db, int *pRc, const char *zSql){ int rc = *pRc; if( rc==SQLITE_OK ){ @@ -6165,6 +6171,9 @@ static void shellExec(sqlite3 *db, int *pRc, const char *zSql){ } } +/* +** Like shellExec(), except that zFmt is a printf() style format string. +*/ static void shellExecPrintf(sqlite3 *db, int *pRc, const char *zFmt, ...){ char *z = 0; if( *pRc==SQLITE_OK ){ @@ -6181,6 +6190,12 @@ static void shellExecPrintf(sqlite3 *db, int *pRc, const char *zFmt, ...){ } } +/* +** If *pRc is not SQLITE_OK when this function is called, it is a no-op. +** Otherwise, an attempt is made to allocate, zero and return a pointer +** to a buffer nByte bytes in size. If an OOM error occurs, *pRc is set +** to SQLITE_NOMEM and NULL returned. +*/ static void *shellMalloc(int *pRc, sqlite3_int64 nByte){ void *pRet = 0; if( *pRc==SQLITE_OK ){ @@ -6194,6 +6209,17 @@ static void *shellMalloc(int *pRc, sqlite3_int64 nByte){ return pRet; } +/* +** If *pRc is not SQLITE_OK when this function is called, it is a no-op. +** Otherwise, zFmt is treated as a printf() style string. The result of +** formatting it along with any trailing arguments is written into a +** buffer obtained from sqlite3_malloc(), and pointer to which is returned. +** It is the responsibility of the caller to eventually free this buffer +** using a call to sqlite3_free(). +** +** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM and a NULL +** pointer returned. +*/ static char *shellMPrintf(int *pRc, const char *zFmt, ...){ char *z = 0; if( *pRc==SQLITE_OK ){ @@ -6208,21 +6234,25 @@ static char *shellMPrintf(int *pRc, const char *zFmt, ...){ return z; } +/* +** When running the ".recover" command, each output table, and the special +** orphaned row table if it is required, is represented by an instance +** of the following struct. +*/ typedef struct RecoverTable RecoverTable; struct RecoverTable { - char *zName; /* Name of table */ - char *zQuoted; /* Quoted version of zName */ + char *zQuoted; /* Quoted version of table name */ int nCol; /* Number of columns in table */ char **azlCol; /* Array of column lists */ - int iPk; + int iPk; /* Index of IPK column */ }; /* -** Free a RecoverTable object allocated by recoverNewTable() +** Free a RecoverTable object allocated by recoverFindTable() or +** recoverOrphanTable(). */ static void recoverFreeTable(RecoverTable *pTab){ if( pTab ){ - sqlite3_free(pTab->zName); sqlite3_free(pTab->zQuoted); if( pTab->azlCol ){ int i; @@ -6235,7 +6265,14 @@ static void recoverFreeTable(RecoverTable *pTab){ } } -static RecoverTable *recoverOldTable( +/* +** This function is a no-op if (*pRc) is not SQLITE_OK when it is called. +** Otherwise, it allocates and returns a RecoverTable object based on the +** final four arguments passed to this function. It is the responsibility +** of the caller to eventually free the returned object using +** recoverFreeTable(). +*/ +static RecoverTable *recoverNewTable( int *pRc, /* IN/OUT: Error code */ const char *zName, /* Name of table */ const char *zSql, /* CREATE TABLE statement */ @@ -6309,8 +6346,7 @@ static RecoverTable *recoverOldTable( } } - pTab->zName = shellMPrintf(&rc, "%s", zName); - pTab->zQuoted = shellMPrintf(&rc, "%Q", pTab->zName); + pTab->zQuoted = shellMPrintf(&rc, "%Q", zName); pTab->azlCol = (char**)shellMalloc(&rc, sizeof(char*) * (nSqlCol+1)); pTab->nCol = nSqlCol; @@ -6349,7 +6385,7 @@ static RecoverTable *recoverOldTable( return pTab; } -static RecoverTable *recoverNewTable( +static RecoverTable *recoverFindTable( ShellState *pState, int *pRc, int iRoot, @@ -6363,7 +6399,6 @@ static RecoverTable *recoverNewTable( const char *zSql = 0; const char *zName = 0; - /* Search the recovered schema for an object with root page iRoot. */ shellPreparePrintf(pState->db, pRc, &pStmt, "SELECT type, name, sql FROM recovery.schema WHERE rootpage=%d", iRoot @@ -6377,7 +6412,7 @@ static RecoverTable *recoverNewTable( if( sqlite3_stricmp(zType, "table")==0 ){ zName = (const char*)sqlite3_column_text(pStmt, 1); zSql = (const char*)sqlite3_column_text(pStmt, 2); - pRet = recoverOldTable(pRc, zName, zSql, bIntkey, nCol); + pRet = recoverNewTable(pRc, zName, zSql, bIntkey, nCol); break; } } @@ -6390,24 +6425,35 @@ static RecoverTable *recoverNewTable( static RecoverTable *recoverOrphanTable( ShellState *pState, int *pRc, + const char *zLostAndFound, int nCol ){ RecoverTable *pTab = 0; if( nCol>=0 && *pRc==SQLITE_OK ){ int i; - raw_printf(pState->out, - "CREATE TABLE recover_orphan(rootpgno INTEGER, " - "pgno INTEGER, nfield INTEGER, id INTEGER" + + /* This block determines the name of the orphan table. The prefered + ** name is zLostAndFound. But if that clashes with another name + ** in the recovered schema, try zLostAndFound_0, zLostAndFound_1 + ** and so on until a non-clashing name is found. */ + int iTab = 0; + char *zTab = shellMPrintf(pRc, "%s", zLostAndFound); + sqlite3_stmt *pTest = 0; + shellPrepare(pState->db, pRc, + "SELECT 1 FROM recovery.schema WHERE name=?", &pTest ); - for(i=0; iout, ", c%d", i); + if( pTest ) sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT); + while( *pRc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pTest) ){ + shellReset(pRc, pTest); + sqlite3_free(zTab); + zTab = shellMPrintf(pRc, "%s_%d", zLostAndFound, iTab++); + sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT); } - raw_printf(pState->out, ");\n"); + shellFinalize(pRc, pTest); pTab = (RecoverTable*)shellMalloc(pRc, sizeof(RecoverTable)); if( pTab ){ - pTab->zName = shellMPrintf(pRc, "%s", "recover_orphan"); - pTab->zQuoted = shellMPrintf(pRc, "%Q", pTab->zName); + pTab->zQuoted = shellMPrintf(pRc, "%Q", zTab); pTab->nCol = nCol; pTab->iPk = -2; if( nCol>0 ){ @@ -6419,12 +6465,22 @@ static RecoverTable *recoverOrphanTable( } } } - } - if( *pRc!=SQLITE_OK ){ - recoverFreeTable(pTab); - pTab = 0; + if( *pRc!=SQLITE_OK ){ + recoverFreeTable(pTab); + pTab = 0; + }else{ + raw_printf(pState->out, + "CREATE TABLE %s(rootpgno INTEGER, " + "pgno INTEGER, nfield INTEGER, id INTEGER", pTab->zQuoted + ); + for(i=0; iout, ", c%d", i); + } + raw_printf(pState->out, ");\n"); + } } + sqlite3_free(zTab); } return pTab; } @@ -6440,6 +6496,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ sqlite3_stmt *pPages = 0; /* Loop through all pages in a group */ sqlite3_stmt *pCells = 0; /* Loop through all cells in a page */ const char *zRecoveryDb = ""; /* Name of "recovery" database */ + const char *zLostAndFound = "lost_and_found"; int i; int nOrphan = -1; RecoverTable *pOrphan = 0; @@ -6452,16 +6509,21 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ n = strlen(z); if( n<=17 && memcmp("-freelist-corrupt", z, n)==0 ){ bFreelist = 0; - } + }else if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){ i++; zRecoveryDb = azArg[i]; + }else + if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){ + i++; + zLostAndFound = azArg[i]; } else{ raw_printf(stderr, "unexpected option: %s\n", azArg[i]); raw_printf(stderr, "options are:\n"); raw_printf(stderr, " --freelist-corrupt\n"); raw_printf(stderr, " --recovery-db DATABASE\n"); + raw_printf(stderr, " --lost-and-found TABLE-NAME\n"); return 1; } } @@ -6599,7 +6661,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ } shellFinalize(&rc, pLoop); pLoop = 0; - pOrphan = recoverOrphanTable(pState, &rc, nOrphan); + pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan); shellPrepare(pState->db, &rc, "SELECT pgno FROM recovery.map WHERE root=?", &pPages @@ -6624,11 +6686,11 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ int bNoop = 0; RecoverTable *pTab; - pTab = recoverNewTable(pState, &rc, iRoot, bIntkey, nCol, &bNoop); + pTab = recoverFindTable(pState, &rc, iRoot, bIntkey, nCol, &bNoop); if( bNoop || rc ) continue; if( pTab==0 ) pTab = pOrphan; - if( 0==sqlite3_stricmp(pTab->zName, "sqlite_sequence") ){ + if( 0==sqlite3_stricmp(pTab->zQuoted, "'sqlite_sequence'") ){ raw_printf(pState->out, "DELETE FROM sqlite_sequence;\n"); } sqlite3_bind_int(pPages, 1, iRoot); @@ -7042,30 +7104,30 @@ static int do_meta_command(char *zLine, ShellState *p){ p->nErr = 0; if( zLike==0 ){ run_schema_dump_query(p, - "SELECT name, type, sql FROM sqlite_master " - "WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'" - ); + "SELECT name, type, sql FROM sqlite_master " + "WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'" + ); run_schema_dump_query(p, - "SELECT name, type, sql FROM sqlite_master " - "WHERE name=='sqlite_sequence'" - ); + "SELECT name, type, sql FROM sqlite_master " + "WHERE name=='sqlite_sequence'" + ); run_table_dump_query(p, - "SELECT sql FROM sqlite_master " - "WHERE sql NOT NULL AND type IN ('index','trigger','view')", 0 - ); + "SELECT sql FROM sqlite_master " + "WHERE sql NOT NULL AND type IN ('index','trigger','view')", 0 + ); }else{ char *zSql; zSql = sqlite3_mprintf( - "SELECT name, type, sql FROM sqlite_master " - "WHERE tbl_name LIKE %Q AND type=='table'" - " AND sql NOT NULL", zLike); + "SELECT name, type, sql FROM sqlite_master " + "WHERE tbl_name LIKE %Q AND type=='table'" + " AND sql NOT NULL", zLike); run_schema_dump_query(p,zSql); sqlite3_free(zSql); zSql = sqlite3_mprintf( - "SELECT sql FROM sqlite_master " - "WHERE sql NOT NULL" - " AND type IN ('index','trigger','view')" - " AND tbl_name LIKE %Q", zLike); + "SELECT sql FROM sqlite_master " + "WHERE sql NOT NULL" + " AND type IN ('index','trigger','view')" + " AND tbl_name LIKE %Q", zLike); run_table_dump_query(p, zSql, 0); sqlite3_free(zSql); } diff --git a/test/recover.test b/test/recover.test index 4de7f503a5..4dad0d646d 100644 --- a/test/recover.test +++ b/test/recover.test @@ -39,7 +39,7 @@ proc compare_dbs {db1 db2} { } } -proc do_recover_test {tn} { +proc do_recover_test {tn {tsql {}} {res {}}} { set fd [open "|$::CLI test.db .recover"] fconfigure $fd -encoding binary fconfigure $fd -translation binary @@ -48,9 +48,12 @@ proc do_recover_test {tn} { forcedelete test.db2 sqlite3 db2 test.db2 - breakpoint execsql $sql db2 - uplevel [list do_test $tn [list compare_dbs db db2] {}] + if {$tsql==""} { + uplevel [list do_test $tn [list compare_dbs db db2] {}] + } else { + uplevel [list do_execsql_test -db db2 $tn $tsql $res] + } db2 close } @@ -96,4 +99,31 @@ do_execsql_test 2.1.0 { do_recover_test 2.1.1 +do_execsql_test 2.2.0 { + PRAGMA writable_schema = 1; + DELETE FROM sqlite_master WHERE name='t1'; +} +do_recover_test 2.2.1 { + SELECT name FROM sqlite_master +} {lost_and_found} + +do_execsql_test 2.3.0 { + CREATE TABLE lost_and_found(a, b, c); +} +do_recover_test 2.3.1 { + SELECT name FROM sqlite_master +} {lost_and_found lost_and_found_0} + +do_execsql_test 2.4.0 { + CREATE TABLE lost_and_found_0(a, b, c); +} +do_recover_test 2.4.1 { + SELECT name FROM sqlite_master; + SELECT * FROM lost_and_found_1; +} {lost_and_found lost_and_found_0 lost_and_found_1 + 2 2 3 {} 2 3 1 + 2 2 3 {} 5 6 4 + 2 2 3 {} 8 9 7 +} + finish_test