From: dan Date: Wed, 2 Feb 2011 17:30:43 +0000 (+0000) Subject: Optimize handling of equality and range constraints on the "term" column of an fts4au... X-Git-Tag: version-3.7.6~173 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=20d5f354e99304c9a5cd4777b0289c83c9234dd6;p=thirdparty%2Fsqlite.git Optimize handling of equality and range constraints on the "term" column of an fts4aux table. FossilOrigin-Name: 386701ded2bcee5309ec9f285d94e6eb1eade193 --- diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c index c8f8d6ba0c..4e8c396aa3 100644 --- a/ext/fts3/fts3.c +++ b/ext/fts3/fts3.c @@ -2052,6 +2052,7 @@ int sqlite3Fts3SegReaderCursor( const char *zTerm, /* Term to query for */ int nTerm, /* Size of zTerm in bytes */ int isPrefix, /* True for a prefix search */ + int isScan, /* True to scan from zTerm to EOF */ Fts3SegReaderCursor *pCsr /* Cursor object to populate */ ){ int rc = SQLITE_OK; @@ -2067,11 +2068,14 @@ int sqlite3Fts3SegReaderCursor( assert( FTS3_SEGCURSOR_PENDING<0 ); assert( FTS3_SEGCURSOR_ALL<0 ); assert( iLevel==FTS3_SEGCURSOR_ALL || (zTerm==0 && isPrefix==1) ); + assert( isPrefix==0 || isScan==0 ); + memset(pCsr, 0, sizeof(Fts3SegReaderCursor)); /* If iLevel is less than 0, include a seg-reader for the pending-terms. */ - if( iLevel<0 ){ + assert( isScan==0 || fts3HashCount(&p->pendingTerms)==0 ); + if( iLevel<0 && isScan==0 ){ rc = sqlite3Fts3SegReaderPending(p, zTerm, nTerm, isPrefix, &pPending); if( rc==SQLITE_OK && pPending ){ int nByte = (sizeof(Fts3SegReader *) * 16); @@ -2117,9 +2121,9 @@ int sqlite3Fts3SegReaderCursor( sqlite3_int64 *pi = (isPrefix ? &iLeavesEndBlock : 0); rc = fts3SelectLeaf(p, zTerm, nTerm, zRoot, nRoot, &iStartBlock, pi); if( rc!=SQLITE_OK ) goto finished; - if( isPrefix==0 ) iLeavesEndBlock = iStartBlock; + if( isPrefix==0 && isScan==0 ) iLeavesEndBlock = iStartBlock; } - + rc = sqlite3Fts3SegReaderNew(iAge, iStartBlock, iLeavesEndBlock, iEndBlock, zRoot, nRoot, &pCsr->apSegment[pCsr->nSegment] ); @@ -2154,7 +2158,7 @@ static int fts3TermSegReaderCursor( int i; int nCost = 0; rc = sqlite3Fts3SegReaderCursor( - p, FTS3_SEGCURSOR_ALL, zTerm, nTerm, isPrefix, pSegcsr); + p, FTS3_SEGCURSOR_ALL, zTerm, nTerm, isPrefix, 0, pSegcsr); for(i=0; rc==SQLITE_OK && inSegment; i++){ rc = sqlite3Fts3SegReaderCost(pCsr, pSegcsr->apSegment[i], &nCost); diff --git a/ext/fts3/fts3Int.h b/ext/fts3/fts3Int.h index 82413e1fd1..b3f1ab55b1 100644 --- a/ext/fts3/fts3Int.h +++ b/ext/fts3/fts3Int.h @@ -310,13 +310,14 @@ int sqlite3Fts3SegReaderStart(Fts3Table*, Fts3SegReaderCursor*, Fts3SegFilter*); int sqlite3Fts3SegReaderStep(Fts3Table *, Fts3SegReaderCursor *); void sqlite3Fts3SegReaderFinish(Fts3SegReaderCursor *); int sqlite3Fts3SegReaderCursor( - Fts3Table *, int, const char *, int, int, Fts3SegReaderCursor *); + Fts3Table *, int, const char *, int, int, int, Fts3SegReaderCursor *); /* Flags allowed as part of the 4th argument to SegmentReaderIterate() */ #define FTS3_SEGMENT_REQUIRE_POS 0x00000001 #define FTS3_SEGMENT_IGNORE_EMPTY 0x00000002 #define FTS3_SEGMENT_COLUMN_FILTER 0x00000004 #define FTS3_SEGMENT_PREFIX 0x00000008 +#define FTS3_SEGMENT_SCAN 0x00000010 /* Type passed as 4th argument to SegmentReaderIterate() */ struct Fts3SegFilter { diff --git a/ext/fts3/fts3_aux.c b/ext/fts3/fts3_aux.c index 9d833c5c6a..cb18079b2a 100644 --- a/ext/fts3/fts3_aux.c +++ b/ext/fts3/fts3_aux.c @@ -28,11 +28,11 @@ struct Fts3auxTable { struct Fts3auxCursor { sqlite3_vtab_cursor base; /* Base class used by SQLite core */ - + Fts3SegReaderCursor csr; /* Must be right after "base" */ Fts3SegFilter filter; - Fts3SegReaderCursor csr; + char *zStop; + int nStop; int isEof; - sqlite3_int64 iRowid; sqlite3_int64 nDoc; sqlite3_int64 nOcc; @@ -114,6 +114,10 @@ static int fts3auxDisconnectMethod(sqlite3_vtab *pVtab){ return SQLITE_OK; } +#define FTS4AUX_EQ_CONSTRAINT 1 +#define FTS4AUX_GE_CONSTRAINT 2 +#define FTS4AUX_LE_CONSTRAINT 4 + /* ** xBestIndex - Analyze a WHERE and ORDER BY clause. */ @@ -121,6 +125,10 @@ static int fts3auxBestIndexMethod( sqlite3_vtab *pVTab, sqlite3_index_info *pInfo ){ + int i; + int iEq = -1; + int iGe = -1; + int iLe = -1; /* This vtab delivers always results in "ORDER BY term ASC" order. */ if( pInfo->nOrderBy==1 @@ -130,6 +138,33 @@ static int fts3auxBestIndexMethod( pInfo->orderByConsumed = 1; } + /* Search for equality and range constraints on the "term" column. */ + for(i=0; inConstraint; i++){ + if( pInfo->aConstraint[i].usable && pInfo->aConstraint[i].iColumn==0 ){ + int op = pInfo->aConstraint[i].op; + if( op==SQLITE_INDEX_CONSTRAINT_EQ ) iEq = i; + if( op==SQLITE_INDEX_CONSTRAINT_LT ) iLe = i; + if( op==SQLITE_INDEX_CONSTRAINT_LE ) iLe = i; + if( op==SQLITE_INDEX_CONSTRAINT_GT ) iGe = i; + if( op==SQLITE_INDEX_CONSTRAINT_GE ) iGe = i; + } + } + + if( iEq>=0 ){ + pInfo->idxNum = FTS4AUX_EQ_CONSTRAINT; + pInfo->aConstraintUsage[iEq].argvIndex = 1; + }else{ + pInfo->idxNum = 0; + if( iGe>=0 ){ + pInfo->idxNum += FTS4AUX_GE_CONSTRAINT; + pInfo->aConstraintUsage[iGe].argvIndex = 1; + } + if( iLe>=0 ){ + pInfo->idxNum += FTS4AUX_LE_CONSTRAINT; + pInfo->aConstraintUsage[iLe].argvIndex = 1 + (iGe>=0); + } + } + pInfo->estimatedCost = 20000; return SQLITE_OK; } @@ -157,6 +192,8 @@ static int fts3auxCloseMethod(sqlite3_vtab_cursor *pCursor){ sqlite3Fts3SegmentsClose(pFts3); sqlite3Fts3SegReaderFinish(&pCsr->csr); + sqlite3_free((void *)pCsr->filter.zTerm); + sqlite3_free(pCsr->zStop); sqlite3_free(pCsr); return SQLITE_OK; } @@ -176,6 +213,15 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){ int nDoclist = pCsr->csr.nDoclist; char *aDoclist = pCsr->csr.aDoclist; + if( pCsr->zStop ){ + int n = (pCsr->nStopcsr.nTerm) ? pCsr->nStop : pCsr->csr.nTerm; + int mc = memcmp(pCsr->zStop, pCsr->csr.zTerm, n); + if( mc<0 || (mc==0 && pCsr->csr.nTerm>pCsr->nStop) ){ + pCsr->isEof = 1; + return SQLITE_OK; + } + } + /* Now count the number of documents and positions in the doclist ** in pCsr->csr.aDoclist[]. Store the number of documents in pCsr->nDoc ** and the number of occurrences in pCsr->nOcc. */ @@ -216,14 +262,42 @@ static int fts3auxFilterMethod( Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor; Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab; int rc; + int isScan; + assert( idxStr==0 ); + assert( idxNum==FTS4AUX_EQ_CONSTRAINT || idxNum==0 + || idxNum==FTS4AUX_LE_CONSTRAINT || idxNum==FTS4AUX_GE_CONSTRAINT + || idxNum==(FTS4AUX_LE_CONSTRAINT|FTS4AUX_GE_CONSTRAINT) + ); + isScan = (idxNum!=FTS4AUX_EQ_CONSTRAINT); + + /* In case this cursor is being reused, close and zero it. */ + testcase(pCsr->filter.zTerm); sqlite3Fts3SegReaderFinish(&pCsr->csr); - memset(&pCsr->csr, 0, sizeof(Fts3SegReaderCursor)); - pCsr->isEof = 0; - pCsr->iRowid = 0; + sqlite3_free((void *)pCsr->filter.zTerm); + memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr); + pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY; + if( isScan ) pCsr->filter.flags |= FTS3_SEGMENT_SCAN; + + if( idxNum&(FTS4AUX_EQ_CONSTRAINT|FTS4AUX_GE_CONSTRAINT) ){ + const char *zStr = sqlite3_value_text(apVal[0]); + if( zStr ){ + pCsr->filter.zTerm = sqlite3_mprintf("%s", zStr); + pCsr->filter.nTerm = sqlite3_value_bytes(apVal[0]); + if( pCsr->filter.zTerm==0 ) return SQLITE_NOMEM; + } + } + if( idxNum&FTS4AUX_LE_CONSTRAINT ){ + int iIdx = (idxNum&FTS4AUX_GE_CONSTRAINT) ? 1 : 0; + pCsr->zStop = sqlite3_mprintf("%s", sqlite3_value_text(apVal[iIdx])); + pCsr->nStop = sqlite3_value_bytes(apVal[iIdx]); + if( pCsr->zStop==0 ) return SQLITE_NOMEM; + } - rc = sqlite3Fts3SegReaderCursor(pFts3, FTS3_SEGCURSOR_ALL, 0, 0,1,&pCsr->csr); + rc = sqlite3Fts3SegReaderCursor(pFts3, FTS3_SEGCURSOR_ALL, + pCsr->filter.zTerm, pCsr->filter.nTerm, 0, isScan, &pCsr->csr + ); if( rc==SQLITE_OK ){ rc = sqlite3Fts3SegReaderStart(pFts3, &pCsr->csr, &pCsr->filter); } diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c index 00b389d88f..7c440cf97d 100644 --- a/ext/fts3/fts3_write.c +++ b/ext/fts3/fts3_write.c @@ -2066,6 +2066,7 @@ int sqlite3Fts3SegReaderStep( int isRequirePos = (pCsr->pFilter->flags & FTS3_SEGMENT_REQUIRE_POS); int isColFilter = (pCsr->pFilter->flags & FTS3_SEGMENT_COLUMN_FILTER); int isPrefix = (pCsr->pFilter->flags & FTS3_SEGMENT_PREFIX); + int isScan = (pCsr->pFilter->flags & FTS3_SEGMENT_SCAN); Fts3SegReader **apSegment = pCsr->apSegment; int nSegment = pCsr->nSegment; @@ -2101,7 +2102,7 @@ int sqlite3Fts3SegReaderStep( ** Similarly, if this is a search for an exact match, and the first term ** of segment apSegment[0] is not a match, exit early. */ - if( pFilter->zTerm ){ + if( pFilter->zTerm && !isScan ){ if( pCsr->nTermnTerm || (!isPrefix && pCsr->nTerm>pFilter->nTerm) || memcmp(pCsr->zTerm, pFilter->zTerm, pFilter->nTerm) @@ -2228,7 +2229,7 @@ static int fts3SegmentMerge(Fts3Table *p, int iLevel){ Fts3SegFilter filter; /* Segment term filter condition */ Fts3SegReaderCursor csr; /* Cursor to iterate through level(s) */ - rc = sqlite3Fts3SegReaderCursor(p, iLevel, 0, 0, 1, &csr); + rc = sqlite3Fts3SegReaderCursor(p, iLevel, 0, 0, 1, 0, &csr); if( rc!=SQLITE_OK || csr.nSegment==0 ) goto finished; if( iLevel==FTS3_SEGCURSOR_ALL ){ diff --git a/manifest b/manifest index 77c138220f..08a9b4f3fa 100644 --- a/manifest +++ b/manifest @@ -1,8 +1,5 @@ ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA1 - -C Merge\sin\sthe\sblocking-checkpoint\senhancement,\sincluding\sthe\snew\nsqlite3_wal_checkpoint_v2()\sinterface\sand\sthe\nPRAGMA\swal_checkpoint(full)\sstatement. -D 2011-02-02T16:34:08.739 +C Optimize\shandling\sof\sequality\sand\srange\sconstraints\son\sthe\s"term"\scolumn\sof\san\sfts4aux\stable. +D 2011-02-02T17:30:43.438 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in de6498556d536ae60bb8bb10e8c1ba011448658c F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -64,10 +61,10 @@ F ext/fts2/mkfts2amal.tcl 974d5d438cb3f7c4a652639262f82418c1e4cff0 F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a F ext/fts3/README.tokenizers 998756696647400de63d5ba60e9655036cb966e9 F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts3/fts3.c 871600c06569af007c9f7d46786a6a8618140862 +F ext/fts3/fts3.c f93f5d614e92b0221e954ea3bd800f11f6f00191 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe -F ext/fts3/fts3Int.h d833c1df0dc9d1b3c9069981001e6db16bf241ce -F ext/fts3/fts3_aux.c d7f99095879d68b3b14b48a5370d9c6188566ecc +F ext/fts3/fts3Int.h 945926ea4b6a686c3e9834640a252d9870b7191e +F ext/fts3/fts3_aux.c 03bc785b04965325ac48a6a6fb0e7babb7751298 F ext/fts3/fts3_expr.c 5f49e0deaf723724b08100bb3ff40aab02ad0c93 F ext/fts3/fts3_hash.c 3c8f6387a4a7f5305588b203fa7c887d753e1f1c F ext/fts3/fts3_hash.h 8331fb2206c609f9fc4c4735b9ab5ad6137c88ec @@ -77,7 +74,7 @@ F ext/fts3/fts3_snippet.c bfefb42b5debf8725a30fd5122572aaeedc99397 F ext/fts3/fts3_tokenizer.c 055f3dc7369585350b28db1ee0f3b214dca6724d F ext/fts3/fts3_tokenizer.h 13ffd9fcb397fec32a05ef5cd9e0fa659bf3dbd3 F ext/fts3/fts3_tokenizer1.c 6e5cbaa588924ac578263a598e4fb9f5c9bb179d -F ext/fts3/fts3_write.c 58a4d891bd438ee279e82be7f2188b1885f56873 +F ext/fts3/fts3_write.c 8cf14b0e1eee656cda0197e99754f980c216dd9a F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100 F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9 @@ -441,7 +438,7 @@ F test/fts3am.test 218aa6ba0dfc50c7c16b2022aac5c6be593d08d8 F test/fts3an.test a49ccadc07a2f7d646ec1b81bc09da2d85a85b18 F test/fts3ao.test b83f99f70e9eec85f27d75801a974b3f820e01f9 F test/fts3atoken.test 25c2070e1e8755d414bf9c8200427b277a9f99fa -F test/fts3aux1.test 9cf2e8499a494b92b77aaf1e5e1f50e4c4389549 +F test/fts3aux1.test 709323862e88b3e4be8278151bee2c56087feeed F test/fts3b.test e93bbb653e52afde110ad53bbd793f14fe7a8984 F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958 F test/fts3comp1.test ef36e5ddf9811c9801f52b2988bca1fce7dc8ce8 @@ -906,14 +903,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f -P c6d9f7d8c48e1ff405e1c1d98a166974fc829f58 ebf74015f09fe241c1c6902dc8954f2b59ab41ec -R 0634baae0b43c2b82e8b9905e8c6ee5b -U drh -Z d29f42a4b972ec3903a5f796629ef74c ------BEGIN PGP SIGNATURE----- -Version: GnuPG v1.4.10 (Darwin) - -iEYEARECAAYFAk1Jh4EACgkQoxKgR168RlEDdgCfTySKHScQj7r5Q2WPZ7harLi+ -uBIAn0+3gnlHjhVd4fzemcV4FGDrMm/Y -=REwF ------END PGP SIGNATURE----- +P bac7342c368a7c4f5f2878e08d9581dcbf57dd58 +R c5cbd8730aa7fc425fd495ad36fc71db +U dan +Z 56b8613b352790d458992ad5625bb3db diff --git a/manifest.uuid b/manifest.uuid index e607a2875c..6427a13d43 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -bac7342c368a7c4f5f2878e08d9581dcbf57dd58 \ No newline at end of file +386701ded2bcee5309ec9f285d94e6eb1eade193 \ No newline at end of file diff --git a/test/fts3aux1.test b/test/fts3aux1.test index 68754ce5f0..3a4278a38b 100644 --- a/test/fts3aux1.test +++ b/test/fts3aux1.test @@ -56,4 +56,272 @@ do_execsql_test 1.4 { SELECT * FROM terms; } {a 256 1024 b 256 768} +#------------------------------------------------------------------------- +# The following tests verify that the fts4aux module uses the full-text +# index to reduce the number of rows scanned in the following circumstances: +# +# * when there is equality comparison against the term column using the +# BINARY collating sequence. +# +# * when there is a range constraint on the term column using the BINARY +# collating sequence. +# +# And also uses the full-text index to optimize ORDER BY clauses of the +# form "ORDER BY term ASC" or equivalent. +# +# Test organization is: +# +# fts3aux1-2.1.*: equality constraints. +# fts3aux1-2.2.*: range constraints. +# fts3aux1-2.3.*: ORDER BY optimization. +# + +do_execsql_test 2.0 { + DROP TABLE t1; + DROP TABLE terms; + + CREATE VIRTUAL TABLE x1 USING fts4(x); + INSERT INTO x1(x1) VALUES('nodesize=24'); + CREATE VIRTUAL TABLE terms USING fts4aux(x1); + + INSERT INTO x1 VALUES('braes brag bragged bragger bragging'); + INSERT INTO x1 VALUES('brags braid braided braiding braids'); + INSERT INTO x1 VALUES('brain brainchild brained braining brains'); + INSERT INTO x1 VALUES('brainstem brainstems brainstorm brainstorms'); +} + +proc rec {varname x} { + global $varname + incr $varname + return 1 +} +db func rec rec + +# Use EQP to show that the WHERE expression "term='braid'" uses a different +# index number (1) than "+term='braid'" (0). +# +do_execsql_test 2.1.1.1 { + EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term='braid' +} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 1: (~0 rows)} } +do_execsql_test 2.1.1.2 { + EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term='braid' +} {0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0: (~0 rows)}} + +# Now show that using "term='braid'" means the virtual table returns +# only 1 row to SQLite, but "+term='braid'" means all 19 are returned. +# +do_test 2.1.2.1 { + set cnt 0 + execsql { SELECT * FROM terms WHERE rec('cnt', term) AND term='braid' } + set cnt +} {1} +do_test 2.1.2.2 { + set cnt 0 + execsql { SELECT * FROM terms WHERE rec('cnt', term) AND +term='braid' } + set cnt +} {19} + +# Similar to the test immediately above, but using a term ("breakfast") that +# is not featured in the dataset. +# +do_test 2.1.3.1 { + set cnt 0 + execsql { SELECT * FROM terms WHERE rec('cnt', term) AND term='breakfast' } + set cnt +} {0} +do_test 2.1.3.2 { + set cnt 0 + execsql { SELECT * FROM terms WHERE rec('cnt', term) AND +term='breakfast' } + set cnt +} {19} + +do_execsql_test 2.1.4.1 { SELECT * FROM terms WHERE term='braid' } {braid 1 1} +do_execsql_test 2.1.4.2 { SELECT * FROM terms WHERE +term='braid' } {braid 1 1} +do_execsql_test 2.1.4.3 { SELECT * FROM terms WHERE term='breakfast' } {} +do_execsql_test 2.1.4.4 { SELECT * FROM terms WHERE +term='breakfast' } {} + +do_execsql_test 2.1.4.5 { SELECT * FROM terms WHERE term='cba' } {} +do_execsql_test 2.1.4.6 { SELECT * FROM terms WHERE +term='cba' } {} +do_execsql_test 2.1.4.7 { SELECT * FROM terms WHERE term='abc' } {} +do_execsql_test 2.1.4.8 { SELECT * FROM terms WHERE +term='abc' } {} + +do_execsql_test 2.2.1.1 { + EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term>'brain' +} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 2: (~0 rows)} } +do_execsql_test 2.2.1.2 { + EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term>'brain' +} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0: (~0 rows)} } + +do_execsql_test 2.2.1.3 { + EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term<'brain' +} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 4: (~0 rows)} } +do_execsql_test 2.2.1.4 { + EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term<'brain' +} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0: (~0 rows)} } + +do_execsql_test 2.2.1.5 { + EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term BETWEEN 'brags' AND 'brain' +} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 6: (~0 rows)} } +do_execsql_test 2.2.1.6 { + EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term BETWEEN 'brags' AND 'brain' +} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0: (~0 rows)} } + +do_test 2.2.2.1 { + set cnt 0 + execsql { SELECT * FROM terms WHERE rec('cnt', term) AND term>'brain' } + set cnt +} {9} +do_test 2.2.2.2 { + set cnt 0 + execsql { SELECT * FROM terms WHERE rec('cnt', term) AND +term>'brain' } + set cnt +} {19} +do_execsql_test 2.2.2.3 { + SELECT * FROM terms WHERE rec('cnt', term) AND term>'brain' +} { + brainchild 1 1 brained 1 1 braining 1 1 brains 1 1 + brainstem 1 1 brainstems 1 1 brainstorm 1 1 brainstorms 1 1 +} +do_execsql_test 2.2.2.4 { + SELECT * FROM terms WHERE rec('cnt', term) AND +term>'brain' +} { + brainchild 1 1 brained 1 1 braining 1 1 brains 1 1 + brainstem 1 1 brainstems 1 1 brainstorm 1 1 brainstorms 1 1 +} +do_execsql_test 2.2.2.5 { + SELECT * FROM terms WHERE rec('cnt', term) AND term>='brain' +} { + brain 1 1 + brainchild 1 1 brained 1 1 braining 1 1 brains 1 1 + brainstem 1 1 brainstems 1 1 brainstorm 1 1 brainstorms 1 1 +} +do_execsql_test 2.2.2.6 { + SELECT * FROM terms WHERE rec('cnt', term) AND +term>='brain' +} { + brain 1 1 + brainchild 1 1 brained 1 1 braining 1 1 brains 1 1 + brainstem 1 1 brainstems 1 1 brainstorm 1 1 brainstorms 1 1 +} + +do_execsql_test 2.2.2.7 { + SELECT * FROM terms WHERE term>='abc' +} { + braes 1 1 brag 1 1 bragged 1 1 bragger 1 1 + bragging 1 1 brags 1 1 braid 1 1 braided 1 1 + braiding 1 1 braids 1 1 brain 1 1 brainchild 1 1 + brained 1 1 braining 1 1 brains 1 1 brainstem 1 1 + brainstems 1 1 brainstorm 1 1 brainstorms 1 1 +} +do_execsql_test 2.2.2.8 { + SELECT * FROM terms WHERE +term>='abc' +} { + braes 1 1 brag 1 1 bragged 1 1 bragger 1 1 + bragging 1 1 brags 1 1 braid 1 1 braided 1 1 + braiding 1 1 braids 1 1 brain 1 1 brainchild 1 1 + brained 1 1 braining 1 1 brains 1 1 brainstem 1 1 + brainstems 1 1 brainstorm 1 1 brainstorms 1 1 +} + +do_execsql_test 2.2.2.9 { + SELECT * FROM terms WHERE term>='brainstorms' +} {brainstorms 1 1} +do_execsql_test 2.2.2.10 { + SELECT * FROM terms WHERE term>='brainstorms' +} {brainstorms 1 1} +do_execsql_test 2.2.2.11 { SELECT * FROM terms WHERE term>'brainstorms' } {} +do_execsql_test 2.2.2.12 { SELECT * FROM terms WHERE term>'brainstorms' } {} + +do_execsql_test 2.2.2.13 { SELECT * FROM terms WHERE term>'cba' } {} +do_execsql_test 2.2.2.14 { SELECT * FROM terms WHERE term>'cba' } {} + +do_test 2.2.3.1 { + set cnt 0 + execsql { SELECT * FROM terms WHERE rec('cnt', term) AND term<'brain' } + set cnt +} {11} +do_test 2.2.3.2 { + set cnt 0 + execsql { SELECT * FROM terms WHERE rec('cnt', term) AND +term<'brain' } + set cnt +} {19} +do_execsql_test 2.2.3.3 { + SELECT * FROM terms WHERE rec('cnt', term) AND term<'brain' +} { + braes 1 1 brag 1 1 bragged 1 1 bragger 1 1 bragging 1 1 + brags 1 1 braid 1 1 braided 1 1 braiding 1 1 braids 1 1 +} +do_execsql_test 2.2.3.4 { + SELECT * FROM terms WHERE rec('cnt', term) AND +term<'brain' +} { + braes 1 1 brag 1 1 bragged 1 1 bragger 1 1 bragging 1 1 + brags 1 1 braid 1 1 braided 1 1 braiding 1 1 braids 1 1 +} +do_execsql_test 2.2.3.5 { + SELECT * FROM terms WHERE rec('cnt', term) AND term<='brain' +} { + braes 1 1 brag 1 1 bragged 1 1 bragger 1 1 bragging 1 1 + brags 1 1 braid 1 1 braided 1 1 braiding 1 1 braids 1 1 + brain 1 1 +} +do_execsql_test 2.2.3.6 { + SELECT * FROM terms WHERE rec('cnt', term) AND +term<='brain' +} { + braes 1 1 brag 1 1 bragged 1 1 bragger 1 1 bragging 1 1 + brags 1 1 braid 1 1 braided 1 1 braiding 1 1 braids 1 1 + brain 1 1 +} + +do_test 2.2.4.1 { + set cnt 0 + execsql { + SELECT * FROM terms + WHERE rec('cnt', term) AND term BETWEEN 'brags' AND 'brain' + } + set cnt +} {6} +do_test 2.2.4.2 { + set cnt 0 + execsql { + SELECT * FROM terms + WHERE rec('cnt', term) AND +term BETWEEN 'brags' AND 'brain' + } + set cnt +} {19} +do_execsql_test 2.2.4.3 { + SELECT * FROM terms + WHERE rec('cnt', term) AND term BETWEEN 'brags' AND 'brain' +} { + brags 1 1 braid 1 1 braided 1 1 braiding 1 1 braids 1 1 brain 1 1 +} +do_execsql_test 2.2.4.4 { + SELECT * FROM terms + WHERE rec('cnt', term) AND +term BETWEEN 'brags' AND 'brain' +} { + brags 1 1 braid 1 1 braided 1 1 braiding 1 1 braids 1 1 brain 1 1 +} +do_execsql_test 2.2.4.5 { + SELECT * FROM terms + WHERE rec('cnt', term) AND term > 'brags' AND term < 'brain' +} { + braid 1 1 braided 1 1 braiding 1 1 braids 1 1 +} +do_execsql_test 2.2.4.6 { + SELECT * FROM terms + WHERE rec('cnt', term) AND +term > 'brags' AND +term < 'brain' +} { + braid 1 1 braided 1 1 braiding 1 1 braids 1 1 +} + +do_execsql_test 2.3.1.1 { + EXPLAIN QUERY PLAN SELECT * FROM terms ORDER BY term ASC; +} { + 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0: (~0 rows)} +} +do_execsql_test 2.3.1.2 { + EXPLAIN QUERY PLAN SELECT * FROM terms ORDER BY term DESC; +} { + 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0: (~0 rows)} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} + finish_test