]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
The first of a planned series of enhancements to the query planner that
authordrh <drh@noemail.net>
Thu, 20 Jan 2011 02:56:37 +0000 (02:56 +0000)
committerdrh <drh@noemail.net>
Thu, 20 Jan 2011 02:56:37 +0000 (02:56 +0000)
enable it to make better use of sqlite_stat2 histograms when the table
has many repeated values.

FossilOrigin-Name: 2cd374cd23fa2fd38f49090d6eeb9b1e521d51d5

manifest
manifest.uuid
src/where.c
test/analyze2.test
test/analyze5.test [new file with mode: 0644]

index f9bb697af73533b7c45c73f26a4c1c049e15d159..00b3208ed7a866c9a80d8cf4d721b518030b173e 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,8 +1,8 @@
 -----BEGIN PGP SIGNED MESSAGE-----
 Hash: SHA1
 
-C Comment\simprovements\sin\spcache1.c.\s\sNo\schanges\sto\scode.
-D 2011-01-19T21:58:56.344
+C The\sfirst\sof\sa\splanned\sseries\sof\senhancements\sto\sthe\squery\splanner\sthat\nenable\sit\sto\smake\sbetter\suse\sof\ssqlite_stat2\shistograms\swhen\sthe\stable\nhas\smany\srepeated\svalues.
+D 2011-01-20T02:56:37.736
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in de6498556d536ae60bb8bb10e8c1ba011448658c
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -243,7 +243,7 @@ F src/vtab.c b297e8fa656ab5e66244ab15680d68db0adbec30
 F src/wal.c dbca424f71678f663a286ab2a98f947af1d412a7
 F src/wal.h c1aac6593a0b02b15dc625987e619edeab39292e
 F src/walker.c 3112bb3afe1d85dc52317cb1d752055e9a781f8f
-F src/where.c af069e6b53234118014dabfece96a9515b69d76b
+F src/where.c 5cd6b88d57bfc816ba7f753a3cdf03686d954b8a
 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
 F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87
 F test/all.test 51756962d522e474338e9b2ebb26e7364d4aa125
@@ -253,9 +253,10 @@ F test/alter3.test 8677e48d95536f7a6ed86a1a774744dadcc22b07
 F test/alter4.test 1e5dd6b951e9f65ca66422edff02e56df82dd403
 F test/altermalloc.test e81ac9657ed25c6c5bb09bebfa5a047cd8e4acfc
 F test/analyze.test c1eb87067fc16ece7c07e823d6395fd831b270c5
-F test/analyze2.test 3bde8f0879d9c1f2df3af21fcf42e706d8ee1e43
+F test/analyze2.test f45ac8d54bdad822139e53fc6307fc6b5ee41c69
 F test/analyze3.test 820ddfb7591b49607fbaf77240c7955ac3cabb04
 F test/analyze4.test 757b37875cf9bb528d46f74497bc789c88365045
+F test/analyze5.test 18987796646efdf009ca0b8c8f060874a8fe57fb
 F test/async.test ad4ba51b77cd118911a3fe1356b0809da9c108c3
 F test/async2.test bf5e2ca2c96763b4cba3d016249ad7259a5603b6
 F test/async3.test 93edaa9122f498e56ea98c36c72abc407f4fb11e
@@ -899,14 +900,18 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P e5ca59e63b18ac45a8c82ca39dc8cce1c4ce903c
-R 246c042190fbeb83b3f1f3c471b7048c
+P 9660a0a22547656cc3765b673d0cee9e1dd829ef
+R 9ff1bb21abd03a28e074b829beb25c52
+T *bgcolor * #a8c7d3
+T *branch * stat2-enhancement
+T *sym-stat2-enhancement *
+T -sym-trunk *
 U drh
-Z d4b4af53a8b712c86792c5f30e91ad66
+Z f191562671825ddb731a0f83c41674dd
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1.4.6 (GNU/Linux)
 
