]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Improve the ability of the query planner to recognize covering indexes even
authordrh <>
Mon, 24 Oct 2022 13:50:04 +0000 (13:50 +0000)
committerdrh <>
Mon, 24 Oct 2022 13:50:04 +0000 (13:50 +0000)
on tables with more than 63 columns and where the index is over columns
beyond the 63rd column.

FossilOrigin-Name: 3d1992de4733d4e155f2d6d5268323410d1104ab56db50f08a2bb26bf87d926b

manifest
manifest.uuid
src/delete.c
src/fkey.c
src/select.c
src/sqliteInt.h
src/update.c
src/where.c
src/whereInt.h
src/wherecode.c
test/widetab1.test [new file with mode: 0644]

index 720b8aa23cb632b1690fd29293e7aebaa2bea550..fbe7326545f175d48e5201d36c3b282faa2e2b25 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C If\sa\squery\suses\san\sindex\swhere\sone\sor\smore\sof\sthe\scolumns\sof\sthe\sindex\sis\nan\sexpression\sand\sif\sthe\scorresponding\sexpression\sis\nused\selsewhere\sin\sthe\squery,\sthen\sstrive\sto\sread\sthe\svalue\sof\sthe\sexpression\nout\sof\sthe\sindex,\srather\sthan\srecomputing\sit.\s\sThis\sis\sthe\n"Indexed\sExpression\sOptimizations".
-D 2022-10-19T11:22:21.760
+C Improve\sthe\sability\sof\sthe\squery\splanner\sto\srecognize\scovering\sindexes\seven\non\stables\swith\smore\sthan\s63\scolumns\sand\swhere\sthe\sindex\sis\sover\scolumns\nbeyond\sthe\s63rd\scolumn.
+D 2022-10-24T13:50:04.830
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -469,10 +469,10 @@ F src/ctime.c 109e58d00f62e8e71ee1eb5944ac18b90171c928ab2e082e058056e1137cc20b
 F src/date.c ebe1dc7c8a347117bb02570f1a931c62dd78f4a2b1b516f4837d45b7d6426957
 F src/dbpage.c 135eb3b5e74f9ef74bde5cec2571192c90c86984fa534c88bf4a055076fa19b7
 F src/dbstat.c c12833de69cb655751487d2c5a59607e36be1c58ba1f4bd536609909ad47b319
-F src/delete.c 2bee826a5e1c2b2018895084850d69a7f60269ae6fa5e8c247e2a4e9faf2ccad
+F src/delete.c fb363f793fc40df36843cc67fb692659164b6109690f4efdd254ab785f8e511d
 F src/expr.c dc17ca9523293e15abe61f3d0002556d765b0fc3b985e67c07ec3de9dd36e636
 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
-F src/fkey.c bd0138acdc008c1845ccf92f8e73787880562de649471804801c06fed814c765
+F src/fkey.c e6408a869edeb222b049f6bb991a862a2c4a161da27794018897f196b1c2f584
 F src/func.c 2ccf4ae12430b1ae7096be5f0675887e1bd0732828af0ac0f7496339b7c6edee
 F src/global.c 0dea3065ea72a65ae941559b6686aad6516d4913e76fa4f79a95ff7787f624ec
 F src/hash.c 8d7dda241d0ebdafb6ffdeda3149a412d7df75102cecfc1021c98d6219823b19
@@ -518,12 +518,12 @@ F src/printf.c 67f79227273a9009d86a017619717c3f554f50b371294526da59faa6014ed2cd
 F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
 F src/resolve.c 567888ee3faec14dae06519b4306201771058364a37560186a3e0e755ebc4cb8
 F src/rowset.c d977b011993aaea002cab3e0bb2ce50cf346000dff94e944d547b989f4b1fe93
-F src/select.c a75028a3d35a4e56f7428fd716d6340f0ca6666a98cd48af246ad450c093a5b1
+F src/select.c cd8413de730aa27beb39420d08b0fd821b998819e4e998573d4c06a7fd9a113b
 F src/shell.c.in c1986496062f9dba4ed5b70db06b5e0f32e1954cdcfab0b30372c6c186796810
 F src/sqlite.h.in 59f5e145b8d7a915ca29c6bf4a1f00e3112c1605c9ac5c627c45060110332ba2
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h 9ecc93b8493bd20c0c07d52e2ac0ed8bab9b549c7f7955b59869597b650dd8b5
-F src/sqliteInt.h d748774587502dcf0e77f54ddde94b3fdff32bdea91f2905b72a290d1a891fb9
+F src/sqliteInt.h c3ee9809b0a1349608a9abd61b3ecf4f6258554f3b777a18457c99aab84eef6f
 F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b
 F src/status.c 46e7aec11f79dad50965a5ca5fa9de009f7d6bde08be2156f1538a0a296d4d0e
 F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34
