From: dan Date: Sat, 20 Apr 2019 20:57:28 +0000 (+0000) Subject: Add the ".recovery" command to the shell tool. For recovering the maximum amount... X-Git-Tag: version-3.29.0~176^2~14 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=68cb86ef23e3bc9e6362fec4937968a65ec025b5;p=thirdparty%2Fsqlite.git Add the ".recovery" command to the shell tool. For recovering the maximum amount data from corrupt databases. Still needs work. FossilOrigin-Name: 7461d2e120f2149315ddac2676d51d7445bcdb8e97543effd9c30603517ef9da --- diff --git a/ext/misc/dbdata.c b/ext/misc/dbdata.c index eb2bc01600..aae862fc7f 100644 --- a/ext/misc/dbdata.c +++ b/ext/misc/dbdata.c @@ -63,11 +63,11 @@ ** schema TEXT HIDDEN ** ); */ -#if !defined(SQLITEINT_H) +#if !defined(SQLITEINT_H) #include "sqlite3ext.h" typedef unsigned char u8; -typedef unsigned int u32; +typedef unsigned long u32; #endif SQLITE_EXTENSION_INIT1 @@ -94,8 +94,11 @@ struct DbdataCursor { /* Only for the sqlite_dbdata table */ u8 *pRec; /* Buffer containing current record */ int nRec; /* Size of pRec[] in bytes */ - int nField; /* Number of fields in pRec */ + int nHdr; /* Size of header in bytes */ int iField; /* Current field number */ + u8 *pHdrPtr; + u8 *pPtr; + sqlite3_int64 iIntkey; /* Integer key value */ }; @@ -306,6 +309,78 @@ static int dbdataGetVarint(const u8 *z, sqlite3_int64 *pVal){ return 9; } +static int dbdataValueBytes(int eType){ + switch( eType ){ + case 0: case 8: case 9: + case 10: case 11: + return 0; + case 1: + return 1; + case 2: + return 2; + case 3: + return 3; + case 4: + return 4; + case 5: + return 6; + case 6: + case 7: + return 8; + default: + return ((eType-12) / 2); + } +} + +static void dbdataValue(sqlite3_context *pCtx, int eType, u8 *pData){ + switch( eType ){ + case 0: + case 10: + case 11: + sqlite3_result_null(pCtx); + break; + + case 8: + sqlite3_result_int(pCtx, 0); + break; + case 9: + sqlite3_result_int(pCtx, 1); + break; + + case 1: case 2: case 3: case 4: case 5: case 6: case 7: { + sqlite3_uint64 v = (signed char)pData[0]; + pData++; + switch( eType ){ + case 7: + case 6: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2; + case 5: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2; + case 4: v = (v<<8) + pData[0]; pData++; + case 3: v = (v<<8) + pData[0]; pData++; + case 2: v = (v<<8) + pData[0]; pData++; + } + + if( eType==7 ){ + double r; + memcpy(&r, &v, sizeof(r)); + sqlite3_result_double(pCtx, r); + }else{ + sqlite3_result_int64(pCtx, (sqlite3_int64)v); + } + break; + } + + default: { + int n = ((eType-12) / 2); + if( eType % 2 ){ + sqlite3_result_text(pCtx, (const char*)pData, n, SQLITE_TRANSIENT); + }else{ + sqlite3_result_blob(pCtx, pData, n, SQLITE_TRANSIENT); + } + } + } +} + + /* ** Move a dbdata cursor to the next entry in the file. */ @@ -435,20 +510,23 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ } } - /* Figure out how many fields in the record */ - pCsr->nField = 0; iHdr = dbdataGetVarint(pCsr->pRec, &nHdr); - while( iHdrpRec[iHdr], &iDummy); - pCsr->nField++; + pCsr->nHdr = nHdr; + pCsr->pHdrPtr = &pCsr->pRec[iHdr]; + pCsr->pPtr = &pCsr->pRec[pCsr->nHdr]; + pCsr->iField = (bHasRowid ? -1 : 0); + }else{ + pCsr->iField++; + if( pCsr->iField>0 ){ + sqlite3_int64 iType; + pCsr->pHdrPtr += dbdataGetVarint(pCsr->pHdrPtr, &iType); + pCsr->pPtr += dbdataValueBytes(iType); } - - pCsr->iField = (bHasRowid ? -2 : -1); } - - pCsr->iField++; - if( pCsr->iFieldnField ) return SQLITE_OK; + + if( pCsr->iField<0 || pCsr->pHdrPtr<&pCsr->pRec[pCsr->nHdr] ){ + return SQLITE_OK; + } /* Advance to the next cell. The next iteration of the loop will load ** the record and so on. */ @@ -485,7 +563,7 @@ static int dbdataFilter( dbdataResetCursor(pCsr); assert( pCsr->iPgno==1 ); if( idxNum & 0x01 ){ - zSchema = sqlite3_value_text(argv[0]); + zSchema = (const char*)sqlite3_value_text(argv[0]); } if( idxNum & 0x02 ){ pCsr->iPgno = sqlite3_value_int(argv[(idxNum & 0x01)]); @@ -498,6 +576,8 @@ static int dbdataFilter( ); if( rc==SQLITE_OK ){ rc = sqlite3_bind_text(pCsr->pStmt, 1, zSchema, -1, SQLITE_TRANSIENT); + }else{ + pTab->base.zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db)); } if( rc==SQLITE_OK ){ rc = dbdataNext(pCursor); @@ -505,77 +585,6 @@ static int dbdataFilter( return rc; } -static int dbdataValueBytes(int eType){ - switch( eType ){ - case 0: case 8: case 9: - case 10: case 11: - return 0; - case 1: - return 1; - case 2: - return 2; - case 3: - return 3; - case 4: - return 4; - case 5: - return 6; - case 6: - case 7: - return 8; - default: - return ((eType-12) / 2); - } -} - -static void dbdataValue(sqlite3_context *pCtx, int eType, u8 *pData){ - switch( eType ){ - case 0: - case 10: - case 11: - sqlite3_result_null(pCtx); - break; - - case 8: - sqlite3_result_int(pCtx, 0); - break; - case 9: - sqlite3_result_int(pCtx, 1); - break; - - case 1: case 2: case 3: case 4: case 5: case 6: case 7: { - sqlite3_uint64 v = (signed char)pData[0]; - pData++; - switch( eType ){ - case 7: - case 6: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2; - case 5: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2; - case 4: v = (v<<8) + pData[0]; pData++; - case 3: v = (v<<8) + pData[0]; pData++; - case 2: v = (v<<8) + pData[0]; pData++; - } - - if( eType==7 ){ - double r; - memcpy(&r, &v, sizeof(r)); - sqlite3_result_double(pCtx, r); - }else{ - sqlite3_result_int64(pCtx, (sqlite3_int64)v); - } - break; - } - - default: { - int n = ((eType-12) / 2); - if( eType % 2 ){ - sqlite3_result_text(pCtx, pData, n, SQLITE_TRANSIENT); - }else{ - sqlite3_result_blob(pCtx, pData, n, SQLITE_TRANSIENT); - } - } - } -} - /* Return a column for the sqlite_dbdata table */ static int dbdataColumn( sqlite3_vtab_cursor *pCursor, @@ -616,18 +625,9 @@ static int dbdataColumn( if( pCsr->iField<0 ){ sqlite3_result_int64(ctx, pCsr->iIntkey); }else{ - int iHdr; sqlite3_int64 iType; - sqlite3_int64 iOff; - int i; - iHdr = dbdataGetVarint(pCsr->pRec, &iOff); - for(i=0; iiField; i++){ - iHdr += dbdataGetVarint(&pCsr->pRec[iHdr], &iType); - iOff += dbdataValueBytes(iType); - } - dbdataGetVarint(&pCsr->pRec[iHdr], &iType); - - dbdataValue(ctx, iType, &pCsr->pRec[iOff]); + dbdataGetVarint(pCsr->pHdrPtr, &iType); + dbdataValue(ctx, iType, pCsr->pPtr); } break; } diff --git a/main.mk b/main.mk index f418eec68a..508554d733 100644 --- a/main.mk +++ b/main.mk @@ -738,6 +738,7 @@ SHELL_SRC = \ $(TOP)/ext/expert/sqlite3expert.h \ $(TOP)/ext/misc/zipfile.c \ $(TOP)/ext/misc/memtrace.c \ + $(TOP)/ext/misc/dbdata.c \ $(TOP)/src/test_windirent.c shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl diff --git a/manifest b/manifest index d6a2b045e8..c103da1624 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sthe\ssqlite_dbptr\svirtual\stable\sto\sthe\sdbdata\sextension.\sFor\squerying\sthe\slinks\sbetween\sb-tree\spages. -D 2019-04-18T21:14:11.260 +C Add\sthe\s".recovery"\scommand\sto\sthe\sshell\stool.\sFor\srecovering\sthe\smaximum\samount\sdata\sfrom\scorrupt\sdatabases.\sStill\sneeds\swork. +D 2019-04-20T20:57:28.136 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 20d85d7d7503817a5f9ac7d93c4ae7bf5fcc40570a77adc417acab0500058c99 +F ext/misc/dbdata.c 8f74f25565e1f57942f75134ef94ab73058849bed87d92be619be7d3b8a7d054 F ext/misc/dbdump.c baf6e37447c9d6968417b1cd34cbedb0b0ab3f91b5329501d8a8d5be3287c336 F ext/misc/eval.c 4b4757592d00fd32e44c7a067e6a0e4839c81a4d57abc4131ee7806d1be3104e F ext/misc/explain.c d5c12962d79913ef774b297006872af1fccda388f61a11d37758f9179a09551f @@ -441,7 +441,7 @@ F ext/userauth/userauth.c f81aa5a3ecacf406f170c62a144405858f6f6de51dbdc0920134e6 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 -F main.mk 23d3660f7053d196aef76938bf78b10fc3ce1831a85d96bd71565758788f34d4 +F main.mk 125adda36bb32c99dc3a11340bd029ef373b9523eac2b2af76087bfe82d4fdf8 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 @@ -520,7 +520,7 @@ F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384 F src/resolve.c 567888ee3faec14dae06519b4306201771058364a37560186a3e0e755ebc4cb8 F src/rowset.c d977b011993aaea002cab3e0bb2ce50cf346000dff94e944d547b989f4b1fe93 F src/select.c 9263f5c30dd44c7ac2eb29f40a7ec64322a96885b71c00de6bc30b756c2e1c49 -F src/shell.c.in c1986496062f9dba4ed5b70db06b5e0f32e1954cdcfab0b30372c6c186796810 +F src/shell.c.in 3646e448cc207fa3118266c2a23f177306b068e42aae6fee64323ffb13307680 F src/sqlite.h.in 38390767acc1914d58930e03149595ee4710afa4e3c43ab6c3a8aea3f1a6b8cd F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 9ecc93b8493bd20c0c07d52e2ac0ed8bab9b549c7f7955b59869597b650dd8b5 @@ -1754,7 +1754,7 @@ F tool/mkopcodec.tcl d1b6362bd3aa80d5520d4d6f3765badf01f6c43c F tool/mkopcodeh.tcl 352a4319c0ad869eb26442bf7c3b015aa15594c21f1cce5a6420dbe999367c21 F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa F tool/mkpragmatab.tcl 49039adedafbc430d2959400da2e0e8f20ef8dcf6898e447c946e7d50ef5906b -F tool/mkshellc.tcl 1f45770aea226ac093a9c72f718efbb88a2a2833409ec2e1c4cecae4202626f5 +F tool/mkshellc.tcl d18a6769f766bd5a0fd5fd60e6bbde38ed6d1a41fced5fe32ac01ab9d2e0d5ec F tool/mksourceid.c d458f9004c837bee87a6382228ac20d3eae3c49ea3b0a5aace936f8b60748d3b F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f @@ -1820,7 +1820,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 a3ab58832935e1399ecc7e4d8daefa3a6afa6b301792ce7176bc5d7c173510fb -R 901e9e476021499846a29c847f49c578 +P 3213a15f2133afbb0a4fec3b8f6e0eeca8c0befafd6658c41074e84f589d5d32 +R 9a7e323fe9ebfe9eb0759e1c70136795 U dan -Z e0c2ac18f5bd53328c7a0485e8c7ef97 +Z 100e3ccaddf1d19d0bacd74d773a93b7 diff --git a/manifest.uuid b/manifest.uuid index 6ee3f97f4e..0b854fa40a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -3213a15f2133afbb0a4fec3b8f6e0eeca8c0befafd6658c41074e84f589d5d32 \ No newline at end of file +7461d2e120f2149315ddac2676d51d7445bcdb8e97543effd9c30603517ef9da \ No newline at end of file diff --git a/src/shell.c.in b/src/shell.c.in index d8c57b481e..c38bfb64b9 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -948,6 +948,8 @@ INCLUDE ../ext/misc/sqlar.c INCLUDE ../ext/expert/sqlite3expert.h INCLUDE ../ext/expert/sqlite3expert.c +INCLUDE ../ext/misc/dbdata.c + #if defined(SQLITE_ENABLE_SESSION) /* ** State information for a single open session @@ -1102,6 +1104,7 @@ 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 @@ -3999,6 +4002,7 @@ static void open_db(ShellState *p, int openFlags){ sqlite3_fileio_init(p->db, 0, 0); sqlite3_shathree_init(p->db, 0, 0); sqlite3_completion_init(p->db, 0, 0); + sqlite3_dbdata_init(p->db, 0, 0); #ifdef SQLITE_HAVE_ZLIB sqlite3_zipfile_init(p->db, 0, 0); sqlite3_sqlar_init(p->db, 0, 0); @@ -6026,6 +6030,238 @@ end_ar_command: **********************************************************************************/ #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */ +static void shellExec(sqlite3 *db, int *pRc, const char *zSql){ + int rc = *pRc; + if( rc==SQLITE_OK ){ + char *zErr = 0; + rc = sqlite3_exec(db, zSql, 0, 0, &zErr); + if( rc!=SQLITE_OK ){ + raw_printf(stderr, "SQL error: %s\n", zErr); + } + *pRc = rc; + } +} + +static void *shellMalloc(int *pRc, sqlite3_int64 nByte){ + void *pRet = 0; + if( *pRc==SQLITE_OK ){ + pRet = sqlite3_malloc64(nByte); + if( pRet==0 ){ + *pRc = SQLITE_NOMEM; + }else{ + memset(pRet, 0, nByte); + } + } + return pRet; +} + +static char *shellMPrintf(int *pRc, const char *zFmt, ...){ + char *z = 0; + if( *pRc==SQLITE_OK ){ + va_list ap; + va_start(ap, zFmt); + z = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + if( z==0 ){ + *pRc = SQLITE_NOMEM; + } + } + return z; +} + +typedef struct RecoverTable RecoverTable; +struct RecoverTable { + char *zName; /* Name of table */ + char *zQuoted; /* Quoted version of zName */ + char *zCreate; /* SQL to create table in default schema */ + int nCol; /* Number of columns in table */ + char **azlCol; /* Array of column lists */ +}; + +/* +** Free a RecoverTable object allocated by recoverNewTable() +*/ +static void recoverFreeTable(RecoverTable *pTab){ + if( pTab ){ + sqlite3_free(pTab->zName); + sqlite3_free(pTab->zQuoted); + sqlite3_free(pTab->zCreate); + if( pTab->azlCol ){ + int i; + for(i=0; inCol; i++){ + sqlite3_free(pTab->azlCol[i]); + } + sqlite3_free(pTab->azlCol); + } + sqlite3_free(pTab); + } +} + +static RecoverTable *recoverNewTable( + ShellState *pState, + int *pRc, + int iRoot, + int nCol +){ + RecoverTable *pRet = 0; + + pRet = (RecoverTable*)shellMalloc(pRc, sizeof(RecoverTable)); + if( pRet ){ + sqlite3_stmt *pStmt = 0; + pRet->zName = shellMPrintf(pRc, "orphan_%d_%d", nCol, iRoot); + pRet->zQuoted = shellMPrintf(pRc, "%Q", pRet->zName); + pRet->azlCol = (char**)shellMalloc(pRc, sizeof(char*) * nCol); + pRet->nCol = nCol; + + shellPreparePrintf(pState->db, pRc, &pStmt, + "WITH s(i) AS (" + " SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<%d" + ")" + "SELECT i-1, group_concat('c' || i, ', ') OVER (ORDER BY i) FROM s", + nCol + ); + while( *pRc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + int idx = sqlite3_column_int(pStmt, 0); + const char *zText = (const char*)sqlite3_column_text(pStmt, 1); + pRet->azlCol[idx] = shellMPrintf(pRc, "%s", zText); + } + shellFinalize(pRc, pStmt); + + pRet->zCreate = shellMPrintf(pRc, "CREATE TABLE %Q (id, %s)", + pRet->zName, pRet->azlCol[nCol-1] + ); + } + + if( *pRc!=SQLITE_OK ){ + recoverFreeTable(pRet); + pRet = 0; + } + + return pRet; +} + +/* +** This function is called to recover data from the database. A script +** to construct a new database containing all recovered data is output +** on stream pState->out. +*/ +static int recoverDatabaseCmd(ShellState *pState){ + const char *zSql; + int rc = SQLITE_OK; + sqlite3_stmt *pLoop = 0; /* Loop through all root pages */ + + shellExec(pState->db, &rc, + /* Attach an in-memory database named 'recovery'. Create an indexed + ** cache of the sqlite_dbptr virtual table. */ + "ATTACH '' AS recovery;" + "CREATE TABLE recovery.dbptr(" + " pgno, child, PRIMARY KEY(child, pgno)" + ") WITHOUT ROWID;" + "INSERT OR IGNORE INTO dbptr(pgno, child) SELECT * FROM sqlite_dbptr;" + + /* Delete any pointer to page 1. This ensures that page 1 is considered + ** a root page, regardless of how corrupt the db is. */ + "DELETE FROM recovery.dbptr WHERE child = 1;" + + /* Delete all pointers to any pages that have more than one pointer + ** to them. Such pages will be treated as root pages when recovering + ** data. */ + "DELETE FROM recovery.dbptr WHERE child IN (" + " SELECT child FROM recovery.dbptr GROUP BY child HAVING count(*)>1" + ");" + + /* Create the "map" table that will (eventually) contain instructions + ** for dealing with each page in the db that contains one or more + ** records. */ + "CREATE TABLE recovery.map(pgno INTEGER PRIMARY KEY, maxlen INT, root INT);" + + /* Populate table [map]. If there are circular loops of pages in the + ** database, the following adds all pages in such a loop to the map + ** as individual root pages. This could be handled better. */ + "WITH pages(i, maxlen) AS (" + " SELECT page_count, max(field+1) " + " FROM pragma_page_count, sqlite_dbdata WHERE pgno=page_count" + " UNION ALL" + " SELECT * FROM (SELECT i-1, max(field+1)" + " FROM pages, sqlite_dbdata WHERE pgno=i-1 AND i>=2)" + ")" + "INSERT INTO recovery.map(pgno, maxlen, root) SELECT i, maxlen, (" + " WITH p(orig, pgno, parent) AS (" + " SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)" + " UNION ALL" + " SELECT i, p.parent, " + " (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p" + " )" + " SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)" + ") " + "FROM pages WHERE maxlen > 0;" + + /* Extract data from page 1 and any linked pages into table + ** recovery.schema. With the same schema as an sqlite_master table. */ + "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);" + "INSERT INTO recovery.schema SELECT " + " max(CASE WHEN field=0 THEN value ELSE NULL END)," + " max(CASE WHEN field=1 THEN value ELSE NULL END)," + " max(CASE WHEN field=2 THEN value ELSE NULL END)," + " max(CASE WHEN field=3 THEN value ELSE NULL END)," + " max(CASE WHEN field=4 THEN value ELSE NULL END)" + "FROM sqlite_dbdata WHERE pgno IN (" + " SELECT pgno FROM recovery.map WHERE root=1" + ")" + "GROUP BY pgno, cell;" + ); + +#if 0 + zSql = "SELECT type ||','|| name ||','|| tbl_name ||','|| rootpage ||','|| sql FROM recovery.schema;"; + shellPrepare(pState->db, &rc, zSql, &pLoop); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){ + raw_printf(pState->out, "%s\n", (const char*)sqlite3_column_text(pLoop, 0)); + } + shellFinalize(&rc, pLoop); + return rc; +#endif + + /* Loop through each root page. */ + zSql = "SELECT root,max(maxlen) FROM recovery.map WHERE root>1 GROUP BY root"; + shellPrepare(pState->db, &rc, zSql, &pLoop); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){ + int iRoot = sqlite3_column_int(pLoop, 0); + int nCol = sqlite3_column_int(pLoop, 1); + RecoverTable *pTab; + + pTab = recoverNewTable(pState, &rc, iRoot, nCol); + if( pTab ){ + sqlite3_stmt *pData = 0; + raw_printf(pState->out, "%s;\n", pTab->zCreate); + shellPreparePrintf(pState->db, &rc, &pData, + "SELECT max(field), group_concat(quote(value), ', ') " + "FROM sqlite_dbdata WHERE pgno IN (" + " SELECT pgno FROM recovery.map WHERE root=%d" + ")" + "GROUP BY pgno, cell;", iRoot + ); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pData) ){ + int iMax = sqlite3_column_int(pData, 0); + const char *zVal = (const char*)sqlite3_column_text(pData, 1); + if( iMax+1==pTab->nCol ){ + raw_printf(pState->out, "INSERT INTO %s VALUES( %s );\n", + pTab->zQuoted, zVal); + }else{ + raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n", + pTab->zQuoted, pTab->azlCol[iMax], zVal + ); + } + } + shellFinalize(&rc, pData); + } + recoverFreeTable(pTab); + } + shellFinalize(&rc, pLoop); + + sqlite3_exec(pState->db, "DETACH recovery", 0, 0, 0); + return rc; +} + /* ** If an input line begins with "." then invoke this routine to @@ -6313,6 +6549,11 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = shell_dbinfo_command(p, nArg, azArg); }else + if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){ + open_db(p, 0); + rc = recoverDatabaseCmd(p); + }else + if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){ const char *zLike = 0; int i; @@ -6350,7 +6591,9 @@ static int do_meta_command(char *zLine, ShellState *p){ zLike = azArg[i]; } } + open_db(p, 0); + /* When playing back a "dump", the content might appear in an order ** which causes immediate foreign key constraints to be violated. ** So disable foreign-key constraint enforcement to prevent problems. */ @@ -6365,30 +6608,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); } @@ -6398,7 +6641,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0); - raw_printf(p->out, p->nErr ? "ROLLBACK; -- due to errors\n" : "COMMIT;\n"); + raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n"); p->showHeader = savedShowHeader; p->shellFlgs = savedShellFlags; }else diff --git a/tool/mkshellc.tcl b/tool/mkshellc.tcl index 534ac6156a..73eacdb9da 100644 --- a/tool/mkshellc.tcl +++ b/tool/mkshellc.tcl @@ -40,16 +40,21 @@ proc omit_redundant_typedefs {line} { } return $line } +set iLine 0 while {1} { set lx [omit_redundant_typedefs [gets $in]] if {[eof $in]} break; + incr iLine if {[regexp {^INCLUDE } $lx]} { set cfile [lindex $lx 1] puts $out "/************************* Begin $cfile ******************/" + puts $out "#line 1 \"$cfile\"" set in2 [open $topdir/src/$cfile rb] while {![eof $in2]} { set lx [omit_redundant_typedefs [gets $in2]] - if {[regexp {^#include "sqlite} $lx]} continue + if {[regexp {^#include "sqlite} $lx]} { + set lx "/* $lx */" + } if {[regexp {^# *include "test_windirent.h"} $lx]} { set lx "/* $lx */" } @@ -58,6 +63,7 @@ while {1} { } close $in2 puts $out "/************************* End $cfile ********************/" + puts $out "#line [expr $iLine+1] \"shell.c.in\"" continue } puts $out $lx