Add the ".recover" command to the shell tool. For recovering as much data as possible from corrupt databases.

FossilOrigin-Name: 50fe48458942fa7a6bcc76316c6321f95b23dc34f2f8e0a483826483b2fb16f6
This commit is contained in:
dan 2019-04-27 20:30:19 +00:00
commit 73c0d272a9
8 changed files with 1838 additions and 16 deletions

798
ext/misc/dbdata.c Normal file
View File

@ -0,0 +1,798 @@
/*
** 2019-04-17
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
******************************************************************************
**
** This file contains an implementation of two eponymous virtual tables,
** "sqlite_dbdata" and "sqlite_dbptr". Both modules require that the
** "sqlite_dbpage" eponymous virtual table be available.
**
** 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(
** pgno INTEGER,
** cell INTEGER,
** field INTEGER,
** value ANY,
** schema TEXT HIDDEN
** );
**
** IMPORTANT: THE VIRTUAL TABLE SCHEMA ABOVE IS SUBJECT TO CHANGE. IN THE
** 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 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 contains one row for each field in the record associated with
** each cell on the page. For intkey b-trees, the key value is stored in
** field -1.
**
** For example, for the database:
**
** 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(10, 'x', 'ten');
**
** the sqlite_dbdata table contains, as well as from entries related to
** page 1, content equivalent to:
**
** INSERT INTO sqlite_dbdata(pgno, cell, field, value) VALUES
** (2, 0, -1, 5 ),
** (2, 0, 0, 'v' ),
** (2, 0, 1, 'five'),
** (2, 1, -1, 10 ),
** (2, 1, 0, 'x' ),
** (2, 1, 1, 'ten' );
**
** If database corruption is encountered, this module does not report an
** error. Instead, it attempts to extract as much data as possible and
** ignores the corruption.
**
** SQLITE_DBPTR:
** The sqlite_dbptr table has the following schema:
**
** CREATE TABLE sqlite_dbptr(
** pgno INTEGER,
** child INTEGER,
** 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)
#include "sqlite3ext.h"
typedef unsigned char u8;
#endif
SQLITE_EXTENSION_INIT1
#include <string.h>
#include <assert.h>
typedef struct DbdataTable DbdataTable;
typedef struct DbdataCursor DbdataCursor;
/* Cursor object */
struct DbdataCursor {
sqlite3_vtab_cursor base; /* Base class. Must be first */
sqlite3_stmt *pStmt; /* For fetching database pages */
int iPgno; /* Current page number */
u8 *aPage; /* Buffer containing page */
int nPage; /* Size of aPage[] in bytes */
int nCell; /* Number of cells on aPage[] */
int iCell; /* Current cell number */
int bOnePage; /* True to stop after one page */
int szDb;
sqlite3_int64 iRowid;
/* Only for the sqlite_dbdata table */
u8 *pRec; /* Buffer containing current record */
int nRec; /* Size of pRec[] in bytes */
int nHdr; /* Size of header in bytes */
int iField; /* Current field number */
u8 *pHdrPtr;
u8 *pPtr;
sqlite3_int64 iIntkey; /* Integer key value */
};
/* Table object */
struct DbdataTable {
sqlite3_vtab base; /* Base class. Must be first */
sqlite3 *db; /* The database connection */
sqlite3_stmt *pStmt; /* For fetching database pages */
int bPtr; /* True for sqlite3_dbptr table */
};
/* Column and schema definitions for sqlite_dbdata */
#define DBDATA_COLUMN_PGNO 0
#define DBDATA_COLUMN_CELL 1
#define DBDATA_COLUMN_FIELD 2
#define DBDATA_COLUMN_VALUE 3
#define DBDATA_COLUMN_SCHEMA 4
#define DBDATA_SCHEMA \
"CREATE TABLE x(" \
" pgno INTEGER," \
" cell INTEGER," \
" field INTEGER," \
" value ANY," \
" 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 \
"CREATE TABLE x(" \
" pgno INTEGER," \
" child INTEGER," \
" schema TEXT HIDDEN" \
")"
/*
** Connect to an sqlite_dbdata (pAux==0) or sqlite_dbptr (pAux!=0) virtual
** table.
*/
static int dbdataConnect(
sqlite3 *db,
void *pAux,
int argc, const char *const*argv,
sqlite3_vtab **ppVtab,
char **pzErr
){
DbdataTable *pTab = 0;
int rc = sqlite3_declare_vtab(db, pAux ? DBPTR_SCHEMA : DBDATA_SCHEMA);
if( rc==SQLITE_OK ){
pTab = (DbdataTable*)sqlite3_malloc64(sizeof(DbdataTable));
if( pTab==0 ){
rc = SQLITE_NOMEM;
}else{
memset(pTab, 0, sizeof(DbdataTable));
pTab->db = db;
pTab->bPtr = (pAux!=0);
}
}
*ppVtab = (sqlite3_vtab*)pTab;
return rc;
}
/*
** Disconnect from or destroy a sqlite_dbdata or sqlite_dbptr virtual table.
*/
static int dbdataDisconnect(sqlite3_vtab *pVtab){
DbdataTable *pTab = (DbdataTable*)pVtab;
if( pTab ){
sqlite3_finalize(pTab->pStmt);
sqlite3_free(pVtab);
}
return SQLITE_OK;
}
/*
** This function interprets two types of constraints:
**
** schema=?
** pgno=?
**
** If neither are present, idxNum is set to 0. If schema=? is present,
** the 0x01 bit in idxNum is set. If pgno=? is present, the 0x02 bit
** in idxNum is set.
**
** If both parameters are present, schema is in position 0 and pgno in
** position 1.
*/
static int dbdataBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdx){
DbdataTable *pTab = (DbdataTable*)tab;
int i;
int iSchema = -1;
int iPgno = -1;
int colSchema = (pTab->bPtr ? DBPTR_COLUMN_SCHEMA : DBDATA_COLUMN_SCHEMA);
for(i=0; i<pIdx->nConstraint; i++){
struct sqlite3_index_constraint *p = &pIdx->aConstraint[i];
if( p->op==SQLITE_INDEX_CONSTRAINT_EQ ){
if( p->iColumn==colSchema ){
if( p->usable==0 ) return SQLITE_CONSTRAINT;
iSchema = i;
}
if( p->iColumn==DBDATA_COLUMN_PGNO && p->usable ){
iPgno = i;
}
}
}
if( iSchema>=0 ){
pIdx->aConstraintUsage[iSchema].argvIndex = 1;
pIdx->aConstraintUsage[iSchema].omit = 1;
}
if( iPgno>=0 ){
pIdx->aConstraintUsage[iPgno].argvIndex = 1 + (iSchema>=0);
pIdx->aConstraintUsage[iPgno].omit = 1;
pIdx->estimatedCost = 100;
pIdx->estimatedRows = 50;
if( pTab->bPtr==0 && pIdx->nOrderBy && pIdx->aOrderBy[0].desc==0 ){
int iCol = pIdx->aOrderBy[0].iColumn;
if( pIdx->nOrderBy==1 ){
pIdx->orderByConsumed = (iCol==0 || iCol==1);
}else if( pIdx->nOrderBy==2 && pIdx->aOrderBy[1].desc==0 && iCol==0 ){
pIdx->orderByConsumed = (pIdx->aOrderBy[1].iColumn==1);
}
}
}else{
pIdx->estimatedCost = 100000000;
pIdx->estimatedRows = 1000000000;
}
pIdx->idxNum = (iSchema>=0 ? 0x01 : 0x00) | (iPgno>=0 ? 0x02 : 0x00);
return SQLITE_OK;
}
/*
** Open a new sqlite_dbdata or sqlite_dbptr cursor.
*/
static int dbdataOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
DbdataCursor *pCsr;
pCsr = (DbdataCursor*)sqlite3_malloc64(sizeof(DbdataCursor));
if( pCsr==0 ){
return SQLITE_NOMEM;
}else{
memset(pCsr, 0, sizeof(DbdataCursor));
pCsr->base.pVtab = pVTab;
}
*ppCursor = (sqlite3_vtab_cursor *)pCsr;
return SQLITE_OK;
}
/*
** Restore a cursor object to the state it was in when first allocated
** by dbdataOpen().
*/
static void dbdataResetCursor(DbdataCursor *pCsr){
DbdataTable *pTab = (DbdataTable*)(pCsr->base.pVtab);
if( pTab->pStmt==0 ){
pTab->pStmt = pCsr->pStmt;
}else{
sqlite3_finalize(pCsr->pStmt);
}
pCsr->pStmt = 0;
pCsr->iPgno = 1;
pCsr->iCell = 0;
pCsr->iField = 0;
pCsr->bOnePage = 0;
}
/*
** Close an sqlite_dbdata or sqlite_dbptr cursor.
*/
static int dbdataClose(sqlite3_vtab_cursor *pCursor){
DbdataCursor *pCsr = (DbdataCursor*)pCursor;
dbdataResetCursor(pCsr);
sqlite3_free(pCsr);
return SQLITE_OK;
}
/*
** Utility methods to decode 16 and 32-bit big-endian unsigned integers.
*/
static unsigned int get_uint16(unsigned char *a){
return (a[0]<<8)|a[1];
}
static unsigned int get_uint32(unsigned char *a){
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(
DbdataCursor *pCsr, /* Cursor object */
unsigned int pgno, /* Page number of page to load */
u8 **ppPage, /* OUT: pointer to page buffer */
int *pnPage /* OUT: Size of (*ppPage) in bytes */
){
int rc2;
int rc = SQLITE_OK;
sqlite3_stmt *pStmt = pCsr->pStmt;
*ppPage = 0;
*pnPage = 0;
sqlite3_bind_int64(pStmt, 2, pgno);
if( SQLITE_ROW==sqlite3_step(pStmt) ){
int nCopy = sqlite3_column_bytes(pStmt, 0);
if( nCopy>0 ){
u8 *pPage;
pPage = (u8*)sqlite3_malloc64(nCopy);
if( pPage==0 ){
rc = SQLITE_NOMEM;
}else{
const u8 *pCopy = sqlite3_column_blob(pStmt, 0);
memcpy(pPage, pCopy, nCopy);
}
*ppPage = pPage;
*pnPage = nCopy;
}
}
rc2 = sqlite3_reset(pStmt);
if( rc==SQLITE_OK ) rc = rc2;
return rc;
}
/*
** Read a varint. Put the value in *pVal and return the number of bytes.
*/
static int dbdataGetVarint(const u8 *z, sqlite3_int64 *pVal){
sqlite3_int64 v = 0;
int i;
for(i=0; i<8; i++){
v = (v<<7) + (z[i]&0x7f);
if( (z[i]&0x80)==0 ){ *pVal = v; return i+1; }
}
v = (v<<8) + (z[i]&0xff);
*pVal = v;
return 9;
}
/*
** Return the number of bytes of space used by an SQLite value of type
** eType.
*/
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);
}
}
/*
** 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){
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 an sqlite_dbdata or sqlite_dbptr cursor to the next entry.
*/
static int dbdataNext(sqlite3_vtab_cursor *pCursor){
DbdataCursor *pCsr = (DbdataCursor*)pCursor;
DbdataTable *pTab = (DbdataTable*)pCursor->pVtab;
pCsr->iRowid++;
while( 1 ){
int rc;
int iOff = (pCsr->iPgno==1 ? 100 : 0);
if( pCsr->aPage==0 ){
while( 1 ){
if( pCsr->bOnePage==0 && pCsr->iPgno>pCsr->szDb ) return SQLITE_OK;
rc = dbdataLoadPage(pCsr, pCsr->iPgno, &pCsr->aPage, &pCsr->nPage);
if( rc!=SQLITE_OK ) return rc;
if( pCsr->aPage ) break;
pCsr->iPgno++;
}
pCsr->iCell = pTab->bPtr ? -2 : 0;
pCsr->nCell = get_uint16(&pCsr->aPage[iOff+3]);
}
if( pTab->bPtr ){
if( pCsr->aPage[iOff]!=0x02 && pCsr->aPage[iOff]!=0x05 ){
pCsr->iCell = pCsr->nCell;
}
pCsr->iCell++;
if( pCsr->iCell>=pCsr->nCell ){
sqlite3_free(pCsr->aPage);
pCsr->aPage = 0;
if( pCsr->bOnePage ) return SQLITE_OK;
pCsr->iPgno++;
}else{
return SQLITE_OK;
}
}else{
/* If there is no record loaded, load it now. */
if( pCsr->pRec==0 ){
int bHasRowid = 0;
int nPointer = 0;
sqlite3_int64 nPayload = 0;
sqlite3_int64 nHdr = 0;
int iHdr;
int U, X;
int nLocal;
switch( pCsr->aPage[iOff] ){
case 0x02:
nPointer = 4;
break;
case 0x0a:
break;
case 0x0d:
bHasRowid = 1;
break;
default:
/* This is not a b-tree page with records on it. Continue. */
pCsr->iCell = pCsr->nCell;
break;
}
if( pCsr->iCell>=pCsr->nCell ){
sqlite3_free(pCsr->aPage);
pCsr->aPage = 0;
if( pCsr->bOnePage ) return SQLITE_OK;
pCsr->iPgno++;
continue;
}
iOff += 8 + nPointer + pCsr->iCell*2;
iOff = get_uint16(&pCsr->aPage[iOff]);
/* For an interior node cell, skip past the child-page number */
iOff += nPointer;
/* Load the "byte of payload including overflow" field */
iOff += dbdataGetVarint(&pCsr->aPage[iOff], &nPayload);
/* If this is a leaf intkey cell, load the rowid */
if( bHasRowid ){
iOff += dbdataGetVarint(&pCsr->aPage[iOff], &pCsr->iIntkey);
}
/* Allocate space for payload */
pCsr->pRec = (u8*)sqlite3_malloc64(nPayload);
if( pCsr->pRec==0 ) return SQLITE_NOMEM;
pCsr->nRec = nPayload;
U = pCsr->nPage;
if( bHasRowid ){
X = U-35;
}else{
X = ((U-12)*64/255)-23;
}
if( nPayload<=X ){
nLocal = nPayload;
}else{
int M, K;
M = ((U-12)*32/255)-23;
K = M+((nPayload-M)%(U-4));
if( K<=X ){
nLocal = K;
}else{
nLocal = M;
}
}
/* Load the nLocal bytes of payload */
memcpy(pCsr->pRec, &pCsr->aPage[iOff], nLocal);
iOff += nLocal;
/* Load content from overflow pages */
if( nPayload>nLocal ){
sqlite3_int64 nRem = nPayload - nLocal;
unsigned int pgnoOvfl = get_uint32(&pCsr->aPage[iOff]);
while( nRem>0 ){
u8 *aOvfl = 0;
int nOvfl = 0;
int nCopy;
rc = dbdataLoadPage(pCsr, pgnoOvfl, &aOvfl, &nOvfl);
assert( rc!=SQLITE_OK || nOvfl==pCsr->nPage );
if( rc!=SQLITE_OK ) return rc;
nCopy = U-4;
if( nCopy>nRem ) nCopy = nRem;
memcpy(&pCsr->pRec[nPayload-nRem], &aOvfl[4], nCopy);
nRem -= nCopy;
pgnoOvfl = get_uint32(aOvfl);
sqlite3_free(aOvfl);
}
}
iHdr = dbdataGetVarint(pCsr->pRec, &nHdr);
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);
}
}
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. */
sqlite3_free(pCsr->pRec);
pCsr->pRec = 0;
pCsr->iCell++;
}
}
assert( !"can't get here" );
return SQLITE_OK;
}
/*
** Return true if the cursor is at EOF.
*/
static int dbdataEof(sqlite3_vtab_cursor *pCursor){
DbdataCursor *pCsr = (DbdataCursor*)pCursor;
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){
DbdataTable *pTab = (DbdataTable*)pCsr->base.pVtab;
char *zSql = 0;
int rc, rc2;
sqlite3_stmt *pStmt = 0;
zSql = sqlite3_mprintf("PRAGMA %Q.page_count", zSchema);
if( zSql==0 ) return SQLITE_NOMEM;
rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
pCsr->szDb = sqlite3_column_int(pStmt, 0);
}
rc2 = sqlite3_finalize(pStmt);
if( rc==SQLITE_OK ) rc = rc2;
return rc;
}
/*
** xFilter method for sqlite_dbdata and sqlite_dbptr.
*/
static int dbdataFilter(
sqlite3_vtab_cursor *pCursor,
int idxNum, const char *idxStr,
int argc, sqlite3_value **argv
){
DbdataCursor *pCsr = (DbdataCursor*)pCursor;
DbdataTable *pTab = (DbdataTable*)pCursor->pVtab;
int rc = SQLITE_OK;
const char *zSchema = "main";
dbdataResetCursor(pCsr);
assert( pCsr->iPgno==1 );
if( idxNum & 0x01 ){
zSchema = (const char*)sqlite3_value_text(argv[0]);
}
if( idxNum & 0x02 ){
pCsr->iPgno = sqlite3_value_int(argv[(idxNum & 0x01)]);
pCsr->bOnePage = 1;
}else{
pCsr->nPage = dbdataDbsize(pCsr, zSchema);
rc = dbdataDbsize(pCsr, zSchema);
}
if( rc==SQLITE_OK ){
if( pTab->pStmt ){
pCsr->pStmt = pTab->pStmt;
pTab->pStmt = 0;
}else{
rc = sqlite3_prepare_v2(pTab->db,
"SELECT data FROM sqlite_dbpage(?) WHERE pgno=?", -1,
&pCsr->pStmt, 0
);
}
}
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);
}
return rc;
}
/*
** Return a column for the sqlite_dbdata or sqlite_dbptr table.
*/
static int dbdataColumn(
sqlite3_vtab_cursor *pCursor,
sqlite3_context *ctx,
int i
){
DbdataCursor *pCsr = (DbdataCursor*)pCursor;
DbdataTable *pTab = (DbdataTable*)pCursor->pVtab;
if( pTab->bPtr ){
switch( i ){
case DBPTR_COLUMN_PGNO:
sqlite3_result_int64(ctx, pCsr->iPgno);
break;
case DBPTR_COLUMN_CHILD: {
int iOff = pCsr->iPgno==1 ? 100 : 0;
if( pCsr->iCell<0 ){
iOff += 8;
}else{
iOff += 12 + pCsr->iCell*2;
iOff = get_uint16(&pCsr->aPage[iOff]);
}
sqlite3_result_int64(ctx, get_uint32(&pCsr->aPage[iOff]));
break;
}
}
}else{
switch( i ){
case DBDATA_COLUMN_PGNO:
sqlite3_result_int64(ctx, pCsr->iPgno);
break;
case DBDATA_COLUMN_CELL:
sqlite3_result_int(ctx, pCsr->iCell);
break;
case DBDATA_COLUMN_FIELD:
sqlite3_result_int(ctx, pCsr->iField);
break;
case DBDATA_COLUMN_VALUE: {
if( pCsr->iField<0 ){
sqlite3_result_int64(ctx, pCsr->iIntkey);
}else{
sqlite3_int64 iType;
dbdataGetVarint(pCsr->pHdrPtr, &iType);
dbdataValue(ctx, iType, pCsr->pPtr);
}
break;
}
}
}
return SQLITE_OK;
}
/*
** Return the rowid for an sqlite_dbdata or sqlite_dptr table.
*/
static int dbdataRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
DbdataCursor *pCsr = (DbdataCursor*)pCursor;
*pRowid = pCsr->iRowid;
return SQLITE_OK;
}
/*
** Invoke this routine to register the "sqlite_dbdata" virtual table module
*/
static int sqlite3DbdataRegister(sqlite3 *db){
static sqlite3_module dbdata_module = {
0, /* iVersion */
0, /* xCreate */
dbdataConnect, /* xConnect */
dbdataBestIndex, /* xBestIndex */
dbdataDisconnect, /* xDisconnect */
0, /* xDestroy */
dbdataOpen, /* xOpen - open a cursor */
dbdataClose, /* xClose - close a cursor */
dbdataFilter, /* xFilter - configure scan constraints */
dbdataNext, /* xNext - advance a cursor */
dbdataEof, /* xEof - check for end of scan */
dbdataColumn, /* xColumn - read data */
dbdataRowid, /* xRowid - read data */
0, /* xUpdate */
0, /* xBegin */
0, /* xSync */
0, /* xCommit */
0, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
0 /* xShadowName */
};
int rc = sqlite3_create_module(db, "sqlite_dbdata", &dbdata_module, 0);
if( rc==SQLITE_OK ){
rc = sqlite3_create_module(db, "sqlite_dbptr", &dbdata_module, (void*)1);
}
return rc;
}
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_dbdata_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
SQLITE_EXTENSION_INIT2(pApi);
return sqlite3DbdataRegister(db);
}

