mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-30 19:03:16 +03:00
Fix issues in [/info/1e227ad9f413227f|LIMIT/OFFSET support for virtual tables].
The first problem was reported by [forum:/forumpost/c243b8f856|forum post c243b8f856]. That report prompted an enhancement to the generate_series() (also included in this merge) which in turn identified other similar issues. FossilOrigin-Name: 5f6c079d847e3664ec5acaf1b3e989efe0d548c211ae4a18936162b36df89065
This commit is contained in:
@ -376,13 +376,13 @@ static int seriesEof(sqlite3_vtab_cursor *cur){
|
||||
** parameter. (idxStr is not used in this implementation.) idxNum
|
||||
** is a bitmask showing which constraints are available:
|
||||
**
|
||||
** 1: start=VALUE
|
||||
** 2: stop=VALUE
|
||||
** 4: step=VALUE
|
||||
**
|
||||
** Also, if bit 8 is set, that means that the series should be output
|
||||
** in descending order rather than in ascending order. If bit 16 is
|
||||
** set, then output must appear in ascending order.
|
||||
** 0x01: start=VALUE
|
||||
** 0x02: stop=VALUE
|
||||
** 0x04: step=VALUE
|
||||
** 0x08: descending order
|
||||
** 0x10: ascending order
|
||||
** 0x20: LIMIT VALUE
|
||||
** 0x40: OFFSET VALUE
|
||||
**
|
||||
** This routine should initialize the cursor and position it so that it
|
||||
** is pointing at the first row, or pointing off the end of the table
|
||||
@ -396,26 +396,44 @@ static int seriesFilter(
|
||||
series_cursor *pCur = (series_cursor *)pVtabCursor;
|
||||
int i = 0;
|
||||
(void)idxStrUnused;
|
||||
if( idxNum & 1 ){
|
||||
if( idxNum & 0x01 ){
|
||||
pCur->ss.iBase = sqlite3_value_int64(argv[i++]);
|
||||
}else{
|
||||
pCur->ss.iBase = 0;
|
||||
}
|
||||
if( idxNum & 2 ){
|
||||
if( idxNum & 0x02 ){
|
||||
pCur->ss.iTerm = sqlite3_value_int64(argv[i++]);
|
||||
}else{
|
||||
pCur->ss.iTerm = 0xffffffff;
|
||||
}
|
||||
if( idxNum & 4 ){
|
||||
if( idxNum & 0x04 ){
|
||||
pCur->ss.iStep = sqlite3_value_int64(argv[i++]);
|
||||
if( pCur->ss.iStep==0 ){
|
||||
pCur->ss.iStep = 1;
|
||||
}else if( pCur->ss.iStep<0 ){
|
||||
if( (idxNum & 16)==0 ) idxNum |= 8;
|
||||
if( (idxNum & 0x10)==0 ) idxNum |= 0x08;
|
||||
}
|
||||
}else{
|
||||
pCur->ss.iStep = 1;
|
||||
}
|
||||
if( idxNum & 0x20 ){
|
||||
sqlite3_int64 iLimit = sqlite3_value_int64(argv[i++]);
|
||||
sqlite3_int64 iTerm;
|
||||
if( idxNum & 0x40 ){
|
||||
sqlite3_int64 iOffset = sqlite3_value_int64(argv[i++]);
|
||||
if( iOffset>0 ){
|
||||
pCur->ss.iBase += pCur->ss.iStep*iOffset;
|
||||
}
|
||||
}
|
||||
if( iLimit>=0 ){
|
||||
iTerm = pCur->ss.iBase + (iLimit - 1)*pCur->ss.iStep;
|
||||
if( pCur->ss.iStep<0 ){
|
||||
if( iTerm>pCur->ss.iTerm ) pCur->ss.iTerm = iTerm;
|
||||
}else{
|
||||
if( iTerm<pCur->ss.iTerm ) pCur->ss.iTerm = iTerm;
|
||||
}
|
||||
}
|
||||
}
|
||||
for(i=0; i<argc; i++){
|
||||
if( sqlite3_value_type(argv[i])==SQLITE_NULL ){
|
||||
/* If any of the constraints have a NULL value, then return no rows.
|
||||
@ -426,7 +444,7 @@ static int seriesFilter(
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( idxNum & 8 ){
|
||||
if( idxNum & 0x08 ){
|
||||
pCur->ss.isReversing = pCur->ss.iStep > 0;
|
||||
}else{
|
||||
pCur->ss.isReversing = pCur->ss.iStep < 0;
|
||||
@ -446,10 +464,13 @@ static int seriesFilter(
|
||||
**
|
||||
** The query plan is represented by bits in idxNum:
|
||||
**
|
||||
** (1) start = $value -- constraint exists
|
||||
** (2) stop = $value -- constraint exists
|
||||
** (4) step = $value -- constraint exists
|
||||
** (8) output in descending order
|
||||
** 0x01 start = $value -- constraint exists
|
||||
** 0x02 stop = $value -- constraint exists
|
||||
** 0x04 step = $value -- constraint exists
|
||||
** 0x08 output is in descending order
|
||||
** 0x10 output is in ascending order
|
||||
** 0x20 LIMIT $value -- constraint exists
|
||||
** 0x40 OFFSET $value -- constraint exists
|
||||
*/
|
||||
static int seriesBestIndex(
|
||||
sqlite3_vtab *pVTab,
|
||||
@ -457,10 +478,12 @@ static int seriesBestIndex(
|
||||
){
|
||||
int i, j; /* Loop over constraints */
|
||||
int idxNum = 0; /* The query plan bitmask */
|
||||
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
|
||||
int bStartSeen = 0; /* EQ constraint seen on the START column */
|
||||
#endif
|
||||
int unusableMask = 0; /* Mask of unusable constraints */
|
||||
int nArg = 0; /* Number of arguments that seriesFilter() expects */
|
||||
int aIdx[3]; /* Constraints on start, stop, and step */
|
||||
int aIdx[5]; /* Constraints on start, stop, step, LIMIT, OFFSET */
|
||||
const struct sqlite3_index_constraint *pConstraint;
|
||||
|
||||
/* This implementation assumes that the start, stop, and step columns
|
||||
@ -468,28 +491,54 @@ static int seriesBestIndex(
|
||||
assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 );
|
||||
assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 );
|
||||
|
||||
aIdx[0] = aIdx[1] = aIdx[2] = -1;
|
||||
aIdx[0] = aIdx[1] = aIdx[2] = aIdx[3] = aIdx[4] = -1;
|
||||
pConstraint = pIdxInfo->aConstraint;
|
||||
for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
|
||||
int iCol; /* 0 for start, 1 for stop, 2 for step */
|
||||
int iMask; /* bitmask for those column */
|
||||
int op = pConstraint->op;
|
||||
if( op>=SQLITE_INDEX_CONSTRAINT_LIMIT
|
||||
&& op<=SQLITE_INDEX_CONSTRAINT_OFFSET
|
||||
){
|
||||
if( pConstraint->usable==0 ){
|
||||
/* do nothing */
|
||||
}else if( op==SQLITE_INDEX_CONSTRAINT_LIMIT ){
|
||||
aIdx[3] = i;
|
||||
idxNum |= 0x20;
|
||||
}else{
|
||||
assert( op==SQLITE_INDEX_CONSTRAINT_OFFSET );
|
||||
aIdx[4] = i;
|
||||
idxNum |= 0x40;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if( pConstraint->iColumn<SERIES_COLUMN_START ) continue;
|
||||
iCol = pConstraint->iColumn - SERIES_COLUMN_START;
|
||||
assert( iCol>=0 && iCol<=2 );
|
||||
iMask = 1 << iCol;
|
||||
if( iCol==0 ) bStartSeen = 1;
|
||||
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
|
||||
if( iCol==0 && op==SQLITE_INDEX_CONSTRAINT_EQ ){
|
||||
bStartSeen = 1;
|
||||
}
|
||||
#endif
|
||||
if( pConstraint->usable==0 ){
|
||||
unusableMask |= iMask;
|
||||
continue;
|
||||
}else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){
|
||||
}else if( op==SQLITE_INDEX_CONSTRAINT_EQ ){
|
||||
idxNum |= iMask;
|
||||
aIdx[iCol] = i;
|
||||
}
|
||||
}
|
||||
for(i=0; i<3; i++){
|
||||
if( aIdx[3]==0 ){
|
||||
/* Ignore OFFSET if LIMIT is omitted */
|
||||
idxNum &= ~0x60;
|
||||
aIdx[4] = 0;
|
||||
}
|
||||
for(i=0; i<5; i++){
|
||||
if( (j = aIdx[i])>=0 ){
|
||||
pIdxInfo->aConstraintUsage[j].argvIndex = ++nArg;
|
||||
pIdxInfo->aConstraintUsage[j].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY;
|
||||
pIdxInfo->aConstraintUsage[j].omit =
|
||||
!SQLITE_SERIES_CONSTRAINT_VERIFY || i>=3;
|
||||
}
|
||||
}
|
||||
/* The current generate_column() implementation requires at least one
|
||||
@ -510,19 +559,22 @@ static int seriesBestIndex(
|
||||
** this plan is unusable */
|
||||
return SQLITE_CONSTRAINT;
|
||||
}
|
||||
if( (idxNum & 3)==3 ){
|
||||
if( (idxNum & 0x03)==0x03 ){
|
||||
/* Both start= and stop= boundaries are available. This is the
|
||||
** the preferred case */
|
||||
pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0));
|
||||
pIdxInfo->estimatedRows = 1000;
|
||||
if( pIdxInfo->nOrderBy>=1 && pIdxInfo->aOrderBy[0].iColumn==0 ){
|
||||
if( pIdxInfo->aOrderBy[0].desc ){
|
||||
idxNum |= 8;
|
||||
idxNum |= 0x08;
|
||||
}else{
|
||||
idxNum |= 16;
|
||||
idxNum |= 0x10;
|
||||
}
|
||||
pIdxInfo->orderByConsumed = 1;
|
||||
}
|
||||
}else if( (idxNum & 0x21)==0x21 ){
|
||||
/* We have start= and LIMIT */
|
||||
pIdxInfo->estimatedRows = 2500;
|
||||
}else{
|
||||
/* If either boundary is missing, we have to generate a huge span
|
||||
** of numbers. Make this case very expensive so that the query
|
||||
|
24
manifest
24
manifest
@ -1,5 +1,5 @@
|
||||
C Internal\sJS\sdoc\stouchups.\sNo\sfunctional\schanges.
|
||||
D 2024-04-26T18:42:50.715
|
||||
C Fix\sissues\sin\s[/info/1e227ad9f413227f|LIMIT/OFFSET\ssupport\sfor\svirtual\stables].\nThe\sfirst\sproblem\swas\sreported\sby\n[forum:/forumpost/c243b8f856|forum\spost\sc243b8f856].\s\sThat\sreport\sprompted\nan\senhancement\sto\sthe\sgenerate_series()\s(also\sincluded\sin\sthis\smerge)\swhich\nin\sturn\sidentified\sother\ssimilar\sissues.
|
||||
D 2024-04-26T19:10:15.851
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||
@ -411,7 +411,7 @@ F ext/misc/regexp.c 4bdd0045912f81c84908bd535ec5ad3b1c8540b4287c70ab840709636240
|
||||
F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c
|
||||
F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c
|
||||
F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946
|
||||
F ext/misc/series.c 384f93a8a09cf45e1aa6575660cb580ed61d372c590aad05cdcd4a84fbd8f6ab
|
||||
F ext/misc/series.c d96e5aac21658c6b5d54f918ac140460ec7197734c1a4fba806950831a7b1e7a
|
||||
F ext/misc/sha1.c 4011aef176616872b2a0d5bccf0ecfb1f7ce3fe5c3d107f3a8e949d8e1e3f08d
|
||||
F ext/misc/shathree.c 543af7ce71d391cd3a9ab6924a6a1124efc63211fd0f2e240dc4b56077ba88ac
|
||||
F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
|
||||
@ -836,10 +836,10 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
|
||||
F src/wal.c 887fc4ca3f020ebb2e376f222069570834ac63bf50111ef0cbf3ae417048ed89
|
||||
F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452
|
||||
F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2
|
||||
F src/where.c 447d8761632fb0a18b03077161415d9713cbd0a81bf34a35cee63480e5c401c5
|
||||
F src/where.c 0ef9638651b900d64d7e1e877af37cd7900159ff875547ec29b918a1497e5c9c
|
||||
F src/whereInt.h 82a13766f13d1a53b05387c2e60726289ef26404bc7b9b1f7770204d97357fb8
|
||||
F src/wherecode.c 1f6940349e92a6e056aecd70163b00f331554c815c362b4cc80906c48151d73d
|
||||
F src/whereexpr.c 7b64295f1d82ad0928df435925dd7bbd5997b44a026153113eace0d9e71ff435
|
||||
F src/whereexpr.c 67d15caf88a1a9528283d68ff578e024cf9fe810b517bb0343e5aaf695ad97dd
|
||||
F src/window.c 5d95122dd330bfaebd732358c8ef067c5a9394a53ac249470d611d0ce2c52be2
|
||||
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
|
||||
F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627
|
||||
@ -932,10 +932,11 @@ F test/bestindex4.test 3039894f2dad50f3a68443dffad1b44c9b067ac03870102df1ce3d9a4
|
||||
F test/bestindex5.test a0c90b2dad7836e80a01379e200e5f8ec9476d49b349af02c0dbff2fb75dc98d
|
||||
F test/bestindex6.test 16942535b551273f3ad9df8d7cc4b7f22b1fcd8882714358859eb049a6f99dd4
|
||||
F test/bestindex7.test f094c669a6400777f4d2ddc3ed28e39169f1adb5be3d59b55f22ccf8c414b71e
|
||||
F test/bestindex8.test 333ad8c6a554b885a49b68c019166eda92b05f493a92b36b0acdf7f766d04dad
|
||||
F test/bestindex8.test b63a4f171a2c83d481bb14c431a8b72e85d27b2ffdaa0435a95d58ca941678f9
|
||||
F test/bestindex9.test 1a4b93db117fd8abe74ae9be982f86aa72f01e60cd4ac541e6ede39673a451a0
|
||||
F test/bestindexA.test e1b5def6b190797cacf008e6815ffb78fb30261999030d60a728d572eef44c7f
|
||||
F test/bestindexB.test 328b97b69cd1a20928d5997f9ecb04d2e00f1d18e19ab27f9e9adb44d7bc51ce
|
||||
F test/bestindexC.test c14a8c8639b6825b0efa1ae693f34ec04f41a46e3056e7063d6e0f46bf4ff692
|
||||
F test/between.test b9a65fb065391980119e8a781a7409d3fcf059d89968279c750e190a9a1d5263
|
||||
F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59
|
||||
F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc
|
||||
@ -1678,7 +1679,7 @@ F test/sync.test 89539f4973c010eda5638407e71ca7fddbcd8e0594f4c9980229f804d433309
|
||||
F test/sync2.test 8f9f7d4f6d5be8ca8941a8dadcc4299e558cb6a1ff653a9469146c7a76ef2039
|
||||
F test/syscall.test a39d9a36f852ae6e4800f861bc2f2e83f68bbc2112d9399931ecfadeabd2d69d
|
||||
F test/sysfault.test c9f2b0d8d677558f74de750c75e12a5454719d04
|
||||
F test/tabfunc01.test 54f27eacd054aa528a8b6e3331192c484104f30aaee351ad035f2b39a00f87c4
|
||||
F test/tabfunc01.test f150d206294471d20f50029e6b46b76b87a7a010b16dc57eb44245c76dd02802
|
||||
F test/table.test 7862a00b58b5541511a26757ea9c5c7c3f8298766e98aa099deec703d9c0a8e0
|
||||
F test/tableapi.test ecbcc29c4ab62c1912c3717c48ea5c5e59f7d64e4a91034e6148bd2b82f177f4
|
||||
F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930
|
||||
@ -2185,8 +2186,9 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P 5a2245a9ebae6d23cd343e46b9d730f66ec4d5ffc91b83ed11a0fbd2194ad807
|
||||
R 2adc6cf9c9a2aba61ab0a2581495ddf7
|
||||
U stephan
|
||||
Z fc84f35cfa574620d41dff4fcae199b4
|
||||
P 626b997b4e727554d7ec8b60fb37b2a94cb861a87325da4ae484bd9aa8961ca5 90e5c8226a695e838e8c1703a9b8598e654d216799e8806c4d1a1f20c28c6486
|
||||
R 2a7971e3914aa76c70593754ba02f5ed
|
||||
T +closed 90e5c8226a695e838e8c1703a9b8598e654d216799e8806c4d1a1f20c28c6486
|
||||
U drh
|
||||
Z e187ac779304121a02fd3106018bb12d
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
@ -1 +1 @@
|
||||
626b997b4e727554d7ec8b60fb37b2a94cb861a87325da4ae484bd9aa8961ca5
|
||||
5f6c079d847e3664ec5acaf1b3e989efe0d548c211ae4a18936162b36df89065
|
30
src/where.c
30
src/where.c
@ -4057,6 +4057,21 @@ static int isLimitTerm(WhereTerm *pTerm){
|
||||
&& pTerm->eMatchOp<=SQLITE_INDEX_CONSTRAINT_OFFSET;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return true if the first nCons constraints in the pUsage array are
|
||||
** marked as in-use (have argvIndex>0). False otherwise.
|
||||
*/
|
||||
static int allConstraintsUsed(
|
||||
struct sqlite3_index_constraint_usage *aUsage,
|
||||
int nCons
|
||||
){
|
||||
int ii;
|
||||
for(ii=0; ii<nCons; ii++){
|
||||
if( aUsage[ii].argvIndex<=0 ) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
** Argument pIdxInfo is already populated with all constraints that may
|
||||
** be used by the virtual table identified by pBuilder->pNew->iTab. This
|
||||
@ -4197,13 +4212,20 @@ static int whereLoopAddVirtualOne(
|
||||
*pbIn = 1; assert( (mExclude & WO_IN)==0 );
|
||||
}
|
||||
|
||||
/* Unless pbRetryLimit is non-NULL, there should be no LIMIT/OFFSET
|
||||
** terms. And if there are any, they should follow all other terms. */
|
||||
assert( pbRetryLimit || !isLimitTerm(pTerm) );
|
||||
if( isLimitTerm(pTerm) && *pbIn ){
|
||||
assert( !isLimitTerm(pTerm) || i>=nConstraint-2 );
|
||||
assert( !isLimitTerm(pTerm) || i==nConstraint-1 || isLimitTerm(pTerm+1) );
|
||||
|
||||
if( isLimitTerm(pTerm) && (*pbIn || !allConstraintsUsed(pUsage, i)) ){
|
||||
/* If there is an IN(...) term handled as an == (separate call to
|
||||
** xFilter for each value on the RHS of the IN) and a LIMIT or
|
||||
** OFFSET term handled as well, the plan is unusable. Set output
|
||||
** variable *pbRetryLimit to true to tell the caller to retry with
|
||||
** LIMIT and OFFSET disabled. */
|
||||
** OFFSET term handled as well, the plan is unusable. Similarly,
|
||||
** if there is a LIMIT/OFFSET and there are other unused terms,
|
||||
** the plan cannot be used. In these cases set variable *pbRetryLimit
|
||||
** to true to tell the caller to retry with LIMIT and OFFSET
|
||||
** disabled. */
|
||||
if( pIdxInfo->needToFreeIdxStr ){
|
||||
sqlite3_free(pIdxInfo->idxStr);
|
||||
pIdxInfo->idxStr = 0;
|
||||
|
@ -1638,6 +1638,7 @@ void SQLITE_NOINLINE sqlite3WhereAddLimit(WhereClause *pWC, Select *p){
|
||||
continue;
|
||||
}
|
||||
if( pWC->a[ii].leftCursor!=iCsr ) return;
|
||||
if( pWC->a[ii].prereqRight!=0 ) return;
|
||||
}
|
||||
|
||||
/* Check condition (5). Return early if it is not met. */
|
||||
@ -1652,12 +1653,14 @@ void SQLITE_NOINLINE sqlite3WhereAddLimit(WhereClause *pWC, Select *p){
|
||||
|
||||
/* All conditions are met. Add the terms to the where-clause object. */
|
||||
assert( p->pLimit->op==TK_LIMIT );
|
||||
whereAddLimitExpr(pWC, p->iLimit, p->pLimit->pLeft,
|
||||
iCsr, SQLITE_INDEX_CONSTRAINT_LIMIT);
|
||||
if( p->iOffset>0 ){
|
||||
if( p->iOffset!=0 && (p->selFlags & SF_Compound)==0 ){
|
||||
whereAddLimitExpr(pWC, p->iOffset, p->pLimit->pRight,
|
||||
iCsr, SQLITE_INDEX_CONSTRAINT_OFFSET);
|
||||
}
|
||||
if( p->iOffset==0 || (p->selFlags & SF_Compound)==0 ){
|
||||
whereAddLimitExpr(pWC, p->iLimit, p->pLimit->pLeft,
|
||||
iCsr, SQLITE_INDEX_CONSTRAINT_LIMIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,7 +158,7 @@ do_test 2.2 {
|
||||
set ::lFilterArgs [list]
|
||||
execsql { SELECT * FROM vt1 LIMIT 5 OFFSET 50 }
|
||||
set ::lFilterArgs
|
||||
} {{5 50}}
|
||||
} {{50 5}}
|
||||
|
||||
do_test 2.3 {
|
||||
set ::lFilterArgs [list]
|
||||
|
178
test/bestindexC.test
Normal file
178
test/bestindexC.test
Normal file
@ -0,0 +1,178 @@
|
||||
# 2024-04-26
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix bestindexC
|
||||
|
||||
ifcapable !vtab {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
register_tcl_module db
|
||||
|
||||
proc vtab_command {lVal method args} {
|
||||
switch -- $method {
|
||||
xConnect {
|
||||
return "CREATE TABLE t1(a)"
|
||||
}
|
||||
|
||||
xBestIndex {
|
||||
set hdl [lindex $args 0]
|
||||
set clist [$hdl constraints]
|
||||
set orderby [$hdl orderby]
|
||||
|
||||
set idxstr [list]
|
||||
set res [list]
|
||||
|
||||
set idx 0
|
||||
foreach c $clist {
|
||||
array set a $c
|
||||
if {$a(usable)==0} continue
|
||||
if {$a(op)=="limit" && ![info exists ::do_not_use_limit]} {
|
||||
lappend idxstr limit
|
||||
lappend res omit $idx
|
||||
}
|
||||
if {$a(op)=="offset" && ![info exists ::do_not_use_offset]} {
|
||||
lappend idxstr offset
|
||||
lappend res omit $idx
|
||||
}
|
||||
incr idx
|
||||
}
|
||||
|
||||
return "cost 1000000 rows 1000000 idxnum 0 idxstr {$idxstr} $res"
|
||||
}
|
||||
|
||||
xFilter {
|
||||
set idxstr [lindex $args 1]
|
||||
set LIMIT ""
|
||||
foreach a $idxstr b [lindex $args 2] {
|
||||
set x($a) $b
|
||||
}
|
||||
|
||||
if {![info exists x(limit)]} { set x(limit) -1 }
|
||||
if {![info exists x(offset)]} { set x(offset) -1 }
|
||||
set LIMIT " LIMIT $x(limit) OFFSET $x(offset)"
|
||||
|
||||
set idx 1
|
||||
foreach v $lVal {
|
||||
lappend lRow "($idx, '$v')"
|
||||
incr idx
|
||||
}
|
||||
|
||||
return [list sql "
|
||||
SELECT * FROM ( VALUES [join $lRow ,]) $LIMIT
|
||||
"]
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE x1 USING tcl(vtab_command "a b c d e f");
|
||||
CREATE VIRTUAL TABLE x2 USING tcl(vtab_command "A B C D E F a b");
|
||||
} {}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
CREATE TEMP TABLE t_unionall AS
|
||||
SELECT * FROM x1 UNION ALL SELECT * FROM x2;
|
||||
|
||||
CREATE TEMP TABLE t_intersect AS
|
||||
SELECT * FROM x1 INTERSECT SELECT * FROM x2;
|
||||
|
||||
CREATE TEMP TABLE t_union AS
|
||||
SELECT * FROM x1 UNION SELECT * FROM x2;
|
||||
|
||||
CREATE TEMP TABLE t_except AS
|
||||
SELECT * FROM x1 EXCEPT SELECT * FROM x2;
|
||||
}
|
||||
|
||||
foreach {tn limit} {
|
||||
1 "LIMIT 8"
|
||||
2 "LIMIT 4"
|
||||
3 "LIMIT 4 OFFSET 2"
|
||||
4 "LIMIT 8 OFFSET 4"
|
||||
} {
|
||||
|
||||
foreach {op tbl} {
|
||||
"UNION ALL" t_unionall
|
||||
"UNION" t_union
|
||||
"INTERSECT" t_intersect
|
||||
"EXCEPT" t_except
|
||||
} {
|
||||
|
||||
set expect [execsql "SELECT * FROM $tbl $limit"]
|
||||
do_execsql_test 1.2.$tbl.$tn "SELECT * FROM (
|
||||
SELECT * FROM x1 $op SELECT * FROM x2
|
||||
) $limit" $expect
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
register_tcl_module db
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE x1 USING tcl(vtab_command "a b c d e f");
|
||||
CREATE VIRTUAL TABLE x2 USING tcl(vtab_command "a b e f");
|
||||
} {}
|
||||
|
||||
do_execsql_test 2.1 {
|
||||
SELECT * FROM x1
|
||||
EXCEPT
|
||||
SELECT * FROM x2
|
||||
LIMIT 3
|
||||
} {c d}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
register_tcl_module db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE VIRTUAL TABLE y1 USING tcl(vtab_command "1 2 3 4 5 6 7 8 9 10");
|
||||
} {}
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
SELECT * FROM y1 WHERE a = COALESCE('8', a) LIMIT 3
|
||||
} {8}
|
||||
|
||||
do_execsql_test 3.2 {
|
||||
SELECT * FROM y1 WHERE a = '2' LIMIT 3
|
||||
} {2}
|
||||
|
||||
load_static_extension db series
|
||||
do_execsql_test 3.3 {
|
||||
SELECT * FROM generate_series(1, 5) WHERE value = (value & 14) LIMIT 3
|
||||
} {2 4}
|
||||
|
||||
do_execsql_test 3.4 {
|
||||
SELECT value FROM generate_series(1,10) WHERE value>2 LIMIT 4 OFFSET 1;
|
||||
} {4 5 6 7}
|
||||
|
||||
set ::do_not_use_limit 1
|
||||
do_execsql_test 3.5 {
|
||||
SELECT * FROM y1 LIMIT 5 OFFSET 3
|
||||
} {4 5 6 7 8}
|
||||
unset ::do_not_use_limit
|
||||
set ::do_not_use_offset 1
|
||||
do_execsql_test 3.6 {
|
||||
SELECT * FROM y1 LIMIT 5 OFFSET 3
|
||||
} {4 5 6 7 8}
|
||||
unset ::do_not_use_offset
|
||||
|
||||
|
||||
|
||||
finish_test
|
@ -322,6 +322,32 @@ ifcapable altertable {
|
||||
} {1 {table pragma_compile_options may not be altered}}
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# 2024-04-26 LIMIT and OFFSET passed into virtual tables
|
||||
# https://sqlite.org/forum/forumpost/c243b8f856
|
||||
#
|
||||
do_execsql_test tabfunc01-900 {
|
||||
SELECT * FROM (
|
||||
SELECT * FROM generate_series(1,10)
|
||||
UNION ALL
|
||||
SELECT * FROM generate_series(101,104)
|
||||
) LIMIT 10 OFFSET 5;
|
||||
} {6 7 8 9 10 101 102 103 104}
|
||||
do_execsql_test tabfunc01-910 {
|
||||
SELECT * FROM (
|
||||
SELECT * FROM generate_series(1,10)
|
||||
UNION ALL
|
||||
SELECT * FROM generate_series(101,104)
|
||||
) LIMIT -1 OFFSET 5;
|
||||
} {6 7 8 9 10 101 102 103 104}
|
||||
do_execsql_test tabfunc01-920 {
|
||||
SELECT * FROM (
|
||||
SELECT * FROM generate_series(1,10)
|
||||
UNION ALL
|
||||
SELECT * FROM generate_series(101,104)
|
||||
) LIMIT -1 OFFSET 0;
|
||||
} {1 2 3 4 5 6 7 8 9 10 101 102 103 104}
|
||||
|
||||
|
||||
# Free up memory allocations
|
||||
intarray_addr
|
||||
|
Reference in New Issue
Block a user