]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Refactor the ORDER BY optimizer in the NGQP so that it is easier to maintain
authordrh <drh@noemail.net>
Tue, 4 Jun 2013 12:42:29 +0000 (12:42 +0000)
committerdrh <drh@noemail.net>
Tue, 4 Jun 2013 12:42:29 +0000 (12:42 +0000)
and so that it can support optimizing out GROUP BY and DISTINCT clauses.

FossilOrigin-Name: e605c468e3a1163167831c4a6220825c0b5d083b

manifest
manifest.uuid
src/build.c
src/sqliteInt.h
src/where.c
test/tkt-2a5629202f.test

index e5312f39307954a88978bdacb30b5f3fab23bc89..3b65c600a9281cc100078f832cf4b72fb154fed3 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Remove\smore\svestiges\sof\ssqlite_query_plan\sfrom\sthe\stest\scases.
-D 2013-06-03T22:08:20.442
+C Refactor\sthe\sORDER\sBY\soptimizer\sin\sthe\sNGQP\sso\sthat\sit\sis\seasier\sto\smaintain\nand\sso\sthat\sit\scan\ssupport\soptimizing\sout\sGROUP\sBY\sand\sDISTINCT\sclauses.
+D 2013-06-04T12:42:29.293
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 5e41da95d92656a5004b03d3576e8b226858a28e
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -165,7 +165,7 @@ F src/btmutex.c 976f45a12e37293e32cae0281b15a21d48a8aaa7
 F src/btree.c 7fba377c29573adfc6091832e27ee1fcbefb51d0
 F src/btree.h 6fa8a3ff2483d0bb64a9f0105a8cedeac9e00cca
 F src/btreeInt.h eecc84f02375b2bb7a44abbcbbe3747dde73edb2
-F src/build.c 92ef9483189389828966153c5950f2e5a49c1182
+F src/build.c 838cbdcbf18de2fd5723ad5864a78cc806f6c75b
 F src/callback.c d7e46f40c3cf53c43550b7da7a1d0479910b62cc
 F src/complete.c dc1d136c0feee03c2f7550bafc0d29075e36deac
 F src/ctime.c 4262c227bc91cecc61ae37ed3a40f08069cfa267
@@ -220,7 +220,7 @@ F src/shell.c ab6eea968c8745be3aa74e45fedb37d057b4cd0d
 F src/sqlite.h.in 5b390ca5d94e09e56e7fee6a51ddde4721b89f8e
 F src/sqlite3.rc fea433eb0a59f4c9393c8e6d76a6e2596b1fe0c0
 F src/sqlite3ext.h d936f797812c28b81b26ed18345baf8db28a21a5
-F src/sqliteInt.h 3ddccdf8ef912119da26945f2d8dff98f59e1d58
+F src/sqliteInt.h 259d999abebf4498615e9237a18c0815257a1f2f
 F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
 F src/status.c bedc37ec1a6bb9399944024d63f4c769971955a9
 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
@@ -289,7 +289,7 @@ F src/vtab.c b05e5f1f4902461ba9f5fc49bb7eb7c3a0741a83
 F src/wal.c 436bfceb141b9423c45119e68e444358ee0ed35d
 F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4
 F src/walker.c 4fa43583d0a84b48f93b1e88f11adf2065be4e73
-F src/where.c 0082ef59948fc8c28b35226f44e1718cb1b0300a
+F src/where.c cca3284b915ee9c51dd9188e3a74e648ddf3db47
 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
 F test/aggnested.test 45c0201e28045ad38a530b5a144b73cd4aa2cfd6
@@ -820,7 +820,7 @@ F test/threadtest2.c ace893054fa134af3fc8d6e7cfecddb8e3acefb9
 F test/threadtest3.c 0ed13e09690f6204d7455fac3b0e8ece490f6eef
 F test/tkt-02a8e81d44.test 6c80d9c7514e2a42d4918bf87bf6bc54f379110c
 F test/tkt-26ff0c2d1e.test 888324e751512972c6e0d1a09df740d8f5aaf660