@@ -585,7 +585,7 @@ F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c
 F src/tokenize.c d3615f0cbe4db5949503bf5916f3cd4fa5de855d5b4ef560f3b6dd5629423a1e
 F src/treeview.c 56724725c62a0d0f408f7c257475dc33309198afee36a1d18be1bc268b09055e
 F src/trigger.c 132009e29dc8099e2cbabc67b5608f03487e61d5dc4b1c5c98187ef703326e2c
-F src/update.c 48cfd516a07cd095e9b6a83d345dd683a89dd53724d092bf6ed633c32fdeca16
+F src/update.c d714633472eb810723130c3fe2af8ba9a24368890d25a0be9c813ff304e3c04d
 F src/upsert.c 0dd81b40206841814d46942a7337786932475f085716042d0cb2fc7791bf8ca4
 F src/utf.c 2f0fac345c7660d5c5bd3df9e9d8d33d4c27f366bcfb09e07443064d751a0507
 F src/util.c e12939405e77906d06ab0b78c5f513dcd2b7cec2fbb553877b0abfece6067141
@@ -604,9 +604,9 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
 F src/wal.c 9eccc7ebb532a7b0fd3cabc16cff576b9afa763472272db67d84fb8cec96f5c0
 F src/wal.h 606292549f5a7be50b6227bd685fa76e3a4affad71bb8ac5ce4cb5c79f6a176a
 F src/walker.c 7607f1a68130c028255d8d56094ea602fc402c79e1e35a46e6282849d90d5fe4
-F src/where.c 5f54a44d4aa05c592ed8e19bad656ebb1a9d05157488edfa991e12b3098290f1
-F src/whereInt.h ffebbbad9359cc602c9cbb24d926f73fc1bf696f0edb4ff896afa32018aad690
-F src/wherecode.c 5e0b6dec8591e13f1f0af828d350e4a5dd2e3518b63d328f21bb38e2456dfeb7
+F src/where.c 6a8b2a825d3d2af6c9c290e4b93804a54551b75bef1b8c40407e298d5fae54a5
+F src/whereInt.h 752a870900422b8a6d77c51d74b9e9ce935ff7d5e7b6caf1e786823c51a18a14
+F src/wherecode.c cf67460973119c7b2141ad67daf8368dfb4871f225e2489f95effaa139007bfd
 F src/whereexpr.c ca55a11c2443700fe084a1e039660688d7733c594a37697ee4bd99462e2c2f6a
 F src/window.c 038c248267e74ff70a2bb9b1884d40fd145c5183b017823ecb6cbb14bc781478
 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
@@ -1679,6 +1679,7 @@ F test/wherefault.test 1374c3aa198388925246475f84ad4cd5f9528864
 F test/wherelfault.test 9012e4ef5259058b771606616bd007af5d154e64cc25fa9fd4170f6411db44e3
 F test/wherelimit.test 592081800806d297dd7449b1030c863d2883d6d42901837ccd2e5a9bd962edb0
 F test/wherelimit2.test 9bf0aa56cca40ea0e4c5e2915341355a2bbc0859ec4ce1589197fe2a9d94635f
+F test/widetab1.test ea6e1d8ce3cf3fc9f0b6f5e4264a45d287944ee9becabbe8ad55ba80bb3cc225
 F test/wild001.test bca33f499866f04c24510d74baf1e578d4e44b1c
 F test/win32heap.test 10fd891266bd00af68671e702317726375e5407561d859be1aa04696f2aeee74
 F test/win32lock.test fbf107c91d8f5512be5a5b87c4c42ab9fdd54972
@@ -1819,9 +1820,9 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P e3754cc18824aad4113ad6d81e33e5c763beb9705fc6af88d8f8c870a03c731d
-Q +2435112867fbd7b6ebb7f2c2b9da57cdf1e23fab6c2869870b66133a9f9faedc
-R ffc6c9dee666f2afa446429523ea6435
+P 3da1032878bdc93f69b02926fb7243b31fe6b1a0ee93af68df52b203b0603dad
+Q +f058773e41495ddbae698f9e9a4f62b7003112ea8614dfad69471340058735e4
+R f77ec747567c3bc99f563b3d82675b4b
 U drh