-iD8DBQFNN16joxKgR168RlERApzpAJ9ub7vBM3MbU8WJio56+Ng2W8PfBQCeLClb
-tiAK9YA5ekgfGTTQ91uwQlY=
-=gzKU
+iD8DBQFNN6RpoxKgR168RlERAtbyAJ4tlGP5CKHBEdaaRtF9LD6pnMJo9QCaAnxY
+oc508+oZBxzr/UoIZL3o+G4=
+=2eyE
 -----END PGP SIGNATURE-----
index 96c640d6cfa1f71bb3aaa24432acd86a70081565..a32e6ed42f8d84f985c44665249d39152c974dc5 100644 (file)
@@ -1 +1 @@
-9660a0a22547656cc3765b673d0cee9e1dd829ef
\ No newline at end of file
+2cd374cd23fa2fd38f49090d6eeb9b1e521d51d5
\ No newline at end of file
index 4a5026f5f45fbe939aed188770c2e4c06d9f1f0c..734f24019ff9a33bd2d91f9d44fc2289c82c4bb2 100644 (file)
@@ -2201,11 +2201,18 @@ static void bestVirtualIndex(
 /*
 ** Argument pIdx is a pointer to an index structure that has an array of
 ** SQLITE_INDEX_SAMPLES evenly spaced samples of the first indexed column
-** stored in Index.aSample. The domain of values stored in said column
-** may be thought of as divided into (SQLITE_INDEX_SAMPLES+1) regions.
-** Region 0 contains all values smaller than the first sample value. Region
-** 1 contains values larger than or equal to the value of the first sample,
-** but smaller than the value of the second. And so on.
+** stored in Index.aSample. These samples divide the domain of values stored
+** the index into (SQLITE_INDEX_SAMPLES+1) regions.
+** Region 0 contains all values less than the first sample value. Region
+** 1 contains values between the first and second samples.  Region 2 contains
+** values between samples 2 and 3.  And so on.  Region SQLITE_INDEX_SAMPLES
+** contains values larger than the last sample.
+**
+** If the index contains many duplicates of a single value, then it is
+** possible that two or more adjacent samples can hold the same value.
+** When that is the case, the smallest possible region code is returned
+** when roundUp is false and the largest possible region code is returned
+** when roundUp is true.
 **
 ** If successful, this function determines which of the regions value 
 ** pVal lies in, sets *piRegion to the region index (a value between 0
@@ -2218,8 +2225,10 @@ static int whereRangeRegion(
   Parse *pParse,              /* Database connection */
   Index *pIdx,                /* Index to consider domain of */
   sqlite3_value *pVal,        /* Value to consider */
+  int roundUp,                /* Return largest valid region if true */
   int *piRegion               /* OUT: Region of domain in which value lies */
 ){
+  assert( roundUp==0 || roundUp==1 );
   if( ALWAYS(pVal) ){
     IndexSample *aSample = pIdx->aSample;
     int i = 0;
@@ -2229,7 +2238,12 @@ static int whereRangeRegion(
       double r = sqlite3_value_double(pVal);
       for(i=0; i<SQLITE_INDEX_SAMPLES; i++){
         if( aSample[i].eType==SQLITE_NULL ) continue;
-        if( aSample[i].eType>=SQLITE_TEXT || aSample[i].u.r>r ) break;
+        if( aSample[i].eType>=SQLITE_TEXT ) break;
+        if( roundUp ){
+          if( aSample[i].u.r>r ) break;
+        }else{
+          if( aSample[i].u.r>=r ) break;
+        }
       }
     }else{ 
       sqlite3 *db = pParse->db;
@@ -2260,7 +2274,7 @@ static int whereRangeRegion(
       n = sqlite3ValueBytes(pVal, pColl->enc);
 
       for(i=0; i<SQLITE_INDEX_SAMPLES; i++){
-        int r;
+        int c;
         int eSampletype = aSample[i].eType;
         if( eSampletype==SQLITE_NULL || eSampletype<eType ) continue;
         if( (eSampletype!=eType) ) break;
@@ -2274,14 +2288,14 @@ static int whereRangeRegion(
             assert( db->mallocFailed );
             return SQLITE_NOMEM;
           }
-          r = pColl->xCmp(pColl->pUser, nSample, zSample, n, z);
+          c = pColl->xCmp(pColl->pUser, nSample, zSample, n, z);
           sqlite3DbFree(db, zSample);
         }else
 #endif
         {
-          r = pColl->xCmp(pColl->pUser, aSample[i].nByte, aSample[i].u.z, n, z);
+          c = pColl->xCmp(pColl->pUser, aSample[i].nByte, aSample[i].u.z, n, z);
         }
-        if( r>0 ) break;
+        if( c-roundUp>=0 ) break;
       }
     }
 
@@ -2386,15 +2400,21 @@ static int whereRangeScanEst(
     int iEst;
     int iLower = 0;
     int iUpper = SQLITE_INDEX_SAMPLES;
+    int roundUpUpper;
+    int roundUpLower;
     u8 aff = p->pTable->aCol[p->aiColumn[0]].affinity;
 
     if( pLower ){
       Expr *pExpr = pLower->pExpr->pRight;
       rc = valueFromExpr(pParse, pExpr, aff, &pLowerVal);
+      assert( pLower->eOperator==WO_GT || pLower->eOperator==WO_GE );
+      roundUpLower = (pLower->eOperator==WO_GT) ?1:0;
     }
     if( rc==SQLITE_OK && pUpper ){
       Expr *pExpr = pUpper->pExpr->pRight;
       rc = valueFromExpr(pParse, pExpr, aff, &pUpperVal);
+      assert( pUpper->eOperator==WO_LT || pUpper->eOperator==WO_LE );
+      roundUpUpper = (pUpper->eOperator==WO_LE) ?1:0;
     }
 
     if( rc!=SQLITE_OK || (pLowerVal==0 && pUpperVal==0) ){
@@ -2402,15 +2422,15 @@ static int whereRangeScanEst(
       sqlite3ValueFree(pUpperVal);
       goto range_est_fallback;
     }else if( pLowerVal==0 ){
-      rc = whereRangeRegion(pParse, p, pUpperVal, &iUpper);
+      rc = whereRangeRegion(pParse, p, pUpperVal, roundUpUpper, &iUpper);
       if( pLower ) iLower = iUpper/2;
     }else if( pUpperVal==0 ){
-      rc = whereRangeRegion(pParse, p, pLowerVal, &iLower);
+      rc = whereRangeRegion(pParse, p, pLowerVal, roundUpLower, &iLower);
       if( pUpper ) iUpper = (iLower + SQLITE_INDEX_SAMPLES + 1)/2;
     }else{
-      rc = whereRangeRegion(pParse, p, pUpperVal, &iUpper);
+      rc = whereRangeRegion(pParse, p, pUpperVal, roundUpUpper, &iUpper);
       if( rc==SQLITE_OK ){
-        rc = whereRangeRegion(pParse, p, pLowerVal, &iLower);
+        rc = whereRangeRegion(pParse, p, pLowerVal, roundUpLower, &iLower);
       }
     }
 
@@ -2418,12 +2438,12 @@ static int whereRangeScanEst(
     testcase( iEst==SQLITE_INDEX_SAMPLES );
     assert( iEst<=SQLITE_INDEX_SAMPLES );
     if( iEst<1 ){
-      iEst = 1;
+      *piEst = 50/SQLITE_INDEX_SAMPLES;
+    }else{
+      *piEst = (iEst*100)/SQLITE_INDEX_SAMPLES;
     }
-
     sqlite3ValueFree(pLowerVal);
     sqlite3ValueFree(pUpperVal);
-    *piEst = (iEst * 100)/SQLITE_INDEX_SAMPLES;
     return rc;
   }
 range_est_fallback:
index 039fb378cce274db6b131553c3e13d3802500838..add73af43a8ef5e41696468ed54cdec3a9da9ffa 100644 (file)
@@ -154,22 +154,22 @@ do_eqp_test 2.6 {
 do_eqp_test 2.7 {
   SELECT * FROM t1 WHERE x BETWEEN -400 AND -300 AND y BETWEEN 100 AND 300
 } {
-  0 0 0 {SEARCH TABLE t1 USING INDEX t1_x (x>? AND x<?) (~25 rows)}
+  0 0 0 {SEARCH TABLE t1 USING INDEX t1_x (x>? AND x<?) (~12 rows)}
 }
 do_eqp_test 2.8 {
   SELECT * FROM t1 WHERE x BETWEEN 100 AND 300 AND y BETWEEN -400 AND -300
 } {
-  0 0 0 {SEARCH TABLE t1 USING INDEX t1_y (y>? AND y<?) (~25 rows)}
+  0 0 0 {SEARCH TABLE t1 USING INDEX t1_y (y>? AND y<?) (~12 rows)}
 }
 do_eqp_test 2.9 {
   SELECT * FROM t1 WHERE x BETWEEN 500 AND 100 AND y BETWEEN 100 AND 300
 } {
-  0 0 0 {SEARCH TABLE t1 USING INDEX t1_x (x>? AND x<?) (~25 rows)}
+  0 0 0 {SEARCH TABLE t1 USING INDEX t1_x (x>? AND x<?) (~12 rows)}
 }
 do_eqp_test 2.10 {
   SELECT * FROM t1 WHERE x BETWEEN 100 AND 300 AND y BETWEEN 500 AND 100
 } {
-  0 0 0 {SEARCH TABLE t1 USING INDEX t1_y (y>? AND y<?) (~25 rows)}
+  0 0 0 {SEARCH TABLE t1 USING INDEX t1_y (y>? AND y<?) (~12 rows)}
 }
 
 do_test analyze2-3.1 {
@@ -207,7 +207,7 @@ do_eqp_test 3.3 {
 do_eqp_test 3.4 {
   SELECT * FROM t1 WHERE x BETWEEN 100 AND 400 AND y BETWEEN 'a' AND 'h'
 } {
-  0 0 0 {SEARCH TABLE t1 USING INDEX t1_x (x>? AND x<?) (~50 rows)}
+  0 0 0 {SEARCH TABLE t1 USING INDEX t1_x (x>? AND x<?) (~100 rows)}
 }
 do_eqp_test 3.5 {
   SELECT * FROM t1 WHERE x<'a' AND y>'h'
@@ -416,14 +416,14 @@ do_test analyze2-6.2.2 {
         t5.a>1 AND t5.a<15 AND
         t6.a>1
   }
-} {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
+} {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~1 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
 do_test analyze2-6.2.3 {
   sqlite3 db test.db
   eqp { SELECT * FROM t5,t6 WHERE t5.rowid=t6.rowid AND 
         t5.a>1 AND t5.a<15 AND
         t6.a>1
   }
-} {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
+} {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~1 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
 do_test analyze2-6.2.4 {
   execsql { 
     PRAGMA writable_schema = 1;
@@ -457,7 +457,7 @@ do_test analyze2-6.2.6 {
         t5.a>1 AND t5.a<15 AND
         t6.a>1
   }
-} {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
+} {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~1 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
 
 #--------------------------------------------------------------------
 # These tests, analyze2-7.*, test that the sqlite_stat2 functionality
@@ -501,7 +501,7 @@ ifcapable shared_cache {
           t5.a>1 AND t5.a<15 AND
           t6.a>1
     } db1
-  } {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
+  } {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~1 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
   do_test analyze2-7.6 {
     incr_schema_cookie test.db
     execsql { SELECT * FROM sqlite_master } db2
@@ -509,7 +509,7 @@ ifcapable shared_cache {
           t5.a>1 AND t5.a<15 AND
           t6.a>1
     } db2
-  } {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
+  } {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~1 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
   do_test analyze2-7.7 {
     incr_schema_cookie test.db
     execsql { SELECT * FROM sqlite_master } db1
@@ -517,7 +517,7 @@ ifcapable shared_cache {
           t5.a>1 AND t5.a<15 AND
           t6.a>1
     } db1
-  } {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
+  } {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~1 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
 
   do_test analyze2-7.8 {
     execsql { DELETE FROM sqlite_stat2 } db2
@@ -526,14 +526,14 @@ ifcapable shared_cache {
           t5.a>1 AND t5.a<15 AND
           t6.a>1
     } db1
-  } {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
+  } {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~1 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
   do_test analyze2-7.9 {
     execsql { SELECT * FROM sqlite_master } db2
     eqp { SELECT * FROM t5,t6 WHERE t5.rowid=t6.rowid AND 
           t5.a>1 AND t5.a<15 AND
           t6.a>1
     } db2
-  } {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
+  } {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~1 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}}
 
   do_test analyze2-7.10 {
     incr_schema_cookie test.db
diff --git a/test/analyze5.test b/test/analyze5.test
new file mode 100644 (file)
index 0000000..233e7ff
--- /dev/null
@@ -0,0 +1,225 @@
+# 2011 January 19
+#
+# 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 tests for SQLite library.  The focus of the tests
+# in this file is the use of the sqlite_stat2 histogram data on tables
+# with many repeated values and only a few distinct values.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+ifcapable !stat2 {
+  finish_test
+  return
+}
+
+set testprefix analyze5
+
+proc eqp {sql {db db}} {
+  uplevel execsql [list "EXPLAIN QUERY PLAN $sql"] $db
+}
+
+do_test analyze5-1.0 {
+  execsql { CREATE TABLE t1(x INTEGER PRIMARY KEY, y, z) }
+  for {set i 0} {$i < 1000} {incr i} {
+    set j [expr {$i>=25 && $i<=50}]
+    set k [expr {($i>=400) + ($i>=700) + ($i>=875)}]
+    execsql { INSERT INTO t1 VALUES($i,$j,$k) }
+  }
+  execsql { 
+    CREATE INDEX t1y ON t1(y);
+    CREATE INDEX t1z ON t1(z);
+    ANALYZE;
+    SELECT * FROM sqlite_stat2 ORDER BY 1, 2, 3;
+  }
+} [list t1 t1y 0 0 \
+        t1 t1y 1 0 \
+        t1 t1y 2 0 \
+        t1 t1y 3 0 \
+        t1 t1y 4 0 \
+        t1 t1y 5 0 \
+        t1 t1y 6 0 \
+        t1 t1y 7 0 \
+        t1 t1y 8 0 \
+        t1 t1y 9 0 \
+        t1 t1z 0 0 \
+        t1 t1z 1 0 \
+        t1 t1z 2 0 \
+        t1 t1z 3 0 \
+        t1 t1z 4 1 \
+        t1 t1z 5 1 \
+        t1 t1z 6 1 \
+        t1 t1z 7 2 \
+        t1 t1z 8 2 \
+        t1 t1z 9 3]
+
+# Verify that range queries generate the correct row count estimates
+#
+foreach {testid where rows} {
+  1  {z>=0 AND z<=0}     400
+  2  {z>=1 AND z<=1}     300
+  3  {z>=2 AND z<=2}     200
+  4  {z>=3 AND z<=3}     100
+  5  {z>=4 AND z<=4}      50
+  6  {z>=-1 AND z<=-1}    50
+  7  {z>1 AND z<3}       200
+  8  {z>0 AND z<100}     600
+  9  {z>=1 AND z<100}    600
+ 10  {z>1 AND z<100}     300
+ 11  {z>=2 AND z<100}    300
+ 12  {z>2 AND z<100}     100
+ 13  {z>=3 AND z<100}    100
+ 14  {z>3 AND z<100}      50
+ 15  {z>=4 AND z<100}     50
+ 16  {z>=-100 AND z<=-1}  50
+ 17  {z>=-100 AND z<=0}  400
+ 18  {z>=-100 AND z<0}    50
+ 19  {z>=-100 AND z<=1}  700
+ 20  {z>=-100 AND z<2}   700
+ 21  {z>=-100 AND z<=2}  900
+ 22  {z>=-100 AND z<3}   900
+
+ 31  {z>=0.0 AND z<=0.0}   400
+ 32  {z>=1.0 AND z<=1.0}   300
+ 33  {z>=2.0 AND z<=2.0}   200
+ 34  {z>=3.0 AND z<=3.0}   100
+ 35  {z>=4.0 AND z<=4.0}    50
+ 36  {z>=-1.0 AND z<=-1.0}  50
+ 37  {z>1.5 AND z<3.0}     200
+ 38  {z>0.5 AND z<100}     600
+ 39  {z>=1.0 AND z<100}    600
+ 40  {z>1.5 AND z<100}     300
+ 41  {z>=2.0 AND z<100}    300
+ 42  {z>2.1 AND z<100}     100
+ 43  {z>=3.0 AND z<100}    100
+ 44  {z>3.2 AND z<100}      50
+ 45  {z>=4.0 AND z<100}     50
+ 46  {z>=-100 AND z<=-1.0}  50
+ 47  {z>=-100 AND z<=0.0}  400
+ 48  {z>=-100 AND z<0.0}    50
+ 49  {z>=-100 AND z<=1.0}  700
+ 50  {z>=-100 AND z<2.0}   700
+ 51  {z>=-100 AND z<=2.0}  900
+ 52  {z>=-100 AND z<3.0}   900
+
+} {
+  do_test analyze5-1.$testid {
+    eqp "SELECT * FROM t1 WHERE $where"
+  } [format {0 0 0 {SEARCH TABLE t1 USING INDEX t1z (z>? AND z<?) (~%d rows)}} \
+       $rows]
+}
+
+# Change the table values from integer to floating point and then
+# repeat the same sequence of tests.  We should get the same results.
+#
+do_test analyze5-2.0 {
+  db eval {
+    UPDATE t1 SET z=z+0.0;
+    ANALYZE;
+    SELECT sample FROM sqlite_stat2 WHERE idx='t1z' ORDER BY sampleno;
+  }
+} {0.0 0.0 0.0 0.0 1.0 1.0 1.0 2.0 2.0 3.0}
+foreach {testid where rows} {
+  1  {z>=0 AND z<=0}     400
+  2  {z>=1 AND z<=1}     300
+  3  {z>=2 AND z<=2}     200
+  4  {z>=3 AND z<=3}     100
+  5  {z>=4 AND z<=4}      50
+  6  {z>=-1 AND z<=-1}    50
+  7  {z>1 AND z<3}       200
+  8  {z>0 AND z<100}     600
+  9  {z>=1 AND z<100}    600
+ 10  {z>1 AND z<100}     300
+ 11  {z>=2 AND z<100}    300
+ 12  {z>2 AND z<100}     100
+ 13  {z>=3 AND z<100}    100
+ 14  {z>3 AND z<100}      50
+ 15  {z>=4 AND z<100}     50
+ 16  {z>=-100 AND z<=-1}  50
+ 17  {z>=-100 AND z<=0}  400
+ 18  {z>=-100 AND z<0}    50
+ 19  {z>=-100 AND z<=1}  700
+ 20  {z>=-100 AND z<2}   700
+ 21  {z>=-100 AND z<=2}  900
+ 22  {z>=-100 AND z<3}   900
+
+ 31  {z>=0.0 AND z<=0.0}   400
+ 32  {z>=1.0 AND z<=1.0}   300
+ 33  {z>=2.0 AND z<=2.0}   200
+ 34  {z>=3.0 AND z<=3.0}   100
+ 35  {z>=4.0 AND z<=4.0}    50
+ 36  {z>=-1.0 AND z<=-1.0}  50
+ 37  {z>1.5 AND z<3.0}     200
+ 38  {z>0.5 AND z<100}     600
+ 39  {z>=1.0 AND z<100}    600
+ 40  {z>1.5 AND z<100}     300
+ 41  {z>=2.0 AND z<100}    300
+ 42  {z>2.1 AND z<100}     100
+ 43  {z>=3.0 AND z<100}    100
+ 44  {z>3.2 AND z<100}      50
+ 45  {z>=4.0 AND z<100}     50
+ 46  {z>=-100 AND z<=-1.0}  50
+ 47  {z>=-100 AND z<=0.0}  400
+ 48  {z>=-100 AND z<0.0}    50
+ 49  {z>=-100 AND z<=1.0}  700
+ 50  {z>=-100 AND z<2.0}   700
+ 51  {z>=-100 AND z<=2.0}  900
+ 52  {z>=-100 AND z<3.0}   900
+} {
+  do_test analyze5-2.$testid {
+    eqp "SELECT * FROM t1 WHERE $where"
+  } [format {0 0 0 {SEARCH TABLE t1 USING INDEX t1z (z>? AND z<?) (~%d rows)}} \
+       $rows]
+}
+
+# Repeat the same range query tests using TEXT columns.
+#
+do_test analyze5-3.0 {
+  db eval {
+    UPDATE t1 SET y=CASE z WHEN 0 THEN 'alpha' WHEN 1 THEN 'bravo'
+                           WHEN 2 THEN 'charlie' ELSE 'delta' END;
+    ANALYZE;
+    SELECT sample FROM sqlite_stat2 WHERE idx='t1y' ORDER BY sampleno;
+  }
+} {alpha alpha alpha alpha bravo bravo bravo charlie charlie delta}
+foreach {testid where rows} {
+  1  {y>='alpha' AND y<='alpha'}     400
+  2  {y>='bravo' AND y<='bravo'}     300
+  3  {y>='charlie' AND y<='charlie'} 200
+  4  {y>='delta' AND y<='delta'}     100
+  5  {y>='echo' AND y<='echo'}        50
+  6  {y>='' AND y<=''}                50
+  7  {y>'bravo' AND y<'delta'}       200
+  8  {y>'alpha' AND y<'zzz'}         600
+  9  {y>='bravo' AND y<'zzz'}        600
+ 10  {y>'bravo' AND y<'zzz'}         300
+ 11  {y>='charlie' AND y<'zzz'}      300
+ 12  {y>'charlie' AND y<'zzz'}       100
+ 13  {y>='delta' AND y<'zzz'}        100
+ 14  {y>'delta' AND y<'zzz'}          50
+ 15  {y>='echo' AND y<'zzz'}          50
+ 16  {y>=0 AND y<=''}                 50
+ 17  {y>=0 AND y<='alpha'}           400
+ 18  {y>=0 AND y<'alpha'}             50
+ 19  {y>=0 AND y<='bravo'}           700
+ 20  {y>=0 AND y<'charlie'}          700
+ 21  {y>=0 AND y<='charlie'}         900
+ 22  {y>=0 AND y<'delta'}            900
+} {
+  do_test analyze5-3.$testid {
+    eqp "SELECT * FROM t1 WHERE $where"
+  } [format {0 0 0 {SEARCH TABLE t1 USING INDEX t1y (y>? AND y<?) (~%d rows)}} \
+       $rows]
+}
+
+
+finish_test