diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/shell.c.in | 150 |
1 files changed, 106 insertions, 44 deletions
diff --git a/src/shell.c.in b/src/shell.c.in index f906205bf..8c967a83d 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; i<nCol; i++){ - raw_printf(pState->out, ", 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; i<nCol; i++){ + raw_printf(pState->out, ", 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); } |