-Z cbff34b53dc91f70749ae3ac7b08f467
+Z 75744969cee191ad91201d95cd6f1fac
 # Remove this line to create a well-formed Fossil manifest.
index 722622687f79deaee5f63ea828720aaea48e0783..2dffe91d0ba4950d30b72a2d3822ee465e6d9875 100644 (file)
@@ -1 +1 @@
-3da1032878bdc93f69b02926fb7243b31fe6b1a0ee93af68df52b203b0603dad
\ No newline at end of file
+3d1992de4733d4e155f2d6d5268323410d1104ab56db50f08a2bb26bf87d926b
\ No newline at end of file
index 73709bb4a08c7a0a8a62ad5bb91d1a78431dfba8..5999015a2085dada78835752586a017005dff9db 100644 (file)
@@ -455,7 +455,7 @@ void sqlite3DeleteFrom(
     **  ONEPASS_SINGLE: One-pass approach - at most one row deleted.
     **  ONEPASS_MULTI:  One-pass approach - any number of rows may be deleted.
     */
-    pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf, iTabCur+1);
+    pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0,0,0,wcf, iTabCur+1);
     if( pWInfo==0 ) goto delete_from_cleanup;
     eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
     assert( IsVirtual(pTab)==0 || eOnePass!=ONEPASS_MULTI );
index 9633c414497029848c5a7836716753c55d495ad4..21d1cd503aa28396e3982f4238339d00587899a1 100644 (file)
@@ -642,7 +642,7 @@ static void fkScanChildren(
   ** clause. For each row found, increment either the deferred or immediate
   ** foreign key constraint counter. */
   if( pParse->nErr==0 ){
-    pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0);
+    pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0, 0);
     sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr);
     if( pWInfo ){
       sqlite3WhereEnd(pWInfo);
index 60b915b6c3c0e1df55916058caa20e43740fe0a1..0c8ecd1c9e398a3cdce8677768c2ddbca852c21d 100644 (file)
@@ -6073,7 +6073,7 @@ int sqlite3Select(
     /* Begin the database scan. */
     SELECTTRACE(1,pParse,p,("WhereBegin\n"));
     pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy,
-                               p->pEList, wctrlFlags, p->nSelectRow);
+                               p->pEList, p, wctrlFlags, p->nSelectRow);
     if( pWInfo==0 ) goto select_end;
     if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){
       p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo);
@@ -6284,7 +6284,7 @@ int sqlite3Select(
       */
       sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset);
       SELECTTRACE(1,pParse,p,("WhereBegin\n"));
-      pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, 0,
+      pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, 0, p,
           WHERE_GROUPBY | (orderByGrp ? WHERE_SORTBYGROUP : 0), 0
       );
       if( pWInfo==0 ) goto select_end;
@@ -6556,7 +6556,7 @@ int sqlite3Select(
 
         SELECTTRACE(1,pParse,p,("WhereBegin\n"));
         pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pMinMaxOrderBy,
-                                   0, minMaxFlag, 0);
+                                   0, p, minMaxFlag, 0);
         if( pWInfo==0 ){
           goto select_end;
         }
index f274866b761529fe2c43a78532a486177935d3ec..325785a03a6f2aba70d583a1f7fd223cef77beae 100644 (file)
@@ -1138,6 +1138,7 @@ typedef struct With With;
 #define MASKBIT(n)   (((Bitmask)1)<<(n))
 #define MASKBIT32(n) (((unsigned int)1)<<(n))
 #define ALLBITS      ((Bitmask)-1)
+#define TOPBIT        (((Bitmask)1)<<(BMS-1))
 
 /* A VList object records a mapping between parameters/variables/wildcards
 ** in the SQL statement (such as $abc, @pqr, or :xyz) and the integer
@@ -3521,6 +3522,7 @@ struct Walker {
     struct WindowRewrite *pRewrite;           /* Window rewrite context */
     struct WhereConst *pConst;                /* WHERE clause constants */
     struct RenameCtx *pRename;                /* RENAME COLUMN context */
+    struct CoveringIndexCheck *pCovIdxCk;     /* Check for covering index */
   } u;
 };
 
