]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the ".recovery" command to the shell tool. For recovering the maximum amount...
authordan <dan@noemail.net>
Sat, 20 Apr 2019 20:57:28 +0000 (20:57 +0000)
committerdan <dan@noemail.net>
Sat, 20 Apr 2019 20:57:28 +0000 (20:57 +0000)
FossilOrigin-Name: 7461d2e120f2149315ddac2676d51d7445bcdb8e97543effd9c30603517ef9da

ext/misc/dbdata.c
main.mk
manifest
manifest.uuid
src/shell.c.in
tool/mkshellc.tcl

index eb2bc01600651dac7ccb6cf83dc77f339a620d03..aae862fc7f4c08625121f3073975f46145264c36 100644 (file)
 **       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( iHdr<nHdr ){
-          sqlite3_int64 iDummy;
-          iHdr += dbdataGetVarint(&pCsr->pRec[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->iField<pCsr->nField ) 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; i<pCsr->iField; 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 f418eec68aed071652badefe3b831eabc25b038c..508554d733ce89ea11f2a48052dbe3b2d733c431 100644 (file)
--- 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
index d6a2b045e82cfc9c7f76bfda0be63b29ff16392a..c103da1624ac38565ad2d22d5e6dcbe8a7c46e8f 100644 (file)
--- 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
index 6ee3f97f4ef9db11e0a688a4e9f1d6fc476d7a45..0b854fa40afeaee991d0c857f47ac90391f0a0e7 100644 (file)
@@ -1 +1 @@
-3213a15f2133afbb0a4fec3b8f6e0eeca8c0befafd6658c41074e84f589d5d32
\ No newline at end of file
+7461d2e120f2149315ddac2676d51d7445bcdb8e97543effd9c30603517ef9da
\ No newline at end of file
index d8c57b481ecdb302221d51cfb598743e079b4753..c38bfb64b989c2b7cfa0fec4a1effaabda731b6a 100644 (file)
@@ -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; i<pTab->nCol; 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
index 534ac6156a4e607c3c0bb77b2f5468b9ebe92ef3..73eacdb9da701f974a4150d6991612255430dd20 100644 (file)
@@ -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