View File

@ -738,6 +738,7 @@ SHELL_SRC = \
$(TOP)/ext/expert/sqlite3expert.h \ $(TOP)/ext/expert/sqlite3expert.h \
$(TOP)/ext/misc/zipfile.c \ $(TOP)/ext/misc/zipfile.c \
$(TOP)/ext/misc/memtrace.c \ $(TOP)/ext/misc/memtrace.c \
$(TOP)/ext/misc/dbdata.c \
$(TOP)/src/test_windirent.c $(TOP)/src/test_windirent.c
shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl

View File

@ -1,5 +1,5 @@
C Fix\sa\sminor\stypo\sin\sa\scomment.\s\sNo\schanges\sto\scode. C Add\sthe\s".recover"\scommand\sto\sthe\sshell\stool.\sFor\srecovering\sas\smuch\sdata\sas\spossible\sfrom\scorrupt\sdatabases.
D 2019-04-27T20:16:42.497 D 2019-04-27T20:30:19.423
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,6 +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 1b3751b02d8f575d25c6bda358670d2e39ace368a0d05595989c308a10c615f6
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
@ -440,7 +441,7 @@ F ext/userauth/userauth.c f81aa5a3ecacf406f170c62a144405858f6f6de51dbdc0920134e6
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
F main.mk 23d3660f7053d196aef76938bf78b10fc3ce1831a85d96bd71565758788f34d4 F main.mk 125adda36bb32c99dc3a11340bd029ef373b9523eac2b2af76087bfe82d4fdf8
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
@ -519,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 bcfa17eb257bf8dc2359e99ba7e6bdfab7901705db013bc47a5be6d7fa7a037e F src/shell.c.in 104bbae904a2b67bc6c0c95337447544d15d0594dc46468608aae769d5f51da9
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
@ -786,6 +787,7 @@ F test/cursorhint2.test 6f3aa9cb19e7418967a10ec6905209bcbb5968054da855fc36c8beee
F test/dataversion1.test 6e5e86ac681f0782e766ebcb56c019ae001522d114e0e111e5ebf68ccf2a7bb8 F test/dataversion1.test 6e5e86ac681f0782e766ebcb56c019ae001522d114e0e111e5ebf68ccf2a7bb8
F test/date.test 9b73bbeb1b82d9c1f44dec5cf563bf7da58d2373 F test/date.test 9b73bbeb1b82d9c1f44dec5cf563bf7da58d2373
F test/date2.test 74c234bece1b016e94dd4ef9c8cc7a199a8806c0e2291cab7ba64bace6350b10 F test/date2.test 74c234bece1b016e94dd4ef9c8cc7a199a8806c0e2291cab7ba64bace6350b10
F test/dbdata.test c8d97bafd1b2efb1e445871c4641208dcd91e686d2dfbb6463d83934adbd1ac5
F test/dbfuzz.c 73047c920d6210e5912c87cdffd9a1c281d4252e F test/dbfuzz.c 73047c920d6210e5912c87cdffd9a1c281d4252e
F test/dbfuzz001.test e32d14465f1c77712896fda6a1ccc0f037b481c191c1696a9c44f6c9e4964faf F test/dbfuzz001.test e32d14465f1c77712896fda6a1ccc0f037b481c191c1696a9c44f6c9e4964faf
F test/dbfuzz2-seed1.db e6225c6f3d7b63f9c5b6867146a5f329d997ab105bee64644dc2b3a2f2aebaee F test/dbfuzz2-seed1.db e6225c6f3d7b63f9c5b6867146a5f329d997ab105bee64644dc2b3a2f2aebaee
@ -1224,6 +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 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
@ -1752,7 +1755,7 @@ F tool/mkopcodec.tcl d1b6362bd3aa80d5520d4d6f3765badf01f6c43c
F tool/mkopcodeh.tcl 352a4319c0ad869eb26442bf7c3b015aa15594c21f1cce5a6420dbe999367c21 F tool/mkopcodeh.tcl 352a4319c0ad869eb26442bf7c3b015aa15594c21f1cce5a6420dbe999367c21
F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa
F tool/mkpragmatab.tcl 49039adedafbc430d2959400da2e0e8f20ef8dcf6898e447c946e7d50ef5906b F tool/mkpragmatab.tcl 49039adedafbc430d2959400da2e0e8f20ef8dcf6898e447c946e7d50ef5906b
F tool/mkshellc.tcl 1f45770aea226ac093a9c72f718efbb88a2a2833409ec2e1c4cecae4202626f5 F tool/mkshellc.tcl 70a9978e363b0f3280ca9ce1c46d72563ff479c1930a12a7375e3881b7325712
F tool/mksourceid.c d458f9004c837bee87a6382228ac20d3eae3c49ea3b0a5aace936f8b60748d3b F tool/mksourceid.c d458f9004c837bee87a6382228ac20d3eae3c49ea3b0a5aace936f8b60748d3b
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f
@ -1818,7 +1821,8 @@ 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 af53c41a127c314c0608f3fd016d3a26896783745e46cd180976a188400cdb75 P 95209072176ff21a91e96d5bd014b35ef100da2b0b93958baf6df4294a8daa85 425d708c3908fe74f69b62e6dd1722a0018088977e12f14b312dad1df0fbb804
R 5d8e72521d268ff442af4eda367a86ab R f359cf293ea45304e0ddadd16c7a1206
U drh T +closed 425d708c3908fe74f69b62e6dd1722a0018088977e12f14b312dad1df0fbb804
Z c27e4c4c6167f0761ffa85b745f3dbd7 U dan
Z ee7f2d083d1fca64ba3bb33b40a19208