@@ -4013,7 +4015,7 @@ Expr *sqlite3LimitWhere(Parse*,SrcList*,Expr*,ExprList*,Expr*,char*);
 void sqlite3DeleteFrom(Parse*, SrcList*, Expr*, ExprList*, Expr*);
 void sqlite3Update(Parse*, SrcList*, ExprList*,Expr*,int,ExprList*,Expr*,
                    Upsert*);
-WhereInfo *sqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*,ExprList*,u16,int);
+WhereInfo *sqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*,ExprList*,Select*,u16,int);
 void sqlite3WhereEnd(WhereInfo*);
 LogEst sqlite3WhereOutputRowCount(WhereInfo*);
 int sqlite3WhereIsDistinct(WhereInfo*);
index 73e4faadec30056721237c97e3acf53a3e556af6..4ed376a3a2cb2bc1824fe93e94e31f608ebc1901 100644 (file)
@@ -490,7 +490,7 @@ void sqlite3Update(
     if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){
       flags |= WHERE_ONEPASS_MULTIROW;
     }
-    pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags, iIdxCur);
+    pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0,0,0,flags, iIdxCur);
     if( pWInfo==0 ) goto update_cleanup;
   
     /* A one-pass strategy that might update more than one row may not
@@ -905,7 +905,7 @@ static void updateVirtualTable(
   regRowid = ++pParse->nMem;
 
   /* Start scanning the virtual table */
-  pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0,0,WHERE_ONEPASS_DESIRED,0);
+  pWInfo = sqlite3WhereBegin(pParse, pSrc,pWhere,0,0,0,WHERE_ONEPASS_DESIRED,0);
   if( pWInfo==0 ) return;
 
   /* Populate the argument registers. */
index 115f4e25aad5548755daba5fa634602dda7ae1c9..6bbf4b8cc13af7fea1bc3afd130c28057281242c 100644 (file)
@@ -2817,6 +2817,94 @@ static int whereUsablePartialIndex(int iTab, WhereClause *pWC, Expr *pWhere){
   return 0;
 }
 
