Index: src/test1.c ================================================================== --- src/test1.c +++ src/test1.c @@ -4947,10 +4947,51 @@ rc = sqlite3_wal_checkpoint(db, zDb); Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC); return TCL_OK; } +/* +** tclcmd: test_sqlite3_log ?SCRIPT? +*/ +static struct LogCallback { + Tcl_Interp *pInterp; + Tcl_Obj *pObj; +} logcallback = {0, 0}; +static void xLogcallback(void *unused, int err, char *zMsg){ + Tcl_Obj *pNew = Tcl_DuplicateObj(logcallback.pObj); + Tcl_IncrRefCount(pNew); + Tcl_ListObjAppendElement( + 0, pNew, Tcl_NewStringObj(sqlite3TestErrorName(err), -1) + ); + Tcl_ListObjAppendElement(0, pNew, Tcl_NewStringObj(zMsg, -1)); + Tcl_EvalObjEx(logcallback.pInterp, pNew, TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT); + Tcl_DecrRefCount(pNew); +} +static int test_sqlite3_log( + ClientData clientData, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + if( objc>2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "SCRIPT"); + return TCL_ERROR; + } + if( logcallback.pObj ){ + Tcl_DecrRefCount(logcallback.pObj); + logcallback.pObj = 0; + logcallback.pInterp = 0; + sqlite3_config(SQLITE_CONFIG_LOG, 0, 0); + } + if( objc>1 ){ + logcallback.pObj = objv[1]; + Tcl_IncrRefCount(logcallback.pObj); + logcallback.pInterp = interp; + sqlite3_config(SQLITE_CONFIG_LOG, xLogcallback, 0); + } + return TCL_OK; +} /* ** tcl_objproc COMMANDNAME ARGS... ** ** Run a TCL command using its objProc interface. Throw an error if @@ -5170,10 +5211,11 @@ { "pcache_stats", test_pcache_stats, 0 }, #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY { "sqlite3_unlock_notify", test_unlock_notify, 0 }, #endif { "sqlite3_wal_checkpoint", test_wal_checkpoint, 0 }, + { "test_sqlite3_log", test_sqlite3_log, 0 }, }; static int bitmask_size = sizeof(Bitmask)*8; int i; extern int sqlite3_sync_count, sqlite3_fullsync_count; extern int sqlite3_opentemp_count; Index: src/wal.c ================================================================== --- src/wal.c +++ src/wal.c @@ -1160,10 +1160,21 @@ */ pInfo = walCkptInfo(pWal); pInfo->nBackfill = 0; pInfo->aReadMark[0] = 0; for(i=1; iaReadMark[i] = READMARK_NOT_USED; + + /* If more than one frame was recovered from the log file, report an + ** event via sqlite3_log(). This is to help with identifying performance + ** problems caused by applications routinely shutting down without + ** checkpointing the log file. + */ + if( pWal->hdr.nPage ){ + sqlite3_log(SQLITE_OK, "Recovered %d frames from WAL file %s", + pWal->hdr.nPage, pWal->zWalName + ); + } } recovery_error: WALTRACE(("WAL%p: recovery %s\n", pWal, rc ? "failed" : "ok")); walUnlockExclusive(pWal, iLock, nLock); Index: test/wal.test ================================================================== --- test/wal.test +++ test/wal.test @@ -1447,11 +1447,11 @@ #------------------------------------------------------------------------- # Test reading and writing of databases with different page-sizes. # foreach pgsz {512 1024 2048 4096 8192 16384 32768 65536} { do_multiclient_test tn [string map [list %PGSZ% $pgsz] { - do_test e_expr-22.%PGSZ%.$tn.1 { + do_test wal-22.%PGSZ%.$tn.1 { sql1 { PRAGMA main.page_size = %PGSZ%; PRAGMA auto_vacuum = 0; PRAGMA journal_mode = WAL; CREATE TABLE t1(x UNIQUE); @@ -1458,14 +1458,54 @@ INSERT INTO t1 SELECT randomblob(800); INSERT INTO t1 SELECT randomblob(800); INSERT INTO t1 SELECT randomblob(800); } } {wal} - do_test e_expr-22.%PGSZ%.$tn.2 { sql2 { PRAGMA integrity_check } } {ok} - do_test e_expr-22.%PGSZ%.$tn.3 { + do_test wal-22.%PGSZ%.$tn.2 { sql2 { PRAGMA integrity_check } } {ok} + do_test wal-22.%PGSZ%.$tn.3 { sql1 {PRAGMA wal_checkpoint} expr {[file size test.db] % %PGSZ%} } {0} }] } + +#------------------------------------------------------------------------- +# Test that when 1 or more pages are recovered from a WAL file, +# sqlite3_log() is invoked to report this to the user. +# +set walfile [file join [pwd] test.db-wal] +catch {db close} +file delete -force test.db +do_test wal-23.1 { + faultsim_delete_and_reopen + execsql { + CREATE TABLE t1(a, b); + PRAGMA journal_mode = WAL; + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); + } + faultsim_save_and_close + + sqlite3_shutdown + test_sqlite3_log [list lappend ::log] + set ::log [list] + sqlite3 db test.db + execsql { SELECT * FROM t1 } +} {1 2 3 4} +do_test wal-23.2 { set ::log } {} + +do_test wal-23.3 { + db close + set ::log [list] + faultsim_restore_and_reopen + execsql { SELECT * FROM t1 } +} {1 2 3 4} +do_test wal-23.4 { + set ::log +} [list SQLITE_OK "Recovered 2 frames from WAL file $walfile"] + +db close +sqlite3_shutdown +test_sqlite3_log +sqlite3_initialize finish_test