View File

@ -1 +1 @@
95209072176ff21a91e96d5bd014b35ef100da2b0b93958baf6df4294a8daa85 50fe48458942fa7a6bcc76316c6321f95b23dc34f2f8e0a483826483b2fb16f6

View File

@ -948,6 +948,10 @@ INCLUDE ../ext/misc/sqlar.c
INCLUDE ../ext/expert/sqlite3expert.h INCLUDE ../ext/expert/sqlite3expert.h
INCLUDE ../ext/expert/sqlite3expert.c INCLUDE ../ext/expert/sqlite3expert.c
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
INCLUDE ../ext/misc/dbdata.c
#endif
#if defined(SQLITE_ENABLE_SESSION) #if defined(SQLITE_ENABLE_SESSION)
/* /*
** State information for a single open session ** State information for a single open session
@ -3574,6 +3578,9 @@ 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",
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
".recover Recover as much data as possible from corrupt db.",
#endif
".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",
@ -3931,6 +3938,125 @@ readHexDb_error:
} }
#endif /* SQLITE_ENABLE_DESERIALIZE */ #endif /* SQLITE_ENABLE_DESERIALIZE */
/*
** Scalar function "shell_int32". The first argument to this function
** must be a blob. The second a non-negative integer. This function
** reads and returns a 32-bit big-endian integer from byte
** offset (4*<arg2>) of the blob.
*/
static void shellInt32(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
const unsigned char *pBlob;
int nBlob;
int iInt;
nBlob = sqlite3_value_bytes(argv[0]);
pBlob = (const unsigned char*)sqlite3_value_blob(argv[0]);
iInt = sqlite3_value_int(argv[1]);
if( iInt>=0 && (iInt+1)*4<=nBlob ){
const unsigned char *a = &pBlob[iInt*4];
sqlite3_int64 iVal = ((sqlite3_int64)a[0]<<24)
+ ((sqlite3_int64)a[1]<<16)
+ ((sqlite3_int64)a[2]<< 8)
+ ((sqlite3_int64)a[3]<< 0);
sqlite3_result_int64(context, iVal);
}
}
/*
** Scalar function "shell_escape_crnl" used by the .recover command.
** The argument passed to this function is the output of built-in
** function quote(). If the first character of the input is "'",
** indicating that the value passed to quote() was a text value,
** then this function searches the input for "\n" and "\r" characters
** and adds a wrapper similar to the following:
**
** replace(replace(<input>, '\n', char(10), '\r', char(13));
**
** Or, if the first character of the input is not "'", then a copy
** of the input is returned.
*/
static void shellEscapeCrnl(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
const char *zText = (const char*)sqlite3_value_text(argv[0]);
if( zText[0]=='\'' ){
int nText = sqlite3_value_bytes(argv[0]);
int i;
char zBuf1[20];
char zBuf2[20];
const char *zNL = 0;
const char *zCR = 0;
int nCR = 0;
int nNL = 0;
for(i=0; zText[i]; i++){
if( zNL==0 && zText[i]=='\n' ){
zNL = unused_string(zText, "\\n", "\\012", zBuf1);
nNL = (int)strlen(zNL);
}
if( zCR==0 && zText[i]=='\r' ){
zCR = unused_string(zText, "\\r", "\\015", zBuf2);
nCR = (int)strlen(zCR);
}
}
if( zNL || zCR ){
int iOut = 0;
i64 nMax = (nNL > nCR) ? nNL : nCR;
i64 nAlloc = nMax * nText + (nMax+12)*2;
char *zOut = (char*)sqlite3_malloc64(nAlloc);
if( zOut==0 ){
sqlite3_result_error_nomem(context);
return;
}
if( zNL && zCR ){
memcpy(&zOut[iOut], "replace(replace(", 16);
iOut += 16;
}else{
memcpy(&zOut[iOut], "replace(", 8);
iOut += 8;
}
for(i=0; zText[i]; i++){
if( zText[i]=='\n' ){
memcpy(&zOut[iOut], zNL, nNL);
iOut += nNL;
}else if( zText[i]=='\r' ){
memcpy(&zOut[iOut], zCR, nCR);
iOut += nCR;
}else{
zOut[iOut] = zText[i];
iOut++;
}
}
if( zNL ){
memcpy(&zOut[iOut], ",'", 2); iOut += 2;
memcpy(&zOut[iOut], zNL, nNL); iOut += nNL;
memcpy(&zOut[iOut], "', char(10))", 12); iOut += 12;
}
if( zCR ){
memcpy(&zOut[iOut], ",'", 2); iOut += 2;
memcpy(&zOut[iOut], zCR, nCR); iOut += nCR;
memcpy(&zOut[iOut], "', char(13))", 12); iOut += 12;
}
sqlite3_result_text(context, zOut, iOut, SQLITE_TRANSIENT);
sqlite3_free(zOut);
return;
}
}
sqlite3_result_value(context, argv[0]);
}
/* Flags for open_db(). /* Flags for open_db().
** **
** The default behavior of open_db() is to exit(1) if the database fails to ** The default behavior of open_db() is to exit(1) if the database fails to
@ -3999,6 +4125,9 @@ static void open_db(ShellState *p, int openFlags){
sqlite3_fileio_init(p->db, 0, 0); sqlite3_fileio_init(p->db, 0, 0);
sqlite3_shathree_init(p->db, 0, 0); sqlite3_shathree_init(p->db, 0, 0);
sqlite3_completion_init(p->db, 0, 0); sqlite3_completion_init(p->db, 0, 0);
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
sqlite3_dbdata_init(p->db, 0, 0);
#endif
#ifdef SQLITE_HAVE_ZLIB #ifdef SQLITE_HAVE_ZLIB
sqlite3_zipfile_init(p->db, 0, 0); sqlite3_zipfile_init(p->db, 0, 0);
sqlite3_sqlar_init(p->db, 0, 0); sqlite3_sqlar_init(p->db, 0, 0);
@ -4009,6 +4138,10 @@ static void open_db(ShellState *p, int openFlags){
shellModuleSchema, 0, 0); shellModuleSchema, 0, 0);
sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p, sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p,
shellPutsFunc, 0, 0); shellPutsFunc, 0, 0);
sqlite3_create_function(p->db, "shell_escape_crnl", 1, SQLITE_UTF8, 0,
shellEscapeCrnl, 0, 0);
sqlite3_create_function(p->db, "shell_int32", 2, SQLITE_UTF8, 0,
shellInt32, 0, 0);
#ifndef SQLITE_NOHAVE_SYSTEM #ifndef SQLITE_NOHAVE_SYSTEM
sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0, sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0,
editFunc, 0, 0); editFunc, 0, 0);
@ -5263,10 +5396,7 @@ static int lintDotCommand(
return SQLITE_ERROR; return SQLITE_ERROR;
} }
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) #if !defined SQLITE_OMIT_VIRTUALTABLE
/*********************************************************************************
** The ".archive" or ".ar" command.
*/
static void shellPrepare( static void shellPrepare(
sqlite3 *db, sqlite3 *db,
int *pRc, int *pRc,
@ -5337,6 +5467,12 @@ static void shellReset(
*pRc = rc; *pRc = rc;
} }
} }
#endif /* !defined SQLITE_OMIT_VIRTUALTABLE */
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
/*********************************************************************************
** The ".archive" or ".ar" command.
*/
/* /*
** Structure representing a single ".ar" command. ** Structure representing a single ".ar" command.
*/ */
@ -6026,6 +6162,631 @@ end_ar_command:
**********************************************************************************/ **********************************************************************************/
#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */ #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);
}
*pRc = rc;
}
}
/*
** 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 ){
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 ){
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_master 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);
}
}
pTab->zQuoted = shellMPrintf(&rc, "%Q", zName);
pTab->azlCol = (char**)shellMalloc(&rc, sizeof(char*) * (nSqlCol+1));
pTab->nCol = nSqlCol;
if( bIntkey ){
pTab->azlCol[0] = shellMPrintf(&rc, "%Q", zPk);
}else{
pTab->azlCol[0] = shellMPrintf(&rc, "");
}
i = 1;
shellPreparePrintf(dbtmp, &rc, &pStmt,
"SELECT %Q || group_concat(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 ){
recoverFreeTable(pTab);
pTab = 0;
}
return pTab;
}
/*
** This function is called to search the schema recovered from the
** sqlite_master 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);
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, "%Q", 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;
}
/*
** 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, 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;
int bFreelist = 1; /* 0 if --freelist-corrupt is specified */
for(i=1; i<nArg; i++){
char *z = azArg[i];
int n;
if( z[0]=='-' && z[1]=='-' ) z++;
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;
}
}
shellExecPrintf(pState->db, &rc,
/* Attach an in-memory database named 'recovery'. Create an indexed
** cache of the sqlite_dbptr virtual table. */
"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
);
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, 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;"
);
}
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"
" 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 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 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_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;"
"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;
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 AND root NOT IN (SELECT rootpage FROM recovery.schema)"
, &pLoop
);
if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
nOrphan = sqlite3_column_int(pLoop, 0);
}
shellFinalize(&rc, pLoop);
pLoop = 0;
pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
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(value)), ', ')"
"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;
pTab = recoverFindTable(pState, &rc, iRoot, bIntkey, nCol, &bNoop);
if( bNoop || rc ) continue;
if( pTab==0 ) pTab = pOrphan;
if( 0==sqlite3_stricmp(pTab->zQuoted, "'sqlite_sequence'") ){
raw_printf(pState->out, "DELETE FROM sqlite_sequence;\n");
}
sqlite3_bind_int(pPages, 1, iRoot);
sqlite3_bind_int(pCells, 2, pTab->iPk);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){
int iPgno = sqlite3_column_int(pPages, 0);
sqlite3_bind_int(pCells, 1, iPgno);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){
int nField = sqlite3_column_int(pCells, 0);
const char *zVal = (const char*)sqlite3_column_text(pCells, 1);
nField = nField+1;
if( pTab==pOrphan ){
raw_printf(pState->out,
"INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n",
pTab->zQuoted, iRoot, iPgno, nField,
bIntkey ? "" : "NULL, ", zVal, pTab->azlCol[nField]
);
}else{
raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n",
pTab->zQuoted, pTab->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_master 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);
return rc;
}
#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
/* /*
** If an input line begins with "." then invoke this routine to ** If an input line begins with "." then invoke this routine to
@ -6313,6 +7074,13 @@ static int do_meta_command(char *zLine, ShellState *p){
rc = shell_dbinfo_command(p, nArg, azArg); rc = shell_dbinfo_command(p, nArg, azArg);
}else }else
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){
open_db(p, 0);
rc = recoverDatabaseCmd(p, nArg, azArg);
}else
#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){ if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
const char *zLike = 0; const char *zLike = 0;
int i; int i;
@ -6350,7 +7118,9 @@ static int do_meta_command(char *zLine, ShellState *p){
zLike = azArg[i]; zLike = azArg[i];
} }
} }
open_db(p, 0); open_db(p, 0);
/* When playing back a "dump", the content might appear in an order /* When playing back a "dump", the content might appear in an order
** which causes immediate foreign key constraints to be violated. ** which causes immediate foreign key constraints to be violated.
** So disable foreign-key constraint enforcement to prevent problems. */ ** So disable foreign-key constraint enforcement to prevent problems. */
@ -6398,7 +7168,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, "PRAGMA writable_schema=OFF;", 0, 0, 0);
sqlite3_exec(p->db, "RELEASE dump;", 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->showHeader = savedShowHeader;
p->shellFlgs = savedShellFlags; p->shellFlgs = savedShellFlags;
}else }else