+/*
+** Structure passed to the whereIsCoveringIndex Walker callback.
+*/
+struct CoveringIndexCheck {
+  Index *pIdx;       /* The index */
+  int iTabCur;       /* Cursor number for the corresponding table */
+};
+
+/*
+** Information passed in is pWalk->u.pCovIdxCk.  Call is pCk.
+**
+** If the Expr node references the table with cursor pCk->iTabCur, then
+** make sure that column is covered by the index pCk->pIdx.  We know that
+** all columns less than 63 (really BMS-1) are covered, so we don't need
+** to check them.  But we do need to check any column at 63 or greater.
+**
+** If the index does not cover the column, then set pWalk->eCode to 
+** non-zero and return WRC_Abort to stop the search.
+**
+** If this node does not disprove that the index can be a covering index,
+** then just return WRC_Continue, to continue the search.
+*/
+static int whereIsCoveringIndexWalkCallback(Walker *pWalk, Expr *pExpr){
+  int i;                  /* Loop counter */
+  const Index *pIdx;      /* The index of interest */
+  const i16 *aiColumn;    /* Columns contained in the index */
+  u16 nColumn;            /* Number of columns in the index */
+  if( pExpr->op!=TK_COLUMN && pExpr->op!=TK_AGG_COLUMN ) return WRC_Continue;
+  if( pExpr->iColumn<(BMS-1) ) return WRC_Continue;
+  if( pExpr->iTable!=pWalk->u.pCovIdxCk->iTabCur ) return WRC_Continue;
+  pIdx = pWalk->u.pCovIdxCk->pIdx;
+  aiColumn = pIdx->aiColumn;
+  nColumn = pIdx->nColumn;
+  for(i=0; i<nColumn; i++){
+    if( aiColumn[i]==pExpr->iColumn ) return WRC_Continue;
+  }
+  pWalk->eCode = 1;
+  return WRC_Abort;
+}
+
+
+/*
+** pIdx is an index that covers all of the low-number columns used by
+** pWInfo->pSelect (columns from 0 through 62).  But there are columns
+** in pWInfo->pSelect beyond 62.  This routine tries to answer the question
+** of whether pIdx covers *all* columns in the query.
+**
+** Return 0 if pIdx is a covering index.   Return non-zero if pIdx is
+** not a covering index or if we are unable to determine if pIdx is a
+** covering index.
+**
+** This routine is an optimization.  It is always safe to return non-zero.
+** But returning zero when non-zero should have been returned can lead to
+** incorrect bytecode and assertion faults.
+*/
+static SQLITE_NOINLINE u32 whereIsCoveringIndex(
+  WhereInfo *pWInfo,     /* The WHERE clause context */
+  Index *pIdx,           /* Index that is being tested */
+  int iTabCur            /* Cursor for the table being indexed */
+){
+  int i;
+  struct CoveringIndexCheck ck;
+  Walker w;
+  if( pWInfo->pSelect==0 ){
+    /* We don't have access to the full query, so we cannot check to see
+    ** if pIdx is covering.  Assume it is not. */
+    return 1;
+  }
+  for(i=0; i<pIdx->nColumn; i++){
+    if( pIdx->aiColumn[i]>=BMS-1 ) break;
+  }
+  if( i>=pIdx->nColumn ){
+    /* pIdx does not index any columns greater than 62, but we know from
+    ** colMask that columns greater than 62 are used, so this is not a
+    ** covering index */
+    return 1;
+  }
+  ck.pIdx = pIdx;
+  ck.iTabCur = iTabCur;
+  memset(&w, 0, sizeof(w));
+  w.xExprCallback = whereIsCoveringIndexWalkCallback;
+  w.xSelectCallback = sqlite3SelectWalkNoop;
+  w.u.pCovIdxCk = &ck;
+  w.eCode = 0;
+  sqlite3WalkSelect(&w, pWInfo->pSelect);
+  return w.eCode;
+}
+
 /*
 ** Add all WhereLoop objects for a single table of the join where the table
 ** is identified by pBuilder->pNew->iTab.  That table is guaranteed to be
@@ -3029,6 +3117,9 @@ static int whereLoopAddBtree(
         m = 0;
       }else{
         m = pSrc->colUsed & pProbe->colNotIdxed;
+        if( m==TOPBIT ){
+          m = whereIsCoveringIndex(pWInfo, pProbe, pSrc->iCursor);
+        }
         pNew->wsFlags = (m==0) ? (WHERE_IDX_ONLY|WHERE_INDEXED) : WHERE_INDEXED;
       }
 
@@ -4657,6 +4748,7 @@ WhereInfo *sqlite3WhereBegin(
   Expr *pWhere,           /* The WHERE clause */
   ExprList *pOrderBy,     /* An ORDER BY (or GROUP BY) clause, or NULL */
   ExprList *pResultSet,   /* Query result set.  Req'd for DISTINCT */
+  Select *pSelect,        /* The entire SELECT statement */
   u16 wctrlFlags,         /* The WHERE_* flags defined in sqliteInt.h */
   int iAuxArg             /* If WHERE_OR_SUBCLAUSE is set, index cursor number
                           ** If WHERE_USE_LIMIT, then the limit amount */
