]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
In partial index scans, if the WHERE clause implies a constant value for a table...
authordan <Dan Kennedy>
Fri, 22 Sep 2023 20:21:27 +0000 (20:21 +0000)
committerdan <Dan Kennedy>
Fri, 22 Sep 2023 20:21:27 +0000 (20:21 +0000)
FossilOrigin-Name: 66ed7abdfa228abde2052e3988589371f0e49b11582b1b4a83255d2df3a0aefa

manifest
manifest.uuid
src/build.c
src/expr.c
src/sqliteInt.h
src/where.c
test/indexA.test [new file with mode: 0644]

index fc2752435a3a8f501ed98f9dcf1181676e49262a..f702ae3651734a4499e21177a5055bac6547318a 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\sextra\stests\sfor\sjava\sFts5ExtensionApi\sAPI.
-D 2023-09-18T20:42:06.838
+C In\spartial\sindex\sscans,\sif\sthe\sWHERE\sclause\simplies\sa\sconstant\svalue\sfor\sa\stable\scolumn,\sreplace\soccurences\sof\sthat\stable\scolumn\swith\sthe\sconstant.\sThis\sincreases\sthe\slikelihood\sof\sthe\spartial\sindex\sbeing\sa\scovering\sindex.
+D 2023-09-22T20:21:27.468
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -652,7 +652,7 @@ F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522
 F src/btree.c d2e73513d382e6e4829b823fb41b5f2eddd9c5984b1492a7a6333cd91be15601
 F src/btree.h 03e3356f5208bcab8eed4e094240fdac4a7f9f5ddf5e91045ce589f67d47c240
 F src/btreeInt.h 91a9e0c41a0e71fa91a742ec285c63dd8dcb38b73d14fae0ed7209174ff0fdc1
-F src/build.c 79a4edcee69df5f20963d7b11faf0820d25e6f8c11ef3a9f868d14be87834711
+F src/build.c 41a29ee957aa2c16a1521e06aec8de3dcb0d38ca5c4888de734a57cd98e1cf5b
 F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490
 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
 F src/ctime.c db847fac81837ff5e5028a5f7505147ac645ae676104adc5bc08e356f243de40
@@ -660,7 +660,7 @@ F src/date.c eebc54a00e888d3c56147779e9f361b77d62fd69ff2008c5373946aa1ba1d574
 F src/dbpage.c f3eea5f7ec47e09ee7da40f42b25092ecbe961fc59566b8e5f705f34335b2387
 F src/dbstat.c ec92074baa61d883de58c945162d9e666c13cd7cf3a23bc38b4d1c4d0b2c2bef
 F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500
-F src/expr.c 9902bebcc9fa2b2c4cc94b7aa5615afe1affc98a986553aa7b239971c54ddea8
+F src/expr.c a0b26b208a770e8d07cdef2141c820169c916ecef8b18e49c76d12900ec5edbb
 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
 F src/fkey.c a7fcbf7e66d14dbb73cf49f31489ebf66d0e6006c62b95246924a3bae9f37b36
 F src/func.c 154f08966f8a3a7cad6c438205df1abf58fb2826961a0683e82e120fa647e84c
@@ -715,7 +715,7 @@ F src/shell.c.in 62708bea44d4e43aa7b1270ed422d1d29e82297924d4e0f223c39336a3f582f
 F src/sqlite.h.in 931a58d119d5cf87110648f39fa0bb9f1738b0068cb68250d893304a471bd6c0
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h 2f30b2671f4c03cd27a43f039e11251391066c97d11385f5f963bb40b03038ac
-F src/sqliteInt.h f2d713fac835f32b131d8a334595b0c471ede3796dab527c705d2b03c32d14e9
+F src/sqliteInt.h 876f5ffc0eaa876a55b2b220fff6cc3e8250fa207b864ab10b07949f1fba9b57
 F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6
 F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749
 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@@ -795,7 +795,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
 F src/wal.c 01e051a1e713d9eabdb25df38602837cec8f4c2cae448ce2cf6accc87af903e9
 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452
 F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2
