diff --git a/ext/recover/recover1.test b/ext/recover/recover1.test index 73832c8a12..167b2796bd 100644 --- a/ext/recover/recover1.test +++ b/ext/recover/recover1.test @@ -52,8 +52,27 @@ proc do_recover_test {tn} { sqlite3 db2 test.db2 uplevel [list do_test $tn.2 [list compare_dbs db db2] {}] db2 close + + forcedelete test.db2 + forcedelete rstate.db + + uplevel [list do_test $tn.3 { + set ::sqlhook [list] + set R [sqlite3_recover_init_sql db main my_sql_hook] + $R config testdb rstate.db + $R step + $R finish + } {}] + + sqlite3 db2 test.db2 + execsql [join $::sqlhook ";"] db2 + uplevel [list do_test $tn.4 [list compare_dbs db db2] {}] + db2 close } +proc my_sql_hook {sql} { + lappend ::sqlhook $sql +} do_execsql_test 1.0 { CREATE TABLE t1(a INTEGER PRIMARY KEY, b); @@ -112,6 +131,7 @@ do_execsql_test 7.1 { SELECT * FROM t2 } {10 11 ten} +breakpoint do_recover_test 7.2 finish_test diff --git a/ext/recover/recoverold.test b/ext/recover/recoverold.test index 8f70a7473d..83f210aebc 100644 --- a/ext/recover/recoverold.test +++ b/ext/recover/recoverold.test @@ -53,13 +53,37 @@ proc do_recover_test {tn {tsql {}} {res {}}} { sqlite3 db2 test.db2 if {$tsql==""} { - uplevel [list do_test $tn [list compare_dbs db db2] {}] + uplevel [list do_test $tn.1 [list compare_dbs db db2] {}] } else { - uplevel [list do_execsql_test -db db2 $tn $tsql $res] + uplevel [list do_execsql_test -db db2 $tn.1 $tsql $res] + } + db2 close + + forcedelete test.db2 + forcedelete rstate.db + + set ::sqlhook [list] + set R [sqlite3_recover_init_sql db main my_sql_hook] + $R config lostandfound lost_and_found + $R step + $R finish + + sqlite3 db2 test.db2 + db2 eval [join $::sqlhook ";"] + + if {$tsql==""} { + uplevel [list do_test $tn.sql [list compare_dbs db db2] {}] + } else { + uplevel [list do_execsql_test -db db2 $tn.sql $tsql $res] } db2 close } +proc my_sql_hook {sql} { + lappend ::sqlhook $sql +} + + set doc { hello world diff --git a/ext/recover/sqlite3recover.c b/ext/recover/sqlite3recover.c index e0eda09d82..1014e576a4 100644 --- a/ext/recover/sqlite3recover.c +++ b/ext/recover/sqlite3recover.c @@ -64,12 +64,6 @@ struct RecoverBitmap { ** */ #define RECOVERY_SCHEMA \ -" CREATE TABLE recovery.freelist(" \ -" pgno INTEGER PRIMARY KEY" \ -" );" \ -" CREATE TABLE recovery.dbptr(" \ -" pgno, child, PRIMARY KEY(child, pgno)" \ -" ) WITHOUT ROWID;" \ " CREATE TABLE recovery.map(" \ " pgno INTEGER PRIMARY KEY, parent INT" \ " );" \ @@ -96,6 +90,9 @@ struct sqlite3_recover { char *zLostAndFound; /* Name of lost-and-found table (or NULL) */ int bFreelistCorrupt; int bRecoverRowid; + + void *pSqlCtx; + int (*xSql)(void*,const char*); }; /* @@ -445,13 +442,6 @@ static int recoverOpenOutput(sqlite3_recover *p){ return rc; } -static int recoverCacheDbptr(sqlite3_recover *p){ - return recoverExec(p, p->dbOut, - "INSERT INTO recovery.dbptr " - "SELECT pgno, child FROM sqlite_dbptr('getpage()')" - ); -} - static int recoverCacheSchema(sqlite3_recover *p){ return recoverExec(p, p->dbOut, "WITH RECURSIVE pages(p) AS (" @@ -555,6 +545,15 @@ static void recoverAddTable(sqlite3_recover *p, const char *zName, i64 iRoot){ } } +static void recoverSqlCallback(sqlite3_recover *p, const char *zSql){ + if( p->errCode==SQLITE_OK && p->xSql ){ + int res = p->xSql(p->pSqlCtx, zSql); + if( res ){ + recoverError(p, SQLITE_ERROR, "callback returned an error - %d", res); + } + } +} + /* ** */ @@ -581,6 +580,7 @@ static int recoverWriteSchema1(sqlite3_recover *p){ int rc = sqlite3_exec(p->dbOut, zSql, 0, 0, 0); if( rc==SQLITE_OK ){ + recoverSqlCallback(p, zSql); if( bTable ){ if( SQLITE_ROW==sqlite3_step(pTblname) ){ const char *zName = sqlite3_column_text(pTblname, 0); @@ -614,6 +614,8 @@ static int recoverWriteSchema2(sqlite3_recover *p){ int rc = sqlite3_exec(p->dbOut, zSql, 0, 0, 0); if( rc!=SQLITE_OK && rc!=SQLITE_ERROR ){ recoverDbError(p, p->dbOut); + }else if( rc==SQLITE_OK ){ + recoverSqlCallback(p, zSql); } } } @@ -644,9 +646,12 @@ static sqlite3_stmt *recoverInsertStmt( int nField ){ const char *zSep = ""; + const char *zSqlSep = ""; char *zSql = 0; + char *zFinal = 0; char *zBind = 0; int ii; + int bSql = p->xSql ? 1 : 0; sqlite3_stmt *pRet = 0; assert( nField<=pTab->nCol ); @@ -656,9 +661,16 @@ static sqlite3_stmt *recoverInsertStmt( if( pTab->iRowidBind ){ assert( pTab->bIntkey ); zSql = recoverMPrintf(p, "%z_rowid_", zSql); - zBind = recoverMPrintf(p, "%z?%d", zBind, pTab->iRowidBind); + if( bSql ){ + zBind = recoverMPrintf(p, "%zquote(?%d)", zBind, pTab->iRowidBind); + }else{ + zBind = recoverMPrintf(p, "%z?%d", zBind, pTab->iRowidBind); + } + zSqlSep = "||', '||"; zSep = ", "; } + + for(ii=0; iiaCol[ii].eHidden; if( eHidden!=RECOVER_EHIDDEN_VIRTUAL @@ -666,14 +678,31 @@ static sqlite3_stmt *recoverInsertStmt( ){ 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?%d", zBind, zSep, pTab->aCol[ii].iBind); + + if( bSql ){ + zBind = recoverMPrintf( + p, "%z%squote(?%d)", zBind, zSqlSep, pTab->aCol[ii].iBind + ); + zSqlSep = "||', '||"; + }else{ + zBind = recoverMPrintf(p, "%z%s?%d", zBind, zSep, pTab->aCol[ii].iBind); + } zSep = ", "; } } - zSql = recoverMPrintf(p, "%z) VALUES (%z)", zSql, zBind); - pRet = recoverPrepare(p, p->dbOut, zSql); + if( bSql ){ + zFinal = recoverMPrintf(p, "SELECT %Q || ') VALUES (' || %s || ')'", + zSql, zBind + ); + }else{ + zFinal = recoverMPrintf(p, "%s) VALUES (%s)", zSql, zBind); + } + + pRet = recoverPrepare(p, p->dbOut, zFinal); sqlite3_free(zSql); + sqlite3_free(zBind); + sqlite3_free(zFinal); return pRet; } @@ -744,6 +773,7 @@ static char *recoverLostAndFoundCreate( sqlite3_free(zField); recoverExec(p, p->dbOut, zSql); + recoverSqlCallback(p, zSql); sqlite3_free(zSql); }else if( p->errCode==SQLITE_OK ){ recoverError( @@ -838,6 +868,7 @@ static void recoverLostAndFoundPopulate( sqlite3_value_free(apVal[ii]); apVal[ii] = 0; } + sqlite3_clear_bindings(pInsert); bHaveRowid = 0; nVal = -1; } @@ -1052,7 +1083,10 @@ static int recoverWriteData(sqlite3_recover *p){ sqlite3_bind_int64(pInsert, pTab->iRowidBind, iRowid); } - sqlite3_step(pInsert); + if( SQLITE_ROW==sqlite3_step(pInsert) && p->xSql ){ + const char *zSql = (const char*)sqlite3_column_text(pInsert, 0); + recoverSqlCallback(p, zSql); + } recoverReset(p, pInsert); assert( p->errCode || pInsert ); if( pInsert ) sqlite3_clear_bindings(pInsert); @@ -1098,10 +1132,12 @@ static int recoverWriteData(sqlite3_recover *p){ return p->errCode; } -sqlite3_recover *sqlite3_recover_init( +sqlite3_recover *recoverInit( sqlite3* db, const char *zDb, - const char *zUri + const char *zUri, + int (*xSql)(void*, const char*), + void *pSqlCtx ){ sqlite3_recover *pRet = 0; int nDb = 0; @@ -1123,11 +1159,30 @@ sqlite3_recover *sqlite3_recover_init( pRet->zUri = &pRet->zDb[nDb+1]; memcpy(pRet->zDb, zDb, nDb); memcpy(pRet->zUri, zUri, nUri); + pRet->xSql = xSql; + pRet->pSqlCtx = pSqlCtx; } return pRet; } +sqlite3_recover *sqlite3_recover_init( + sqlite3* db, + const char *zDb, + const char *zUri +){ + return recoverInit(db, zDb, zUri, 0, 0); +} + +sqlite3_recover *sqlite3_recover_init_sql( + sqlite3* db, + const char *zDb, + int (*xSql)(void*, const char*), + void *pSqlCtx +){ + return recoverInit(db, zDb, "", xSql, pSqlCtx); +} + const char *sqlite3_recover_errmsg(sqlite3_recover *p){ return p ? p->zErrMsg : "not an error"; } @@ -1176,7 +1231,6 @@ static void recoverStep(sqlite3_recover *p){ if( p->dbOut==0 ){ if( recoverOpenOutput(p) ) return; - if( recoverCacheDbptr(p) ) return; if( recoverCacheSchema(p) ) return; if( recoverWriteSchema1(p) ) return; if( recoverWriteData(p) ) return; diff --git a/ext/recover/sqlite3recover.h b/ext/recover/sqlite3recover.h index 0294baddaa..5dc2a56d30 100644 --- a/ext/recover/sqlite3recover.h +++ b/ext/recover/sqlite3recover.h @@ -24,7 +24,8 @@ extern "C" { typedef struct sqlite3_recover sqlite3_recover; -/* Create an object to recover data from database zDb (e.g. "main") +/* +** Create an object to recover data from database zDb (e.g. "main") ** opened by handle db. Data will be recovered into the database ** identified by parameter zUri. Database zUri is clobbered if it ** already exists. @@ -35,6 +36,13 @@ sqlite3_recover *sqlite3_recover_init( const char *zUri ); +sqlite3_recover *sqlite3_recover_init_sql( + sqlite3* db, + const char *zDb, + int (*xSql)(void*, const char*), + void *pCtx +); + /* Details TBD. */ int sqlite3_recover_config(sqlite3_recover*, int op, void *pArg); @@ -60,6 +68,8 @@ int sqlite3_recover_config(sqlite3_recover*, int op, void *pArg); ** database, but often ends up "recovering" deleted records. ** ** SQLITE_RECOVER_ROWIDS: +** +** SQLITE_RECOVER_SQLHOOK: */ #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 0646ff78b8..acfcf8c7a3 100644 --- a/ext/recover/test_recover.c +++ b/ext/recover/test_recover.c @@ -20,8 +20,31 @@ typedef struct TestRecover TestRecover; struct TestRecover { sqlite3_recover *p; + Tcl_Interp *interp; + Tcl_Obj *pScript; }; +static int xSqlCallback(void *pSqlArg, const char *zSql){ + TestRecover *p = (TestRecover*)pSqlArg; + Tcl_Obj *pEval = 0; + int res = 0; + + pEval = Tcl_DuplicateObj(p->pScript); + Tcl_IncrRefCount(pEval); + + res = Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zSql, -1)); + if( res==TCL_OK ){ + res = Tcl_EvalObjEx(p->interp, pEval, 0); + } + + Tcl_DecrRefCount(pEval); + if( res ){ + Tcl_BackgroundError(p->interp); + return TCL_ERROR; + } + return SQLITE_OK; +} + static int getDbPointer(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite3 **pDb){ Tcl_CmdInfo info; if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(pObj), &info) ){ @@ -171,17 +194,26 @@ static int test_sqlite3_recover_init( const char *zDb = 0; const char *zUri = 0; char zCmd[128]; + int bSql = clientData ? 1 : 0; if( objc!=4 ){ - Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME URI"); + const char *zErr = (bSql ? "DB DBNAME SCRIPT" : "DB DBNAME URI"); + Tcl_WrongNumArgs(interp, 1, objv, zErr); return TCL_ERROR; } if( getDbPointer(interp, objv[1], &db) ) return TCL_ERROR; zDb = Tcl_GetString(objv[2]); - zUri = Tcl_GetString(objv[3]); pNew = ckalloc(sizeof(TestRecover)); - pNew->p = sqlite3_recover_init(db, zDb, zUri); + if( bSql==0 ){ + zUri = Tcl_GetString(objv[3]); + pNew->p = sqlite3_recover_init(db, zDb, zUri); + }else{ + pNew->interp = interp; + pNew->pScript = objv[3]; + Tcl_IncrRefCount(pNew->pScript); + pNew->p = sqlite3_recover_init_sql(db, zDb, xSqlCallback, (void*)pNew); + } sprintf(zCmd, "sqlite_recover%d", iTestRecoverCmd++); Tcl_CreateObjCommand(interp, zCmd, testRecoverCmd, (void*)pNew, 0); @@ -194,14 +226,16 @@ int TestRecover_Init(Tcl_Interp *interp){ struct Cmd { const char *zCmd; Tcl_ObjCmdProc *xProc; + void *pArg; } aCmd[] = { - { "sqlite3_recover_init", test_sqlite3_recover_init }, + { "sqlite3_recover_init", test_sqlite3_recover_init, 0 }, + { "sqlite3_recover_init_sql", test_sqlite3_recover_init, (void*)1 }, }; int i; for(i=0; izCmd, p->xProc, 0, 0); + Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, p->pArg, 0); } return TCL_OK; diff --git a/manifest b/manifest index 9f01e7c919..9244609811 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sthe\sSQLITE_RECOVER_ROWIDS\soption.\sTo\sspecify\sthat\srowid\svalues\sthat\sare\snot\salso\sexplicit\sINTEGER\sPRIMARY\sKEY\svalues\sshould\sbe\spreserved. -D 2022-09-05T15:56:09.391 +C Add\sa\smode\sto\soutput\sSQL\sstatements\sinstead\sof\spopulating\sa\sdatabase\sto\sthe\srecover\sextension. +D 2022-09-05T21:00:22.268 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -387,12 +387,12 @@ F ext/rbu/rbuvacuum4.test a78898e438a44803eb2bc897ba3323373c9f277418e2d6d76e90f2 F ext/rbu/sqlite3rbu.c 8737cabdfbee84bb25a7851ecef8b1312be332761238da9be6ddb10c62ad4291 F ext/rbu/sqlite3rbu.h 1dc88ab7bd32d0f15890ea08d23476c4198d3da3056985403991f8c9cd389812 F ext/rbu/test_rbu.c 03f6f177096a5f822d68d8e4069ad8907fe572c62ff2d19b141f59742821828a -F ext/recover/recover1.test a848af8c82fe0731af835ff99475724f8654d2f24f772cc4e6f7ec4eb2ab71ea +F ext/recover/recover1.test ae8ce9828210aa6c466bf88e23b0933849d5bb43091abe48cf2e56d636e51e93 F ext/recover/recover_common.tcl 6679af7dffc858e345053a91c9b0a897595b4a13007aceffafca75304ccb137c -F ext/recover/recoverold.test e7e00c78ec35b60488369ddf99e36a3b30e686566571969b05781e5063bdffe8 -F ext/recover/sqlite3recover.c 13e78c8a9d5521e06ebe5ac992a90169155e685f5c4b3cebc632c50b41e061c9 -F ext/recover/sqlite3recover.h ed34bc96befdf581a7de039d99772caabf0b338eb2a841d869acd9f7893ffce7 -F ext/recover/test_recover.c 9b8144ac94e6a2f3aabfcd24db6416179afdd32a3d654d1ef603c570e0384b2f +F ext/recover/recoverold.test f368a6ae2db12b6017257b332a19ab5df527f4061e43f12f5c85d8e2b236f074 +F ext/recover/sqlite3recover.c 47767b52f09fb1bba47009236285f09bcf68b6b5d1ec0de96b1a30b508e536a4 +F ext/recover/sqlite3recover.h 32f89b66f0235c0d94dfee0f1c3e9ed1ad726b3c4f3716ef0262b31cc33e8301 +F ext/recover/test_recover.c be0d74f0eba44fe7964e22d287dba0f3fa2baf197f630d51f0f9066af9b5eb2a F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15 F ext/repair/checkfreelist.c e21f06995ff4efdc1622dcceaea4dcba2caa83ca2f31a1607b98a8509168a996 F ext/repair/checkindex.c 4383e4469c21e5b9ae321d0d63cec53e981af9d7a6564be6374f0eeb93dfc890 @@ -2005,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 253e498f5200b8b3e2bc309587af108dd1cec8a884b3d2a49d5406525c9a4b4c -R e5ada01eb624b55d2f64c4994830b06d +P 69cc9aba56a196bbd159bd24868aa5ccc60bed0dc612d09ed8a3ae898f156809 +R fe94b03379801a454a2e4362e6c6eea7 U dan -Z a7e3c99975f0369d94d22f82581262dd +Z bbf845b5205dd27415c5eec6b9356a28 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index f34597368d..89f3b16ef4 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -69cc9aba56a196bbd159bd24868aa5ccc60bed0dc612d09ed8a3ae898f156809 \ No newline at end of file +73058416e7da6581000898b7988a7010e2ce6632246f4c12b4398700c7744b83 \ No newline at end of file