|
|
|
@ -1039,6 +1039,8 @@ INCLUDE ../ext/expert/sqlite3expert.c
|
|
|
|
|
|
|
|
|
|
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
|
|
|
|
|
INCLUDE ../ext/misc/dbdata.c
|
|
|
|
|
INCLUDE ../ext/recover/sqlite3recover.h
|
|
|
|
|
INCLUDE ../ext/recover/sqlite3recover.c
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if defined(SQLITE_ENABLE_SESSION)
|
|
|
|
@ -7252,363 +7254,15 @@ end_ar_command:
|
|
|
|
|
#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */
|
|
|
|
|
|
|
|
|
|
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
|
|
|
|
|
/*
|
|
|
|
|
** 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 ){
|
|
|
|
|
char *zErr = 0;
|
|
|
|
|
rc = sqlite3_exec(db, zSql, 0, 0, &zErr);
|
|
|
|
|
if( rc!=SQLITE_OK ){
|
|
|
|
|
raw_printf(stderr, "SQL error: %s\n", zErr);
|
|
|
|
|
}
|
|
|
|
|
sqlite3_free(zErr);
|
|
|
|
|
*pRc = rc;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** Like shellExec(), except that zFmt is a printf() style format string.
|
|
|
|
|
** This function is used as a callback by the recover extension. Simply
|
|
|
|
|
** print the supplied SQL statement to stdout.
|
|
|
|
|
*/
|
|
|
|
|
static void shellExecPrintf(sqlite3 *db, 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;
|
|
|
|
|
}else{
|
|
|
|
|
shellExec(db, pRc, z);
|
|
|
|
|
}
|
|
|
|
|
sqlite3_free(z);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** 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 ){
|
|
|
|
|
pRet = sqlite3_malloc64(nByte);
|
|
|
|
|
if( pRet==0 ){
|
|
|
|
|
*pRc = SQLITE_NOMEM;
|
|
|
|
|
}else{
|
|
|
|
|
memset(pRet, 0, 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 ){
|
|
|
|
|
va_list ap;
|
|
|
|
|
va_start(ap, zFmt);
|
|
|
|
|
z = sqlite3_vmprintf(zFmt, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
if( z==0 ){
|
|
|
|
|
*pRc = SQLITE_NOMEM;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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 *zQuoted; /* Quoted version of table name */
|
|
|
|
|
int nCol; /* Number of columns in table */
|
|
|
|
|
char **azlCol; /* Array of column lists */
|
|
|
|
|
int iPk; /* Index of IPK column */
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** Free a RecoverTable object allocated by recoverFindTable() or
|
|
|
|
|
** recoverOrphanTable().
|
|
|
|
|
*/
|
|
|
|
|
static void recoverFreeTable(RecoverTable *pTab){
|
|
|
|
|
if( pTab ){
|
|
|
|
|
sqlite3_free(pTab->zQuoted);
|
|
|
|
|
if( pTab->azlCol ){
|
|
|
|
|
int i;
|
|
|
|
|
for(i=0; i<=pTab->nCol; i++){
|
|
|
|
|
sqlite3_free(pTab->azlCol[i]);
|
|
|
|
|
}
|
|
|
|
|
sqlite3_free(pTab->azlCol);
|
|
|
|
|
}
|
|
|
|
|
sqlite3_free(pTab);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** 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 */
|
|
|
|
|
int bIntkey,
|
|
|
|
|
int nCol
|
|
|
|
|
){
|
|
|
|
|
sqlite3 *dbtmp = 0; /* sqlite3 handle for testing CREATE TABLE */
|
|
|
|
|
int rc = *pRc;
|
|
|
|
|
RecoverTable *pTab = 0;
|
|
|
|
|
|
|
|
|
|
pTab = (RecoverTable*)shellMalloc(&rc, sizeof(RecoverTable));
|
|
|
|
|
if( rc==SQLITE_OK ){
|
|
|
|
|
int nSqlCol = 0;
|
|
|
|
|
int bSqlIntkey = 0;
|
|
|
|
|
sqlite3_stmt *pStmt = 0;
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_open("", &dbtmp);
|
|
|
|
|
if( rc==SQLITE_OK ){
|
|
|
|
|
sqlite3_create_function(dbtmp, "shell_idquote", 1, SQLITE_UTF8, 0,
|
|
|
|
|
shellIdQuote, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
if( rc==SQLITE_OK ){
|
|
|
|
|
rc = sqlite3_exec(dbtmp, "PRAGMA writable_schema = on", 0, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
if( rc==SQLITE_OK ){
|
|
|
|
|
rc = sqlite3_exec(dbtmp, zSql, 0, 0, 0);
|
|
|
|
|
if( rc==SQLITE_ERROR ){
|
|
|
|
|
rc = SQLITE_OK;
|
|
|
|
|
goto finished;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
shellPreparePrintf(dbtmp, &rc, &pStmt,
|
|
|
|
|
"SELECT count(*) FROM pragma_table_info(%Q)", zName
|
|
|
|
|
);
|
|
|
|
|
if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
|
|
|
|
nSqlCol = sqlite3_column_int(pStmt, 0);
|
|
|
|
|
}
|
|
|
|
|
shellFinalize(&rc, pStmt);
|
|
|
|
|
|
|
|
|
|
if( rc!=SQLITE_OK || nSqlCol<nCol ){
|
|
|
|
|
goto finished;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shellPreparePrintf(dbtmp, &rc, &pStmt,
|
|
|
|
|
"SELECT ("
|
|
|
|
|
" SELECT substr(data,1,1)==X'0D' FROM sqlite_dbpage WHERE pgno=rootpage"
|
|
|
|
|
") FROM sqlite_schema WHERE name = %Q", zName
|
|
|
|
|
);
|
|
|
|
|
if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
|
|
|
|
bSqlIntkey = sqlite3_column_int(pStmt, 0);
|
|
|
|
|
}
|
|
|
|
|
shellFinalize(&rc, pStmt);
|
|
|
|
|
|
|
|
|
|
if( bIntkey==bSqlIntkey ){
|
|
|
|
|
int i;
|
|
|
|
|
const char *zPk = "_rowid_";
|
|
|
|
|
sqlite3_stmt *pPkFinder = 0;
|
|
|
|
|
|
|
|
|
|
/* If this is an intkey table and there is an INTEGER PRIMARY KEY,
|
|
|
|
|
** set zPk to the name of the PK column, and pTab->iPk to the index
|
|
|
|
|
** of the column, where columns are 0-numbered from left to right.
|
|
|
|
|
** Or, if this is a WITHOUT ROWID table or if there is no IPK column,
|
|
|
|
|
** leave zPk as "_rowid_" and pTab->iPk at -2. */
|
|
|
|
|
pTab->iPk = -2;
|
|
|
|
|
if( bIntkey ){
|
|
|
|
|
shellPreparePrintf(dbtmp, &rc, &pPkFinder,
|
|
|
|
|
"SELECT cid, name FROM pragma_table_info(%Q) "
|
|
|
|
|
" WHERE pk=1 AND type='integer' COLLATE nocase"
|
|
|
|
|
" AND NOT EXISTS (SELECT cid FROM pragma_table_info(%Q) WHERE pk=2)"
|
|
|
|
|
, zName, zName
|
|
|
|
|
);
|
|
|
|
|
if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPkFinder) ){
|
|
|
|
|
pTab->iPk = sqlite3_column_int(pPkFinder, 0);
|
|
|
|
|
zPk = (const char*)sqlite3_column_text(pPkFinder, 1);
|
|
|
|
|
if( zPk==0 ){ zPk = "_"; /* Defensive. Should never happen */ }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pTab->zQuoted = shellMPrintf(&rc, "\"%w\"", zName);
|
|
|
|
|
pTab->azlCol = (char**)shellMalloc(&rc, sizeof(char*) * (nSqlCol+1));
|
|
|
|
|
pTab->nCol = nSqlCol;
|
|
|
|
|
|
|
|
|
|
if( bIntkey ){
|
|
|
|
|
pTab->azlCol[0] = shellMPrintf(&rc, "\"%w\"", zPk);
|
|
|
|
|
}else{
|
|
|
|
|
pTab->azlCol[0] = shellMPrintf(&rc, "");
|
|
|
|
|
}
|
|
|
|
|
i = 1;
|
|
|
|
|
shellPreparePrintf(dbtmp, &rc, &pStmt,
|
|
|
|
|
"SELECT %Q || group_concat(shell_idquote(name), ', ') "
|
|
|
|
|
" FILTER (WHERE cid!=%d) OVER (ORDER BY %s cid) "
|
|
|
|
|
"FROM pragma_table_info(%Q)",
|
|
|
|
|
bIntkey ? ", " : "", pTab->iPk,
|
|
|
|
|
bIntkey ? "" : "(CASE WHEN pk=0 THEN 1000000 ELSE pk END), ",
|
|
|
|
|
zName
|
|
|
|
|
);
|
|
|
|
|
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
|
|
|
|
const char *zText = (const char*)sqlite3_column_text(pStmt, 0);
|
|
|
|
|
pTab->azlCol[i] = shellMPrintf(&rc, "%s%s", pTab->azlCol[0], zText);
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
shellFinalize(&rc, pStmt);
|
|
|
|
|
|
|
|
|
|
shellFinalize(&rc, pPkFinder);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
finished:
|
|
|
|
|
sqlite3_close(dbtmp);
|
|
|
|
|
*pRc = rc;
|
|
|
|
|
if( rc!=SQLITE_OK || (pTab && pTab->zQuoted==0) ){
|
|
|
|
|
recoverFreeTable(pTab);
|
|
|
|
|
pTab = 0;
|
|
|
|
|
}
|
|
|
|
|
return pTab;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** This function is called to search the schema recovered from the
|
|
|
|
|
** sqlite_schema table of the (possibly) corrupt database as part
|
|
|
|
|
** of a ".recover" command. Specifically, for a table with root page
|
|
|
|
|
** iRoot and at least nCol columns. Additionally, if bIntkey is 0, the
|
|
|
|
|
** table must be a WITHOUT ROWID table, or if non-zero, not one of
|
|
|
|
|
** those.
|
|
|
|
|
**
|
|
|
|
|
** If a table is found, a (RecoverTable*) object is returned. Or, if
|
|
|
|
|
** no such table is found, but bIntkey is false and iRoot is the
|
|
|
|
|
** root page of an index in the recovered schema, then (*pbNoop) is
|
|
|
|
|
** set to true and NULL returned. Or, if there is no such table or
|
|
|
|
|
** index, NULL is returned and (*pbNoop) set to 0, indicating that
|
|
|
|
|
** the caller should write data to the orphans table.
|
|
|
|
|
*/
|
|
|
|
|
static RecoverTable *recoverFindTable(
|
|
|
|
|
ShellState *pState, /* Shell state object */
|
|
|
|
|
int *pRc, /* IN/OUT: Error code */
|
|
|
|
|
int iRoot, /* Root page of table */
|
|
|
|
|
int bIntkey, /* True for an intkey table */
|
|
|
|
|
int nCol, /* Number of columns in table */
|
|
|
|
|
int *pbNoop /* OUT: True if iRoot is root of index */
|
|
|
|
|
){
|
|
|
|
|
sqlite3_stmt *pStmt = 0;
|
|
|
|
|
RecoverTable *pRet = 0;
|
|
|
|
|
int bNoop = 0;
|
|
|
|
|
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
|
|
|
|
|
);
|
|
|
|
|
while( *pRc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
|
|
|
|
const char *zType = (const char*)sqlite3_column_text(pStmt, 0);
|
|
|
|
|
if( bIntkey==0 && sqlite3_stricmp(zType, "index")==0 ){
|
|
|
|
|
bNoop = 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if( sqlite3_stricmp(zType, "table")==0 ){
|
|
|
|
|
zName = (const char*)sqlite3_column_text(pStmt, 1);
|
|
|
|
|
zSql = (const char*)sqlite3_column_text(pStmt, 2);
|
|
|
|
|
if( zName!=0 && zSql!=0 ){
|
|
|
|
|
pRet = recoverNewTable(pRc, zName, zSql, bIntkey, nCol);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shellFinalize(pRc, pStmt);
|
|
|
|
|
*pbNoop = bNoop;
|
|
|
|
|
return pRet;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** Return a RecoverTable object representing the orphans table.
|
|
|
|
|
*/
|
|
|
|
|
static RecoverTable *recoverOrphanTable(
|
|
|
|
|
ShellState *pState, /* Shell state object */
|
|
|
|
|
int *pRc, /* IN/OUT: Error code */
|
|
|
|
|
const char *zLostAndFound, /* Base name for orphans table */
|
|
|
|
|
int nCol /* Number of user data columns */
|
|
|
|
|
){
|
|
|
|
|
RecoverTable *pTab = 0;
|
|
|
|
|
if( nCol>=0 && *pRc==SQLITE_OK ){
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
/* 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
|
|
|
|
|
);
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
shellFinalize(pRc, pTest);
|
|
|
|
|
|
|
|
|
|
pTab = (RecoverTable*)shellMalloc(pRc, sizeof(RecoverTable));
|
|
|
|
|
if( pTab ){
|
|
|
|
|
pTab->zQuoted = shellMPrintf(pRc, "\"%w\"", zTab);
|
|
|
|
|
pTab->nCol = nCol;
|
|
|
|
|
pTab->iPk = -2;
|
|
|
|
|
if( nCol>0 ){
|
|
|
|
|
pTab->azlCol = (char**)shellMalloc(pRc, sizeof(char*) * (nCol+1));
|
|
|
|
|
if( pTab->azlCol ){
|
|
|
|
|
pTab->azlCol[nCol] = shellMPrintf(pRc, "");
|
|
|
|
|
for(i=nCol-1; i>=0; i--){
|
|
|
|
|
pTab->azlCol[i] = shellMPrintf(pRc, "%s, NULL", pTab->azlCol[i+1]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
static int recoverSqlCb(void *pCtx, const char *zSql){
|
|
|
|
|
ShellState *pState = (ShellState*)pCtx;
|
|
|
|
|
raw_printf(stdout, "%s;\n", zSql);
|
|
|
|
|
return SQLITE_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
@ -7618,17 +7272,13 @@ static RecoverTable *recoverOrphanTable(
|
|
|
|
|
*/
|
|
|
|
|
static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
|
|
|
|
|
int rc = SQLITE_OK;
|
|
|
|
|
sqlite3_stmt *pLoop = 0; /* Loop through all root pages */
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
const char *zLAF = "lost_and_found";
|
|
|
|
|
int bFreelist = 1; /* 0 if --freelist-corrupt is specified */
|
|
|
|
|
int bRowids = 1; /* 0 if --no-rowids */
|
|
|
|
|
sqlite3_recover *p = 0;
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
|
|
for(i=1; i<nArg; i++){
|
|
|
|
|
char *z = azArg[i];
|
|
|
|
|
int n;
|
|
|
|
@ -7643,7 +7293,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
|
|
|
|
|
}else
|
|
|
|
|
if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
|
|
|
|
|
i++;
|
|
|
|
|
zLostAndFound = azArg[i];
|
|
|
|
|
zLAF = azArg[i];
|
|
|
|
|
}else
|
|
|
|
|
if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
|
|
|
|
|
bRowids = 0;
|
|
|
|
@ -7655,278 +7305,22 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shellExecPrintf(pState->db, &rc,
|
|
|
|
|
/* Attach an in-memory database named 'recovery'. Create an indexed
|
|
|
|
|
** cache of the sqlite_dbptr virtual table. */
|
|
|
|
|
"PRAGMA writable_schema = on;"
|
|
|
|
|
"ATTACH %Q AS recovery;"
|
|
|
|
|
"DROP TABLE IF EXISTS recovery.dbptr;"
|
|
|
|
|
"DROP TABLE IF EXISTS recovery.freelist;"
|
|
|
|
|
"DROP TABLE IF EXISTS recovery.map;"
|
|
|
|
|
"DROP TABLE IF EXISTS recovery.schema;"
|
|
|
|
|
"CREATE TABLE recovery.freelist(pgno INTEGER PRIMARY KEY);", zRecoveryDb
|
|
|
|
|
p = sqlite3_recover_init_sql(
|
|
|
|
|
pState->db, "main", recoverSqlCb, (void*)pState
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if( bFreelist ){
|
|
|
|
|
shellExec(pState->db, &rc,
|
|
|
|
|
"WITH trunk(pgno) AS ("
|
|
|
|
|
" SELECT shell_int32("
|
|
|
|
|
" (SELECT data FROM sqlite_dbpage WHERE pgno=1), 8) AS x "
|
|
|
|
|
" WHERE x>0"
|
|
|
|
|
" UNION"
|
|
|
|
|
" SELECT shell_int32("
|
|
|
|
|
" (SELECT data FROM sqlite_dbpage WHERE pgno=trunk.pgno), 0) AS x "
|
|
|
|
|
" FROM trunk WHERE x>0"
|
|
|
|
|
"),"
|
|
|
|
|
"freelist(data, n, freepgno) AS ("
|
|
|
|
|
" SELECT data, min(16384, shell_int32(data, 1)-1), t.pgno "
|
|
|
|
|
" FROM trunk t, sqlite_dbpage s WHERE s.pgno=t.pgno"
|
|
|
|
|
" UNION ALL"
|
|
|
|
|
" SELECT data, n-1, shell_int32(data, 2+n) "
|
|
|
|
|
" FROM freelist WHERE n>=0"
|
|
|
|
|
")"
|
|
|
|
|
"REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;"
|
|
|
|
|
);
|
|
|
|
|
sqlite3_recover_config(p, SQLITE_RECOVER_TESTDB, (void*)zRecoveryDb);
|
|
|
|
|
sqlite3_recover_config(p, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF);
|
|
|
|
|
sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids);
|
|
|
|
|
sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist);
|
|
|
|
|
|
|
|
|
|
sqlite3_recover_step(p);
|
|
|
|
|
if( sqlite3_recover_errcode(p)!=SQLITE_OK ){
|
|
|
|
|
const char *zErr = sqlite3_recover_errmsg(p);
|
|
|
|
|
int errCode = sqlite3_recover_errcode(p);
|
|
|
|
|
raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If this is an auto-vacuum database, add all pointer-map pages to
|
|
|
|
|
** the freelist table. Do this regardless of whether or not
|
|
|
|
|
** --freelist-corrupt was specified. */
|
|
|
|
|
shellExec(pState->db, &rc,
|
|
|
|
|
"WITH ptrmap(pgno) AS ("
|
|
|
|
|
" SELECT 2 WHERE shell_int32("
|
|
|
|
|
" (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13"
|
|
|
|
|
" )"
|
|
|
|
|
" UNION ALL "
|
|
|
|
|
" SELECT pgno+1+(SELECT page_size FROM pragma_page_size)/5 AS pp "
|
|
|
|
|
" FROM ptrmap WHERE pp<=(SELECT page_count FROM pragma_page_count)"
|
|
|
|
|
")"
|
|
|
|
|
"REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
shellExec(pState->db, &rc,
|
|
|
|
|
"CREATE TABLE recovery.dbptr("
|
|
|
|
|
" pgno, child, PRIMARY KEY(child, pgno)"
|
|
|
|
|
") WITHOUT ROWID;"
|
|
|
|
|
"INSERT OR IGNORE INTO recovery.dbptr(pgno, child) "
|
|
|
|
|
" SELECT * FROM sqlite_dbptr"
|
|
|
|
|
" WHERE pgno NOT IN freelist AND child NOT IN freelist;"
|
|
|
|
|
|
|
|
|
|
/* 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, intkey, 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, ("
|
|
|
|
|
" SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=page_count"
|
|
|
|
|
" ) FROM pragma_page_count WHERE page_count>0"
|
|
|
|
|
" UNION ALL"
|
|
|
|
|
" SELECT i-1, ("
|
|
|
|
|
" SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=i-1"
|
|
|
|
|
" ) FROM pages WHERE i>=2"
|
|
|
|
|
")"
|
|
|
|
|
"INSERT INTO recovery.map(pgno, maxlen, intkey, root) "
|
|
|
|
|
" SELECT i, maxlen, NULL, ("
|
|
|
|
|
" WITH p(orig, pgno, parent) AS ("
|
|
|
|
|
" SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)"
|
|
|
|
|
" UNION "
|
|
|
|
|
" 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 IS NOT NULL AND i NOT IN freelist;"
|
|
|
|
|
"UPDATE recovery.map AS o SET intkey = ("
|
|
|
|
|
" SELECT substr(data, 1, 1)==X'0D' FROM sqlite_dbpage WHERE pgno=o.pgno"
|
|
|
|
|
");"
|
|
|
|
|
|
|
|
|
|
/* Extract data from page 1 and any linked pages into table
|
|
|
|
|
** recovery.schema. With the same schema as an sqlite_schema 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;"
|
|
|
|
|
"CREATE INDEX recovery.schema_rootpage ON schema(rootpage);"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Open a transaction, then print out all non-virtual, non-"sqlite_%"
|
|
|
|
|
** CREATE TABLE statements that extracted from the existing schema. */
|
|
|
|
|
if( rc==SQLITE_OK ){
|
|
|
|
|
sqlite3_stmt *pStmt = 0;
|
|
|
|
|
/* ".recover" might output content in an order which causes immediate
|
|
|
|
|
** foreign key constraints to be violated. So disable foreign-key
|
|
|
|
|
** constraint enforcement to prevent problems when running the output
|
|
|
|
|
** script. */
|
|
|
|
|
raw_printf(pState->out, "PRAGMA foreign_keys=OFF;\n");
|
|
|
|
|
raw_printf(pState->out, "BEGIN;\n");
|
|
|
|
|
raw_printf(pState->out, "PRAGMA writable_schema = on;\n");
|
|
|
|
|
shellPrepare(pState->db, &rc,
|
|
|
|
|
"SELECT sql FROM recovery.schema "
|
|
|
|
|
"WHERE type='table' AND sql LIKE 'create table%'", &pStmt
|
|
|
|
|
);
|
|
|
|
|
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
|
|
|
|
const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0);
|
|
|
|
|
raw_printf(pState->out, "CREATE TABLE IF NOT EXISTS %s;\n",
|
|
|
|
|
&zCreateTable[12]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
shellFinalize(&rc, pStmt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Figure out if an orphan table will be required. And if so, how many
|
|
|
|
|
** user columns it should contain */
|
|
|
|
|
shellPrepare(pState->db, &rc,
|
|
|
|
|
"SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1"
|
|
|
|
|
, &pLoop
|
|
|
|
|
);
|
|
|
|
|
if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
|
|
|
|
|
nOrphan = sqlite3_column_int(pLoop, 0);
|
|
|
|
|
}
|
|
|
|
|
shellFinalize(&rc, pLoop);
|
|
|
|
|
pLoop = 0;
|
|
|
|
|
|
|
|
|
|
shellPrepare(pState->db, &rc,
|
|
|
|
|
"SELECT pgno FROM recovery.map WHERE root=?", &pPages
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
shellPrepare(pState->db, &rc,
|
|
|
|
|
"SELECT max(field), group_concat(shell_escape_crnl(quote"
|
|
|
|
|
"(case when (? AND field<0) then NULL else value end)"
|
|
|
|
|
"), ', ')"
|
|
|
|
|
", min(field) "
|
|
|
|
|
"FROM sqlite_dbdata WHERE pgno = ? AND field != ?"
|
|
|
|
|
"GROUP BY cell", &pCells
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Loop through each root page. */
|
|
|
|
|
shellPrepare(pState->db, &rc,
|
|
|
|
|
"SELECT root, intkey, max(maxlen) FROM recovery.map"
|
|
|
|
|
" WHERE root>1 GROUP BY root, intkey ORDER BY root=("
|
|
|
|
|
" SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'"
|
|
|
|
|
")", &pLoop
|
|
|
|
|
);
|
|
|
|
|
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
|
|
|
|
|
int iRoot = sqlite3_column_int(pLoop, 0);
|
|
|
|
|
int bIntkey = sqlite3_column_int(pLoop, 1);
|
|
|
|
|
int nCol = sqlite3_column_int(pLoop, 2);
|
|
|
|
|
int bNoop = 0;
|
|
|
|
|
RecoverTable *pTab;
|
|
|
|
|
|
|
|
|
|
assert( bIntkey==0 || bIntkey==1 );
|
|
|
|
|
pTab = recoverFindTable(pState, &rc, iRoot, bIntkey, nCol, &bNoop);
|
|
|
|
|
if( bNoop || rc ) continue;
|
|
|
|
|
if( pTab==0 ){
|
|
|
|
|
if( pOrphan==0 ){
|
|
|
|
|
pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
|
|
|
|
|
}
|
|
|
|
|
pTab = pOrphan;
|
|
|
|
|
if( pTab==0 ) break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){
|
|
|
|
|
raw_printf(pState->out, "DELETE FROM sqlite_sequence;\n");
|
|
|
|
|
}
|
|
|
|
|
sqlite3_bind_int(pPages, 1, iRoot);
|
|
|
|
|
if( bRowids==0 && pTab->iPk<0 ){
|
|
|
|
|
sqlite3_bind_int(pCells, 1, 1);
|
|
|
|
|
}else{
|
|
|
|
|
sqlite3_bind_int(pCells, 1, 0);
|
|
|
|
|
}
|
|
|
|
|
sqlite3_bind_int(pCells, 3, pTab->iPk);
|
|
|
|
|
|
|
|
|
|
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){
|
|
|
|
|
int iPgno = sqlite3_column_int(pPages, 0);
|
|
|
|
|
sqlite3_bind_int(pCells, 2, iPgno);
|
|
|
|
|
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){
|
|
|
|
|
int nField = sqlite3_column_int(pCells, 0);
|
|
|
|
|
int iMin = sqlite3_column_int(pCells, 2);
|
|
|
|
|
const char *zVal = (const char*)sqlite3_column_text(pCells, 1);
|
|
|
|
|
|
|
|
|
|
RecoverTable *pTab2 = pTab;
|
|
|
|
|
if( pTab!=pOrphan && (iMin<0)!=bIntkey ){
|
|
|
|
|
if( pOrphan==0 ){
|
|
|
|
|
pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
|
|
|
|
|
}
|
|
|
|
|
pTab2 = pOrphan;
|
|
|
|
|
if( pTab2==0 ) break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nField = nField+1;
|
|
|
|
|
if( pTab2==pOrphan ){
|
|
|
|
|
raw_printf(pState->out,
|
|
|
|
|
"INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n",
|
|
|
|
|
pTab2->zQuoted, iRoot, iPgno, nField,
|
|
|
|
|
iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField]
|
|
|
|
|
);
|
|
|
|
|
}else{
|
|
|
|
|
raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n",
|
|
|
|
|
pTab2->zQuoted, pTab2->azlCol[nField], zVal
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
shellReset(&rc, pCells);
|
|
|
|
|
}
|
|
|
|
|
shellReset(&rc, pPages);
|
|
|
|
|
if( pTab!=pOrphan ) recoverFreeTable(pTab);
|
|
|
|
|
}
|
|
|
|
|
shellFinalize(&rc, pLoop);
|
|
|
|
|
shellFinalize(&rc, pPages);
|
|
|
|
|
shellFinalize(&rc, pCells);
|
|
|
|
|
recoverFreeTable(pOrphan);
|
|
|
|
|
|
|
|
|
|
/* The rest of the schema */
|
|
|
|
|
if( rc==SQLITE_OK ){
|
|
|
|
|
sqlite3_stmt *pStmt = 0;
|
|
|
|
|
shellPrepare(pState->db, &rc,
|
|
|
|
|
"SELECT sql, name FROM recovery.schema "
|
|
|
|
|
"WHERE sql NOT LIKE 'create table%'", &pStmt
|
|
|
|
|
);
|
|
|
|
|
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
|
|
|
|
const char *zSql = (const char*)sqlite3_column_text(pStmt, 0);
|
|
|
|
|
if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){
|
|
|
|
|
const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
|
|
|
|
|
char *zPrint = shellMPrintf(&rc,
|
|
|
|
|
"INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)",
|
|
|
|
|
zName, zName, zSql
|
|
|
|
|
);
|
|
|
|
|
raw_printf(pState->out, "%s;\n", zPrint);
|
|
|
|
|
sqlite3_free(zPrint);
|
|
|
|
|
}else{
|
|
|
|
|
raw_printf(pState->out, "%s;\n", zSql);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
shellFinalize(&rc, pStmt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( rc==SQLITE_OK ){
|
|
|
|
|
raw_printf(pState->out, "PRAGMA writable_schema = off;\n");
|
|
|
|
|
raw_printf(pState->out, "COMMIT;\n");
|
|
|
|
|
}
|
|
|
|
|
sqlite3_exec(pState->db, "DETACH recovery", 0, 0, 0);
|
|
|
|
|
rc = sqlite3_recover_finish(p);
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
|
|
|
|
|