-F src/where.c b05f3e60d576a0415948ca1e86754a3c564c0d9e89e3011e35f849cc4d818ef8
+F src/where.c cf43f273642ec541f70d6b56a883f5ab1b8c7c7cfd03ac95ee653b996035d911
 F src/whereInt.h 4b38c5889514e3aead3f27d0ee9a26e47c3f150efc59e2a8b4e3bc8835e4d7a1
 F src/wherecode.c 5d77db30a2a3dd532492ae882de114edba2fae672622056b1c7fd61f5917a8f1
 F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00
@@ -1249,6 +1249,7 @@ F test/index6.test b376a648e85aa71c50074382784e6cb0c126ec46e43d1ad15af9a4d234c52
 F test/index7.test b238344318e0b4e42126717f6554f0e7dfd0b39cecad4b736039b43e1e3b6eb3
 F test/index8.test caa097735c91dbc23d8a402f5e63a2a03c83840ba3928733ed7f9a03f8a912a3
 F test/index9.test 2ac891806a4136ef3e91280477e23114e67575207dc331e6797fa0ed9379f997
+F test/indexA.test 3658485f1df2556dde8f00eba4f9c95d717e909bbafa8292eddb2c9a43af4a93
 F test/indexedby.test f21eca4f7a6ffe14c8500a7ad6cd53166666c99e5ccd311842a28bc94a195fe0
 F test/indexexpr1.test 62558b1cfd7ccbe7bc015849cc6d1a13ef124e80cbd5b3a98dc66c3c9cce0cf4
 F test/indexexpr2.test 1c382e81ef996d8ae8b834a74f2a9013dddf59214c32201d7c8a656d739f999a
@@ -2121,8 +2122,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P c923893f3604b278277de1bb919ef713bf7a4296b7ff71451cfe19bc2ff03190
-R dce10d8ae94249d41d42f7dff2440548
+P f9d62b853ce8bfbfdc9f137e984e7a1b51d70e88c38b136b4fad1e8ae6ee8913
+R 5ebdc1be1f95cefdd9940dda57b5154b
+T *branch * partial-index-terms
+T *sym-partial-index-terms *
+T -sym-trunk *
 U dan
-Z 7d6c76f5fce3d6a1618c1597ed085ea5
+Z 9db635c6c629de6371be0619bb9d842d
 # Remove this line to create a well-formed Fossil manifest.
index 81b26d4090a78b534bd4f2f58002ac9911bde16d..5bda592322388aafff333fa71a67247a0bb622ff 100644 (file)
@@ -1 +1 @@
-f9d62b853ce8bfbfdc9f137e984e7a1b51d70e88c38b136b4fad1e8ae6ee8913
\ No newline at end of file
+66ed7abdfa228abde2052e3988589371f0e49b11582b1b4a83255d2df3a0aefa
\ No newline at end of file
index 59e3e23f09d68d9eb803467c590992b4c00197ce..2f672653e67d4ff0861f4d2cab19baf257f09898 100644 (file)
@@ -2302,7 +2302,7 @@ static int isDupColumn(Index *pIdx, int nKey, Index *pPk, int iCol){
 ** The colNotIdxed mask is AND-ed with the SrcList.a[].colUsed mask
 ** to determine if the index is covering index.
 */
-static void recomputeColumnsNotIndexed(Index *pIdx){
+static void recomputeColumnsNotIndexed(Parse *pParse, Index *pIdx){
   Bitmask m = 0;
   int j;
   Table *pTab = pIdx->pTable;
@@ -2315,6 +2315,9 @@ static void recomputeColumnsNotIndexed(Index *pIdx){
     }
   }
   pIdx->colNotIdxed = ~m;
+  if( pIdx->pPartIdxWhere ){
+    sqlite3WherePartIdxExpr(pParse, pIdx, pIdx->pPartIdxWhere, 0, 0);
+  }
   assert( (pIdx->colNotIdxed>>63)==1 );  /* See note-20221022-a */
 }
 
@@ -2493,7 +2496,7 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
   }
   assert( pPk->nColumn==j );
   assert( pTab->nNVCol<=j );
