mirror of
https://github.com/sqlite/sqlite.git
synced 2025-12-24 14:17:58 +03:00
Further work on making the recover extension compatible with the .recover command.
FossilOrigin-Name: f2ac315844d8db1bd1c6950a4fef7c459ddd37cc21a8f3daafa5639fad8118e2
This commit is contained in:
@@ -477,6 +477,7 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
|
||||
rc = dbdataLoadPage(pCsr, pCsr->iPgno, &pCsr->aPage, &pCsr->nPage);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
if( pCsr->aPage ) break;
|
||||
if( pCsr->bOnePage ) return SQLITE_OK;
|
||||
pCsr->iPgno++;
|
||||
}
|
||||
pCsr->iCell = pTab->bPtr ? -2 : 0;
|
||||
|
||||
139
ext/recover/recoverold.test
Normal file
139
ext/recover/recoverold.test
Normal file
@@ -0,0 +1,139 @@
|
||||
# 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] recover_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix recoverold
|
||||
|
||||
ifcapable !vtab {
|
||||
finish_test; return
|
||||
}
|
||||
|
||||
proc compare_result {db1 db2 sql} {
|
||||
set r1 [$db1 eval $sql]
|
||||
set r2 [$db2 eval $sql]
|
||||
if {$r1 != $r2} {
|
||||
puts "sql: $sql"
|
||||
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 {}}} {
|
||||
forcedelete test.db2
|
||||
forcedelete rstate.db
|
||||
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
$R config lostandfound lost_and_found
|
||||
$R config testdb rstate.db
|
||||
$R step
|
||||
$R finish
|
||||
|
||||
sqlite3 db2 test.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 {
|
||||
PRAGMA auto_vacuum = 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
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
breakpoint
|
||||
reset_db
|
||||
do_recover_test 3.0
|
||||
|
||||
finish_test
|
||||
@@ -22,6 +22,9 @@ typedef sqlite3_int64 i64;
|
||||
|
||||
typedef struct RecoverColumn RecoverColumn;
|
||||
struct RecoverColumn {
|
||||
int iField; /* Field in record on disk */
|
||||
int iBind; /* Binding to use in INSERT */
|
||||
int bIPK; /* True for IPK column */
|
||||
char *zCol;
|
||||
int eHidden;
|
||||
};
|
||||
@@ -35,6 +38,9 @@ struct RecoverColumn {
|
||||
** 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.
|
||||
**
|
||||
** aCol[]:
|
||||
** Array of nCol columns. In the order in which they appear in the table.
|
||||
*/
|
||||
typedef struct RecoverTable RecoverTable;
|
||||
struct RecoverTable {
|
||||
@@ -43,7 +49,6 @@ struct RecoverTable {
|
||||
int nCol; /* Number of columns in table */
|
||||
RecoverColumn *aCol; /* Array of columns */
|
||||
int bIntkey; /* True for intkey, false for without rowid */
|
||||
int iPk; /* Index of IPK column, if bIntkey */
|
||||
|
||||
RecoverTable *pNext;
|
||||
};
|
||||
@@ -81,13 +86,13 @@ struct sqlite3_recover {
|
||||
char *zDb;
|
||||
char *zUri;
|
||||
RecoverTable *pTblList;
|
||||
RecoverBitmap *pUsed; /* Used by recoverWriteLostAndFound() */
|
||||
RecoverBitmap *pUsed; /* Used by recoverLostAndFound() */
|
||||
|
||||
int errCode; /* For sqlite3_recover_errcode() */
|
||||
char *zErrMsg; /* For sqlite3_recover_errmsg() */
|
||||
|
||||
char *zStateDb;
|
||||
int bLostAndFound;
|
||||
char *zLostAndFound; /* Name of lost-and-found table (or NULL) */
|
||||
|
||||
};
|
||||
|
||||
@@ -238,6 +243,21 @@ static int recoverExec(sqlite3_recover *p, sqlite3 *db, const char *zSql){
|
||||
return p->errCode;
|
||||
}
|
||||
|
||||
static char *recoverPrintf(sqlite3_recover *p, const char *zFmt, ...){
|
||||
va_list ap;
|
||||
char *z;
|
||||
va_start(ap, zFmt);
|
||||
z = sqlite3_vmprintf(zFmt, ap);
|
||||
va_end(ap);
|
||||
if( p->errCode==SQLITE_OK ){
|
||||
if( z==0 ) p->errCode = SQLITE_NOMEM;
|
||||
}else{
|
||||
sqlite3_free(z);
|
||||
z = 0;
|
||||
}
|
||||
return z;
|
||||
}
|
||||
|
||||
/*
|
||||
** Execute "PRAGMA page_count" against the input database. If successful,
|
||||
** return the integer result. Or, if an error occurs, leave an error code
|
||||
@@ -256,6 +276,24 @@ static i64 recoverPageCount(sqlite3_recover *p){
|
||||
return nPg;
|
||||
}
|
||||
|
||||
/*
|
||||
** SELECT page_is_used(pgno);
|
||||
*/
|
||||
static void recoverPageIsUsed(
|
||||
sqlite3_context *pCtx,
|
||||
int nArg,
|
||||
sqlite3_value **apArg
|
||||
){
|
||||
sqlite3_recover *p = (sqlite3_recover*)sqlite3_user_data(pCtx);
|
||||
sqlite3_int64 pgno = sqlite3_value_int64(apArg[0]);
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
int bRet;
|
||||
|
||||
assert( nArg==1 );
|
||||
bRet = recoverBitmapQuery(p->pUsed, pgno);
|
||||
sqlite3_result_int(pCtx, bRet);
|
||||
}
|
||||
|
||||
/*
|
||||
** The implementation of a user-defined SQL function invoked by the
|
||||
** sqlite_dbdata and sqlite_dbptr virtual table modules to access pages
|
||||
@@ -281,13 +319,14 @@ static void recoverGetPage(
|
||||
|
||||
assert( nArg==1 );
|
||||
if( pgno==0 ){
|
||||
sqlite3_result_int64(pCtx, recoverPageCount(p));
|
||||
i64 nPg = recoverPageCount(p);
|
||||
sqlite3_result_int64(pCtx, nPg);
|
||||
return;
|
||||
}else{
|
||||
if( p->pGetPage==0 ){
|
||||
pStmt = recoverPreparePrintf(
|
||||
p, p->dbIn, "SELECT data FROM sqlite_dbpage(%Q) WHERE pgno=?", p->zDb
|
||||
);
|
||||
);
|
||||
}else{
|
||||
pStmt = p->pGetPage;
|
||||
}
|
||||
@@ -321,6 +360,9 @@ static int recoverOpenOutput(sqlite3_recover *p){
|
||||
assert( p->dbOut==0 );
|
||||
|
||||
rc = sqlite3_open_v2(p->zUri, &db, flags, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_exec(db, "PRAGMA writable_schema = 1", 0, 0, 0);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
const char *zPath = p->zStateDb ? p->zStateDb : ":memory:";
|
||||
char *zSql = sqlite3_mprintf("ATTACH %Q AS recovery", zPath);
|
||||
@@ -332,6 +374,7 @@ static int recoverOpenOutput(sqlite3_recover *p){
|
||||
sqlite3_free(zSql);
|
||||
}
|
||||
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_backup *pBackup = sqlite3_backup_init(db, "main", db, "recovery");
|
||||
if( pBackup ){
|
||||
@@ -349,6 +392,11 @@ static int recoverOpenOutput(sqlite3_recover *p){
|
||||
db, "getpage", 1, SQLITE_UTF8, (void*)p, recoverGetPage, 0, 0
|
||||
);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_create_function(
|
||||
db, "page_is_used", 1, SQLITE_UTF8, (void*)p, recoverPageIsUsed, 0, 0
|
||||
);
|
||||
}
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
if( p->errCode==SQLITE_OK ) rc = recoverDbError(p, db);
|
||||
@@ -392,6 +440,7 @@ static void recoverAddTable(sqlite3_recover *p, const char *zName, i64 iRoot){
|
||||
);
|
||||
|
||||
if( pStmt ){
|
||||
int iPk = -1;
|
||||
RecoverTable *pNew = 0;
|
||||
int nCol = 0;
|
||||
int nName = recoverStrlen(zName);
|
||||
@@ -406,24 +455,37 @@ static void recoverAddTable(sqlite3_recover *p, const char *zName, i64 iRoot){
|
||||
pNew = recoverMalloc(p, nByte);
|
||||
if( pNew ){
|
||||
int i = 0;
|
||||
int iField = 0;
|
||||
int iBind = 1;
|
||||
char *csr = 0;
|
||||
pNew->aCol = (RecoverColumn*)&pNew[1];
|
||||
pNew->zTab = csr = (char*)&pNew->aCol[nCol];
|
||||
pNew->nCol = nCol;
|
||||
pNew->iRoot = iRoot;
|
||||
pNew->iPk = -1;
|
||||
memcpy(csr, zName, nName);
|
||||
csr += nName+1;
|
||||
|
||||
for(i=0; sqlite3_step(pStmt)==SQLITE_ROW; i++){
|
||||
int bPk = sqlite3_column_int(pStmt, 5);
|
||||
int iPKF = sqlite3_column_int(pStmt, 5);
|
||||
int n = sqlite3_column_bytes(pStmt, 1);
|
||||
const char *z = (const char*)sqlite3_column_text(pStmt, 1);
|
||||
const char *zType = (const char*)sqlite3_column_text(pStmt, 2);
|
||||
int eHidden = sqlite3_column_int(pStmt, 6);
|
||||
|
||||
if( bPk ) pNew->iPk = i;
|
||||
if( iPk==-1 && iPKF==1 && !sqlite3_stricmp("integer", zType) ) iPk = i;
|
||||
if( iPKF>1 ) iPk = -2;
|
||||
pNew->aCol[i].zCol = csr;
|
||||
pNew->aCol[i].eHidden = eHidden;
|
||||
if( eHidden==RECOVER_EHIDDEN_VIRTUAL ){
|
||||
pNew->aCol[i].iField = -1;
|
||||
}else{
|
||||
pNew->aCol[i].iField = iField++;
|
||||
}
|
||||
if( eHidden!=RECOVER_EHIDDEN_VIRTUAL
|
||||
&& eHidden!=RECOVER_EHIDDEN_STORED
|
||||
){
|
||||
pNew->aCol[i].iBind = iBind++;
|
||||
}
|
||||
memcpy(csr, z, n);
|
||||
csr += (n+1);
|
||||
}
|
||||
@@ -434,18 +496,26 @@ static void recoverAddTable(sqlite3_recover *p, const char *zName, i64 iRoot){
|
||||
|
||||
recoverFinalize(p, pStmt);
|
||||
|
||||
pStmt = recoverPreparePrintf(p, p->dbOut, "PRAGMA index_info(%Q)", zName);
|
||||
if( pStmt && sqlite3_step(pStmt)!=SQLITE_ROW ){
|
||||
pNew->bIntkey = 1;
|
||||
}else{
|
||||
pNew->iPk = -1;
|
||||
pNew->bIntkey = 1;
|
||||
pStmt = recoverPreparePrintf(p, p->dbOut, "PRAGMA index_xinfo(%Q)", zName);
|
||||
while( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){
|
||||
int iField = sqlite3_column_int(pStmt, 0);
|
||||
int iCol = sqlite3_column_int(pStmt, 1);
|
||||
|
||||
assert( iField<pNew->nCol && iCol<pNew->nCol );
|
||||
pNew->aCol[iCol].iField = iField;
|
||||
|
||||
pNew->bIntkey = 0;
|
||||
iPk = -2;
|
||||
}
|
||||
recoverFinalize(p, pStmt);
|
||||
|
||||
if( iPk>=0 ) pNew->aCol[iPk].bIPK = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
**
|
||||
**
|
||||
*/
|
||||
static int recoverWriteSchema1(sqlite3_recover *p){
|
||||
sqlite3_stmt *pSelect = 0;
|
||||
@@ -453,7 +523,8 @@ static int recoverWriteSchema1(sqlite3_recover *p){
|
||||
|
||||
pSelect = recoverPrepare(p, p->dbOut,
|
||||
"SELECT rootpage, sql, type='table' FROM recovery.schema "
|
||||
" WHERE type='table' OR (type='index' AND sql LIKE '%unique%')"
|
||||
" WHERE type='table' OR (type='index' AND sql LIKE '%unique%') "
|
||||
" ORDER BY type!='table', name!='sqlite_sequence'"
|
||||
);
|
||||
|
||||
pTblname = recoverPrepare(p, p->dbOut,
|
||||
@@ -545,8 +616,9 @@ static sqlite3_stmt *recoverInsertStmt(
|
||||
if( eHidden!=RECOVER_EHIDDEN_VIRTUAL
|
||||
&& eHidden!=RECOVER_EHIDDEN_STORED
|
||||
){
|
||||
assert( pTab->aCol[ii].iField>=0 && pTab->aCol[ii].iBind>=1 );
|
||||
zSql = recoverMPrintf(p, "%z%s%Q", zSql, zSep, pTab->aCol[ii].zCol);
|
||||
zBind = recoverMPrintf(p, "%z%s?", zBind, zSep);
|
||||
zBind = recoverMPrintf(p, "%z%s?%d", zBind, zSep, pTab->aCol[ii].iBind);
|
||||
zSep = ", ";
|
||||
}
|
||||
}
|
||||
@@ -565,74 +637,268 @@ static RecoverTable *recoverFindTable(sqlite3_recover *p, u32 iRoot){
|
||||
return pRet;
|
||||
}
|
||||
|
||||
static int recoverWriteLostAndFound(sqlite3_recover *p){
|
||||
/*
|
||||
** This function attempts to create a lost and found table within the
|
||||
** output db. If successful, it returns a pointer to a buffer containing
|
||||
** the name of the new table. It is the responsibility of the caller to
|
||||
** eventually free this buffer using sqlite3_free().
|
||||
**
|
||||
** If an error occurs, NULL is returned and an error code and error
|
||||
** message left in the recover handle.
|
||||
*/
|
||||
static char *recoverLostAndFoundCreate(
|
||||
sqlite3_recover *p, /* Recover object */
|
||||
int nField /* Number of column fields in new table */
|
||||
){
|
||||
char *zTbl = 0;
|
||||
sqlite3_stmt *pProbe = 0;
|
||||
int ii = 0;
|
||||
|
||||
pProbe = recoverPrepare(p, p->dbOut,
|
||||
"SELECT 1 FROM sqlite_schema WHERE name=?"
|
||||
);
|
||||
for(ii=-1; zTbl==0 && p->errCode==SQLITE_OK && ii<1000; ii++){
|
||||
int bFail = 0;
|
||||
if( ii<0 ){
|
||||
zTbl = recoverPrintf(p, "%s", p->zLostAndFound);
|
||||
}else{
|
||||
zTbl = recoverPrintf(p, "%s_%d", p->zLostAndFound, ii);
|
||||
}
|
||||
|
||||
if( p->errCode==SQLITE_OK ){
|
||||
sqlite3_bind_text(pProbe, 1, zTbl, -1, SQLITE_STATIC);
|
||||
if( SQLITE_ROW==sqlite3_step(pProbe) ){
|
||||
bFail = 1;
|
||||
}
|
||||
recoverReset(p, pProbe);
|
||||
}
|
||||
|
||||
if( bFail ){
|
||||
sqlite3_clear_bindings(pProbe);
|
||||
sqlite3_free(zTbl);
|
||||
zTbl = 0;
|
||||
}
|
||||
}
|
||||
recoverFinalize(p, pProbe);
|
||||
|
||||
if( zTbl ){
|
||||
const char *zSep = 0;
|
||||
char *zField = 0;
|
||||
char *zSql = 0;
|
||||
|
||||
zSep = "rootpgno INTEGER, pgno INTEGER, nfield INTEGER, id INTEGER, ";
|
||||
for(ii=0; p->errCode==SQLITE_OK && ii<nField; ii++){
|
||||
zField = recoverMPrintf(p, "%z%sc%d", zField, zSep, ii);
|
||||
zSep = ", ";
|
||||
}
|
||||
|
||||
zSql = recoverPrintf(p, "CREATE TABLE %s(%s)", zTbl, zField);
|
||||
sqlite3_free(zField);
|
||||
|
||||
recoverExec(p, p->dbOut, zSql);
|
||||
sqlite3_free(zSql);
|
||||
}else if( p->errCode==SQLITE_OK ){
|
||||
recoverError(
|
||||
p, SQLITE_ERROR, "failed to create %s output table", p->zLostAndFound
|
||||
);
|
||||
}
|
||||
|
||||
return zTbl;
|
||||
}
|
||||
|
||||
/*
|
||||
** Synthesize and prepare an INSERT statement to write to the lost_and_found
|
||||
** table in the output database. The name of the table is zTab, and it has
|
||||
** nField c* fields.
|
||||
*/
|
||||
static sqlite3_stmt *recoverLostAndFoundInsert(
|
||||
sqlite3_recover *p,
|
||||
const char *zTab,
|
||||
int nField
|
||||
){
|
||||
int nTotal = nField + 4;
|
||||
int ii;
|
||||
char *zBind = 0;
|
||||
const char *zSep = "";
|
||||
sqlite3_stmt *pRet = 0;
|
||||
|
||||
for(ii=0; ii<nTotal; ii++){
|
||||
zBind = recoverMPrintf(p, "%z%s?", zBind, zBind?", ":"", ii);
|
||||
}
|
||||
|
||||
pRet = recoverPreparePrintf(
|
||||
p, p->dbOut, "INSERT INTO %s VALUES(%s)", zTab, zBind
|
||||
);
|
||||
sqlite3_free(zBind);
|
||||
return pRet;
|
||||
}
|
||||
|
||||
static void recoverLostAndFoundPopulate(
|
||||
sqlite3_recover *p,
|
||||
sqlite3_stmt *pInsert,
|
||||
int nField
|
||||
){
|
||||
sqlite3_stmt *pStmt = recoverPrepare(p, p->dbOut,
|
||||
"WITH RECURSIVE pages(root, page) AS ("
|
||||
" SELECT pgno, pgno FROM recovery.map WHERE parent IS NULL"
|
||||
" UNION"
|
||||
" SELECT root, child FROM sqlite_dbptr('getpage()'), pages "
|
||||
" WHERE pgno=page"
|
||||
") "
|
||||
"SELECT root, page, cell, field, value "
|
||||
"FROM sqlite_dbdata('getpage()') d, pages p WHERE p.page=d.pgno "
|
||||
" AND NOT page_is_used(page) "
|
||||
"UNION ALL "
|
||||
"SELECT 0, 0, 0, 0, 0"
|
||||
);
|
||||
|
||||
sqlite3_value **apVal = 0;
|
||||
int nVal = -1;
|
||||
i64 iRowid = 0;
|
||||
int bHaveRowid = 0;
|
||||
int ii;
|
||||
|
||||
i64 iPrevRoot = -1;
|
||||
i64 iPrevPage = -1;
|
||||
int iPrevCell = -1;
|
||||
|
||||
apVal = (sqlite3_value**)recoverMalloc(p, nField*sizeof(sqlite3_value*));
|
||||
while( p->errCode==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
i64 iRoot = sqlite3_column_int64(pStmt, 0);
|
||||
i64 iPage = sqlite3_column_int64(pStmt, 1);
|
||||
int iCell = sqlite3_column_int64(pStmt, 2);
|
||||
int iField = sqlite3_column_int64(pStmt, 3);
|
||||
|
||||
if( iPrevRoot>0 && (
|
||||
iPrevRoot!=iRoot || iPrevPage!=iPage || iPrevCell!=iCell
|
||||
)){
|
||||
/* Insert the new row */
|
||||
sqlite3_bind_int64(pInsert, 1, iPrevRoot); /* rootpgno */
|
||||
sqlite3_bind_int64(pInsert, 2, iPrevPage); /* pgno */
|
||||
sqlite3_bind_int(pInsert, 3, nVal); /* nfield */
|
||||
if( bHaveRowid ){
|
||||
sqlite3_bind_int64(pInsert, 4, iRowid); /* id */
|
||||
}
|
||||
for(ii=0; ii<nVal; ii++){
|
||||
sqlite3_bind_value(pInsert, 5+ii, apVal[ii]);
|
||||
}
|
||||
sqlite3_step(pInsert);
|
||||
recoverReset(p, pInsert);
|
||||
|
||||
/* Discard the accumulated row data */
|
||||
for(ii=0; ii<nVal; ii++){
|
||||
sqlite3_value_free(apVal[ii]);
|
||||
apVal[ii] = 0;
|
||||
}
|
||||
bHaveRowid = 0;
|
||||
nVal = -1;
|
||||
}
|
||||
|
||||
if( iField<0 ){
|
||||
assert( nVal==-1 );
|
||||
iRowid = sqlite3_column_int64(pStmt, 4);
|
||||
bHaveRowid = 1;
|
||||
nVal = 0;
|
||||
}else if( iField<nField && iRoot!=0 ){
|
||||
sqlite3_value *pVal = sqlite3_column_value(pStmt, 4);
|
||||
apVal[iField] = sqlite3_value_dup(pVal);
|
||||
assert( iField==nVal || (nVal==-1 && iField==0) );
|
||||
nVal = iField+1;
|
||||
}
|
||||
|
||||
iPrevRoot = iRoot;
|
||||
iPrevPage = iPage;
|
||||
iPrevCell = iCell;
|
||||
}
|
||||
recoverFinalize(p, pStmt);
|
||||
|
||||
for(ii=0; ii<nVal; ii++){
|
||||
sqlite3_value_free(apVal[ii]);
|
||||
apVal[ii] = 0;
|
||||
}
|
||||
sqlite3_free(apVal);
|
||||
}
|
||||
|
||||
static int recoverLostAndFound(sqlite3_recover *p){
|
||||
i64 nPg = 0;
|
||||
RecoverBitmap *pMap = 0;
|
||||
|
||||
nPg = recoverPageCount(p);
|
||||
pMap = p->pUsed = recoverBitmapAlloc(p, nPg);
|
||||
if( pMap ){
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
char *zField = 0;
|
||||
const char *zSep = 0;
|
||||
int ii;
|
||||
char *zTab = 0; /* Name of lost_and_found table */
|
||||
sqlite3_stmt *pInsert = 0; /* INSERT INTO lost_and_found ... */
|
||||
int nField = 0;
|
||||
|
||||
/* Add all pages that are part of any tree in the recoverable part of
|
||||
** the input database schema to the bitmap. */
|
||||
sqlite3_stmt *pStmt = recoverPrepare(
|
||||
p, p->dbOut,
|
||||
"WITH RECURSIVE used(page) AS ("
|
||||
"WITH roots(r) AS ("
|
||||
" SELECT 1 UNION ALL"
|
||||
" SELECT rootpage FROM recovery.schema WHERE rootpage>0"
|
||||
"),"
|
||||
"used(page) AS ("
|
||||
" SELECT r FROM roots"
|
||||
" UNION"
|
||||
" SELECT child FROM sqlite_dbptr('getpage()'), used "
|
||||
" WHERE pgno=page"
|
||||
") "
|
||||
"SELECT page FROM used"
|
||||
);
|
||||
while( pStmt && sqlite3_step(pStmt) ){
|
||||
i64 iPg = sqlite3_column_int64(pStmt);
|
||||
while( pStmt && SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
i64 iPg = sqlite3_column_int64(pStmt, 0);
|
||||
recoverBitmapSet(pMap, iPg);
|
||||
}
|
||||
recoverFinalize(pStmt);
|
||||
recoverFinalize(p, pStmt);
|
||||
|
||||
/* Add an entry for each page not already added to the bitmap to
|
||||
** the recovery.map table. This loop leaves the "parent" column
|
||||
** of each recovery.map row set to NULL - to be filled in below. */
|
||||
pStmt = recoverPreparePrintf(
|
||||
p, p->dbOut,
|
||||
"WITH RECURSIVE seq(ii) AS ("
|
||||
" SELECT 1 UNION ALL SELECT ii+1 FROM seq WHERE ii<%lld"
|
||||
")"
|
||||
"INSERT INTO recover.map(pgno) "
|
||||
" SELECT ii FROM seq WHERE !page_is_used(ii)"
|
||||
"INSERT INTO recovery.map(pgno) "
|
||||
" SELECT ii FROM seq WHERE NOT page_is_used(ii)", nPg
|
||||
);
|
||||
sqlite3_step(pStmt);
|
||||
recoverFinalize(pStmt);
|
||||
recoverFinalize(p, pStmt);
|
||||
|
||||
/* Set the "parent" column for each row of the recovery.map table */
|
||||
pStmt = recoverPrepare(
|
||||
p, p->dbOut,
|
||||
"UPDATE recover.map SET parent = ptr.pgno "
|
||||
" FROM sqlite_dbptr('getpage()') WHERE recover.map.pgno=ptr.child"
|
||||
"UPDATE recovery.map SET parent = ptr.pgno "
|
||||
" FROM sqlite_dbptr('getpage()') AS ptr "
|
||||
" WHERE recovery.map.pgno=ptr.child"
|
||||
);
|
||||
sqlite3_step(pStmt);
|
||||
recoverFinalize(pStmt);
|
||||
recoverFinalize(p, pStmt);
|
||||
|
||||
/* Figure out the number of fields in the longest record that will be
|
||||
** recovered into the lost_and_found table. Set nField to this value. */
|
||||
pStmt = recoverPrepare(
|
||||
p, p->dbOut,
|
||||
"SELECT max(field) FROM sqlite_dbdata('getpage') WHERE pgno IN ("
|
||||
" SELECT pgno FROM recover.map"
|
||||
"SELECT max(field)+1 FROM sqlite_dbdata('getpage') WHERE pgno IN ("
|
||||
" SELECT pgno FROM recovery.map"
|
||||
")"
|
||||
);
|
||||
if( pStmt && sqlite3_step(pStmt) ){
|
||||
nMaxField = sqlite3_column_int64(pStmt, 0);
|
||||
if( pStmt && SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
nField = sqlite3_column_int64(pStmt, 0);
|
||||
}
|
||||
recoverFinalize(pStmt);
|
||||
recoverFinalize(p, pStmt);
|
||||
|
||||
if( nMaxField==0 || p->errCode!=SQLITE_OK ) return p->errCode;
|
||||
|
||||
zSep = "rootpgno INTEGER, pgno INTEGER, nfield INTEGER, id INTEGER, ";
|
||||
for(ii=0; ii<nMaxField; ii++){
|
||||
zField = sqlite3_mprintf("%z%sc%d", zField, zSep, ii);
|
||||
zSep = ", ";
|
||||
if( zField==0 ){
|
||||
p->errCode = SQLITE_NOMEM;
|
||||
}
|
||||
if( nField>0 ){
|
||||
zTab = recoverLostAndFoundCreate(p, nField);
|
||||
pInsert = recoverLostAndFoundInsert(p, zTab, nField);
|
||||
recoverLostAndFoundPopulate(p, pInsert, nField);
|
||||
recoverFinalize(p, pInsert);
|
||||
sqlite3_free(zTab);
|
||||
}
|
||||
|
||||
recoverBitmapFree(pMap);
|
||||
p->pUsed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -647,7 +913,7 @@ static int recoverWriteData(sqlite3_recover *p){
|
||||
if( pTbl->nCol>nMax ) nMax = pTbl->nCol;
|
||||
}
|
||||
|
||||
apVal = (sqlite3_value**)recoverMalloc(p, sizeof(sqlite3_value*) * nMax);
|
||||
apVal = (sqlite3_value**)recoverMalloc(p, sizeof(sqlite3_value*) * (nMax+1));
|
||||
if( apVal==0 ) return p->errCode;
|
||||
|
||||
pSel = recoverPrepare(p, p->dbOut,
|
||||
@@ -697,26 +963,15 @@ static int recoverWriteData(sqlite3_recover *p){
|
||||
nInsert = nVal;
|
||||
}
|
||||
|
||||
for(ii=0; ii<pTab->nCol && iVal<nVal; ii++){
|
||||
int eHidden = pTab->aCol[ii].eHidden;
|
||||
switch( eHidden ){
|
||||
case RECOVER_EHIDDEN_NONE:
|
||||
case RECOVER_EHIDDEN_HIDDEN:
|
||||
if( ii==pTab->iPk ){
|
||||
sqlite3_bind_int64(pInsert, iBind, iRowid);
|
||||
}else{
|
||||
sqlite3_bind_value(pInsert, iBind, apVal[iVal]);
|
||||
}
|
||||
iBind++;
|
||||
iVal++;
|
||||
break;
|
||||
for(ii=0; ii<pTab->nCol; ii++){
|
||||
RecoverColumn *pCol = &pTab->aCol[ii];
|
||||
|
||||
case RECOVER_EHIDDEN_VIRTUAL:
|
||||
break;
|
||||
|
||||
case RECOVER_EHIDDEN_STORED:
|
||||
iVal++;
|
||||
break;
|
||||
if( pCol->iBind>0 ){
|
||||
if( pCol->bIPK ){
|
||||
sqlite3_bind_int64(pInsert, pCol->iBind, iRowid);
|
||||
}else if( pCol->iField<nVal ){
|
||||
sqlite3_bind_value(pInsert, pCol->iBind, apVal[pCol->iField]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -811,7 +1066,13 @@ int sqlite3_recover_config(sqlite3_recover *p, int op, void *pArg){
|
||||
break;
|
||||
|
||||
case SQLITE_RECOVER_LOST_AND_FOUND:
|
||||
p->bLostAndFound = (pArg ? 1 : 0);
|
||||
const char *zArg = (const char*)pArg;
|
||||
sqlite3_free(p->zLostAndFound);
|
||||
if( zArg ){
|
||||
p->zLostAndFound = recoverPrintf(p, "%s", zArg);
|
||||
}else{
|
||||
p->zLostAndFound = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -832,7 +1093,7 @@ static void recoverStep(sqlite3_recover *p){
|
||||
if( recoverCacheSchema(p) ) return;
|
||||
if( recoverWriteSchema1(p) ) return;
|
||||
if( recoverWriteData(p) ) return;
|
||||
if( p->bLostAndFound && recoverWriteLostAndFound(p) ) return;
|
||||
if( p->zLostAndFound && recoverLostAndFound(p) ) return;
|
||||
if( recoverWriteSchema2(p) ) return;
|
||||
}
|
||||
}
|
||||
@@ -861,6 +1122,7 @@ int sqlite3_recover_finish(sqlite3_recover *p){
|
||||
rc = p->errCode;
|
||||
|
||||
sqlite3_free(p->zStateDb);
|
||||
sqlite3_free(p->zLostAndFound);
|
||||
sqlite3_free(p);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,18 @@ sqlite3_recover *sqlite3_recover_init(
|
||||
/* Details TBD. */
|
||||
int sqlite3_recover_config(sqlite3_recover*, int op, void *pArg);
|
||||
|
||||
/*
|
||||
** SQLITE_RECOVER_TESTDB:
|
||||
**
|
||||
**
|
||||
** SQLITE_RECOVER_LOST_AND_FOUND:
|
||||
** The pArg argument points to a string buffer containing the name
|
||||
** of a "lost-and-found" table in the output database, or NULL. If
|
||||
** the argument is non-NULL and the database contains seemingly
|
||||
** valid pages that cannot be associated with any table in the
|
||||
** recovered part of the schema, data is extracted from these
|
||||
** pages to add to the lost-and-found table.
|
||||
*/
|
||||
#define SQLITE_RECOVER_TESTDB 789
|
||||
#define SQLITE_RECOVER_LOST_AND_FOUND 790
|
||||
|
||||
|
||||
@@ -79,7 +79,8 @@ static int testRecoverCmd(
|
||||
switch( iSub ){
|
||||
case 0: assert( sqlite3_stricmp("config", aSub[iSub].zSub)==0 ); {
|
||||
const char *aOp[] = {
|
||||
"testdb", /* 0 */
|
||||
"testdb", /* 0 */
|
||||
"lostandfound", /* 1 */
|
||||
0
|
||||
};
|
||||
int iOp = 0;
|
||||
@@ -89,8 +90,13 @@ static int testRecoverCmd(
|
||||
}
|
||||
switch( iOp ){
|
||||
case 0:
|
||||
res = sqlite3_recover_config(
|
||||
pTest->p, SQLITE_RECOVER_TESTDB, (void*)Tcl_GetString(objv[3])
|
||||
res = sqlite3_recover_config(pTest->p,
|
||||
SQLITE_RECOVER_TESTDB, (void*)Tcl_GetString(objv[3])
|
||||
);
|
||||
break;
|
||||
case 1:
|
||||
res = sqlite3_recover_config(pTest->p,
|
||||
SQLITE_RECOVER_LOST_AND_FOUND, (void*)Tcl_GetString(objv[3])
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
21
manifest
21
manifest
@@ -1,5 +1,5 @@
|
||||
C Further\swork\son\smaking\srecovery\sextension\scompatible\swith\sthe\sshell\stool\s".recover"\scode.
|
||||
D 2022-09-01T21:00:39.747
|
||||
C Further\swork\son\smaking\sthe\srecover\sextension\scompatible\swith\sthe\s.recover\scommand.
|
||||
D 2022-09-03T20:07:39.011
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||
@@ -299,7 +299,7 @@ F ext/misc/closure.c dbfd8543b2a017ae6b1a5843986b22ddf99ff126ec9634a2f4047cd14c8
|
||||
F ext/misc/completion.c 6dafd7f4348eecc7be9e920d4b419d1fb2af75d938cd9c59a20cfe8beb2f22b9
|
||||
F ext/misc/compress.c 3354c77a7c8e86e07d849916000cdac451ed96500bfb5bd83b20eb61eee012c9
|
||||
F ext/misc/csv.c ca8d6dafc5469639de81937cb66ae2e6b358542aba94c4f791910d355a8e7f73
|
||||
F ext/misc/dbdata.c f317980cea788e67932828b94a16ee8a8b859e3c2d62859d09ba3d5ca85f87cb
|
||||
F ext/misc/dbdata.c 9bb3666519bd8a54cce4934076a557fe6441c5bafce7e9c24d8b5ced148e8154
|
||||
F ext/misc/dbdump.c b8592f6f2da292c62991a13864a60d6c573c47a9cc58362131b9e6a64f823e01
|
||||
F ext/misc/decimal.c 09f967dcf4a1ee35a76309829308ec278d3648168733f4a1147820e11ebefd12
|
||||
F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1
|
||||
@@ -389,9 +389,10 @@ F ext/rbu/sqlite3rbu.h 1dc88ab7bd32d0f15890ea08d23476c4198d3da3056985403991f8c9c
|
||||
F ext/rbu/test_rbu.c 03f6f177096a5f822d68d8e4069ad8907fe572c62ff2d19b141f59742821828a
|
||||
F ext/recover/recover1.test a848af8c82fe0731af835ff99475724f8654d2f24f772cc4e6f7ec4eb2ab71ea
|
||||
F ext/recover/recover_common.tcl 6679af7dffc858e345053a91c9b0a897595b4a13007aceffafca75304ccb137c
|
||||
F ext/recover/sqlite3recover.c d81b430f968d838035ebf5ca168b43ae8a0bec1e7c2c950b74ec4fd5e16ca47b
|
||||
F ext/recover/sqlite3recover.h 94e277a9b314a03df46b5e94cc44b70ed6c6893d2776d09c7ea0b55c969ad854
|
||||
F ext/recover/test_recover.c 919f61df54776598b350250057fd2d3ea9cc2cef1aeac0dbb760958d26fe1afb
|
||||
F ext/recover/recoverold.test 33ccbe2393af0e82f292c135b725e3eca1e803960681cf6da41fc00d28bd8683
|
||||
F ext/recover/sqlite3recover.c 8d93b9aa056c3fae9a5e2736a4ffa71414bdb502863ef879e55bec7b37030266
|
||||
F ext/recover/sqlite3recover.h b82974790b528480163d87dcd84afffe7568393194c9ec8241cfbc3ee6bbdd1b
|
||||
F ext/recover/test_recover.c b8dddd96ccd4a62bc14cb3a8d5696407892e184fe7d45ecbedde954577857de2
|
||||
F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15
|
||||
F ext/repair/checkfreelist.c e21f06995ff4efdc1622dcceaea4dcba2caa83ca2f31a1607b98a8509168a996
|
||||
F ext/repair/checkindex.c 4383e4469c21e5b9ae321d0d63cec53e981af9d7a6564be6374f0eeb93dfc890
|
||||
@@ -1341,7 +1342,7 @@ F test/parser1.test 6ccdf5e459a5dc4673d3273dc311a7e9742ca952dd0551a6a6320d27035c
|
||||
F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
|
||||
F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
|
||||
F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
|
||||
F test/permutations.test 909c84575ac50f6d30fa125a109a01986e08c26b9ea38d28501a0711b50b5627
|
||||
F test/permutations.test 847df2d81f0172ab7032e55145f0f3da460dd65759ac2b02864e385add1947d5
|
||||
F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f
|
||||
F test/pragma.test cae534c12a033a5c319ccc94f50b32811acdef9f67bf19a82ff42697caccd69f
|
||||
F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f
|
||||
@@ -2004,8 +2005,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P f8298eeba01cb5b02ac4d642c06f3801331ca90edea533ea898a3283981a9e49
|
||||
R 185cdcc7bc591773e93e55f9de4bfb4c
|
||||
P 8df7c7d0dcd1b2fcdad00e765a9868407f0ced02ac4432ee2cdf25d83b130759
|
||||
R 93788fe8081a2011cde8fac3142d518f
|
||||
U dan
|
||||
Z 5d96e275b1b3f6a2d23b93e2a033e9c9
|
||||
Z 21514150d144c868f0bbea27057fe308
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
||||
@@ -1 +1 @@
|
||||
8df7c7d0dcd1b2fcdad00e765a9868407f0ced02ac4432ee2cdf25d83b130759
|
||||
f2ac315844d8db1bd1c6950a4fef7c459ddd37cc21a8f3daafa5639fad8118e2
|
||||
@@ -91,6 +91,7 @@ foreach f [glob -nocomplain \
|
||||
$testdir/../ext/fts5/test/*.test \
|
||||
$testdir/../ext/expert/*.test \
|
||||
$testdir/../ext/lsm1/test/*.test \
|
||||
$testdir/../ext/recover/*.test \
|
||||
] {
|
||||
lappend alltests $f
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user