114
test/dbdata.test Normal file
View File

@ -0,0 +1,114 @@
# 2019-04-11
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library. The
# focus of this file is testing the sqlite_dbpage virtual table.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix dbdata
ifcapable !vtab||!compound {
finish_test
return
}
db enable_load_extension 1
if { [catch { db eval { SELECT load_extension('../dbdata') } }] } {
finish_test
return
}
do_execsql_test 1.0 {
CREATE TABLE T1(a, b);
INSERT INTO t1(rowid, a ,b) VALUES(5, 'v', 'five');
INSERT INTO t1(rowid, a, b) VALUES(10, 'x', 'ten');
}
do_execsql_test 1.1 {
SELECT pgno, cell, field, quote(value) FROM sqlite_dbdata WHERE pgno=2;
} {
2 0 -1 5
2 0 0 'v'
2 0 1 'five'
2 1 -1 10
2 1 0 'x'
2 1 1 'ten'
}
breakpoint
do_execsql_test 1.2 {
SELECT pgno, cell, field, quote(value) FROM sqlite_dbdata;
} {
1 0 -1 1
1 0 0 'table'
1 0 1 'T1'
1 0 2 'T1'
1 0 3 2
1 0 4 {'CREATE TABLE T1(a, b)'}
2 0 -1 5
2 0 0 'v'
2 0 1 'five'
2 1 -1 10
2 1 0 'x'
2 1 1 'ten'
}
set big [string repeat big 2000]
do_execsql_test 1.3 {
INSERT INTO t1 VALUES(NULL, $big);
SELECT value FROM sqlite_dbdata WHERE pgno=2 AND cell=2 AND field=1;
} $big
do_execsql_test 1.4 {
DELETE FROM t1;
INSERT INTO t1 VALUES(NULL, randomblob(5050));
}
do_test 1.5 {
execsql {
SELECT quote(value) FROM sqlite_dbdata WHERE pgno=2 AND cell=0 AND field=1;
}
} [db one {SELECT quote(b) FROM t1}]
#-------------------------------------------------------------------------
reset_db
db enable_load_extension 1
db eval { SELECT load_extension('../dbdata') }
do_execsql_test 2.0 {
CREATE TABLE t1(a);
CREATE INDEX i1 ON t1(a);
WITH s(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<10
)
INSERT INTO t1 SELECT randomblob(900) FROM s;
}
do_execsql_test 2.1 {
SELECT * FROM sqlite_dbptr WHERE pgno=2;
} {
2 25 2 6 2 7 2 9 2 11 2 13 2 15 2 17 2 19 2 21
}
do_execsql_test 2.2 {
SELECT * FROM sqlite_dbptr WHERE pgno=3;
} {
3 24 3 23
}
do_execsql_test 2.3 {
SELECT * FROM sqlite_dbptr
} {
2 25 2 6 2 7 2 9 2 11 2 13 2 15 2 17 2 19 2 21
3 24 3 23
}
finish_test

