1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +03:00

Add the "--lost-and-found" option to the ".recover" command. For setting the name of the orphaned rows table.

FossilOrigin-Name: 67bb88e24c74d02ae0c4ac6ff2f873f6b0035ccefe5cccfc71c5686cbc76b4c3
This commit is contained in:
dan
2019-04-27 18:47:03 +00:00
parent f57bea31ba
commit 42ebb01e9f
5 changed files with 234 additions and 101 deletions

View File

@ -10,10 +10,14 @@
** **
****************************************************************************** ******************************************************************************
** **
** This file contains an implementation of the eponymous "sqlite_dbdata" ** This file contains an implementation of two eponymous virtual tables,
** virtual table. sqlite_dbdata is used to extract data directly from a ** "sqlite_dbdata" and "sqlite_dbptr". Both modules require that the
** database b-tree page and its associated overflow pages, bypassing the b-tree ** "sqlite_dbpage" eponymous virtual table be available.
** layer. The table schema is equivalent to: **
** 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( ** CREATE TABLE sqlite_dbdata(
** pgno INTEGER, ** pgno INTEGER,
@ -23,23 +27,25 @@
** schema TEXT HIDDEN ** schema TEXT HIDDEN
** ); ** );
** **
** IMPORTANT: THE VIRTUAL TABLE SCHEMA ABOVE IS SUBJECT TO CHANGE. IN THE ** IMPORTANT: THE VIRTUAL TABLE SCHEMA ABOVE IS SUBJECT TO CHANGE. IN THE
** FUTURE NEW NON-HIDDEN COLUMNS MAY BE ADDED BETWEEN "value" AND "schema". ** 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 ** Each page of the database is inspected. If it cannot be interpreted as
** b-tree page, or if it is a b-tree page containing 0 entries, the ** 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 ** sqlite_dbdata table contains no rows for that page. Otherwise, the
** contains one row for each field in the record associated with each ** table contains one row for each field in the record associated with
** cell on the page. For intkey b-trees, the key value is stored in field -1. ** 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 ** 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(5, 'v', 'five');
** INSERT INTO t1(rowid, a, b) VALUES(10, 'x', 'ten'); ** INSERT INTO t1(rowid, a, b) VALUES(10, 'x', 'ten');
** **
** the sqlite_dbdata table contains, as well as from entries related to ** the sqlite_dbdata table contains, as well as from entries related to
** page 1, content equivalent to: ** page 1, content equivalent to:
** **
** INSERT INTO sqlite_dbdata(pgno, cell, field, value) VALUES ** INSERT INTO sqlite_dbdata(pgno, cell, field, value) VALUES
** (2, 0, -1, 5 ), ** (2, 0, -1, 5 ),
@ -49,19 +55,21 @@
** (2, 1, 0, 'x' ), ** (2, 1, 0, 'x' ),
** (2, 1, 1, 'ten' ); ** (2, 1, 1, 'ten' );
** **
** If database corruption is encountered, this module does not report an ** If database corruption is encountered, this module does not report an
** error. Instead, it attempts to extract as much data as possible and ** error. Instead, it attempts to extract as much data as possible and
** ignores the corruption. ** ignores the corruption.
**
** This module requires that the "sqlite_dbpage" eponymous virtual table be
** available.
** **
** SQLITE_DBPTR:
** The sqlite_dbptr table has the following schema:
** **
** CREATE TABLE sqlite_dbptr( ** CREATE TABLE sqlite_dbptr(
** pgno INTEGER, ** pgno INTEGER,
** child INTEGER, ** child INTEGER,
** schema TEXT HIDDEN ** 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) #if !defined(SQLITEINT_H)
#include "sqlite3ext.h" #include "sqlite3ext.h"
@ -77,8 +85,7 @@ SQLITE_EXTENSION_INIT1
typedef struct DbdataTable DbdataTable; typedef struct DbdataTable DbdataTable;
typedef struct DbdataCursor DbdataCursor; typedef struct DbdataCursor DbdataCursor;
/* Cursor object */
/* A cursor for the sqlite_dbdata table */
struct DbdataCursor { struct DbdataCursor {
sqlite3_vtab_cursor base; /* Base class. Must be first */ sqlite3_vtab_cursor base; /* Base class. Must be first */
sqlite3_stmt *pStmt; /* For fetching database pages */ sqlite3_stmt *pStmt; /* For fetching database pages */
@ -103,7 +110,7 @@ struct DbdataCursor {
sqlite3_int64 iIntkey; /* Integer key value */ sqlite3_int64 iIntkey; /* Integer key value */
}; };
/* The sqlite_dbdata table */ /* Table object */
struct DbdataTable { struct DbdataTable {
sqlite3_vtab base; /* Base class. Must be first */ sqlite3_vtab base; /* Base class. Must be first */
sqlite3 *db; /* The database connection */ sqlite3 *db; /* The database connection */
@ -111,16 +118,12 @@ struct DbdataTable {
int bPtr; /* True for sqlite3_dbptr table */ int bPtr; /* True for sqlite3_dbptr table */
}; };
/* Column and schema definitions for sqlite_dbdata */
#define DBDATA_COLUMN_PGNO 0 #define DBDATA_COLUMN_PGNO 0
#define DBDATA_COLUMN_CELL 1 #define DBDATA_COLUMN_CELL 1
#define DBDATA_COLUMN_FIELD 2 #define DBDATA_COLUMN_FIELD 2
#define DBDATA_COLUMN_VALUE 3 #define DBDATA_COLUMN_VALUE 3
#define DBDATA_COLUMN_SCHEMA 4 #define DBDATA_COLUMN_SCHEMA 4
#define DBPTR_COLUMN_PGNO 0
#define DBPTR_COLUMN_CHILD 1
#define DBPTR_COLUMN_SCHEMA 2
#define DBDATA_SCHEMA \ #define DBDATA_SCHEMA \
"CREATE TABLE x(" \ "CREATE TABLE x(" \
" pgno INTEGER," \ " pgno INTEGER," \
@ -130,6 +133,10 @@ struct DbdataTable {
" schema TEXT HIDDEN" \ " 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 \ #define DBPTR_SCHEMA \
"CREATE TABLE x(" \ "CREATE TABLE x(" \
" pgno INTEGER," \ " 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( static int dbdataConnect(
sqlite3 *db, 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){ static int dbdataDisconnect(sqlite3_vtab *pVtab){
DbdataTable *pTab = (DbdataTable*)pVtab; DbdataTable *pTab = (DbdataTable*)pVtab;
@ -178,7 +186,6 @@ static int dbdataDisconnect(sqlite3_vtab *pVtab){
} }
/* /*
**
** This function interprets two types of constraints: ** This function interprets two types of constraints:
** **
** schema=? ** 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){ static int dbdataOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
DbdataCursor *pCsr; DbdataCursor *pCsr;
@ -256,6 +263,10 @@ static int dbdataOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
return SQLITE_OK; return SQLITE_OK;
} }
/*
** Restore a cursor object to the state it was in when first allocated
** by dbdataOpen().
*/
static void dbdataResetCursor(DbdataCursor *pCsr){ static void dbdataResetCursor(DbdataCursor *pCsr){
DbdataTable *pTab = (DbdataTable*)(pCsr->base.pVtab); DbdataTable *pTab = (DbdataTable*)(pCsr->base.pVtab);
if( pTab->pStmt==0 ){ 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){ static int dbdataClose(sqlite3_vtab_cursor *pCursor){
DbdataCursor *pCsr = (DbdataCursor*)pCursor; DbdataCursor *pCsr = (DbdataCursor*)pCursor;
@ -280,8 +291,9 @@ static int dbdataClose(sqlite3_vtab_cursor *pCursor){
return SQLITE_OK; 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){ static unsigned int get_uint16(unsigned char *a){
return (a[0]<<8)|a[1]; 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]; 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( static int dbdataLoadPage(
DbdataCursor *pCsr, DbdataCursor *pCsr, /* Cursor object */
u32 pgno, u32 pgno, /* Page number of page to load */
u8 **ppPage, u8 **ppPage, /* OUT: pointer to page buffer */
int *pnPage int *pnPage /* OUT: Size of (*ppPage) in bytes */
){ ){
int rc2; int rc2;
int rc = SQLITE_OK; int rc = SQLITE_OK;
@ -338,6 +360,10 @@ static int dbdataGetVarint(const u8 *z, sqlite3_int64 *pVal){
return 9; return 9;
} }
/*
** Return the number of bytes of space used by an SQLite value of type
** eType.
*/
static int dbdataValueBytes(int eType){ static int dbdataValueBytes(int eType){
switch( eType ){ switch( eType ){
case 0: case 8: case 9: 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){ static void dbdataValue(sqlite3_context *pCtx, int eType, u8 *pData){
switch( eType ){ switch( eType ){
case 0: 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){ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
DbdataCursor *pCsr = (DbdataCursor*)pCursor; DbdataCursor *pCsr = (DbdataCursor*)pCursor;
@ -575,14 +605,20 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
return SQLITE_OK; 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){ static int dbdataEof(sqlite3_vtab_cursor *pCursor){
DbdataCursor *pCsr = (DbdataCursor*)pCursor; DbdataCursor *pCsr = (DbdataCursor*)pCursor;
return pCsr->aPage==0; 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){ static int dbdataDbsize(DbdataCursor *pCsr, const char *zSchema){
DbdataTable *pTab = (DbdataTable*)pCsr->base.pVtab; DbdataTable *pTab = (DbdataTable*)pCsr->base.pVtab;
char *zSql = 0; char *zSql = 0;
@ -601,7 +637,8 @@ static int dbdataDbsize(DbdataCursor *pCsr, const char *zSchema){
return rc; return rc;
} }
/* Position a cursor back to the beginning. /*
** xFilter method for sqlite_dbdata and sqlite_dbptr.
*/ */
static int dbdataFilter( static int dbdataFilter(
sqlite3_vtab_cursor *pCursor, sqlite3_vtab_cursor *pCursor,
@ -648,7 +685,9 @@ static int dbdataFilter(
return rc; return rc;
} }
/* Return a column for the sqlite_dbdata table */ /*
** Return a column for the sqlite_dbdata or sqlite_dbptr table.
*/
static int dbdataColumn( static int dbdataColumn(
sqlite3_vtab_cursor *pCursor, sqlite3_vtab_cursor *pCursor,
sqlite3_context *ctx, sqlite3_context *ctx,
@ -699,7 +738,9 @@ static int dbdataColumn(
return SQLITE_OK; 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){ static int dbdataRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
DbdataCursor *pCsr = (DbdataCursor*)pCursor; DbdataCursor *pCsr = (DbdataCursor*)pCursor;
*pRowid = pCsr->iRowid; *pRowid = pCsr->iRowid;

View File

@ -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. 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-27T15:35:45.828 D 2019-04-27T18:47:03.466
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -284,7 +284,7 @@ F ext/misc/closure.c dbfd8543b2a017ae6b1a5843986b22ddf99ff126ec9634a2f4047cd14c8
F ext/misc/completion.c cec672d40604075bb341a7f11ac48393efdcd90a979269b8fe7977ea62d0547f F ext/misc/completion.c cec672d40604075bb341a7f11ac48393efdcd90a979269b8fe7977ea62d0547f
F ext/misc/compress.c dd4f8a6d0baccff3c694757db5b430f3bbd821d8686d1fc24df55cf9f035b189 F ext/misc/compress.c dd4f8a6d0baccff3c694757db5b430f3bbd821d8686d1fc24df55cf9f035b189
F ext/misc/csv.c 7f047aeb68f5802e7ce6639292095d622a488bb43526ed04810e0649faa71ceb 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/dbdump.c baf6e37447c9d6968417b1cd34cbedb0b0ab3f91b5329501d8a8d5be3287c336
F ext/misc/eval.c 4b4757592d00fd32e44c7a067e6a0e4839c81a4d57abc4131ee7806d1be3104e F ext/misc/eval.c 4b4757592d00fd32e44c7a067e6a0e4839c81a4d57abc4131ee7806d1be3104e
F ext/misc/explain.c d5c12962d79913ef774b297006872af1fccda388f61a11d37758f9179a09551f F ext/misc/explain.c d5c12962d79913ef774b297006872af1fccda388f61a11d37758f9179a09551f
@ -520,7 +520,7 @@ F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
F src/resolve.c 567888ee3faec14dae06519b4306201771058364a37560186a3e0e755ebc4cb8 F src/resolve.c 567888ee3faec14dae06519b4306201771058364a37560186a3e0e755ebc4cb8
F src/rowset.c d977b011993aaea002cab3e0bb2ce50cf346000dff94e944d547b989f4b1fe93 F src/rowset.c d977b011993aaea002cab3e0bb2ce50cf346000dff94e944d547b989f4b1fe93
F src/select.c b7304d2f491c11a03a7fbdf34bc218282ac54052377809d4dc3b4b1e7f4bfc93 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/sqlite.h.in 38390767acc1914d58930e03149595ee4710afa4e3c43ab6c3a8aea3f1a6b8cd
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h 9ecc93b8493bd20c0c07d52e2ac0ed8bab9b549c7f7955b59869597b650dd8b5 F src/sqlite3ext.h 9ecc93b8493bd20c0c07d52e2ac0ed8bab9b549c7f7955b59869597b650dd8b5
@ -1226,7 +1226,7 @@ F test/randexpr1.tcl 40dec52119ed3a2b8b2a773bce24b63a3a746459
F test/randexpr1.test eda062a97e60f9c38ae8d806b03b0ddf23d796df F test/randexpr1.test eda062a97e60f9c38ae8d806b03b0ddf23d796df
F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736 F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736
F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8 F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8
F test/recover.test bfeb5ab4574f9a264b3893ce0e41f04c2052b72b174e5dd9d847b6e7b8f4d15c F test/recover.test 52609c8cc24e72d3d8a20fb8bc32ba2ce8ca2093a7f4573bd4f2969f78f6d2b4
F test/regexp1.test 497ea812f264d12b6198d6e50a76be4a1973a9d8 F test/regexp1.test 497ea812f264d12b6198d6e50a76be4a1973a9d8
F test/regexp2.test 40e894223b3d6672655481493f1be12012f2b33c F test/regexp2.test 40e894223b3d6672655481493f1be12012f2b33c
F test/reindex.test 44edd3966b474468b823d481eafef0c305022254 F test/reindex.test 44edd3966b474468b823d481eafef0c305022254
@ -1821,7 +1821,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 7221f6e33ed6a5a96ec61e25f2a1f70b84aae66e503d897eb7b7ff1aec42355d P 91df4b8e0386105d01614921e8410994b621404a3d46ec4af8687b8743c52d52
R e71b5bc56d773e53b87820c68d2be836 R a9542f620433fc5d37a56caa32ea75c3
U dan U dan
Z d098c243e647f537ccde95cbf714168f Z 07c2469e7eeced685ce45289494bc94e

View File

@ -1 +1 @@
91df4b8e0386105d01614921e8410994b621404a3d46ec4af8687b8743c52d52 67bb88e24c74d02ae0c4ac6ff2f873f6b0035ccefe5cccfc71c5686cbc76b4c3

View File

@ -1104,7 +1104,6 @@ struct ShellState {
#define SHFLG_Newlines 0x00000010 /* .dump --newline flag */ #define SHFLG_Newlines 0x00000010 /* .dump --newline flag */
#define SHFLG_CountChanges 0x00000020 /* .changes setting */ #define SHFLG_CountChanges 0x00000020 /* .changes setting */
#define SHFLG_Echo 0x00000040 /* .echo or --echo setting */ #define SHFLG_Echo 0x00000040 /* .echo or --echo setting */
#define SHFLG_Recover 0x00000080 /* .dump is --recover */
/* /*
** Macros for testing and setting shellFlgs ** Macros for testing and setting shellFlgs
@ -3577,6 +3576,7 @@ static const char *(azHelp[]) = {
".prompt MAIN CONTINUE Replace the standard prompts", ".prompt MAIN CONTINUE Replace the standard prompts",
".quit Exit this program", ".quit Exit this program",
".read FILE Read input from FILE", ".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", ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE",
".save FILE Write in-memory database into FILE", ".save FILE Write in-memory database into FILE",
".scanstats on|off Turn sqlite3_stmt_scanstatus() metrics on or off", ".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) */ #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){ static void shellExec(sqlite3 *db, int *pRc, const char *zSql){
int rc = *pRc; int rc = *pRc;
if( rc==SQLITE_OK ){ 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, ...){ static void shellExecPrintf(sqlite3 *db, int *pRc, const char *zFmt, ...){
char *z = 0; char *z = 0;
if( *pRc==SQLITE_OK ){ 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){ static void *shellMalloc(int *pRc, sqlite3_int64 nByte){
void *pRet = 0; void *pRet = 0;
if( *pRc==SQLITE_OK ){ if( *pRc==SQLITE_OK ){
@ -6194,6 +6209,17 @@ static void *shellMalloc(int *pRc, sqlite3_int64 nByte){
return pRet; 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, ...){ static char *shellMPrintf(int *pRc, const char *zFmt, ...){
char *z = 0; char *z = 0;
if( *pRc==SQLITE_OK ){ if( *pRc==SQLITE_OK ){
@ -6208,21 +6234,25 @@ static char *shellMPrintf(int *pRc, const char *zFmt, ...){
return z; 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; typedef struct RecoverTable RecoverTable;
struct RecoverTable { struct RecoverTable {
char *zName; /* Name of table */ char *zQuoted; /* Quoted version of table name */
char *zQuoted; /* Quoted version of zName */
int nCol; /* Number of columns in table */ int nCol; /* Number of columns in table */
char **azlCol; /* Array of column lists */ 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){ static void recoverFreeTable(RecoverTable *pTab){
if( pTab ){ if( pTab ){
sqlite3_free(pTab->zName);
sqlite3_free(pTab->zQuoted); sqlite3_free(pTab->zQuoted);
if( pTab->azlCol ){ if( pTab->azlCol ){
int i; 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 */ int *pRc, /* IN/OUT: Error code */
const char *zName, /* Name of table */ const char *zName, /* Name of table */
const char *zSql, /* CREATE TABLE statement */ const char *zSql, /* CREATE TABLE statement */
@ -6309,8 +6346,7 @@ static RecoverTable *recoverOldTable(
} }
} }
pTab->zName = shellMPrintf(&rc, "%s", zName); pTab->zQuoted = shellMPrintf(&rc, "%Q", zName);
pTab->zQuoted = shellMPrintf(&rc, "%Q", pTab->zName);
pTab->azlCol = (char**)shellMalloc(&rc, sizeof(char*) * (nSqlCol+1)); pTab->azlCol = (char**)shellMalloc(&rc, sizeof(char*) * (nSqlCol+1));
pTab->nCol = nSqlCol; pTab->nCol = nSqlCol;
@ -6349,7 +6385,7 @@ static RecoverTable *recoverOldTable(
return pTab; return pTab;
} }
static RecoverTable *recoverNewTable( static RecoverTable *recoverFindTable(
ShellState *pState, ShellState *pState,
int *pRc, int *pRc,
int iRoot, int iRoot,
@ -6363,7 +6399,6 @@ static RecoverTable *recoverNewTable(
const char *zSql = 0; const char *zSql = 0;
const char *zName = 0; const char *zName = 0;
/* Search the recovered schema for an object with root page iRoot. */ /* Search the recovered schema for an object with root page iRoot. */
shellPreparePrintf(pState->db, pRc, &pStmt, shellPreparePrintf(pState->db, pRc, &pStmt,
"SELECT type, name, sql FROM recovery.schema WHERE rootpage=%d", iRoot "SELECT type, name, sql FROM recovery.schema WHERE rootpage=%d", iRoot
@ -6377,7 +6412,7 @@ static RecoverTable *recoverNewTable(
if( sqlite3_stricmp(zType, "table")==0 ){ if( sqlite3_stricmp(zType, "table")==0 ){
zName = (const char*)sqlite3_column_text(pStmt, 1); zName = (const char*)sqlite3_column_text(pStmt, 1);
zSql = (const char*)sqlite3_column_text(pStmt, 2); zSql = (const char*)sqlite3_column_text(pStmt, 2);
pRet = recoverOldTable(pRc, zName, zSql, bIntkey, nCol); pRet = recoverNewTable(pRc, zName, zSql, bIntkey, nCol);
break; break;
} }
} }
@ -6390,24 +6425,35 @@ static RecoverTable *recoverNewTable(
static RecoverTable *recoverOrphanTable( static RecoverTable *recoverOrphanTable(
ShellState *pState, ShellState *pState,
int *pRc, int *pRc,
const char *zLostAndFound,
int nCol int nCol
){ ){
RecoverTable *pTab = 0; RecoverTable *pTab = 0;
if( nCol>=0 && *pRc==SQLITE_OK ){ if( nCol>=0 && *pRc==SQLITE_OK ){
int i; int i;
raw_printf(pState->out,
"CREATE TABLE recover_orphan(rootpgno INTEGER, " /* This block determines the name of the orphan table. The prefered
"pgno INTEGER, nfield INTEGER, id INTEGER" ** 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; i<nCol; i++){ if( pTest ) sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT);
raw_printf(pState->out, ", c%d", i); 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)); pTab = (RecoverTable*)shellMalloc(pRc, sizeof(RecoverTable));
if( pTab ){ if( pTab ){
pTab->zName = shellMPrintf(pRc, "%s", "recover_orphan"); pTab->zQuoted = shellMPrintf(pRc, "%Q", zTab);
pTab->zQuoted = shellMPrintf(pRc, "%Q", pTab->zName);
pTab->nCol = nCol; pTab->nCol = nCol;
pTab->iPk = -2; pTab->iPk = -2;
if( nCol>0 ){ if( nCol>0 ){
@ -6419,12 +6465,22 @@ static RecoverTable *recoverOrphanTable(
} }
} }
} }
}
if( *pRc!=SQLITE_OK ){ if( *pRc!=SQLITE_OK ){
recoverFreeTable(pTab); recoverFreeTable(pTab);
pTab = 0; pTab = 0;
}else{
raw_printf(pState->out,
"CREATE TABLE %s(rootpgno INTEGER, "
"pgno INTEGER, nfield INTEGER, id INTEGER", pTab->zQuoted
);
for(i=0; i<nCol; i++){
raw_printf(pState->out, ", c%d", i);
}
raw_printf(pState->out, ");\n");
}
} }
sqlite3_free(zTab);
} }
return pTab; 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 *pPages = 0; /* Loop through all pages in a group */
sqlite3_stmt *pCells = 0; /* Loop through all cells in a page */ sqlite3_stmt *pCells = 0; /* Loop through all cells in a page */
const char *zRecoveryDb = ""; /* Name of "recovery" database */ const char *zRecoveryDb = ""; /* Name of "recovery" database */
const char *zLostAndFound = "lost_and_found";
int i; int i;
int nOrphan = -1; int nOrphan = -1;
RecoverTable *pOrphan = 0; RecoverTable *pOrphan = 0;
@ -6452,16 +6509,21 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
n = strlen(z); n = strlen(z);
if( n<=17 && memcmp("-freelist-corrupt", z, n)==0 ){ if( n<=17 && memcmp("-freelist-corrupt", z, n)==0 ){
bFreelist = 0; bFreelist = 0;
} }else
if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){ if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
i++; i++;
zRecoveryDb = azArg[i]; zRecoveryDb = azArg[i];
}else
if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
i++;
zLostAndFound = azArg[i];
} }
else{ else{
raw_printf(stderr, "unexpected option: %s\n", azArg[i]); raw_printf(stderr, "unexpected option: %s\n", azArg[i]);
raw_printf(stderr, "options are:\n"); raw_printf(stderr, "options are:\n");
raw_printf(stderr, " --freelist-corrupt\n"); raw_printf(stderr, " --freelist-corrupt\n");
raw_printf(stderr, " --recovery-db DATABASE\n"); raw_printf(stderr, " --recovery-db DATABASE\n");
raw_printf(stderr, " --lost-and-found TABLE-NAME\n");
return 1; return 1;
} }
} }
@ -6599,7 +6661,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
} }
shellFinalize(&rc, pLoop); shellFinalize(&rc, pLoop);
pLoop = 0; pLoop = 0;
pOrphan = recoverOrphanTable(pState, &rc, nOrphan); pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
shellPrepare(pState->db, &rc, shellPrepare(pState->db, &rc,
"SELECT pgno FROM recovery.map WHERE root=?", &pPages "SELECT pgno FROM recovery.map WHERE root=?", &pPages
@ -6624,11 +6686,11 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
int bNoop = 0; int bNoop = 0;
RecoverTable *pTab; RecoverTable *pTab;
pTab = recoverNewTable(pState, &rc, iRoot, bIntkey, nCol, &bNoop); pTab = recoverFindTable(pState, &rc, iRoot, bIntkey, nCol, &bNoop);
if( bNoop || rc ) continue; if( bNoop || rc ) continue;
if( pTab==0 ) pTab = pOrphan; 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"); raw_printf(pState->out, "DELETE FROM sqlite_sequence;\n");
} }
sqlite3_bind_int(pPages, 1, iRoot); sqlite3_bind_int(pPages, 1, iRoot);
@ -7042,30 +7104,30 @@ static int do_meta_command(char *zLine, ShellState *p){
p->nErr = 0; p->nErr = 0;
if( zLike==0 ){ if( zLike==0 ){
run_schema_dump_query(p, run_schema_dump_query(p,
"SELECT name, type, sql FROM sqlite_master " "SELECT name, type, sql FROM sqlite_master "
"WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'" "WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'"
); );
run_schema_dump_query(p, run_schema_dump_query(p,
"SELECT name, type, sql FROM sqlite_master " "SELECT name, type, sql FROM sqlite_master "
"WHERE name=='sqlite_sequence'" "WHERE name=='sqlite_sequence'"
); );
run_table_dump_query(p, run_table_dump_query(p,
"SELECT sql FROM sqlite_master " "SELECT sql FROM sqlite_master "
"WHERE sql NOT NULL AND type IN ('index','trigger','view')", 0 "WHERE sql NOT NULL AND type IN ('index','trigger','view')", 0
); );
}else{ }else{
char *zSql; char *zSql;
zSql = sqlite3_mprintf( zSql = sqlite3_mprintf(
"SELECT name, type, sql FROM sqlite_master " "SELECT name, type, sql FROM sqlite_master "
"WHERE tbl_name LIKE %Q AND type=='table'" "WHERE tbl_name LIKE %Q AND type=='table'"
" AND sql NOT NULL", zLike); " AND sql NOT NULL", zLike);
run_schema_dump_query(p,zSql); run_schema_dump_query(p,zSql);
sqlite3_free(zSql); sqlite3_free(zSql);
zSql = sqlite3_mprintf( zSql = sqlite3_mprintf(
"SELECT sql FROM sqlite_master " "SELECT sql FROM sqlite_master "
"WHERE sql NOT NULL" "WHERE sql NOT NULL"
" AND type IN ('index','trigger','view')" " AND type IN ('index','trigger','view')"
" AND tbl_name LIKE %Q", zLike); " AND tbl_name LIKE %Q", zLike);
run_table_dump_query(p, zSql, 0); run_table_dump_query(p, zSql, 0);
sqlite3_free(zSql); sqlite3_free(zSql);
} }

View File

@ -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"] set fd [open "|$::CLI test.db .recover"]
fconfigure $fd -encoding binary fconfigure $fd -encoding binary
fconfigure $fd -translation binary fconfigure $fd -translation binary
@ -48,9 +48,12 @@ proc do_recover_test {tn} {
forcedelete test.db2 forcedelete test.db2
sqlite3 db2 test.db2 sqlite3 db2 test.db2
breakpoint
execsql $sql db2 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 db2 close
} }
@ -96,4 +99,31 @@ do_execsql_test 2.1.0 {
do_recover_test 2.1.1 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 finish_test