I found a bug in latest (3.7.14.1) with a very specific #define that can causes
a database corruption after truncation because of missing backup pages.
If you specify this define:
#define SQLITE_OMIT_AUTOVACUUM
And then execute a VACUUM operation that shrinks the database, due to the
#ifndef at sqlite3.c:42830
The code that would otherwise proceed to backup pages before truncation will
not be executed.
Even the comment is wrong, it CAN and WILL happen for a normal vacuum too, not
just in auto vacuum.
/* If this transaction has made the database smaller, then all pages
** being discarded by the truncation must be written to the journal
** file. This can only happen in auto-vacuum mode.
**
** Before reading the pages with page numbers larger than the
** current value of Pager.dbSize, set dbSize back to the value
** that it took at the start of the transaction. Otherwise, the
** calls to sqlite3PagerGet() return zeroed pages instead of
** reading data from the database file.
*/
So here is my fix... (just removed the #ifndef)
/* If this transaction has made the database smaller, then all pages
** being discarded by the truncation must be written to the journal
** file. This can happen in auto-vacuum mode and during a normal
** vacuum operation.
**
** Before reading the pages with page numbers larger than the
** current value of Pager.dbSize, set dbSize back to the value
** that it took at the start of the transaction. Otherwise, the
** calls to sqlite3PagerGet() return zeroed pages instead of
** reading data from the database file.
*/
if( pPager->dbSize<pPager->dbOrigSize
&& pPager->journalMode!=PAGER_JOURNALMODE_OFF
){
Pgno i; /* Iterator variable */
const Pgno iSkip = PAGER_MJ_PGNO(pPager); /* Pending lock page */
const Pgno dbSize = pPager->dbSize; /* Database image size */
pPager->dbSize = pPager->dbOrigSize;
for( i=dbSize+1; i<=pPager->dbOrigSize; i++ ){
if( !sqlite3BitvecTest(pPager->pInJournal, i) && i!=iSkip ){
PgHdr *pPage; /* Page to journal */
rc = sqlite3PagerGet(pPager, i, &pPage);
if( rc!=SQLITE_OK ) goto commit_phase_one_exit;
rc = sqlite3PagerWrite(pPage);
sqlite3PagerUnref(pPage);
if( rc!=SQLITE_OK ) goto commit_phase_one_exit;
}
}
pPager->dbSize = dbSize;
}
You can test it with a really simple application that I included in
attachment...
You just have to put a breakpoint at sqlite3.c:56747 and step over it and then
restart the app right there.
The next integrity_check will fail completely :)
Thanks
Danny Couture
Technical Architect
Ubisoft Montreal
#include "stdafx.h"
//DON'T FORGET TO COMPILE SQLITE WITH #define SQLITE_OMIT_AUTOVACUUM
#include "sqlite3.h"
int callback(void *, int argc, char ** argv, char ** x)
{
for (int i = 0; i < argc; ++i)
printf("%s, ", argv[i]);
printf("\n");
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
sqlite3 * db;
sqlite3_open("test.db", &db);
char * errorMsg;
sqlite3_exec(db, "PRAGMA integrity_check", callback, 0, &errorMsg);
sqlite3_exec(db, "CREATE TABLE test (Key INT, Test DOUBLE, Text
VARCHAR(1024))", callback, 0, &errorMsg);
sqlite3_exec(db, "BEGIN", callback, 0, &errorMsg);
//add some stuff
char query[1024];
for (int i = 0; i < 100000; ++i)
{
sprintf_s(query, "INSERT INTO test VALUES(%d, %d.5, \"%d\")", i, i, i);
sqlite3_exec(db, query, callback, 0, &errorMsg);
}
sqlite3_exec(db, "COMMIT", callback, 0, &errorMsg);
//remove some of the stuff so the vacuum shrinks the DB
sqlite3_exec(db, "DELETE FROM test WHERE Key > 5000", callback, 0,
&errorMsg);
//add a breakpoint at sqlite3.c:58390 and restart the application right
there.
//the next integrity_check will fail completely...
sqlite3_exec(db, "VACUUM", callback, 0, &errorMsg);
return 0;
}
_______________________________________________
sqlite-users mailing list
[email protected]
http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-users