129
test/recover.test Normal file
View File

@ -0,0 +1,129 @@
# 2019 April 23
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
# Test the shell tool ".ar" command.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix recover
ifcapable !vtab {
finish_test; return
}
set CLI [test_find_cli]
proc compare_result {db1 db2 sql} {
set r1 [$db1 eval $sql]
set r2 [$db2 eval $sql]
if {$r1 != $r2} {
puts "r1: $r1"
puts "r2: $r2"
error "mismatch for $sql"
}
return ""
}
proc compare_dbs {db1 db2} {
compare_result $db1 $db2 "SELECT sql FROM sqlite_master ORDER BY 1"
foreach tbl [$db1 eval {SELECT name FROM sqlite_master WHERE type='table'}] {
compare_result $db1 $db2 "SELECT * FROM $tbl"
}
}
proc do_recover_test {tn {tsql {}} {res {}}} {
set fd [open "|$::CLI test.db .recover"]
fconfigure $fd -encoding binary
fconfigure $fd -translation binary
set sql [read $fd]
close $fd
forcedelete test.db2
sqlite3 db2 test.db2
execsql $sql 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
}
set doc {
hello
world
}
do_execsql_test 1.1.1 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
INSERT INTO t1 VALUES(1, 4, X'1234567800');
INSERT INTO t1 VALUES(2, 'test', 8.1);
INSERT INTO t1 VALUES(3, $doc, 8.4);
}
do_recover_test 1.1.2
do_execsql_test 1.2.1 "
DELETE FROM t1;
INSERT INTO t1 VALUES(13, 'hello\r\nworld', 13);
"
do_recover_test 1.2.2
do_execsql_test 1.3.1 "
CREATE TABLE t2(i INTEGER PRIMARY KEY AUTOINCREMENT, b, c);
INSERT INTO t2 VALUES(NULL, 1, 2);
INSERT INTO t2 VALUES(NULL, 3, 4);
INSERT INTO t2 VALUES(NULL, 5, 6);
CREATE TABLE t3(i INTEGER PRIMARY KEY AUTOINCREMENT, b, c);
INSERT INTO t3 VALUES(NULL, 1, 2);
INSERT INTO t3 VALUES(NULL, 3, 4);
INSERT INTO t3 VALUES(NULL, 5, 6);
DELETE FROM t2;
"
do_recover_test 1.3.2
#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.1.0 {
CREATE TABLE t1(a, b, c, PRIMARY KEY(b, c)) WITHOUT ROWID;
INSERT INTO t1 VALUES(1, 2, 3);
INSERT INTO t1 VALUES(4, 5, 6);
INSERT INTO t1 VALUES(7, 8, 9);
}
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

