From f2f8a3a348c59d1faabaf9690422fab09002ae32 Mon Sep 17 00:00:00 2001 From: dan Date: Sat, 3 Sep 2022 20:07:39 +0000 Subject: [PATCH] Further work on making the recover extension compatible with the .recover command. FossilOrigin-Name: f2ac315844d8db1bd1c6950a4fef7c459ddd37cc21a8f3daafa5639fad8118e2 --- ext/misc/dbdata.c | 1 + ext/recover/recoverold.test | 139 ++++++++++++ ext/recover/sqlite3recover.c | 398 +++++++++++++++++++++++++++++------ ext/recover/sqlite3recover.h | 12 ++ ext/recover/test_recover.c | 12 +- manifest | 21 +- manifest.uuid | 2 +- test/permutations.test | 1 + 8 files changed, 504 insertions(+), 82 deletions(-) create mode 100644 ext/recover/recoverold.test diff --git a/ext/misc/dbdata.c b/ext/misc/dbdata.c index b79eafce7c..a18304b96b 100644 --- a/ext/misc/dbdata.c +++ b/ext/misc/dbdata.c @@ -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; diff --git a/ext/recover/recoverold.test b/ext/recover/recoverold.test new file mode 100644 index 0000000000..61f09397b8 --- /dev/null +++ b/ext/recover/recoverold.test @@ -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 diff --git a/ext/recover/sqlite3recover.c b/ext/recover/sqlite3recover.c index d8b151616b..ec333f0d05 100644 --- a/ext/recover/sqlite3recover.c +++ b/ext/recover/sqlite3recover.c @@ -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( iFieldnCol && iColnCol ); + 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 && iidbOut, 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; iidbOut, "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; iipUsed = 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); - - if( nMaxField==0 || p->errCode!=SQLITE_OK ) return p->errCode; + recoverFinalize(p, pStmt); - zSep = "rootpgno INTEGER, pgno INTEGER, nfield INTEGER, id INTEGER, "; - for(ii=0; iierrCode = 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; iinCol && iValaCol[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; - - case RECOVER_EHIDDEN_VIRTUAL: - break; - - case RECOVER_EHIDDEN_STORED: - iVal++; - break; + for(ii=0; iinCol; ii++){ + RecoverColumn *pCol = &pTab->aCol[ii]; + + if( pCol->iBind>0 ){ + if( pCol->bIPK ){ + sqlite3_bind_int64(pInsert, pCol->iBind, iRowid); + }else if( pCol->iFieldiBind, 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; } diff --git a/ext/recover/sqlite3recover.h b/ext/recover/sqlite3recover.h index 0c83f8dea5..8306c67a12 100644 --- a/ext/recover/sqlite3recover.h +++ b/ext/recover/sqlite3recover.h @@ -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 diff --git a/ext/recover/test_recover.c b/ext/recover/test_recover.c index 912b8dec5c..cdd5d090fa 100644 --- a/ext/recover/test_recover.c +++ b/ext/recover/test_recover.c @@ -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; } diff --git a/manifest b/manifest index 7f8f333f25..10281dc64e 100644 --- a/manifest +++ b/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. diff --git a/manifest.uuid b/manifest.uuid index 5c5b60080f..093f196c7a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8df7c7d0dcd1b2fcdad00e765a9868407f0ced02ac4432ee2cdf25d83b130759 \ No newline at end of file +f2ac315844d8db1bd1c6950a4fef7c459ddd37cc21a8f3daafa5639fad8118e2 \ No newline at end of file diff --git a/test/permutations.test b/test/permutations.test index c5044e1a6d..8ab9dc8755 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -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 } -- 2.47.3