Index: ext/fts5/fts5_index.c ================================================================== --- ext/fts5/fts5_index.c +++ ext/fts5/fts5_index.c @@ -1577,10 +1577,27 @@ const u8 *a = &pIter->pLeaf->p[iOff]; pIter->iLeafOffset += fts5GetPoslistSize(a, &pIter->nPos, &pIter->bDel); } } } + +static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){ + u8 *a = pIter->pLeaf->p; /* Buffer to read data from */ + int iOff = pIter->iLeafOffset; + + if( iOff>=pIter->pLeaf->n ){ + fts5SegIterNextPage(p, pIter); + if( pIter->pLeaf==0 ){ + if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT; + return; + } + iOff = 4; + a = pIter->pLeaf->p; + } + iOff += sqlite3Fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid); + pIter->iLeafOffset = iOff; +} /* ** Fts5SegIter.iLeafOffset currently points to the first byte of the ** "nSuffix" field of a term. Function parameter nKeep contains the value ** of the "nPrefix" field (if there was one - it is passed 0 if this is @@ -1604,21 +1621,13 @@ pIter->term.n = nKeep; fts5BufferAppendBlob(&p->rc, &pIter->term, nNew, &a[iOff]); iOff += nNew; pIter->iTermLeafOffset = iOff; pIter->iTermLeafPgno = pIter->iLeafPgno; - if( iOff>=pIter->pLeaf->n ){ - fts5SegIterNextPage(p, pIter); - if( pIter->pLeaf==0 ){ - if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT; - return; - } - iOff = 4; - a = pIter->pLeaf->p; - } - iOff += sqlite3Fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid); pIter->iLeafOffset = iOff; + + fts5SegIterLoadRowid(p, pIter); } /* ** Initialize the iterator object pIter to iterate through the entries in ** segment pSeg. The iterator is left pointing to the first entry when @@ -2121,10 +2130,133 @@ } fts5AssertNodeSeekOk(pNode, pTerm, nTerm, iPg, *pbDlidx); return iPg; } + +/* +** The iterator object passed as the second argument currently contains +** no valid values except for the Fts5SegIter.pLeaf member variable. This +** function searches the leaf page for a term matching (pTerm/nTerm). +** +*/ +static void fts5LeafSeek( + Fts5Index *p, /* Leave any error code here */ + int bGe, /* True for a >= search */ + Fts5SegIter *pIter, /* Iterator to seek */ + const u8 *pTerm, int nTerm /* Term to search for */ +){ + int iOff; + const u8 *a = pIter->pLeaf->p; + int n = pIter->pLeaf->n; + + int nMatch = 0; + int nKeep = 0; + int nNew = 0; + + assert( p->rc==SQLITE_OK ); + assert( pIter->pLeaf ); + + iOff = fts5GetU16(&a[2]); + if( iOff<4 || iOff>=n ){ + p->rc = FTS5_CORRUPT; + return; + } + + while( 1 ){ + int i; + int nCmp; + i64 rowid; + + /* Figure out how many new bytes are in this term */ + + nNew = a[iOff++]; + if( nNew & 0x80 ){ + iOff--; + iOff += fts5GetVarint32(&a[iOff], nNew); + } + + if( nKeep=nMatch ); + if( nKeep==nMatch ){ + nCmp = MIN(nNew, nTerm-nMatch); + for(i=0; ipTerm[nMatch] ){ + goto search_failed; + } + } + iOff += nNew; + + /* Skip past the doclist. If the end of the page is reached, bail out. */ + iOff += fts5GetVarint(&a[iOff], &rowid); + while( iOff=n ) goto search_failed; + + /* Read the nKeep field of the next term. */ + nKeep = a[iOff++]; + if( nKeep & 0x80 ){ + iOff--; + iOff += fts5GetVarint32(&a[iOff], nKeep); + } + } + + search_failed: + if( bGe==0 ){ + fts5DataRelease(pIter->pLeaf); + pIter->pLeaf = 0; + return; + }else if( iOff>=n ){ + do { + fts5SegIterNextPage(p, pIter); + if( pIter->pLeaf==0 ) return; + a = pIter->pLeaf->p; + iOff = fts5GetU16(&a[2]); + if( iOff ){ + if( iOff<4 || iOff>=n ){ + p->rc = FTS5_CORRUPT; + }else{ + nKeep = 0; + iOff += fts5GetVarint32(&a[iOff], nNew); + break; + } + } + }while( 1 ); + } + + search_success: + pIter->iLeafOffset = iOff + nNew; + pIter->iTermLeafOffset = pIter->iLeafOffset; + pIter->iTermLeafPgno = pIter->iLeafPgno; + + fts5BufferSet(&p->rc, &pIter->term, nKeep, pTerm); + fts5BufferAppendBlob(&p->rc, &pIter->term, nNew, &a[iOff]); + + fts5SegIterLoadRowid(p, pIter); + fts5SegIterLoadNPos(p, pIter); +} /* ** Initialize the object pIter to point to term pTerm/nTerm within segment ** pSeg. If there is no such term in the index, the iterator is set to EOF. ** @@ -2166,30 +2298,12 @@ } pIter->iLeafPgno = iPg - 1; fts5SegIterNextPage(p, pIter); - if( (pLeaf = pIter->pLeaf) ){ - int res; - pIter->iLeafOffset = fts5GetU16(&pLeaf->p[2]); - if( pIter->iLeafOffset<4 || pIter->iLeafOffset>=pLeaf->n ){ - p->rc = FTS5_CORRUPT; - }else{ - fts5SegIterLoadTerm(p, pIter, 0); - fts5SegIterLoadNPos(p, pIter); - do { - res = fts5BufferCompareBlob(&pIter->term, pTerm, nTerm); - if( res>=0 ) break; - fts5SegIterNext(p, pIter, 0); - }while( pIter->pLeaf && p->rc==SQLITE_OK ); - - if( bGe==0 && res ){ - /* Set iterator to point to EOF */ - fts5DataRelease(pIter->pLeaf); - pIter->pLeaf = 0; - } - } + if( pIter->pLeaf ){ + fts5LeafSeek(p, bGe, pIter, pTerm, nTerm); } if( p->rc==SQLITE_OK && bGe==0 ){ pIter->flags |= FTS5_SEGITER_ONETERM; if( pIter->pLeaf ){ Index: ext/fts5/test/fts5aa.test ================================================================== --- ext/fts5/test/fts5aa.test +++ ext/fts5/test/fts5aa.test @@ -48,11 +48,21 @@ INSERT INTO t1 VALUES('a b c', 'd e f'); } do_test 2.2 { execsql { SELECT fts5_decode(id, block) FROM t1_data WHERE id==10 } } {/{\(structure\) {lvl=0 nMerge=0 {id=[0123456789]* h=1 leaves=1..1}}}/} -do_execsql_test 2.3 { + +foreach w {a b c d e f} { + do_execsql_test 2.3.$w.asc { + SELECT rowid FROM t1 WHERE t1 MATCH $w; + } {1} + do_execsql_test 2.3.$w.desc { + SELECT rowid FROM t1 WHERE t1 MATCH $w ORDER BY rowid DESC; + } {1} +} + +do_execsql_test 2.4 { INSERT INTO t1(t1) VALUES('integrity-check'); } #------------------------------------------------------------------------- # @@ -188,12 +198,10 @@ execsql { REPLACE INTO t1(rowid,x,y,z) VALUES($rowid, $x, $y, $z) } } execsql { INSERT INTO t1(t1) VALUES('integrity-check'); } } {} } -#db eval {SELECT rowid, fts5_decode(rowid, block) aS r FROM t1_data} {puts $r} -#exit #------------------------------------------------------------------------- # reset_db do_execsql_test 8.0 { Index: ext/fts5/test/fts5ac.test ================================================================== --- ext/fts5/test/fts5ac.test +++ ext/fts5/test/fts5ac.test @@ -202,10 +202,11 @@ foreach {id x y} $data { execsql { INSERT INTO xx(rowid, x, y) VALUES($id, $x, $y) } } execsql { INSERT INTO xx(xx) VALUES('integrity-check') } } {} + #------------------------------------------------------------------------- # Test phrase queries. # foreach {tn phrase} { Index: ext/fts5/test/fts5ad.test ================================================================== --- ext/fts5/test/fts5ad.test +++ ext/fts5/test/fts5ad.test @@ -202,14 +202,15 @@ } if {$bMatch} { lappend ret $rowid } } return $ret } + foreach {bAsc sql} { - 0 {SELECT rowid FROM t1 WHERE t1 MATCH $prefix ORDER BY rowid DESC} 1 {SELECT rowid FROM t1 WHERE t1 MATCH $prefix} + 0 {SELECT rowid FROM t1 WHERE t1 MATCH $prefix ORDER BY rowid DESC} } { foreach {tn prefix} { 1 {a*} 2 {ab*} 3 {abc*} 4 {abcd*} 5 {abcde*} 6 {f*} 7 {fg*} 8 {fgh*} 9 {fghi*} 10 {fghij*} 11 {k*} 12 {kl*} 13 {klm*} 14 {klmn*} 15 {klmno*} Index: ext/fts5/test/fts5alter.test ================================================================== --- ext/fts5/test/fts5alter.test +++ ext/fts5/test/fts5alter.test @@ -79,8 +79,25 @@ do_execsql_test 2.3 { ROLLBACK; SELECT rowid FROM yy WHERE yy MATCH 'a + b + c'; } {-56 -22} +#------------------------------------------------------------------------- + +do_execsql_test 3.1 { + CREATE VIRTUAL TABLE abc USING fts5(a); + INSERT INTO abc(rowid, a) VALUES(1, 'a'); + BEGIN; + INSERT INTO abc(rowid, a) VALUES(2, 'a'); +} +breakpoint +do_execsql_test 3.2 { + SELECT rowid FROM abc WHERE abc MATCH 'a'; +} {1 2} + +do_execsql_test 3.3 { + COMMIT; + SELECT rowid FROM abc WHERE abc MATCH 'a'; +} {1 2} finish_test