View File

@ -40,16 +40,21 @@ proc omit_redundant_typedefs {line} {
} }
return $line return $line
} }
set iLine 0
while {1} { while {1} {
set lx [omit_redundant_typedefs [gets $in]] set lx [omit_redundant_typedefs [gets $in]]
if {[eof $in]} break; if {[eof $in]} break;
incr iLine
if {[regexp {^INCLUDE } $lx]} { if {[regexp {^INCLUDE } $lx]} {
set cfile [lindex $lx 1] set cfile [lindex $lx 1]
puts $out "/************************* Begin $cfile ******************/" puts $out "/************************* Begin $cfile ******************/"
# puts $out "#line 1 \"$cfile\""
set in2 [open $topdir/src/$cfile rb] set in2 [open $topdir/src/$cfile rb]
while {![eof $in2]} { while {![eof $in2]} {
set lx [omit_redundant_typedefs [gets $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]} { if {[regexp {^# *include "test_windirent.h"} $lx]} {
set lx "/* $lx */" set lx "/* $lx */"
} }
@ -58,6 +63,7 @@ while {1} {
} }
close $in2 close $in2
puts $out "/************************* End $cfile ********************/" puts $out "/************************* End $cfile ********************/"
# puts $out "#line [expr $iLine+1] \"shell.c.in\""
continue continue
} }
puts $out $lx puts $out $lx