Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -7,11 +7,11 @@ ** 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. ** ************************************************************************* -** $Id: btree.c,v 1.473 2008/07/08 19:34:07 drh Exp $ +** $Id: btree.c,v 1.474 2008/07/09 11:49:47 danielk1977 Exp $ ** ** This file implements a external (disk-based) database using BTrees. ** See the header comment on "btreeInt.h" for additional information. ** Including a description of file format and an overview of operation. */ @@ -665,11 +665,11 @@ ** Defragment the page given. All Cells are moved to the ** end of the page and all free space is collected into one ** big FreeBlk that occurs in between the header and cell ** pointer array and the cell content area. */ -static int defragmentPage(MemPage *pPage){ +static void defragmentPage(MemPage *pPage){ int i; /* Loop counter */ int pc; /* Address of a i-th cell */ int addr; /* Offset of first byte after cell pointer array */ int hdr; /* Offset to the page header */ int size; /* Size of a cell */ @@ -710,11 +710,10 @@ data[hdr+1] = 0; data[hdr+2] = 0; data[hdr+7] = 0; addr = cellOffset+2*nCell; memset(&data[addr], 0, brk-addr); - return SQLITE_OK; } /* ** Allocate nByte bytes of space on a page. ** @@ -771,11 +770,11 @@ */ top = get2byte(&data[hdr+5]); nCell = get2byte(&data[hdr+3]); cellOffset = pPage->cellOffset; if( nFrag>=60 || cellOffset + 2*nCell > top - nByte ){ - if( defragmentPage(pPage) ) return 0; + defragmentPage(pPage); top = get2byte(&data[hdr+5]); } top -= nByte; assert( cellOffset + 2*nCell <= top ); put2byte(&data[hdr+5], top); @@ -4643,12 +4642,11 @@ top = get2byte(&data[hdr+5]); cellOffset = pPage->cellOffset; end = cellOffset + 2*pPage->nCell + 2; ins = cellOffset + 2*i; if( end > top - sz ){ - rc = defragmentPage(pPage); - if( rc!=SQLITE_OK ) return rc; + defragmentPage(pPage); top = get2byte(&data[hdr+5]); assert( end + sz <= top ); } idx = allocateSpace(pPage, sz); assert( idx>0 ); @@ -4796,23 +4794,28 @@ sqlite3PagerRef(pParent->pDbPage); /* pPage is currently the right-child of pParent. Change this ** so that the right-child is the new page allocated above and ** pPage is the next-to-right child. + ** + ** Ignore the return value of the call to fillInCell(). fillInCell() + ** may only return other than SQLITE_OK if it is required to allocate + ** one or more overflow pages. Since an internal table B-Tree cell + ** may never spill over onto an overflow page (it is a maximum of + ** 13 bytes in size), it is not neccessary to check the return code. + ** + ** Similarly, the insertCell() function cannot fail if the page + ** being inserted into is already writable and the cell does not + ** contain an overflow pointer. So ignore this return code too. */ assert( pPage->nCell>0 ); pCell = findCell(pPage, pPage->nCell-1); sqlite3BtreeParseCellPtr(pPage, pCell, &info); - rc = fillInCell(pParent, parentCell, 0, info.nKey, 0, 0, 0, &parentSize); - if( rc!=SQLITE_OK ){ - return rc; - } + fillInCell(pParent, parentCell, 0, info.nKey, 0, 0, 0, &parentSize); assert( parentSize<64 ); - rc = insertCell(pParent, parentIdx, parentCell, parentSize, 0, 4); - if( rc!=SQLITE_OK ){ - return rc; - } + assert( sqlite3PagerIswriteable(pParent->pDbPage) ); + insertCell(pParent, parentIdx, parentCell, parentSize, 0, 4); put4byte(findOverflowCell(pParent,parentIdx), pPage->pgno); put4byte(&pParent->aData[pParent->hdrOffset+8], pgnoNew); #ifndef SQLITE_OMIT_AUTOVACUUM /* If this is an auto-vacuum database, update the pointer map @@ -4916,10 +4919,11 @@ pParent = pPage->pParent; assert( pParent ); if( SQLITE_OK!=(rc = sqlite3PagerWrite(pParent->pDbPage)) ){ return rc; } + TRACE(("BALANCE: begin page %d child of %d\n", pPage->pgno, pParent->pgno)); #ifndef SQLITE_OMIT_QUICKBALANCE /* ** A special case: If a new entry has just been inserted into a @@ -5597,11 +5601,11 @@ rc = ptrmapPut(pBt, pChild->pgno, PTRMAP_BTREE, pPage->pgno); if( rc ) goto balancedeeper_out; for(i=0; inCell; i++){ rc = ptrmapPutOvfl(pChild, i); if( rc!=SQLITE_OK ){ - return rc; + goto balancedeeper_out; } } } #endif rc = balance_nonroot(pChild); Index: test/ioerr.test ================================================================== --- test/ioerr.test +++ test/ioerr.test @@ -13,11 +13,11 @@ # such as writes failing because the disk is full. # # The tests in this file use special facilities that are only # available in the SQLite test fixture. # -# $Id: ioerr.test,v 1.39 2008/07/08 17:13:59 danielk1977 Exp $ +# $Id: ioerr.test,v 1.40 2008/07/09 11:49:48 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl # If SQLITE_DEFAULT_AUTOVACUUM is set to true, then a simulated IO error @@ -326,8 +326,83 @@ db eval { INSERT INTO t1 VALUES(randomblob(1100)); } } -tclbody { db eval { INSERT INTO t1 VALUES(randomblob(2000)); } } sqlite3_simulate_device -char {} -sectorsize 0 +catch {db close} + +do_ioerr_test ioerr-13 -ckrefcount true -erc 1 -sqlprep { + PRAGMA auto_vacuum = incremental; + CREATE TABLE t1(x); + CREATE TABLE t2(x); + INSERT INTO t2 VALUES(randomblob(1500)); + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + INSERT INTO t1 VALUES(randomblob(20)); + INSERT INTO t1 SELECT x FROM t1; + INSERT INTO t1 SELECT x FROM t1; + INSERT INTO t1 SELECT x FROM t1; + INSERT INTO t1 SELECT x FROM t1; + INSERT INTO t1 SELECT x FROM t1; + INSERT INTO t1 SELECT x FROM t1; /* 64 entries in t1 */ + INSERT INTO t1 SELECT x FROM t1 LIMIT 14; /* 78 entries in t1 */ + DELETE FROM t2 WHERE rowid = 3; +} -sqlbody { + -- This statement uses the balance_quick() optimization. The new page + -- is appended to the database file. But the overflow page used by + -- the new record will be positioned near the start of the database + -- file, in the gap left by the "DELETE FROM t2 WHERE rowid=3" statement + -- above. + -- + -- The point of this is that the statement wil need to update two pointer + -- map pages. Which introduces another opportunity for an IO error. + -- + INSERT INTO t1 VALUES(randomblob(2000)); +} + +do_ioerr_test ioerr-14 -ckrefcount true -erc 1 -sqlprep { + PRAGMA auto_vacuum = incremental; + CREATE TABLE t1(x); + CREATE TABLE t2(x); + INSERT INTO t2 VALUES(randomblob(1500)); + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + INSERT INTO t2 SELECT randomblob(1500) FROM t2; + + -- This statement inserts a row into t1 with an overflow page at the + -- end of the file. A long way from its parent (the root of t1). + INSERT INTO t1 VALUES(randomblob(1500)); + DELETE FROM t2 WHERE rowid<10; +} -sqlbody { + -- This transaction will cause the root-page of table t1 to divide + -- (by calling balance_deeper()). When it does, the "parent" page of the + -- overflow page inserted in the -sqlprep block above will change and + -- the corresponding pointer map page be updated. This test case attempts + -- to cause an IO error during the pointer map page update. + -- + BEGIN; + INSERT INTO t1 VALUES(randomblob(100)); + INSERT INTO t1 VALUES(randomblob(100)); + INSERT INTO t1 VALUES(randomblob(100)); + INSERT INTO t1 VALUES(randomblob(100)); + INSERT INTO t1 VALUES(randomblob(100)); + INSERT INTO t1 VALUES(randomblob(100)); + INSERT INTO t1 VALUES(randomblob(100)); + INSERT INTO t1 VALUES(randomblob(100)); + INSERT INTO t1 VALUES(randomblob(100)); + INSERT INTO t1 VALUES(randomblob(100)); + COMMIT; +} finish_test