@@ -4740,6 +4832,7 @@ WhereInfo *sqlite3WhereBegin(
   pWInfo->wctrlFlags = wctrlFlags;
   pWInfo->iLimit = iAuxArg;
   pWInfo->savedNQueryLoop = pParse->nQueryLoop;
+  pWInfo->pSelect = pSelect;
   memset(&pWInfo->nOBSat, 0, 
          offsetof(WhereInfo,sWC) - offsetof(WhereInfo,nOBSat));
   memset(&pWInfo->a[0], 0, sizeof(WhereLoop)+nTabList*sizeof(WhereLevel));
index ca87134c045b63a5bdfd0bba9ca3cac50d0a013a..1fea3b9ffbb44507b7f4ce7c80daec1a441e6836 100644 (file)
@@ -446,6 +446,7 @@ struct WhereInfo {
   ExprList *pResultSet;     /* Result set of the query */
   Expr *pWhere;             /* The complete WHERE clause */
   LogEst iLimit;            /* LIMIT if wctrlFlags has WHERE_USE_LIMIT */
+  Select *pSelect;          /* The entire SELECT statement containing WHERE */
   int aiCurOnePass[2];      /* OP_OpenWrite cursors for the ONEPASS opt */
   int iContinue;            /* Jump here to continue with next record */
   int iBreak;               /* Jump here to break out of the loop */
index 6f2607692aa06846947b0b0b540ca9244511e5ea..dc0007611f09c818fd80a3aba1e3914b39caee02 100644 (file)
@@ -2004,7 +2004,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
         /* Loop through table entries that match term pOrTerm. */
         ExplainQueryPlan((pParse, 1, "INDEX %d", ii+1));
         WHERETRACE(0xffff, ("Subplan for OR-clause:\n"));
-        pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0,
+        pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0, 0,
                                       WHERE_OR_SUBCLAUSE, iCovCur);
         assert( pSubWInfo || pParse->nErr || db->mallocFailed );
         if( pSubWInfo ){
diff --git a/test/widetab1.test b/test/widetab1.test
new file mode 100644 (file)
index 0000000..f21aa95
--- /dev/null
@@ -0,0 +1,156 @@
+# 2022-10-24
+#
+# 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 test cases for wide table (tables with more than
+# 64 columns) and indexes that reference columns beyond the 63rd or 64th
+# column.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix widetab1
+
+
+# In order to pick the better index in the following query, SQLite needs to
+# be able to detect when an index that references later columns in a wide
+# table is a covering index.
+#
+do_execsql_test 100 {
+  CREATE TABLE a(
+   a00, a01, a02, a03, a04, a05, a06, a07, a08, a09,
+   a10, a11, a12, a13, a14, a15, a16, a17, a18, a19,
+   a20, a21, a22, a23, a24, a25, a26, a27, a28, a29,
+   a30, a31, a32, a33, a34, a35, a36, a37, a38, a39,
+   a40, a41, a42, a43, a44, a45, a46, a47, a48, a49,
+   a50, a51, a52, a53, a54, a55, a56, a57, a58, a59,
+   pd, bn, vb, bc, cn, ie, qm);
+  CREATE INDEX a1 on a(pd, bn, vb, bc, cn); -- preferred index
+  CREATE INDEX a2 on a(pd, bc, ie, qm);     -- suboptimal index
+  CREATE TABLE b(bg, bc, bn, iv, ln, mg);
+  CREATE INDEX b1 on b(bn, iv, bg);
+}
+do_eqp_test 110 {
+  SELECT dc, count(cn)
+    FROM (SELECT coalesce(b.bg, a.bc) as dc, cn
+            FROM a LEFT JOIN b
+                  ON a.bn = b.bn
+                 AND CASE WHEN a.vb IS NOT NULL THEN 1 ELSE 0 END = b.iv
+           WHERE pd BETWEEN 0 AND 10)
+   GROUP BY dc;
+} {
+  QUERY PLAN
+  |--SEARCH TABLE a USING COVERING INDEX a1 (pd>? AND pd<?)
+  |--SEARCH TABLE b USING COVERING INDEX b1 (bn=? AND iv=?)
+  `--USE TEMP B-TREE FOR GROUP BY
+}
+
+reset_db
+do_execsql_test 200 {
+  CREATE TABLE t1(
+    c00,c01,c02,c03,c04,c05,c06,c07,c08,c09,
+    c10,c11,c12,c13,c14,c15,c16,c17,c18,c19,
+    c20,c21,c22,c23,c24,c25,c26,c27,c28,c29,
+    c30,c31,c32,c33,c34,c35,c36,c37,c38,c39,
+    c40,c41,c42,c43,c44,c45,c46,c47,c48,c49,
+    c50,c51,c52,c53,c54,c55,c56,c57,c58,c59,
+    c60,c61,c62,c63,c64,c65,c66,c67,c68,c69,
+    c70,c71,c72,c73,c74,c75,c76,c77,c78,c79,
+    c80,c81,c82,c83,c84,c85,c86,c87,c88,c89,
+    c90,c91,c92,c93,c94,c95,c96,c97,c98,c99,
+    a,b,c,d,e
+  );
+  CREATE INDEX t1x1 on t1(c00,a,b,
+        c01,c02,c03,c04,c05,c06,c07,c08,c09,
+    c10,c11,c12,c13,c14,c15,c16,c17,c18,c19,
+    c20,c21,c22,c23,c24,c25,c26,c27,c28,c29,
+    c30,c31,c32,c33,c34,c35,c36,c37,c38,c39,
+    c40,c41,c42,c43,c44,c45,c46,c47,c48,c49,
+    c50,c51,c52,c53,c54,c55,c56,c57,c58,c59,
+    c60,c61,c62,c63,c64,c65,c66,c67,c68,c69,
+    c70,c71,c72,c73,c74,c75,c76,c77,c78,c79,
+    c80,c81,c82,c83,c84,c85,c86,c87,c88,c89,
+    c90,c91,c92,c93,c94,c00,c96,c97,c98,c99
+  );
+  CREATE INDEX t1cd ON t1(c,d);
+  CREATE INDEX t1x2 ON t1(c01,c02,c03,a,b);
+  WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x+1000 FROM c WHERE x<9000)
+  INSERT INTO t1 SELECT
+    x+00, x+01, x+02, x+03, x+04, x+05, x+06, x+07, x+08, x+09,
+    x+10, x+11, x+12, x+13, x+14, x+15, x+16, x+17, x+18, x+19,
+    x+20, x+21, x+22, x+23, x+24, x+25, x+26, x+27, x+28, x+29,
+    x+30, x+31, x+32, x+33, x+34, x+35, x+36, x+37, x+38, x+39,
+    x+40, x+41, x+42, x+43, x+44, x+45, x+46, x+47, x+48, x+49,
+    x+50, x+51, x+52, x+53, x+54, x+55, x+56, x+57, x+58, x+59,
+    x+60, x+61, x+62, x+63, x+64, x+65, x+66, x+67, x+68, x+69,
+    x+70, x+71, x+72, x+73, x+74, x+75, x+76, x+77, x+78, x+79,
+    x+80, x+81, x+82, x+83, x+84, x+85, x+86, x+87, x+88, x+89,
+    x+90, x+91, x+92, x+93, x+94, x+95, x+96, x+97, x+98, x+99,
+    x+100, x+101, x+102, x+103, x+104 FROM c;
+}
+
+do_execsql_test 210 {SELECT sum(c62) FROM t1;} 45620
+do_execsql_test 220 {SELECT sum(c63) FROM t1;} 45630
+do_execsql_test 230 {SELECT sum(c64) FROM t1;} 45640
+do_execsql_test 240 {SELECT sum(c65) FROM t1;} 45650
+
+do_execsql_test 300 {
+  BEGIN;
+  SELECT sum(c62) FROM t1;
+  UPDATE t1 SET c62=c62+1 WHERE c00=1000;
+  SELECT sum(c62) FROM t1;
+} {45620 45621}
+do_execsql_test 310 {
+  SELECT sum(c65) FROM t1;
+  UPDATE t1 SET c65=c65+1 WHERE c00=1000;
+  SELECT sum(c65) FROM t1;
+  ROLLBACK;
+} {45650 45651}
+
+do_execsql_test 320 {
+  BEGIN;
+  SELECT count(*) FROM t1;
+  DELETE FROM t1 WHERE c=3102;
+  SELECT COUNT(*) FROM t1;
+  ROLLBACK;
+} {10 9}
+do_execsql_test 330 {
+  BEGIN;
+  SELECT count(*) FROM t1;
+  DELETE FROM t1 WHERE c=3102 AND d=3103;
+  SELECT COUNT(*) FROM t1;
+  ROLLBACK;
+} {10 9}
+do_execsql_test 340 {
+  BEGIN;
+  DELETE FROM t1 WHERE (c,d) IN (VALUES(3102,3103),(4102,4103),(5102,5103),(1,2));
+  SELECT count(*) FROM t1;
+  ROLLBACK;
+} {7}
+
+do_execsql_test 400 {
+  DROP INDEX t1cd;
+  DROP INDEX t1x1;
+  DROP INDEX t1x2;
+  CREATE INDEX t1x3 ON t1(c00,c05,c08);
+}
+do_execsql_test 410 {SELECT sum(c08) FROM t1 WHERE c00 IN (1000,5000);} 6016
+do_execsql_test 420 {SELECT sum(c63) FROM t1 WHERE c00 IN (1000,5000);} 6126
+do_execsql_test 430 {SELECT sum(c64) FROM t1 WHERE c00 IN (1000,5000);} 6128
+
+do_execsql_test 500 {
+  DROP INDEX t1x3;
+  CREATE TABLE t2 AS SELECT * FROM t1;
+  CREATE INDEX t1x4 ON t1(c00, c62, a, b);
+  CREATE INDEX t2x4 ON t2(c01, c62, c63, b, c);
+  SELECT t1.b, t2.b FROM t1 JOIN t2 ON t2.c01=t1.c00+1 WHERE +t1.b<7000
+   ORDER BY +t1.b;
+} {101 101 1101 1101 2101 2101 3101 3101 4101 4101 5101 5101 6101 6101}
+
+finish_test