mirror of
https://github.com/sqlite/sqlite.git
synced 2026-10-23 21:49:02 +03:00
When a single loop uses an IN(...) expression and a LIKE/GLOB expression with an index, nest the 2-iteration loop that scans ranges of text and blob values using the LIKE/GLOB pattern inside the IN(...) loop in order to ensure that results are scanned in index order. Report [bugs:/info/2026-10-13T18:54:28Z | 2026-10-13T18:54:28Z].
FossilOrigin-Name: 08e979525ff1eef80c8e824c80cca7b6a3dfb320122258803673ace43c806c9a
This commit is contained in:
17
manifest
17
manifest
@@ -1,5 +1,5 @@
|
||||
C Remove\scode\sthat\sis\sno\slonger\sreachable\safter\s[bbdd13370de93035].\s\sAdd\nan\sassert()\sto\sverify\sthat\sthe\scode\sis\sunreachable.
|
||||
D 2026-10-15T13:55:05.210
|
||||
C When\sa\ssingle\sloop\suses\san\sIN(...)\sexpression\sand\sa\sLIKE/GLOB\sexpression\swith\san\sindex,\snest\sthe\s2-iteration\sloop\sthat\sscans\sranges\sof\stext\sand\sblob\svalues\susing\sthe\sLIKE/GLOB\spattern\sinside\sthe\sIN(...)\sloop\sin\sorder\sto\sensure\sthat\sresults\sare\sscanned\sin\sindex\sorder.\sReport\s[bugs:/info/2026-10-13T18:54:28Z\s|\s2026-10-13T18:54:28Z].
|
||||
D 2026-10-15T15:33:09.085
|
||||
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
@@ -886,9 +886,9 @@ F src/vxworks.h 9d18819c5235b49c2340a8a4d48195ec5d5afb637b152406de95a9436beeaeab
|
||||
F src/wal.c 41438914e455bbc42ccff8545eab4c711f91233e480337e44a3d5fdf121fce74
|
||||
F src/wal.h c6251cfc1950643a976625ca452e46dc9b466b4f5dd2e85f244f29f79bf68ace
|
||||
F src/walker.c d5006d6b005e4ea7302ad390957a8d41ed83faa177e412f89bc5600a7462a014
|
||||
F src/where.c 21926754d085b9689b71bf6188810c0c59f7ad5ec05c60aedf461e765217c350
|
||||
F src/where.c e3373734cf74e7b52a786db5d14beb8d73b937bd947d7eff1a9dc360c52745e7
|
||||
F src/whereInt.h 5d739acb94bf1fdf6edd502a919e3dcf2d340dda63cc1827304e119ea878f6f3
|
||||
F src/wherecode.c 6d4835272cf65ba13379a1620a806d1ef1c2e0f7d6a661ed6ee40f013f357189
|
||||
F src/wherecode.c 69ff3d0fe8697d2ca482d25c15dfdc08cf8e4605ab5ab297e9d12b3dbc6812fd
|
||||
F src/whereexpr.c 76f72a95c5cf0e8a8edb679ca4fb7afa17ba45ff4effe73c414266510109e701
|
||||
F src/window.c 79a17de595e62aa45961ef1cbe3392e7ebae69fdde3fd0b0200254bc4647025e
|
||||
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
|
||||
@@ -1456,6 +1456,7 @@ F test/lemon-test01.y 70110eff607ab137ccc851edb2bc7e14a6d4f246b5d2d25f82a60b69d8
|
||||
F test/like.test 0036f8fe548fceabd1496361bfa262f35cf5ed17c794bd15e22e9f3de12e0eb0
|
||||
F test/like2.test d3be15fefee3e02fc88942a9b98f26c5339bbdef7783c90023c092c4955fe3d3
|
||||
F test/like3.test 3c9be7a0122908d8ead6aa25e47c2b9787eea88e8062926078ea7b3e95c71d99
|
||||
F test/like4.test 3312c44c9e09d94f8e10a2158a63b615eb6ee09afc4aecbd385c31271824f92a
|
||||
F test/limit.test 350f5d03c29e7dff9a2cde016f84f8d368d40bcd02fa2b2a52fa10c4bf3cbfaf
|
||||
F test/limit2.test 621188fc3e5c3b8d2ef9827e05fa8313792ae563579073136efd25cb65325f1b
|
||||
F test/literal.test a65dca9fef86e51b8e45544268e37abbd4bb94ba35fd65f6fdcab2f288cd8f79
|
||||
@@ -2289,8 +2290,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee
|
||||
F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2
|
||||
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
|
||||
F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
|
||||
P 3ce64081cab882b60ad73db77da961e479cd84eb151444f420da9fcc370f740d
|
||||
R 76f43338ac789d41d3609cb388d0106d
|
||||
U drh
|
||||
Z 03d6a8e5c145c41269fb248d2afbccb9
|
||||
P a0f9daf75b2fc0b230e9d2b79eab9e495a911f39d7427eb5ec09f5414d8703b7
|
||||
R 3fedb2628e1212451a990a8e79205119
|
||||
U dan
|
||||
Z 6307e8e29ee9911f6e6ceee7d948f13d
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
||||
@@ -1 +1 @@
|
||||
a0f9daf75b2fc0b230e9d2b79eab9e495a911f39d7427eb5ec09f5414d8703b7
|
||||
08e979525ff1eef80c8e824c80cca7b6a3dfb320122258803673ace43c806c9a
|
||||
|
||||
28
src/where.c
28
src/where.c
@@ -7664,10 +7664,29 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
|
||||
VdbeCoverage(v);
|
||||
}
|
||||
}
|
||||
if( pLevel->addrNxt!=pLevel->addrBrk ){
|
||||
/* This occurs in two cases - (a) for a LIKE/GLOB scan that must visit
|
||||
** ranges of text and blob values separately, and (b) when there is
|
||||
** an IN(...) constraint driving the index. This is asserted() in the
|
||||
** next block, inside the #ifndef. */
|
||||
sqlite3VdbeResolveLabel(v, pLevel->addrNxt);
|
||||
}
|
||||
#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS
|
||||
assert( (pLevel->addrNxt==pLevel->addrBrk)
|
||||
|| (pLevel->addrLikeRep) /* (a) */
|
||||
|| ((pLoop->wsFlags & WHERE_IN_ABLE) && pLevel->u.in.nIn>0) /* (b) */
|
||||
);
|
||||
if( pLevel->addrLikeRep ){
|
||||
/* The LIKE optimization loop is nested inside any IN(...) and
|
||||
** skip-scan loops, so it is closed here, before either of those. */
|
||||
sqlite3VdbeAddOp2(v, OP_DecrJumpZero, (int)(pLevel->iLikeRepCntr>>1),
|
||||
pLevel->addrLikeRep);
|
||||
VdbeCoverage(v);
|
||||
}
|
||||
#endif
|
||||
if( (pLoop->wsFlags & WHERE_IN_ABLE)!=0 && pLevel->u.in.nIn>0 ){
|
||||
struct InLoop *pIn;
|
||||
int j;
|
||||
sqlite3VdbeResolveLabel(v, pLevel->addrNxt);
|
||||
for(j=pLevel->u.in.nIn, pIn=&pLevel->u.in.aInLoop[j-1]; j>0; j--, pIn--){
|
||||
assert( sqlite3VdbeGetOp(v, pIn->addrInTop+1)->opcode==OP_IsNull
|
||||
|| pParse->db->mallocFailed );
|
||||
@@ -7719,13 +7738,6 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
|
||||
sqlite3VdbeJumpHere(v, pLevel->addrSkip);
|
||||
sqlite3VdbeJumpHere(v, pLevel->addrSkip-2);
|
||||
}
|
||||
#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS
|
||||
if( pLevel->addrLikeRep ){
|
||||
sqlite3VdbeAddOp2(v, OP_DecrJumpZero, (int)(pLevel->iLikeRepCntr>>1),
|
||||
pLevel->addrLikeRep);
|
||||
VdbeCoverage(v);
|
||||
}
|
||||
#endif
|
||||
if( pLevel->iLeftJoin ){
|
||||
int ws = pLoop->wsFlags;
|
||||
addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin); VdbeCoverage(v);
|
||||
|
||||
@@ -2093,9 +2093,6 @@ Bitmask sqlite3WhereCodeOneLoopStart(
|
||||
assert( pRangeStart!=0 ); /* LIKE opt constraints */
|
||||
assert( pRangeStart->wtFlags & TERM_LIKEOPT ); /* occur in pairs */
|
||||
pLevel->iLikeRepCntr = (u32)++pParse->nMem;
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, 1, (int)pLevel->iLikeRepCntr);
|
||||
VdbeComment((v, "LIKE loop counter"));
|
||||
pLevel->addrLikeRep = sqlite3VdbeCurrentAddr(v);
|
||||
/* iLikeRepCntr actually stores 2x the counter register number. The
|
||||
** bottom bit indicates whether the search order is ASC or DESC. */
|
||||
testcase( bRev );
|
||||
@@ -2159,6 +2156,20 @@ Bitmask sqlite3WhereCodeOneLoopStart(
|
||||
codeCursorHint(pTabItem, pWInfo, pLevel, pRangeEnd);
|
||||
regBase = codeAllEqualityTerms(pParse,pLevel,bRev,nExtraReg,&zStartAff);
|
||||
assert( zStartAff==0 || sqlite3Strlen30(zStartAff)>=nEq );
|
||||
#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS
|
||||
if( pLevel->iLikeRepCntr ){
|
||||
/* The LIKE optimization loop must be the innermost of any IN(...)
|
||||
** or skip-scan loops. Otherwise rows may not be visited in index
|
||||
** order. This means it needs an addrNxt label for the same reason
|
||||
** IN(...) scans do. */
|
||||
if( pLevel->addrNxt==pLevel->addrBrk ){
|
||||
pLevel->addrNxt = sqlite3VdbeMakeLabel(pParse);
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, 1, (int)(pLevel->iLikeRepCntr>>1));
|
||||
VdbeComment((v, "LIKE loop counter"));
|
||||
pLevel->addrLikeRep = sqlite3VdbeCurrentAddr(v);
|
||||
}
|
||||
#endif
|
||||
if( zStartAff && nTop ){
|
||||
zEndAff = sqlite3DbStrDup(db, &zStartAff[nEq]);
|
||||
}
|
||||
|
||||
216
test/like4.test
Normal file
216
test/like4.test
Normal file
@@ -0,0 +1,216 @@
|
||||
# 2026-10-15
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# This file implements regression tests for SQLite library.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix like4
|
||||
|
||||
ifcapable !like_match_blobs {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t0(a, c);
|
||||
INSERT INTO t0 VALUES(1, 'A');
|
||||
INSERT INTO t0 VALUES(2, 'A');
|
||||
INSERT INTO t0 VALUES(1, X'41');
|
||||
INSERT INTO t0 VALUES(2, X'41');
|
||||
}
|
||||
|
||||
foreach {tn idx} {
|
||||
1 ""
|
||||
2 "CREATE INDEX idx ON t0(a, c)"
|
||||
3 "CREATE INDEX idx ON t0(c, a)"
|
||||
} {
|
||||
|
||||
execsql { DROP INDEX IF EXISTS idx }
|
||||
execsql $idx
|
||||
|
||||
do_execsql_test 1.$tn.1 {
|
||||
SELECT a, count(*) FROM t0 WHERE a IN (1,2) AND c GLOB 'A' GROUP BY a;
|
||||
} {1 2 2 2}
|
||||
|
||||
do_execsql_test 1.$tn.2 {
|
||||
SELECT a, quote(c) FROM t0 WHERE a IN (1,2) AND c GLOB 'A' ORDER BY a;
|
||||
} {
|
||||
1 'A'
|
||||
1 X'41'
|
||||
2 'A'
|
||||
2 X'41'
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
CREATE INDEX t1b ON t1(b);
|
||||
|
||||
INSERT INTO t1 VALUES(1, 'abc');
|
||||
INSERT INTO t1 VALUES(2, X'616263');
|
||||
INSERT INTO t1 VALUES(3, 'abd');
|
||||
INSERT INTO t1 VALUES(4, X'616264');
|
||||
}
|
||||
|
||||
do_execsql_test 2.1 {
|
||||
SELECT a FROM t1 WHERE b GLOB 'ab*' ORDER BY b
|
||||
} {1 3 2 4}
|
||||
do_execsql_test 2.2 {
|
||||
SELECT a FROM t1 WHERE b GLOB 'ab*' ORDER BY b DESC
|
||||
} {4 2 3 1}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
|
||||
do_execsql_test 3.0 {
|
||||
CREATE TABLE t(a, c);
|
||||
CREATE INDEX ti ON t(a, c);
|
||||
WITH RECURSIVE n(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM n WHERE i<100)
|
||||
INSERT INTO t SELECT i%3, 'x'||i FROM n;
|
||||
INSERT INTO t VALUES(2, 'A'), (1, x'41');
|
||||
}
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
SELECT a, quote(c) FROM t WHERE c GLOB 'A' ORDER BY a;
|
||||
} {
|
||||
1 X'41'
|
||||
2 'A'
|
||||
}
|
||||
|
||||
do_execsql_test 3.2 {
|
||||
ANALYZE;
|
||||
}
|
||||
|
||||
do_execsql_test 3.3 {
|
||||
SELECT a, quote(c) FROM t WHERE c GLOB 'A' ORDER BY a;
|
||||
} {
|
||||
1 X'41'
|
||||
2 'A'
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Skip-scan on combined with IN(...) and the LIKE optimization.
|
||||
#
|
||||
reset_db
|
||||
|
||||
do_execsql_test 4.0 {
|
||||
CREATE TABLE t(a, b, c);
|
||||
CREATE INDEX ti ON t(a, b, c);
|
||||
WITH RECURSIVE n(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM n WHERE i<300)
|
||||
INSERT INTO t SELECT i%3, i%2, 'x'||i FROM n;
|
||||
INSERT INTO t VALUES(2, 0, 'A'), (2, 1, x'41');
|
||||
INSERT INTO t VALUES(1, 1, 'A'), (1, 0, x'41');
|
||||
INSERT INTO t VALUES(0, 1, 'A');
|
||||
ANALYZE;
|
||||
}
|
||||
|
||||
do_eqp_test 4.1 {
|
||||
SELECT a, b, quote(c) FROM t WHERE b IN (0,1) AND c GLOB 'A' ORDER BY a, b;
|
||||
} {SEARCH t USING COVERING INDEX ti (ANY(a) AND b=? AND c>? AND c<?)}
|
||||
|
||||
do_execsql_test 4.2 {
|
||||
SELECT a, b, quote(c) FROM t WHERE b IN (0,1) AND c GLOB 'A' ORDER BY a, b;
|
||||
} {
|
||||
0 1 'A'
|
||||
1 0 X'41'
|
||||
1 1 'A'
|
||||
2 0 'A'
|
||||
2 1 X'41'
|
||||
}
|
||||
|
||||
do_execsql_test 4.3 {
|
||||
SELECT a, b, quote(c) FROM t WHERE b IN (0,1) AND c GLOB 'A'
|
||||
ORDER BY a DESC, b DESC;
|
||||
} {
|
||||
2 1 X'41'
|
||||
2 0 'A'
|
||||
1 1 'A'
|
||||
1 0 X'41'
|
||||
0 1 'A'
|
||||
}
|
||||
|
||||
do_execsql_test 4.4 {
|
||||
SELECT a, count(*) FROM t WHERE b IN (0,1) AND c GLOB 'A' GROUP BY a;
|
||||
} {0 1 1 2 2 2}
|
||||
|
||||
# Mixed ASC and DESC.
|
||||
#
|
||||
foreach {tn idx nosort} {
|
||||
1 "a DESC, b, c" {"a, b DESC" "a DESC, b" "a DESC, b, c"
|
||||
"a, b DESC, c DESC"}
|
||||
2 "a, b DESC, c" {"a, b DESC" "a DESC, b"}
|
||||
3 "a, b, c DESC" {"a, b, c DESC" "a DESC, b DESC, c"}
|
||||
4 "a DESC, b DESC, c" {"a, b, c DESC" "a DESC, b DESC, c"}
|
||||
} {
|
||||
execsql "
|
||||
DROP INDEX ti;
|
||||
CREATE INDEX ti ON t($idx);
|
||||
ANALYZE;
|
||||
"
|
||||
|
||||
foreach {tn2 ob res} {
|
||||
1 "a, b DESC" {0 1 'A' 1 1 'A' 1 0 X'41' 2 1 X'41' 2 0 'A'}
|
||||
2 "a DESC, b" {2 0 'A' 2 1 X'41' 1 0 X'41' 1 1 'A' 0 1 'A'}
|
||||
3 "a, b, c DESC" {0 1 'A' 1 0 X'41' 1 1 'A' 2 0 'A' 2 1 X'41'}
|
||||
4 "a DESC, b, c" {2 0 'A' 2 1 X'41' 1 0 X'41' 1 1 'A' 0 1 'A'}
|
||||
5 "a, b DESC, c DESC" {0 1 'A' 1 1 'A' 1 0 X'41' 2 1 X'41' 2 0 'A'}
|
||||
6 "a DESC, b DESC, c" {2 1 X'41' 2 0 'A' 1 1 'A' 1 0 X'41' 0 1 'A'}
|
||||
} {
|
||||
do_execsql_test 4.5.$tn.$tn2 "
|
||||
SELECT a, b, quote(c) FROM t WHERE b IN (0,1) AND c GLOB 'A'
|
||||
ORDER BY $ob
|
||||
" $res
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# LIKE optimization on the right-hand table of a RIGHT JOIN.
|
||||
#
|
||||
reset_db
|
||||
|
||||
do_execsql_test 5.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
CREATE TABLE t2(x, y);
|
||||
CREATE INDEX t2xy ON t2(x, y);
|
||||
INSERT INTO t1 VALUES(1, 'one'), (3, 'three');
|
||||
INSERT INTO t2 VALUES(1, 'Ax'), (1, x'4179'), (1, 'Bz');
|
||||
INSERT INTO t2 VALUES(3, x'417a'), (3, 'Az');
|
||||
INSERT INTO t2 VALUES(4, 'Aq'), (4, x'4171');
|
||||
}
|
||||
|
||||
do_eqp_test 5.1 {
|
||||
SELECT b, x, quote(y) FROM t1 RIGHT JOIN t2 ON a=x AND y GLOB 'A*'
|
||||
ORDER BY x, y
|
||||
} {
|
||||
QUERY PLAN
|
||||
|--SCAN t1
|
||||
|--SEARCH t2 USING INDEX t2xy (x=? AND y>? AND y<?)
|
||||
|--RIGHT-JOIN t2
|
||||
| `--SCAN t2
|
||||
`--USE TEMP B-TREE FOR ORDER BY
|
||||
}
|
||||
|
||||
do_execsql_test 5.2 {
|
||||
SELECT b, x, quote(y) FROM t1 RIGHT JOIN t2 ON a=x AND y GLOB 'A*'
|
||||
ORDER BY x, y
|
||||
} {
|
||||
one 1 'Ax' {} 1 'Bz' one 1 X'4179'
|
||||
three 3 'Az' three 3 X'417A'
|
||||
{} 4 'Aq' {} 4 X'4171'
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
Reference in New Issue
Block a user