diff --git a/manifest b/manifest index 3d977a7ab7..58be463dd1 100644 --- a/manifest +++ b/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. diff --git a/manifest.uuid b/manifest.uuid index 29318a2cb9..67542ea250 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -af570c8fc22dc682ab71d8e97c20268971fb33032530d3fa9908e1995bed529e +7f154192db5a3a79f4666edd31dfc29fd73904ccd3630952e3b032e49fee6455 diff --git a/src/expr.c b/src/expr.c index 26fc07ca12..0507408e55 100644 --- a/src/expr.c +++ b/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; diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 7d7b0239b1..51a6bed242 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -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<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<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; } } diff --git a/src/vdbeaux.c b/src/vdbeaux.c index f6f6d81cbf..f33684b14d 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -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 ); diff --git a/test/reprepare.test b/test/reprepare.test new file mode 100644 index 0000000000..ee6cc9cd8d --- /dev/null +++ b/test/reprepare.test @@ -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 +