Index: ext/fts5/fts5_index.c ================================================================== --- ext/fts5/fts5_index.c +++ ext/fts5/fts5_index.c @@ -706,29 +706,32 @@ pConfig->zDb, p->zDataTbl, "block", iRowid, 0, &p->pReader ); } if( rc==SQLITE_OK ){ + u8 *aOut; /* Read blob data into this buffer */ int nByte = sqlite3_blob_bytes(p->pReader); if( pBuf ){ fts5BufferZero(pBuf); - if( SQLITE_OK==fts5BufferGrow(&rc, pBuf, nByte) ){ - rc = sqlite3_blob_read(p->pReader, pBuf->p, nByte, 0); - if( rc==SQLITE_OK ) pBuf->n = nByte; - } + fts5BufferGrow(&rc, pBuf, nByte); + aOut = pBuf->p; + pBuf->n = nByte; }else{ - pRet = (Fts5Data*)fts5IdxMalloc(p, sizeof(Fts5Data) + nByte); - if( !pRet ) return 0; - - pRet->n = nByte; - pRet->p = (u8*)&pRet[1]; - pRet->nRef = 1; - rc = sqlite3_blob_read(p->pReader, pRet->p, nByte, 0); - if( rc!=SQLITE_OK ){ - sqlite3_free(pRet); - pRet = 0; - } + pRet = (Fts5Data*)sqlite3Fts5MallocZero(&rc, nByte+sizeof(Fts5Data)); + if( pRet ){ + pRet->n = nByte; + aOut = pRet->p = (u8*)&pRet[1]; + pRet->nRef = 1; + } + } + + if( rc==SQLITE_OK ){ + rc = sqlite3_blob_read(p->pReader, aOut, nByte, 0); + } + if( rc!=SQLITE_OK ){ + sqlite3_free(pRet); + pRet = 0; } } p->rc = rc; p->nRead++; } @@ -2979,10 +2982,11 @@ if( iLvl==pStruct->nLevel-1 ){ fts5StructureAddLevel(&p->rc, ppStruct); pStruct = *ppStruct; } fts5StructureExtendLevel(&p->rc, pStruct, iLvl+1, 1, 0); + if( p->rc ) return; pLvl = &pStruct->aLevel[iLvl]; pLvlOut = &pStruct->aLevel[iLvl+1]; fts5WriteInit(p, &writer, iIdx, iSegid); Index: ext/fts5/test/fts5aa.test ================================================================== --- ext/fts5/test/fts5aa.test +++ ext/fts5/test/fts5aa.test @@ -19,12 +19,10 @@ ifcapable !fts5 { finish_test return } -if 0 { - do_execsql_test 1.0 { CREATE VIRTUAL TABLE t1 USING fts5(a, b, c); SELECT name, sql FROM sqlite_master; } { t1 {CREATE VIRTUAL TABLE t1 USING fts5(a, b, c)} @@ -300,12 +298,10 @@ do_test 12.3 { set res [db one { SELECT t2 FROM t2 WHERE t2 MATCH '* reads ' }] string is integer $res } {1} -} - #------------------------------------------------------------------------- # reset_db do_execsql_test 13.1 { CREATE VIRTUAL TABLE t1 USING fts5(x); Index: ext/fts5/test/fts5fault1.test ================================================================== --- ext/fts5/test/fts5fault1.test +++ ext/fts5/test/fts5fault1.test @@ -27,10 +27,11 @@ # 1: CREATE VIRTUAL TABLE # 2: INSERT statement # 3: DELETE statement # 4: MATCH expressions # +# if 1 { faultsim_save_and_close do_faultsim_test 1 -prep { @@ -68,12 +69,10 @@ execsql { DELETE FROM t1 } } -test { faultsim_test_result {0 {}} } -} - reset_db do_execsql_test 4.0 { CREATE VIRTUAL TABLE t2 USING fts5(a, b); INSERT INTO t2 VALUES('m f a jj th q jr ar', 'hj n h h sg j i m'); INSERT INTO t2 VALUES('nr s t g od j kf h', 'sb h aq rg op rb n nl'); @@ -105,8 +104,119 @@ execsql { SELECT rowid FROM t2 WHERE t2 MATCH '$expr' } " -test " faultsim_test_result {[list 0 $res]} " } + +#------------------------------------------------------------------------- +# The following tests use a larger database populated with random data. +# +# The database page size is set to 512 bytes and the FTS5 page size left +# at the default 1000 bytes. This means that reading a node may require +# pulling an overflow page from disk, which is an extra opportunity for +# an error to occur. +# +reset_db +do_execsql_test 5.0.1 { + PRAGMA main.page_size = 512; + CREATE VIRTUAL TABLE x1 USING fts5(a, b); + PRAGMA main.page_size; +} {512} + +proc rnddoc {n} { + set map [list 0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j] + set doc [list] + for {set i 0} {$i < $n} {incr i} { + lappend doc [string map $map [format %.3d [expr int(rand()*1000)]]] + } + set doc +} +db func rnddoc rnddoc + +do_execsql_test 5.0.2 { + WITH r(a, b) AS ( + SELECT rnddoc(6), rnddoc(6) UNION ALL + SELECT rnddoc(6), rnddoc(6) FROM r + ) + INSERT INTO x1 SELECT * FROM r LIMIT 10000; +} + +set res [db one { + SELECT count(*) FROM x1 WHERE x1.a LIKE '%abc%' OR x1.b LIKE '%abc%'} +] + +do_faultsim_test 5.1 -faults oom* -body { + execsql { SELECT count(*) FROM x1 WHERE x1 MATCH 'abc' } +} -test { + faultsim_test_result [list 0 $::res] +} +do_faultsim_test 5.2 -faults oom* -body { + execsql { SELECT count(*) FROM x1 WHERE x1 MATCH 'abcd' } +} -test { + faultsim_test_result [list 0 0] +} + +proc test_astar {a b} { + return [expr { [regexp {a[^ ][^ ]} $a] || [regexp {a[^ ][^ ]} $b] }] +} +db func test_astar test_astar + +set res [db one { SELECT count(*) FROM x1 WHERE test_astar(a, b) } ] +do_faultsim_test 5.3 -faults oom* -body { + execsql { SELECT count(*) FROM x1 WHERE x1 MATCH 'a*' } +} -test { + faultsim_test_result [list 0 $::res] +} + +do_faultsim_test 5.4 -faults oom* -prep { + db close + sqlite3 db test.db +} -body { + execsql { INSERT INTO x1 VALUES('a b c d', 'e f g h') } +} -test { + faultsim_test_result [list 0 {}] +} + +} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 6.0 { + CREATE VIRTUAL TABLE x1 USING fts5(x); + INSERT INTO x1(x1, rank) VALUES('automerge', 0); + + INSERT INTO x1 VALUES('a b c'); -- 1 + INSERT INTO x1 VALUES('a b c'); -- 2 + INSERT INTO x1 VALUES('a b c'); -- 3 + INSERT INTO x1 VALUES('a b c'); -- 4 + INSERT INTO x1 VALUES('a b c'); -- 5 + INSERT INTO x1 VALUES('a b c'); -- 6 + INSERT INTO x1 VALUES('a b c'); -- 7 + INSERT INTO x1 VALUES('a b c'); -- 8 + INSERT INTO x1 VALUES('a b c'); -- 9 + INSERT INTO x1 VALUES('a b c'); -- 10 + INSERT INTO x1 VALUES('a b c'); -- 11 + INSERT INTO x1 VALUES('a b c'); -- 12 + INSERT INTO x1 VALUES('a b c'); -- 13 + INSERT INTO x1 VALUES('a b c'); -- 14 + INSERT INTO x1 VALUES('a b c'); -- 15 + + SELECT count(*) FROM x1_data; +} {17} + +faultsim_save_and_close + +do_faultsim_test 6.1 -faults oom-tr* -prep { + faultsim_restore_and_reopen +} -body { + execsql { INSERT INTO x1 VALUES('d e f') } +} -test { + faultsim_test_result [list 0 {}] + if {$testrc==0} { + set nCnt [db one {SELECT count(*) FROM x1_data}] + if {$nCnt!=3} { error "expected 3 entries but there are $nCnt" } + } +} finish_test