mirror of
https://github.com/sqlite/sqlite.git
synced 2026-10-23 21:49:02 +03:00
Avoid excessive reprepares when there is an unindexed "col=?" constraint on a join query. This should only be reprepared when the bound value toggles between integer values 0 or 1 and any other value. Report [bugs:/info/2026-10-11T16:06:46Z | 2026-10-11T16:06:46Z].
FossilOrigin-Name: 7f154192db5a3a79f4666edd31dfc29fd73904ccd3630952e3b032e49fee6455
This commit is contained in:
20
manifest
20
manifest
@@ -1,5 +1,5 @@
|
||||
C When\sconfiguring\swith\s--editline,\sbe\ssure\sto\sdefine\sHAVE_EDITLINE.\sAddresses\s[forum:8b9b30f6f4|forum\spost\s2026-10-12T12:33:03Z].
|
||||
D 2026-10-12T12:48:17.427
|
||||
C Avoid\sexcessive\sreprepares\swhen\sthere\sis\san\sunindexed\s"col=?"\sconstraint\son\sa\sjoin\squery.\sThis\sshould\sonly\sbe\sreprepared\swhen\sthe\sbound\svalue\stoggles\sbetween\sinteger\svalues\s0\sor\s1\sand\sany\sother\svalue.\sReport\s[bugs:/info/2026-10-11T16:06:46Z\s|\s2026-10-11T16:06:46Z].
|
||||
D 2026-10-12T13:36:00.777
|
||||
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
@@ -752,7 +752,7 @@ F src/date.c 080641f59097d14fc7341f8bc0458d273b3a62d8381916db8ccbc89c2aae07b3
|
||||
F src/dbpage.c ec8590c5e7a564cef28ca3c6156521316a4d26cb572a34841b0bcb26b67b3f97
|
||||
F src/dbstat.c 34d4e825fbf763640ce3dbc56910ab080495ef4a6efb48b2b1852a43d43763fc
|
||||
F src/delete.c 59eeca3fb88c29329afc41bb803ee568b120d9dd7470b5f38ab55cc38390b451
|
||||
F src/expr.c 439921b2b411ca54176f7f069af93743fe587194f6a17032bbb8fa93cf201b27
|
||||
F src/expr.c fd606b947a176f3a4567e41a80f535e0260822a94027c3d493481648895351e5
|
||||
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
|
||||
F src/fkey.c 26788ae6c785d70f9829c4176e9764447014fc73e6ba31e9f1881720a64ae7f1
|
||||
F src/func.c 54d96eeafcb6ec4cff92028ea0f7bb09e27b375b248d969aeb80a61e560ef302
|
||||
@@ -871,8 +871,8 @@ F src/vacuum.c d3d35d8ae893d419ade5fa196d761a83bddcbb62137a1a157ae751ef38b26e82
|
||||
F src/vdbe.c 2954169319fef68d5119e9855637e7a44dc64f34ee474d68001a05c03072cb48
|
||||
F src/vdbe.h b6ffb28da04363fd5d439b8204118fd3f2e751abe4f8e3edb089155fb90d4607
|
||||
F src/vdbeInt.h 68e5ab63f184f2871780c83ce78b425d0360869611baea4c65575f91f76ac1d3
|
||||
F src/vdbeapi.c 8015628172d959885d5df592b9ddc07c66a803f61f86f1197cc17929c095b2ca
|
||||
F src/vdbeaux.c 45685e76ffa1a24bd6e898005c8384868cfd629d6759413890e3ec9316eb3cf1
|
||||
F src/vdbeapi.c 5929b6010966f1f8f5be8605323cfa004b8f68cfa056735f8f283d85fae81391
|
||||
F src/vdbeaux.c 27d92528e7953a80f64782ef75e65a6694277214082e87d125e74e52d9e7ecd5
|
||||
F src/vdbeblob.c b3f0640db9642fbdc88bd6ebcc83d6009514cafc98f062f675f2c8d505d82692
|
||||
F src/vdbemem.c b2113c949e2ed918294e16a5ebaacdaca6be4f820f670517e3362e94c7019432
|
||||
F src/vdbesort.c a72ad2a629df2901fde4aa588e38590d0aa27b7d5bc3b2a416e5f295de839cc1
|
||||
@@ -1622,6 +1622,7 @@ F test/regexp1.sql a57e8d2777c7d62142bc802a7f18791ed5f17561ce6a1ca6ff287ba1a8c87
|
||||
F test/regexp1.test 0023eae4073265641b826a70d81ba34d4dd66ad71871a5b4a1b7cf500d5c0c51
|
||||
F test/regexp2.test 64f9726b2ddc71aea06725fcad53231833d038d58b936d49083ace658b370a13
|
||||
F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d
|
||||
F test/reprepare.test ef52e80ecdc4b0e500324d1891c49a26c51b615a9fdf458ede807993f8e6337f
|
||||
F test/reservebytes.test 6163640b5a5120c0dee6591481e673a0fa0bf0d12d4da7513bad692c1a49a162
|
||||
F test/resetdb.test 54c06f18bc832ac6d6319e5ab23d5c8dd49fdbeec7c696d791682a8006bd5fc3
|
||||
F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb
|
||||
@@ -2283,9 +2284,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee
|
||||
F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2
|
||||
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
|
||||
F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
|
||||
P 313ab6c4e517e599fa92102a1aa367fc97e9fd15a513f12ab9e8f4afebdf31f8
|
||||
Q +85adb10e22ac33500f974d756bcf1fac46b7d98868cb4e00e59f30c075001b5b
|
||||
R 6fdccfc782b336a52a781e68d86c9142
|
||||
U stephan
|
||||
Z a6f2f5825502fd610ee2383a8dcc572b
|
||||
P af570c8fc22dc682ab71d8e97c20268971fb33032530d3fa9908e1995bed529e
|
||||
R 6661c559ed600e427ce67ac1d1dfb05f
|
||||
U dan
|
||||
Z a2c5b3297ece60c69bf73817556b3a17
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
||||
@@ -1 +1 @@
|
||||
af570c8fc22dc682ab71d8e97c20268971fb33032530d3fa9908e1995bed529e
|
||||
7f154192db5a3a79f4666edd31dfc29fd73904ccd3630952e3b032e49fee6455
|
||||
|
||||
11
src/expr.c
11
src/expr.c
@@ -2957,10 +2957,7 @@ int sqlite3ExprContainsSubquery(Expr *p){
|
||||
** is marked so as to cause the statement to be reprepared each time a new
|
||||
** value is bound to it. Except, if parameter bRSI is true, then the statement
|
||||
** will only be reprepared if the rebind changes the value to or from a
|
||||
** "small integer" (either 0 or 1). Note that if p is a variable then
|
||||
** reprepare is always enabled for that variable, regardless of its current
|
||||
** binding. The RSI is only enabled if the current binding is a small
|
||||
** integer. "RSI" stands for "Reprepare Small Integers".
|
||||
** "small integer" (either 0 or 1).
|
||||
*/
|
||||
int sqlite3ExprIsInteger(const Expr *p, int *pValue, Parse *pParse, int bRSI){
|
||||
int iSign = 1; /* Either +1 or -1. */
|
||||
@@ -2983,7 +2980,6 @@ int sqlite3ExprIsInteger(const Expr *p, int *pValue, Parse *pParse, int bRSI){
|
||||
}
|
||||
if( p->op==TK_VARIABLE && pParse!=0 ){
|
||||
sqlite3_value *pVal; /* The variable */
|
||||
int isSmall = 0; /* Only reprepare if change to/from small integer */
|
||||
int rc = 0; /* 1 if successful, 0 if failed */
|
||||
assert( iSign==1 );
|
||||
if( NEVER(pParse->pVdbe==0) ) break;
|
||||
@@ -2997,14 +2993,11 @@ int sqlite3ExprIsInteger(const Expr *p, int *pValue, Parse *pParse, int bRSI){
|
||||
&& vv<=0x7fffffff
|
||||
){
|
||||
*pValue = (int)vv;
|
||||
if( bRSI ){
|
||||
isSmall = vv<=1;
|
||||
}
|
||||
rc = 1;
|
||||
}
|
||||
sqlite3ValueFree(pVal);
|
||||
}
|
||||
sqlite3VdbeReprepareOnBind(pParse->pVdbe, p->iColumn, isSmall);
|
||||
sqlite3VdbeReprepareOnBind(pParse->pVdbe, p->iColumn, bRSI);
|
||||
return rc;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1736,10 +1736,8 @@ static int vdbeUnbind(Vdbe *p, unsigned int i){
|
||||
sqlite3_mutex_leave(p->db->mutex);
|
||||
return SQLITE_RANGE;
|
||||
}
|
||||
|
||||
pVar = &p->aVar[i];
|
||||
sqlite3VdbeMemRelease(pVar);
|
||||
pVar->flags = MEM_Null;
|
||||
p->db->errCode = SQLITE_OK;
|
||||
|
||||
/* If the bit corresponding to this variable in Vdbe.expmask is set, then
|
||||
** binding a new value to this variable invalidates the current query plan.
|
||||
@@ -1753,11 +1751,20 @@ static int vdbeUnbind(Vdbe *p, unsigned int i){
|
||||
assert( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || p->expmask==0 );
|
||||
assert( (p->expmask & p->smimask)==p->smimask );
|
||||
if( p->expmask!=0 && (p->expmask & (i>=31 ? 0x80000000 : (u32)1<<i))!=0 ){
|
||||
/* We might avoid a reprepare here if p->smimask is set and the old
|
||||
** value is an integer other than (0,1). But that is such a corner
|
||||
** case that it does not seem worth the extra code to implement. */
|
||||
p->expired = 1;
|
||||
/* If the p->smimask bit is also set, then only expire the statement if
|
||||
** the value being clobbered was integer value 0 or 1. */
|
||||
if( (p->smimask==0)
|
||||
|| (p->smimask & (i>=31 ? 0x80000000 : (u32)1<<i))==0
|
||||
|| (pVar->flags & MEM_Int)!=0 && pVar->u.i>=0 && pVar->u.i<=1
|
||||
){
|
||||
p->expired = 1;
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3VdbeMemRelease(pVar);
|
||||
pVar->flags = MEM_Null;
|
||||
p->db->errCode = SQLITE_OK;
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
@@ -1885,8 +1892,8 @@ int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){
|
||||
if( sm1!=sm2 ){
|
||||
p->expired = 1;
|
||||
}
|
||||
}else if( (pVar->flags & MEM_Int)==0 || pVar->u.i!=iValue ){
|
||||
/* Always expire if the value really is changing */
|
||||
}else{
|
||||
/* Always expire if the smimask bit is clear */
|
||||
p->expired = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5351,10 +5351,12 @@ void sqlite3VdbeReprepareOnBind(Vdbe *v, int iVar, int bSmallint){
|
||||
|| (v->db->mDbFlags & DBFLAG_InternalFunc)!=0 );
|
||||
|
||||
m = (iVar>=32) ? 0x80000000 : ((u32)1 << (iVar-1));
|
||||
v->expmask |= m;
|
||||
if( bSmallint ){
|
||||
v->smimask |= m;
|
||||
if( (v->expmask & m)==0 ) v->smimask |= m;
|
||||
}else{
|
||||
v->smimask &= ~m;
|
||||
}
|
||||
v->expmask |= m;
|
||||
|
||||
/* smimask is always a subset of expmask */
|
||||
assert( (v->smimask & v->expmask) == v->smimask );
|
||||
|
||||
121
test/reprepare.test
Normal file
121
test/reprepare.test
Normal file
@@ -0,0 +1,121 @@
|
||||
# 2026 October 12
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Specifically, it tests that statements that should only be reprepared
|
||||
# when a bound variable is changed to or from a small integer value
|
||||
# (0 or 1), really is only reprepared in those cases.
|
||||
#
|
||||
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix reprepare
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(id INTEGER PRIMARY KEY, nm TEXT);
|
||||
INSERT INTO t1 VALUES(4294967296,'a'), (4294967297,'b');
|
||||
}
|
||||
|
||||
proc do_reprepare_count_test {tn sql lInt nReprepare} {
|
||||
set stmt [sqlite3_prepare_v2 db $sql -1 dummy]
|
||||
foreach ii $lInt {
|
||||
set ii [expr $ii]
|
||||
sqlite3_bind_int64 $stmt 1 $ii
|
||||
sqlite3_step $stmt
|
||||
sqlite3_reset $stmt
|
||||
}
|
||||
|
||||
do_test $tn [list \
|
||||
sqlite3_stmt_status $stmt SQLITE_STMTSTATUS_REPREPARE 0
|
||||
] [list $nReprepare]
|
||||
|
||||
sqlite3_finalize $stmt
|
||||
}
|
||||
|
||||
do_reprepare_count_test 1.1 {
|
||||
SELECT * FROM t1 WHERE +t1.id=?1
|
||||
} {4294967296 4294967297 4294967296 4294967297} 0
|
||||
|
||||
do_reprepare_count_test 1.2 {
|
||||
SELECT a.nm, b.nm FROM t1 a, t1 b WHERE a.id=?1 AND b.id=a.id
|
||||
} {4294967296 4294967297 4294967296 4294967297} 0
|
||||
|
||||
proc do_reprepare_test {tn sql lTest} {
|
||||
set stmt [sqlite3_prepare_v2 db $sql -1 dummy]
|
||||
|
||||
set cnt 0
|
||||
foreach {t res} $lTest {
|
||||
incr cnt
|
||||
eval $t
|
||||
sqlite3_step $stmt
|
||||
sqlite3_reset $stmt
|
||||
do_test $tn.$cnt [list \
|
||||
sqlite3_stmt_status $stmt SQLITE_STMTSTATUS_REPREPARE 1
|
||||
] [list $res]
|
||||
}
|
||||
|
||||
sqlite3_finalize $stmt
|
||||
}
|
||||
|
||||
do_reprepare_test 1.3 {
|
||||
SELECT a.nm, b.nm FROM t1 a, t1 b WHERE a.id=?1 AND b.id=a.id
|
||||
} {
|
||||
{ sqlite3_bind_int64 $stmt 1 45 } 0
|
||||
{ sqlite3_bind_int64 $stmt 1 0 } 1
|
||||
{ sqlite3_bind_int64 $stmt 1 1 } 0
|
||||
{ sqlite3_bind_int64 $stmt 1 101 } 1
|
||||
{ sqlite3_bind_null $stmt 1 } 0
|
||||
{ sqlite3_bind_text $stmt 1 hello -1 } 0
|
||||
{ sqlite3_bind_int64 $stmt 1 1 } 1
|
||||
{ sqlite3_bind_text $stmt 1 world -1 } 1
|
||||
{ sqlite3_bind_int64 $stmt 1 450 } 0
|
||||
{ sqlite3_bind_int64 $stmt 1 0 } 1
|
||||
{ sqlite3_bind_null $stmt 1 } 1
|
||||
{ sqlite3_bind_int64 $stmt 1 0 } 1
|
||||
{ sqlite3_bind_int64 $stmt 1 1 } 0
|
||||
{ sqlite3_bind_int64 $stmt 1 2 } 1
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that if a variable is used both in a context where only a change
|
||||
# to or from a small integer requires a reprepare (an equality comparison)
|
||||
# and in a context where any change requires a reprepare (a LIMIT clause),
|
||||
# the statement is reprepared every time a new value is bound.
|
||||
#
|
||||
do_execsql_test 2.0 {
|
||||
CREATE TABLE t2(x INTEGER PRIMARY KEY, y);
|
||||
WITH s(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<10)
|
||||
INSERT INTO t2 SELECT i, i FROM s;
|
||||
}
|
||||
|
||||
proc do_limit_test {tn sql lTest} {
|
||||
set stmt [sqlite3_prepare_v2 db $sql -1 dummy]
|
||||
set cnt 0
|
||||
foreach {val res} $lTest {
|
||||
incr cnt
|
||||
sqlite3_bind_int64 $stmt 1 $val
|
||||
set nRow 0
|
||||
while {[sqlite3_step $stmt]=="SQLITE_ROW"} { incr nRow }
|
||||
sqlite3_reset $stmt
|
||||
do_test $tn.$cnt [list set {} $nRow] [list $res]
|
||||
}
|
||||
sqlite3_finalize $stmt
|
||||
}
|
||||
|
||||
do_limit_test 2.1 {
|
||||
SELECT b.x FROM t2 a, t2 b WHERE a.x=?1 AND b.x>=a.x LIMIT ?1
|
||||
} {
|
||||
2 2 3 3 0 0 4 4 6 5 1 1 5 5 7 4 4 4
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
Reference in New Issue
Block a user