-  recomputeColumnsNotIndexed(pPk);
+  recomputeColumnsNotIndexed(pParse, pPk);
 }
 
 
@@ -4273,7 +4276,7 @@ void sqlite3CreateIndex(
   ** it as a covering index */
   assert( HasRowid(pTab)
       || pTab->iPKey<0 || sqlite3TableColumnToIndex(pIndex, pTab->iPKey)>=0 );
-  recomputeColumnsNotIndexed(pIndex);
+  recomputeColumnsNotIndexed(pParse, pIndex);
   if( pTblName!=0 && pIndex->nColumn>=pTab->nCol ){
     pIndex->isCovering = 1;
     for(j=0; j<pTab->nCol; j++){
index 63c5a8faa97960543d7825c5f30b319afbcc30e0..f0575d6298589d6a3df7c4e198fb42fe6dfd2b4d 100644 (file)
@@ -4252,6 +4252,40 @@ static SQLITE_NOINLINE int sqlite3IndexedExprLookup(
 }
 
 
+/*
+** Expresion pExpr is guaranteed to be a TK_COLUMN or equivalent. This
+** function checks the Parse.pIdxPartExpr list to see if this column
+** can be replaced with a constant value. If so, it generates code to
+** put the constant value in a register (ideally, but not necessarily, 
+** register iTarget) and returns the register number.
+**
+** Or, if the TK_COLUMN cannot be replaced by a constant, zero is 
+** returned.
+*/
+static int exprPartidxExprLookup(Parse *pParse, Expr *pExpr, int iTarget){
+  IndexedExpr *p;
+  for(p=pParse->pIdxPartExpr; p; p=p->pIENext){
+    if( pExpr->iColumn==p->iIdxCol && pExpr->iTable==p->iDataCur ){
+      Vdbe *v = pParse->pVdbe;
+      int addr = 0;
+      int ret;
+
+      if( p->bMaybeNullRow ){
+        addr = sqlite3VdbeAddOp1(v, OP_IfNullRow, p->iIdxCur);
+      }
+      ret = sqlite3ExprCodeTarget(pParse, p->pExpr, iTarget);
+      sqlite3VdbeAddOp4(pParse->pVdbe, OP_Affinity, ret, 1, 0, &p->aff, 1);
+      if( addr ){
+        sqlite3VdbeJumpHere(v, addr);
+        sqlite3VdbeChangeP3(v, addr, ret);
+      }
+      return ret;
+    }
+  }
+  return 0;
+}
+
+
 /*
 ** Generate code into the current Vdbe to evaluate the given
 ** expression.  Attempt to store the results in register "target".
@@ -4358,6 +4392,11 @@ expr_code_doover:
         }
         return iReg;
       }
+      if( pParse->pIdxPartExpr 
+       && 0!=(r1 = exprPartidxExprLookup(pParse, pExpr, target))
+      ){
+        return r1;
+      }
       if( iTab<0 ){
         if( pParse->iSelfTab<0 ){
           /* Other columns in the same row for CHECK constraints or
index 225e54800ba1a5b05bf57907d544fddd27cbcc10..35864de5e4f053a33f32ec887ac535187e01070f 100644 (file)
@@ -3790,6 +3790,7 @@ struct Parse {
   int *aLabel;         /* Space to hold the labels */
   ExprList *pConstExpr;/* Constant expressions */
   IndexedExpr *pIdxEpr;/* List of expressions used by active indexes */
+  IndexedExpr *pIdxPartExpr; /* Exprs constrained by index WHERE clauses */
   Token constraintName;/* Name of the constraint currently being parsed */
   yDbMask writeMask;   /* Start a write transaction on these databases */
   yDbMask cookieMask;  /* Bitmask of schema verified databases */
@@ -4939,6 +4940,7 @@ int sqlite3WhereOkOnePass(WhereInfo*, int*);
 #define ONEPASS_SINGLE   1        /* ONEPASS valid for a single row update */
 #define ONEPASS_MULTI    2        /* ONEPASS is valid for multiple rows */
 int sqlite3WhereUsesDeferredSeek(WhereInfo*);
+void sqlite3WherePartIdxExpr(Parse*, Index*, Expr*, int, SrcItem*);
 void sqlite3ExprCodeLoadIndexColumn(Parse*, Index*, int, int, int);
 int sqlite3ExprCodeGetColumn(Parse*, Table*, int, int, int, u8);
 void sqlite3ExprCodeGetColumnOfTable(Vdbe*, Table*, int, int, int);
index 3a865d3f19d65331598aa1b1d4e80bd0dfc1a3a8..2dd40025a7e3302b008a9d86f046a6d4a6ecf190 100644 (file)
@@ -5672,10 +5672,10 @@ static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful(
 ** free the Parse->pIdxEpr list when the Parse object is destroyed.
 */
 static void whereIndexedExprCleanup(sqlite3 *db, void *pObject){
-  Parse *pParse = (Parse*)pObject;
-  while( pParse->pIdxEpr!=0 ){
-    IndexedExpr *p = pParse->pIdxEpr;
-    pParse->pIdxEpr = p->pIENext;
+  IndexedExpr **pp = (IndexedExpr**)pObject;
+  while( *pp!=0 ){
+    IndexedExpr *p = *pp;
+    *pp = p->pIENext;
     sqlite3ExprDelete(db, p->pExpr);
     sqlite3DbFreeNN(db, p);
   }
@@ -5742,7 +5742,87 @@ static SQLITE_NOINLINE void whereAddIndexedExpr(
 #endif
     pParse->pIdxEpr = p;
     if( p->pIENext==0 ){
-      sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pParse);
+      void *pArg = (void*)&pParse->pIdxEpr;
+      sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pArg);
+    }
+  }
+}
+
+/*
+** This function is called for a partial index - one with a WHERE clause - in 
+** two scenarios. In both cases, it determines whether or not the WHERE 
+** clause on the index implies that a column of the table may be safely
+** replaced by a constant expression. For example, in the following 
+** SELECT:
+**
+**   CREATE INDEX i1 ON t1(b, c) WHERE a=<expr>;
+**   SELECT a, b, c FROM t1 WHERE a=<expr> AND b=?;
+**
+** The "a" in the select-list may be replaced by <expr>, iff:
+**
+**    (a) <expr> is a constant expression, and
+**    (b) The (a=<expr>) comparison uses the BINARY collation sequence, and
+**    (c) Column "a" has an affinity other than NONE or BLOB.
+**
+** If argument pTabItem is NULL, then this function is being called as part
+** of parsing the CREATE INDEX statement. In that case the Index.colNotIdxed
+** mask is updated to mark any columns that will be replaced by constant 
+** values as indexed.
+**
+** Otherwise, if pTabItem is not NULL, then this function is being called
+** as part of coding a loop that uses index pIdx. In this case, add entries
+** to the Parse.pIdxPartExpr list for each column that can be replaced
+** by a constant.
+*/
+void sqlite3WherePartIdxExpr(
+  Parse *pParse,                  /* Parse context */
+  Index *pIdx,                    /* Partial index being processed */
+  Expr *pPart,                    /* WHERE clause being processed */
+  int iIdxCur,                    /* Cursor number for index */
+  SrcItem *pTabItem               /* The FROM clause entry for the table */
+){
+  assert( pTabItem==0 || (pTabItem->fg.jointype & JT_RIGHT)==0 );
+  if( pPart->op==TK_AND ){
+    sqlite3WherePartIdxExpr(pParse, pIdx, pPart->pRight, iIdxCur, pTabItem);
+    pPart = pPart->pLeft;
+  }
+
+  if( (pPart->op==TK_EQ || pPart->op==TK_IS) ){
+    Expr *pLeft = pPart->pLeft;
+    Expr *pRight = pPart->pRight;
+    u8 aff;
+
+    if( pRight->op==TK_COLUMN ){
+      SWAP(Expr*, pLeft, pRight);
+    }
+
+    if( pLeft->op!=TK_COLUMN ) return;
+    if( !sqlite3ExprIsConstant(pRight) ) return;
+    if( !sqlite3IsBinary(sqlite3ExprCompareCollSeq(pParse, pPart)) ) return;
+    if( pLeft->iColumn<0 ) return;
+    aff = pIdx->pTable->aCol[pLeft->iColumn].affinity;
+    if( aff>=SQLITE_AFF_TEXT ){
+      if( pTabItem ){
+        sqlite3 *db = pParse->db;
+        IndexedExpr *p = (IndexedExpr*)sqlite3DbMallocZero(db, sizeof(*p));
+        if( p ){
+          int bNullRow = (pTabItem->fg.jointype&(JT_LEFT|JT_LTORJ))!=0;
+          p->pExpr = sqlite3ExprDup(db, pRight, 0);
+          p->iDataCur = pTabItem->iCursor;
+          p->iIdxCur = iIdxCur;
+          p->iIdxCol = pLeft->iColumn;
+          p->bMaybeNullRow = bNullRow;
+          p->pIENext = pParse->pIdxPartExpr;
+          p->aff = aff;
+          pParse->pIdxPartExpr = p;
+          if( p->pIENext==0 ){
+            void *pArg = (void*)&pParse->pIdxPartExpr;
+            sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pArg);
+          }
+        }
+      }else if( pLeft->iColumn<(BMS-1) ){
+        pIdx->colNotIdxed &= ~((Bitmask)1 << pLeft->iColumn);
+      }
     }
   }
 }
@@ -6354,6 +6434,11 @@ WhereInfo *sqlite3WhereBegin(
         if( pIx->bHasExpr && OptimizationEnabled(db, SQLITE_IndexedExpr) ){
           whereAddIndexedExpr(pParse, pIx, iIndexCur, pTabItem);
         }
+        if( pIx->pPartIdxWhere && (pTabItem->fg.jointype & JT_RIGHT)==0 ){
+          sqlite3WherePartIdxExpr(
+              pParse, pIx, pIx->pPartIdxWhere, iIndexCur, pTabItem
+          );
+        }
       }
       pLevel->iIdxCur = iIndexCur;
       assert( pIx!=0 );
diff --git a/test/indexA.test b/test/indexA.test
new file mode 100644 (file)
index 0000000..6de9c94
--- /dev/null
@@ -0,0 +1,144 @@
+# 2023 September 23
+#
+# 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 indexA
+
+do_execsql_test 1.0 {
+  CREATE TABLE t1(a TEXT, b, c);
+  CREATE INDEX i1 ON t1(b, c) WHERE a='abc';
+  INSERT INTO t1 VALUES('abc', 1, 2);
+}
+
+do_execsql_test 1.1 {
+  SELECT * FROM t1 WHERE a='abc'
+} {abc 1 2}
+
+do_eqp_test 1.2 {
+  SELECT * FROM t1 WHERE a='abc'
+} {USING COVERING INDEX i1}
+
+do_execsql_test 1.3 {
+  CREATE INDEX i2 ON t1(b, c) WHERE a=5;
+  INSERT INTO t1 VALUES(5, 4, 3);
+
+  SELECT a, typeof(a), b, c FROM t1 WHERE a=5;
+} {5 text 4 3}
+
+do_execsql_test 1.4 {
+  CREATE TABLE t2(x);
+  INSERT INTO t2 VALUES('v');
+}
+
+do_execsql_test 1.5 {
+  SELECT x, a, b, c FROM t2 LEFT JOIN t1 ON (a=5 AND b=x)
+} {v {} {} {}}
+
+do_execsql_test 1.6 {
+  SELECT x, a, b, c FROM t2 RIGHT JOIN t1 ON (t1.a=5 AND t1.b=t2.x)
+} {{} abc 1 2   {} 5 4 3}
+
+do_eqp_test 1.7 {
+  SELECT x, a, b, c FROM t2 RIGHT JOIN t1 ON (t1.a=5 AND t1.b=t2.x)
+} {USING INDEX i2}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 2.0 {
+  CREATE TABLE x1(a TEXT, b, c);
+  INSERT INTO x1 VALUES('2', 'two', 'ii');
+  INSERT INTO x1 VALUES('2.0', 'twopointoh', 'ii.0');
+
+  CREATE TABLE x2(a NUMERIC, b, c);
+  INSERT INTO x2 VALUES('2', 'two', 'ii');
+  INSERT INTO x2 VALUES('2.0', 'twopointoh', 'ii.0');
+
+  CREATE TABLE x3(a REAL, b, c);
+  INSERT INTO x3 VALUES('2', 'two', 'ii');
+  INSERT INTO x3 VALUES('2.0', 'twopointoh', 'ii.0');
+}
+
+foreach {tn idx} {
+  0 {
+  }
+  1 {
+    CREATE INDEX i1 ON x1(b, c) WHERE a=2;
+    CREATE INDEX i2 ON x2(b, c) WHERE a=2;
+    CREATE INDEX i3 ON x3(b, c) WHERE a=2;
+  }
+  2 {
+    CREATE INDEX i1 ON x1(b, c) WHERE a=2.0;
+    CREATE INDEX i2 ON x2(b, c) WHERE a=2.0;
+    CREATE INDEX i3 ON x3(b, c) WHERE a=2.0;
+  }
+  3 {
+    CREATE INDEX i1 ON x1(b, c) WHERE a='2.0';
+    CREATE INDEX i2 ON x2(b, c) WHERE a='2.0';
+    CREATE INDEX i3 ON x3(b, c) WHERE a='2.0';
+  }
+  4 {
+    CREATE INDEX i1 ON x1(b, c) WHERE a='2';
+    CREATE INDEX i2 ON x2(b, c) WHERE a='2';
+    CREATE INDEX i3 ON x3(b, c) WHERE a='2';
+  }
+} {
+  execsql { DROP INDEX IF EXISTS i1 }
+  execsql { DROP INDEX IF EXISTS i2 }
+  execsql { DROP INDEX IF EXISTS i3 }
+
+  execsql $idx
+  do_execsql_test 2.1.$tn.1 {
+    SELECT *, typeof(a) FROM x1 WHERE a=2
+  } {2 two ii text}
+  do_execsql_test 2.1.$tn.2 {
+    SELECT *, typeof(a) FROM x1 WHERE a=2.0
+  } {2.0 twopointoh ii.0 text}
+  do_execsql_test 2.1.$tn.3 {
+    SELECT *, typeof(a) FROM x1 WHERE a='2'
+  } {2 two ii text}
+  do_execsql_test 2.1.$tn.4 {
+    SELECT *, typeof(a) FROM x1 WHERE a='2.0'
+  } {2.0 twopointoh ii.0 text}
+
+  do_execsql_test 2.1.$tn.5 {
+    SELECT *, typeof(a) FROM x2 WHERE a=2
+  } {2 two ii integer 2 twopointoh ii.0 integer}
+  do_execsql_test 2.1.$tn.6 {
+    SELECT *, typeof(a) FROM x2 WHERE a=2.0
+  } {2 two ii integer 2 twopointoh ii.0 integer}
+  do_execsql_test 2.1.$tn.7 {
+    SELECT *, typeof(a) FROM x2 WHERE a='2'
+  } {2 two ii integer 2 twopointoh ii.0 integer}
+  do_execsql_test 2.1.$tn.8 {
+    SELECT *, typeof(a) FROM x2 WHERE a='2.0'
+  } {2 two ii integer 2 twopointoh ii.0 integer}
+
+  do_execsql_test 2.1.$tn.9 {
+    SELECT *, typeof(a) FROM x3 WHERE a=2
+  } {2.0 two ii real 2.0 twopointoh ii.0 real}
+  do_execsql_test 2.1.$tn.10 {
+    SELECT *, typeof(a) FROM x3 WHERE a=2.0
+  } {2.0 two ii real 2.0 twopointoh ii.0 real}
+  do_execsql_test 2.1.$tn.11 {
+    SELECT *, typeof(a) FROM x3 WHERE a='2'
+  } {2.0 two ii real 2.0 twopointoh ii.0 real}
+  do_execsql_test 2.1.$tn.12 {
+    SELECT *, typeof(a) FROM x3 WHERE a='2.0'
+  } {2.0 two ii real 2.0 twopointoh ii.0 real}
+
+}
+
+finish_test
+