-F test/tkt-2a5629202f.test 1ab32e084e9fc3d36be6dee2617530846a0eb0b6
+F test/tkt-2a5629202f.test befaa77b90426a5ca8025724279e0bc26336610e
 F test/tkt-2d1a5c67d.test d371279946622698ab393ff88cad9f5f6d82960b
 F test/tkt-2ea2425d34.test 1cf13e6f75d149b3209a0cb32927a82d3d79fb28
 F test/tkt-31338dca7e.test 6fb8807851964da0d24e942f2e19c7c705b9fb58
@@ -1093,7 +1093,7 @@ F tool/vdbe-compress.tcl f12c884766bd14277f4fcedcae07078011717381
 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381
 F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac
-P e2c1af78b65a8ace976fa6c035db212e1ffc79b8
-R 85129ff0b9a881f15c06fc2c70cadd93
+P eb27086e8a8a4d5fcb2ea358256a555e34339423
+R 3dd8dcdfd7f56eca682f5b0b9f200abc
 U drh
-Z 30b7be3ba3d79e592e3b359a6ec5ea92
+Z 6cf52a3b39b332b4094772bd0e2b3942
index 387facd0c51927a3e4d39553352eab4098c49133..7dcbb18dc087e0d1cfce5c64e47b14c4cddf7957 100644 (file)
@@ -1 +1 @@
-eb27086e8a8a4d5fcb2ea358256a555e34339423
\ No newline at end of file
+e605c468e3a1163167831c4a6220825c0b5d083b
\ No newline at end of file
index 3c91cdcfdb7b017cbc2d5c29571a98270de071e6..255fd37a014b9f82badbe28544dc7906c3c20a1f 100644 (file)
@@ -2695,6 +2695,7 @@ Index *sqlite3CreateIndex(
   pIndex->pTable = pTab;
   pIndex->nColumn = pList->nExpr;
   pIndex->onError = (u8)onError;
+  pIndex->uniqNotNull = onError==OE_Abort;
   pIndex->autoIndex = (u8)(pName==0);
   pIndex->pSchema = db->aDb[iDb].pSchema;
   assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
@@ -2753,6 +2754,7 @@ Index *sqlite3CreateIndex(
     pIndex->azColl[i] = zColl;
     requestedSortOrder = pListItem->sortOrder & sortOrderMask;
     pIndex->aSortOrder[i] = (u8)requestedSortOrder;
+    if( pTab->aCol[j].notNull==0 ) pIndex->uniqNotNull = 0;
   }
   sqlite3DefaultRowEst(pIndex);
 
index e99098cd0a02e1501cc73d852094117c7c91cfeb..2b07d2ef02f5ccb54d95b170359b69e9bf082c9a 100644 (file)
@@ -1544,6 +1544,7 @@ struct Index {
   u8 onError;              /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */
   unsigned autoIndex:2;    /* 1==UNIQUE, 2==PRIMARY KEY, 0==CREATE INDEX */
   unsigned bUnordered:1;   /* Use this index for == or IN queries only */
+  unsigned uniqNotNull:1;  /* True if UNIQUE and NOT NULL for all columns */
 #ifdef SQLITE_ENABLE_STAT3
   int nSample;             /* Number of elements in aSample[] */
   tRowcnt avgEq;           /* Average nEq value for key values not in aSample */
@@ -1889,6 +1890,11 @@ typedef u64 Bitmask;
 */
 #define BMS  ((int)(sizeof(Bitmask)*8))
 
+/*
+** A bit in a Bitmask
+*/
+#define MASKBIT(n)   (((Bitmask)1)<<(n))
+
 /*
 ** The following structure describes the FROM clause of a SELECT statement.
 ** Each table or subquery in the FROM clause is a separate element of
index 57dc7e8aef3bd5916d425c2b5e1447a0eb364586..7577acbfb37d91283e17845daba830918de62e31 100644 (file)
@@ -327,7 +327,7 @@ struct WhereLoopBuilder {
 #define WHERE_INDEXED      0x00000200  /* WhereLoop.u.btree.pIndex is valid */
 #define WHERE_VIRTUALTABLE 0x00000400  /* WhereLoop.u.vtab is valid */
 #define WHERE_IN_ABLE      0x00000800  /* Able to support an IN operator */
-#define WHERE_UNIQUE       0x00001000  /* Selects no more than one row */
+#define WHERE_ONEROW       0x00001000  /* Selects no more than one row */
 #define WHERE_MULTI_OR     0x00002000  /* OR using multiple indices */
 #define WHERE_TEMP_INDEX   0x00004000  /* Uses an ephemeral index */
 #define WHERE_COVER_SCAN   0x00008000  /* Full scan of a covering index */
@@ -482,7 +482,7 @@ static Bitmask getMask(WhereMaskSet *pMaskSet, int iCursor){
   assert( pMaskSet->n<=(int)sizeof(Bitmask)*8 );
   for(i=0; i<pMaskSet->n; i++){
     if( pMaskSet->ix[i]==iCursor ){
-      return ((Bitmask)1)<<i;
+      return MASKBIT(i);
     }
   }
   return 0;
@@ -1838,7 +1838,7 @@ static void constructAutomaticIndex(
   for(pTerm=pWC->a; pTerm<pWCEnd && pLoop->nTerm<mxConstraint; pTerm++){
     if( termCanDriveIndex(pTerm, pSrc, notReady) ){
       int iCol = pTerm->u.leftColumn;
-      Bitmask cMask = iCol>=BMS ? ((Bitmask)1)<<(BMS-1) : ((Bitmask)1)<<iCol;
+      Bitmask cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol);
       testcase( iCol==BMS );
       testcase( iCol==BMS-1 );
       if( (idxCols & cMask)==0 ){
@@ -1860,14 +1860,14 @@ static void constructAutomaticIndex(
   ** original table changes and the index and table cannot both be used
   ** if they go out of sync.
   */
-  extraCols = pSrc->colUsed & (~idxCols | (((Bitmask)1)<<(BMS-1)));
+  extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1));
   mxBitCol = (pTable->nCol >= BMS-1) ? BMS-1 : pTable->nCol;
   testcase( pTable->nCol==BMS-1 );
   testcase( pTable->nCol==BMS-2 );
   for(i=0; i<mxBitCol; i++){
-    if( extraCols & (((Bitmask)1)<<i) ) nColumn++;
+    if( extraCols & MASKBIT(i) ) nColumn++;
   }
-  if( pSrc->colUsed & (((Bitmask)1)<<(BMS-1)) ){
+  if( pSrc->colUsed & MASKBIT(BMS-1) ){
     nColumn += pTable->nCol - BMS + 1;
   }
   pLoop->wsFlags |= WHERE_COLUMN_EQ | WHERE_IDX_ONLY;
@@ -1891,7 +1891,7 @@ static void constructAutomaticIndex(
   for(pTerm=pWC->a; pTerm<pWCEnd; pTerm++){
     if( termCanDriveIndex(pTerm, pSrc, notReady) ){
       int iCol = pTerm->u.leftColumn;
-      Bitmask cMask = iCol>=BMS ? ((Bitmask)1)<<(BMS-1) : ((Bitmask)1)<<iCol;
+      Bitmask cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol);
       if( (idxCols & cMask)==0 ){
         Expr *pX = pTerm->pExpr;
         idxCols |= cMask;
@@ -1907,13 +1907,13 @@ static void constructAutomaticIndex(
   /* Add additional columns needed to make the automatic index into
   ** a covering index */
   for(i=0; i<mxBitCol; i++){
-    if( extraCols & (((Bitmask)1)<<i) ){
+    if( extraCols & MASKBIT(i) ){
       pIdx->aiColumn[n] = i;
       pIdx->azColl[n] = "BINARY";
       n++;
     }
   }
-  if( pSrc->colUsed & (((Bitmask)1)<<(BMS-1)) ){
+  if( pSrc->colUsed & MASKBIT(BMS-1) ){
     for(i=BMS-1; i<pTable->nCol; i++){
       pIdx->aiColumn[n] = i;
       pIdx->azColl[n] = "BINARY";
@@ -3390,7 +3390,7 @@ static Bitmask codeOneLoopStart(
     /* Record the instruction used to terminate the loop. Disable 
     ** WHERE clause terms made redundant by the index range scan.
     */
-    if( pLoop->wsFlags & WHERE_UNIQUE ){
+    if( pLoop->wsFlags & WHERE_ONEROW ){
       pLevel->op = OP_Noop;
     }else if( bRev ){
       pLevel->op = OP_Prev;
@@ -4004,6 +4004,7 @@ static int whereLoopAddBtreeIndex(
     if( pNew->nTerm>=pBuilder->mxTerm ) break; /* Repeated column in index */
     pNew->aTerm[pNew->nTerm++] = pTerm;
     pNew->prereq = (savedLoop.prereq | pTerm->prereqRight) & ~pNew->maskSelf;
+    pNew->rRun = rLogSize;
     if( pTerm->eOperator & WO_IN ){
       Expr *pExpr = pTerm->pExpr;
       pNew->wsFlags |= WHERE_COLUMN_IN;
@@ -4014,22 +4015,27 @@ static int whereLoopAddBtreeIndex(
         /* "x IN (value, value, ...)" */
         nIn = pExpr->x.pList->nExpr;
       }
+      pNew->rRun *= nIn;
       pNew->u.btree.nEq++;
       pNew->nOut = (double)iRowEst * nInMul * nIn;
     }else if( pTerm->eOperator & (WO_EQ) ){
+      assert( (pNew->wsFlags & (WHERE_COLUMN_NULL|WHERE_COLUMN_IN))!=0
+                  || nInMul==1 );
       pNew->wsFlags |= WHERE_COLUMN_EQ;
-      if( iCol<0 
+      if( iCol<0  
        || (pProbe->onError==OE_Abort && nInMul==1
            && pNew->u.btree.nEq==pProbe->nColumn-1)
       ){
-        pNew->wsFlags |= WHERE_UNIQUE;
+        testcase( pNew->wsFlags & WHERE_COLUMN_IN );
+        pNew->wsFlags |= WHERE_ONEROW;
       }
       pNew->u.btree.nEq++;
       pNew->nOut = (double)iRowEst * nInMul;
     }else if( pTerm->eOperator & (WO_ISNULL) ){
       pNew->wsFlags |= WHERE_COLUMN_NULL;
       pNew->u.btree.nEq++;
-      pNew->nOut = (double)iRowEst * nInMul;
+      nIn = 2;  /* Assume IS NULL matches two rows */
+      pNew->nOut = (double)iRowEst * nInMul * nIn;
     }else if( pTerm->eOperator & (WO_GT|WO_GE) ){
       pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_BTM_LIMIT;
       pBtm = pTerm;
@@ -4040,7 +4046,6 @@ static int whereLoopAddBtreeIndex(
       pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT)!=0 ?
                      pNew->aTerm[pNew->nTerm-2] : 0;
     }
-    pNew->rRun = rLogSize*nIn;  /* Cost for nIn binary searches */
     if( pNew->wsFlags & WHERE_COLUMN_RANGE ){
       /* Adjust nOut and rRun for STAT3 range values */
       double rDiv;
@@ -4218,7 +4223,7 @@ static int whereLoopAddBtree(
       for(j=pProbe->nColumn-1; j>=0; j--){
         int x = pProbe->aiColumn[j];
         if( x<BMS-1 ){
-          m &= ~(((Bitmask)1)<<x);
+          m &= ~MASKBIT(x);
         }
       }
       pNew->wsFlags = (m==0) ? (WHERE_IDX_ONLY|WHERE_INDEXED) : WHERE_INDEXED;
@@ -4529,7 +4534,7 @@ whereLoopAddAll_end:
 }
 
 /*
-** Examine a WherePath (with the addition of the extra WhereLoop of the 4th
+** Examine a WherePath (with the addition of the extra WhereLoop of the 5th
 ** parameters) to see if it outputs rows in the requested ORDER BY
 ** (or GROUP BY) without requiring a separate source operation.  Return:
 ** 
@@ -4542,52 +4547,50 @@ static int wherePathSatisfiesOrderBy(
   WhereInfo *pWInfo,    /* The WHERE clause */
   WherePath *pPath,     /* The WherePath to check */
   int nLoop,            /* Number of entries in pPath->aLoop[] */
-  int isLastLoop,       /* True for the very last loop */
+  int isLastLoop,       /* True if pLast is the inner-most loop */
   WhereLoop *pLast,     /* Add this WhereLoop to the end of pPath->aLoop[] */
   Bitmask *pRevMask     /* Mask of WhereLoops to run in reverse order */
 ){
   u8 revSet;            /* True if rev is known */
   u8 rev;               /* Composite sort order */
   u8 revIdx;            /* Index sort order */
-  u8 isOneRow;          /* Current WhereLoop is a one-row loop */
-  u8 requireOneRow = 0; /* All subsequent loops must be one-row */
-  u8 isUniqueIdx;       /* Current WhereLoop uses a unique index */
-  u16 nColumn;
-  u16 nOrderBy;
-  int i, j;
-  int nUsed = 0;
-  int iCur;
-  int iColumn;
-  WhereLoop *pLoop;
-  ExprList *pOrderBy = pWInfo->pOrderBy;
-  Expr *pOBExpr;
-  CollSeq *pColl;
-  Index *pIndex;
-  sqlite3 *db = pWInfo->pParse->db;
-  Bitmask revMask = 0;
+  u8 isWellOrdered;     /* All WhereLoops are well-ordered so far */
+  u16 nColumn;          /* Number of columns in pIndex */
+  u16 nOrderBy;         /* Number terms in the ORDER BY clause */
+  int iLoop;            /* Index of WhereLoop in pPath being processed */
+  int i, j;             /* Loop counters */
+  int iCur;             /* Cursor number for current WhereLoop */
+  int iColumn;          /* A column number within table iCur */
+  WhereLoop *pLoop;     /* Current WhereLoop being processed. */
+  ExprList *pOrderBy = pWInfo->pOrderBy;  /* the ORDER BY clause */
+  WhereTerm *pTerm;     /* A single term of the WHERE clause */
+  Expr *pOBExpr;        /* An expression from the ORDER BY clause */
+  CollSeq *pColl;       /* COLLATE function from an ORDER BY clause term */
+  Index *pIndex;        /* The index associated with pLoop */
+  sqlite3 *db = pWInfo->pParse->db;  /* Database connection */
+  Bitmask obSat = 0;    /* Mask of ORDER BY terms satisfied so far */
+  Bitmask obDone;       /* Mask of all ORDER BY terms */
+  Bitmask orderedMask;  /* Mask of all well-ordered loops */
+  WhereMaskSet *pMaskSet; /* WhereMaskSet object for this where clause */
+  
 
   /*
-  ** We say the WhereLoop is "one-row" if all of the following are true:
+  ** We say the WhereLoop is "one-row" if it generates no more than one
+  ** row of output.  A WhereLoop is one-row if all of the following are true:
   **  (a) All index columns match with WHERE_COLUMN_EQ.
   **  (b) The index is unique
+  ** Any WhereLoop with an WHERE_COLUMN_EQ constraint on the rowid is one-row.
+  ** Every one-row WhereLoop will have the WHERE_ONEROW bit set in wsFlags.
   **
-  ** General rules:  (not an algorithm!)
-  **
-  **  (1) If the current WhereLoop is one-row, then match over any and all
-  **      ORDER BY terms for the current WhereLoop and proceed to the next
-  **      WhereLoop.
-  **
-  **  (2) If the current WhereLoop is not one-row, then all subsequent
-  **      WhereLoops must be one-row.
-  **
-  **  (3) Optionally match any ORDER BY terms against the first nEq columns
-  **      of the index.
-  **
-  **  (4) Index columns past nEq must match ORDER BY terms one-for-one.
-  **
-  **  (5) If all columns of a UNIQUE index have been matched against ORDER BY
-  **      terms, then any subsequent entries in the ORDER BY clause against the
-  **      same table can be skipped.
+  ** We say the WhereLoop is "well-ordered" if
+  **  (i)  it satisfies at least one term of the ORDER BY clause, and
+  **  (ii) every row output is distinct over the terms that match the
+  **       ORDER BY clause.
+  ** Every one-row WhereLoop is automatically well-ordered, even if it
+  ** does not match any terms of the ORDER BY clause.
+  ** For condition (ii), be mindful that a UNIQUE column can have multiple
+  ** rows that are NULL and so it not necessarily distinct.  The column
+  ** must be UNIQUE and NOT NULL. in order to be well-ordered.
   */
 
   assert( pOrderBy!=0 );
@@ -4595,95 +4598,152 @@ static int wherePathSatisfiesOrderBy(
   /* Sortability of virtual tables is determined by the xBestIndex method
   ** of the virtual table itself */
   if( pLast->wsFlags & WHERE_VIRTUALTABLE ){
-    assert( nLoop==0 );
+    testcase( nLoop>0 );  /* True when outer loops are one-row and match 
+                          ** no ORDER BY terms */
     return pLast->u.vtab.isOrdered;
   }
+  if( nLoop && OptimizationDisabled(db, SQLITE_OrderByIdxJoin) ) return 0;
 
-  /* Sorting is always required if any term of the ORDER BY is not a 
-  ** column reference */
   nOrderBy = pOrderBy->nExpr;
-#if 0
-  for(i=0; i<nOrderBy; i++){
-    pOBExpr = sqlite3ExprSkipCollate(pOrderBy->a[i].pExpr);
-    if( pOBExpr->op!=TK_COLUMN ) return 0;
-  }
-#endif
-    
-  for(i=0; i<=nLoop && nUsed<nOrderBy; i++){
-    pLoop = i<nLoop ? pPath->aLoop[i] : pLast;
+  if( nOrderBy>60 ) return 0;
+  isWellOrdered = 1;
+  obDone = MASKBIT(nOrderBy)-1;
+  orderedMask = 0;
+  pMaskSet = pWInfo->pWC->pMaskSet;
+  for(iLoop=0; isWellOrdered && obSat<obDone && iLoop<=nLoop; iLoop++){
+    pLoop = iLoop<nLoop ? pPath->aLoop[iLoop] : pLast;
     assert( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 );
-    isOneRow = isUniqueIdx = 1;
-    if( pLoop->wsFlags & WHERE_IPK ){
-      if( (pLoop->wsFlags & WHERE_COLUMN_IN)!=0 ) isOneRow = 0;
-      if( pLoop->u.btree.nEq!=1 ) isOneRow = 0;
-      pIndex = 0;
-      nColumn = 0;
-    }else if( (pIndex = pLoop->u.btree.pIndex)==0 || pIndex->bUnordered ){
-      return 0;
-    }else{
-      nColumn = pIndex->nColumn;
-      if( pIndex->onError==OE_None ){
-        isOneRow = isUniqueIdx = 0;
-      }else if( (pLoop->wsFlags & (WHERE_COLUMN_IN|WHERE_COLUMN_RANGE
-                                   |WHERE_COLUMN_NULL))!=0 ){
-        isOneRow = 0;
-      }else if( pLoop->u.btree.nEq < pIndex->nColumn ){
-        isOneRow = 0;
-      }
-    }
-    if( !isOneRow && requireOneRow ) return 0;
-    requireOneRow = !isOneRow;
     iCur = pWInfo->pTabList->a[pLoop->iTab].iCursor;
-    j = 0;
-    revSet = rev = 0;
-    for(j=0; j<=nColumn && nUsed<nOrderBy; j++, nUsed++){
-      int skipable;
-      pOBExpr = sqlite3ExprSkipCollate(pOrderBy->a[nUsed].pExpr);
-      if( pOBExpr->op!=TK_COLUMN ) return 0;
-      if( pOBExpr->iTable!=iCur ) break;
-      if( isOneRow ){ j--; continue; }
-      if( j<nColumn ){
-        /* Normal index columns */
-        iColumn = pIndex->aiColumn[j];
-        revIdx = pIndex->aSortOrder[j];
-        if( iColumn==pIndex->pTable->iPKey ) iColumn = -1;
-      }else{
-        /* The ROWID column at the end */
-        iColumn = -1;
-        revIdx = 0;
-      }
-      skipable = j<pLoop->u.btree.nEq && pLoop->aTerm[j]->eOperator!=WO_IN;
-      if( pOBExpr->iColumn!=iColumn ){
-        if( skipable ){ nUsed--; continue; }
+    if( (pLoop->wsFlags & WHERE_ONEROW)==0 ){
+      if( pLoop->wsFlags & WHERE_IPK ){
+        pIndex = 0;
+        nColumn = 0;
+      }else if( (pIndex = pLoop->u.btree.pIndex)==0 || pIndex->bUnordered ){
         return 0;
+      }else{
+        nColumn = pIndex->nColumn;
       }
-      if( iColumn>=0 ){
-        pColl = sqlite3ExprCollSeq(pWInfo->pParse, pOrderBy->a[nUsed].pExpr);
-        if( !pColl ) pColl = db->pDfltColl;
-        if( sqlite3StrICmp(pColl->zName, pIndex->azColl[j])!=0 ){
-          return 0;
+
+      /* For every term of the index that is constrained by == or IS NULL
+      ** mark off corresponding ORDER BY terms wherever they occur
+      ** in the ORDER BY clause.
+      */
+      for(i=0; i<pLoop->u.btree.nEq; i++){
+        pTerm = pLoop->aTerm[i];
+        if( (pTerm->eOperator & (WO_EQ|WO_ISNULL))==0 ) continue;
+        iColumn = pTerm->u.leftColumn;
+        for(j=0; j<nOrderBy; j++){
+          if( MASKBIT(j) & obSat ) continue;
+          pOBExpr = sqlite3ExprSkipCollate(pOrderBy->a[j].pExpr);
+          if( pOBExpr->op!=TK_COLUMN ) continue;
+          if( pOBExpr->iTable!=iCur ) continue;
+          if( pOBExpr->iColumn!=iColumn ) continue;
+          if( iColumn>=0 ){
+            pColl = sqlite3ExprCollSeq(pWInfo->pParse, pOrderBy->a[j].pExpr);
+            if( !pColl ) pColl = db->pDfltColl;
+            if( sqlite3StrICmp(pColl->zName, pIndex->azColl[i])!=0 ) continue;
+          }
+          obSat |= MASKBIT(j);
         }
+        if( obSat==obDone ) return 1;
       }
-      if( !skipable ){
-        if( revSet ){
-          if( (rev ^ revIdx)!=pOrderBy->a[nUsed].sortOrder ) return 0;
+
+      /* Loop through all columns of the index and deal with the ones
+      ** that are not constrained by == or IN.
+      */
+      rev = revSet = 0;
+      for(j=0; j<=nColumn; j++){
+        u8 bOnce;   /* True to run the ORDER BY search loop */
+
+        if( j<pLoop->u.btree.nEq
+         && (pLoop->aTerm[j]->eOperator & (WO_EQ|WO_ISNULL))!=0
+        ){
+          continue;  /* Skip == and IS NULL terms already processed */
+        }
+
+        /* Get the column number in the table and sort order for the
+        ** j-th column of the index for this WhereLoop
+        */
+        if( j<nColumn ){
+          /* Normal index columns */
+          iColumn = pIndex->aiColumn[j];
+          revIdx = pIndex->aSortOrder[j];
+          if( iColumn==pIndex->pTable->iPKey ) iColumn = -1;
         }else{
-          rev = revIdx ^ pOrderBy->a[nUsed].sortOrder;
-          revSet = 1;
+          /* The ROWID column at the end */
+          iColumn = -1;
+          revIdx = 0;
+        }
+
+        /* An unconstrained column that might be NULL means that this
+        ** WhereLoop is not well-ordered 
+        */
+        if( iColumn>=0
+         && j>=pLoop->u.btree.nEq
+         && pIndex->pTable->aCol[iColumn].notNull==0
+        ){
+          isWellOrdered = 0;
+        }
+
+        /* Find the ORDER BY term that corresponds to the j-th column
+        ** of the index and and mark that ORDER BY term off 
+        */
+        bOnce = 1;
+        for(i=0; bOnce && i<nOrderBy; i++){
+          if( MASKBIT(i) & obSat ) continue;
+          pOBExpr = sqlite3ExprSkipCollate(pOrderBy->a[i].pExpr);
+          if( pOBExpr->op!=TK_COLUMN ) continue;
+          if( (pWInfo->wctrlFlags & WHERE_GROUPBY)==0 ) bOnce = 0;
+          if( pOBExpr->iTable!=iCur ) continue;
+          if( pOBExpr->iColumn!=iColumn ) continue;
+          if( iColumn>=0 ){
+            pColl = sqlite3ExprCollSeq(pWInfo->pParse, pOrderBy->a[i].pExpr);
+            if( !pColl ) pColl = db->pDfltColl;
+            if( sqlite3StrICmp(pColl->zName, pIndex->azColl[j])!=0 ) continue;
+          }
+          bOnce = 1;
+          break;
+        }
+        if( bOnce && i<nOrderBy ){
+          if( iColumn<0 ) isWellOrdered = 1;
+          obSat |= MASKBIT(i);
+          if( (pWInfo->wctrlFlags & WHERE_GROUPBY)==0 ){
+            /* If we have an ORDER BY clause, we must match the next available
+            ** column of the ORDER BY */
+            if( revSet ){
+              if( (rev ^ revIdx)!=pOrderBy->a[i].sortOrder ) return 0;
+            }else{
+              rev = revIdx ^ pOrderBy->a[i].sortOrder;
+              if( rev ) *pRevMask |= MASKBIT(iLoop);
+              revSet = 1;
+            }
+          }
+        }else{
+          /* No match found */
+          if( j<nColumn || pIndex==0 || pIndex->onError!=OE_Abort ){
+            isWellOrdered = 0;
+          }
+          break;
+        }
+      } /* end Loop over all index columns */
+    } /* end-if not one-row */
+
+    /* Mark off any other ORDER BY terms that reference pLoop */
+    if( isWellOrdered ){
+      orderedMask |= pLoop->maskSelf;
+      for(i=0; i<nOrderBy; i++){
+        Expr *p;
+        if( MASKBIT(i) & obSat ) continue;
+        p = pOrderBy->a[i].pExpr;
+        if( (exprTableUsage(pMaskSet, p)&~orderedMask)==0 ){
+          obSat |= MASKBIT(i);
         }
-      }
-      if( j>=nColumn-1 && isUniqueIdx ){
-        if( isLastLoop && i==nLoop ) break;
-        j--;
-        isOneRow = 1;
       }
     }
-    if( rev ) revMask |= ((Bitmask)1)<<i;
-  }
-  if( isLastLoop || nUsed==nOrderBy ){
-    *pRevMask = revMask;
-    return 1;
   }
+  if( obSat==obDone ) return 1;
+  if( !isWellOrdered ) return 0;
+  if( isLastLoop ) return 1;
   return -1;
 }
 
@@ -5216,7 +5276,7 @@ WhereInfo *sqlite3WhereBegin(
   ** the statement to update a single row.
   */
   assert( (wctrlFlags & WHERE_ONEPASS_DESIRED)==0 || pWInfo->nLevel==1 );
-  if( (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 && (andFlags & WHERE_UNIQUE)!=0 ){
+  if( (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 && (andFlags & WHERE_ONEROW)!=0 ){
     pWInfo->okOnePass = 1;
     pWInfo->a[0].plan.wsFlags &= ~WHERE_IDX_ONLY;
   }
index 037f100f6580ca0665c72c6a54fab4581ad07673..5f31d1ecc00dec2cc68e7492f9c2799789eca3ad 100644 (file)
@@ -46,6 +46,12 @@ do_execsql_test 1.3 {
   SELECT coalesce(b, 'null') || '/' || c FROM t8 x ORDER BY x.b, x.c
 } {null/four null/three a/one b/two}
 
+do_execsql_test 1.4 {
+  DROP INDEX t8;
+  CREATE UNIQUE INDEX i1 ON t8(b, c);
+  SELECT coalesce(b, 'null') || '/' || c FROM t8 x ORDER BY x.b, x.c
+} {null/four null/three a/one b/two}
+
 #-------------------------------------------------------------------------
 #
 
@@ -68,4 +74,3 @@ do_test 2.4 {
 } {sort}
 
 finish_test
-