]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
First pass at optimizing max()/min() as described in #2853. Some refinements to come...
authordanielk1977 <danielk1977@noemail.net>
Sat, 5 Jan 2008 17:39:29 +0000 (17:39 +0000)
committerdanielk1977 <danielk1977@noemail.net>
Sat, 5 Jan 2008 17:39:29 +0000 (17:39 +0000)
FossilOrigin-Name: c449e04f1870b1ff726c95c0bf1c6c6a22ca588a

manifest
manifest.uuid
src/delete.c
src/select.c
src/sqliteInt.h
src/update.c
src/where.c
test/collate4.test
test/minmax.test
test/minmax2.test
test/minmax3.test [new file with mode: 0644]

index 88780f56b9aab9de318a39d766c15201c9f153cf..b2c79f67340ee886c12c9222051fb6515901f782 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Registerify\sbinary\soperators.\s\sAdd\sregister\stracing\sto\sdebugging\soutput.\s(CVS\s4686)
-D 2008-01-05T16:29:28
+C First\spass\sat\soptimizing\smax()/min()\sas\sdescribed\sin\s#2853.\sSome\srefinements\sto\scome.\s(CVS\s4687)
+D 2008-01-05T17:39:30
 F Makefile.arm-wince-mingw32ce-gcc ac5f7b2cef0cd850d6f755ba6ee4ab961b1fadf7
 F Makefile.in 30789bf70614bad659351660d76b8e533f3340e9
 F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654
@@ -90,7 +90,7 @@ F src/build.c 60a3ec74b45634654e06a72224ac01bd33ac69f1
 F src/callback.c 77b302b0d41468dcda78c70e706e5b84577f0fa0
 F src/complete.c 4cf68fd75d60257524cbe74f87351b9848399131
 F src/date.c 49c5a6d2de6c12000905b4d36868b07d3011bbf6
-F src/delete.c cb1d5be17c99e41d1675763a57848bb5dd45191c
+F src/delete.c 209f33fdf34dcbaa08752437bf53aff0cef0eca6
 F src/experimental.c 1b2d1a6cd62ecc39610e97670332ca073c50792b
 F src/expr.c cb8b65c3adc8bb39f67503dfe8db8da24ebe5d21
 F src/func.c 996071cf0af9d967e58b69fce1909555059ebc7d
@@ -131,12 +131,12 @@ F src/pragma.c dfb200ec383b5ab3e81cd7bc4e1305e71053ef9a
 F src/prepare.c f1bb8eb642082e618a359c08e3e107490eafe0e3
 F src/printf.c eb27822ba2eec669161409ca31279a24c26ac910
 F src/random.c 4a22746501bf36b0a088c66e38dde5daba6a35da
-F src/select.c 102eb03b6daa3c113fac32019dd281f01a38baa8
+F src/select.c 33c60380c81283c16414040d034b76f1732ffb4e
 F src/server.c 087b92a39d883e3fa113cae259d64e4c7438bc96
 F src/shell.c 5391e889384d2062249f668110d64ed16f601c4b
 F src/sqlite.h.in 2a7e3776534bbe6ff2cdc058f3abebe91e7e429f
 F src/sqlite3ext.h a93f59cdee3638dc0c9c086f80df743a4e68c3cb
-F src/sqliteInt.h 80f81e0a0b712a74f1c841e6336b51126d781df0
+F src/sqliteInt.h 1e7a6545eda5e5e670000775f5d74003e0bc4fbb
 F src/sqliteLimit.h ee4430f88f69bf63527967bb35ca52af7b0ccb1e
 F src/table.c 1aeb9eab57b4235db86fe15a35dec76fb445a9c4
 F src/tclsqlite.c 9923abeffc9b3d7dad58e92b319661521f60debf
@@ -164,7 +164,7 @@ F src/test_tclvar.c b2d1115e4d489179d3f029e765211b2ad527ba59
 F src/test_thread.c e297dd41db0b249646e69f97d36ec13e56e8b730
 F src/tokenize.c a4e04438c11fed2c67ec47fe3edbef9cca2d1b48
 F src/trigger.c 91ff1552b5c2cd66a077563a026d183c1dc993d6
-F src/update.c ac6cdfebf88340fd68550b1d7fd6a15ad7144fd8
+F src/update.c f322317ee492c0a648f8a44fd805dd85dbbe2f05
 F src/utf.c ef4b7d83bae533b76c3e1bf635b113fdad86a736
 F src/util.c 05f31144bbd3f1a24f4139ae029c42545cb72624
 F src/vacuum.c 3f34f278809bf3eb0b62ec46ff779e9c385b28f0
@@ -177,7 +177,7 @@ F src/vdbeblob.c b90f7494c408d47ce6835000b01e40b371e27baf
 F src/vdbefifo.c 334c838c8f42d61a94813d136019ee566b5dc2f6
 F src/vdbemem.c 123994fcd344993d2fb050a83b91b341bbbd08b4
 F src/vtab.c 03014b2bfa8096ecac5fcdc80d34cd76e06af52a
-F src/where.c 941635bb007484330bc1a0f3cc013b67f1c41864
+F src/where.c 0d72b6431c23da6fb1b72422e364ac8fe7eb1d3a
 F tclinstaller.tcl 4356d9d94d2b5ed5e68f9f0c80c4df3048dd7617
 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
 F test/all.test ee350b9ab15b175fc0a8fb51bf2141ed3a3b9cba
@@ -227,7 +227,7 @@ F test/check.test 024ed399600b799160378cf9d9f436bdf5dfd184
 F test/collate1.test e3eaa48c21e150814be1a7b852d2a8af24458d04
 F test/collate2.test 701d9651c5707024fd86a20649af9ea55e2c0eb8
 F test/collate3.test 947a77f5b8227e037a7094d0e338a5504f155cc4
-F test/collate4.test daf498e294dcd596b961d425c3f2dda117e4717e
+F test/collate4.test 4545554388daaa604e5b3def3aa2f7ed6d56e8da
 F test/collate5.test e54df13eb9e1140273680b3153c6e19b39e59888
 F test/collate6.test 8be65a182abaac8011a622131486dafb8076e907
 F test/collate7.test e23677b1fd271505302643a98178952bb65b6f21
@@ -379,8 +379,9 @@ F test/malloc_common.tcl b47137fb36e95fdafb0267745afefcd6b0a5b9dc
 F test/manydb.test 8de36b8d33aab5ef295b11d9e95310aeded31af8
 F test/memdb.test a67bda4ff90a38f2b19f6c7f95aa7289e051d893
 F test/memleak.test d2d2a1ff7105d32dc3fdf691458cf6cba58c7217
-F test/minmax.test 66434d8ee04869fe4c220b665b73748accbb9163
-F test/minmax2.test 8294b6728819608861ba0e06ac1d9a87c4d815b5
+F test/minmax.test 5d56f08a7765dfb5c1fb303333f7444dacb37bef
+F test/minmax2.test 33504c01a03bd99226144e4b03f7631a274d66e0
+F test/minmax3.test 5b89f055df7ed03334e96eec0efb804afb7de638
 F test/misc1.test 1b89c02c4a33b49dee4cd1d20d161aaaba719075
 F test/misc2.test 1ee89298de9c16b61454658b24999c403e86afe4
 F test/misc3.test 7bd937e2c62bcc6be71939faf068d506467b1e03
@@ -603,7 +604,7 @@ F www/tclsqlite.tcl 8be95ee6dba05eabcd27a9d91331c803f2ce2130
 F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0
 F www/version3.tcl 890248cf7b70e60c383b0e84d77d5132b3ead42b
 F www/whentouse.tcl fc46eae081251c3c181bd79c5faef8195d7991a5
-P 6c78d2a49a3e6ee8bc31f16488a430cba9eda59d
-R fdff0af63eb57b92a8e5bcade477a016
-U drh
-Z c3fdc5267d8f427a421314acf39cdbc6
+P 66396d2f0289e36b5fc0af5078c08d1b17f342ae
+R b1dacf0522dc83de3d6ddccfc407074e
+U danielk1977
+Z 85372970e5aef600b6f97b67c85860f6
index 6f2823bcce3003ee3381a9dc9fe3bb0ade4e1f51..74d5a25e9306f0c09821f9705d49101e5509a9e0 100644 (file)
@@ -1 +1 @@
-66396d2f0289e36b5fc0af5078c08d1b17f342ae
\ No newline at end of file
+c449e04f1870b1ff726c95c0bf1c6c6a22ca588a
\ No newline at end of file
index 3fce44414488d21e6ed13f1555b21a839d94b317..c87bf47ab040932f7a378bc203dc033ba7aed1a6 100644 (file)
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** in order to generate code for DELETE FROM statements.
 **
-** $Id: delete.c,v 1.150 2008/01/05 05:20:10 drh Exp $
+** $Id: delete.c,v 1.151 2008/01/05 17:39:30 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 
@@ -313,7 +313,7 @@ void sqlite3DeleteFrom(
 
     /* Begin the database scan
     */
-    pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0);
+    pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0);
     if( pWInfo==0 ) goto delete_from_cleanup;
 
     /* Remember the rowid of every item to be deleted.
index 4ccaf79799fecdec9c60f1e72f2bdda9be7d5b49..445f08a2b049a7a9e6c719724032deeeee4c28d6 100644 (file)
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle SELECT statements in SQLite.
 **
-** $Id: select.c,v 1.386 2008/01/05 05:20:10 drh Exp $
+** $Id: select.c,v 1.387 2008/01/05 17:39:30 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 
@@ -2625,6 +2625,33 @@ static int flattenSubquery(
 }
 #endif /* SQLITE_OMIT_VIEW */
 
+/*
+** Analyze the SELECT statement passed as an argument to see if it
+** is a min() or max() query. Return ORDERBY_MIN or ORDERBY_MAX if 
+** it is, or 0 otherwise. At present, a query is considered to be
+** a min()/max() query if:
+**
+**   1. The result set contains exactly one element, either 
+**      min(x) or max(x), where x is a column identifier.
+*/
+static int minMaxQuery(Parse *pParse, Select *p){
+  Expr *pExpr;
+  ExprList *pEList = p->pEList;
+
+  if( pEList->nExpr!=1 ) return ORDERBY_NORMAL;
+  pExpr = pEList->a[0].pExpr;
+  pEList = pExpr->pList;
+  if( pExpr->op!=TK_AGG_FUNCTION || pEList==0 || pEList->nExpr!=1 ) return 0;
+  if( pEList->a[0].pExpr->op!=TK_AGG_COLUMN ) return ORDERBY_NORMAL;
+  if( pExpr->token.n!=3 ) return ORDERBY_NORMAL;
+  if( sqlite3StrNICmp((char*)pExpr->token.z,"min",3)==0 ){
+    return ORDERBY_MIN;
+  }else if( sqlite3StrNICmp((char*)pExpr->token.z,"max",3)==0 ){
+    return ORDERBY_MAX;
+  }
+  return ORDERBY_NORMAL;
+}
+
 /*
 ** Analyze the SELECT statement passed in as an argument to see if it
 ** is a simple min() or max() query.  If it is and this query can be
@@ -2645,6 +2672,7 @@ static int flattenSubquery(
 ** The parameters to this routine are the same as for sqlite3Select().
 ** See the header comment on that routine for additional information.
 */
+#if 0
 static int simpleMinMaxQuery(Parse *pParse, Select *p, SelectDest *pDest){
   Expr *pExpr;
   int iCol;
@@ -2781,6 +2809,7 @@ static int simpleMinMaxQuery(Parse *pParse, Select *p, SelectDest *pDest){
   
   return 1;
 }
+#endif 
 
 /*
 ** This routine resolves any names used in the result set of the
@@ -3268,10 +3297,12 @@ int sqlite3Select(
   /* Check for the special case of a min() or max() function by itself
   ** in the result set.
   */
+#if 0
   if( simpleMinMaxQuery(pParse, p, pDest) ){
     rc = 0;
     goto select_end;
   }
+#endif
 
   /* Check to see if this is a subquery that can be "flattened" into its parent.
   ** If flattening is a possiblity, do so and return immediately.  
@@ -3345,7 +3376,7 @@ int sqlite3Select(
     /* This case is for non-aggregate queries
     ** Begin the database scan
     */
-    pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, &pOrderBy);
+    pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, &pOrderBy, 0);
     if( pWInfo==0 ) goto select_end;
 
     /* If sorting index that was created by a prior OP_OpenEphemeral 
@@ -3501,7 +3532,7 @@ int sqlite3Select(
       */
       sqlite3VdbeResolveLabel(v, addrInitializeLoop);
       sqlite3VdbeAddOp2(v, OP_Gosub, 0, addrReset);
-      pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, &pGroupBy);
+      pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, &pGroupBy, 0);
       if( pWInfo==0 ) goto select_end;
       if( pGroupBy==0 ){
         /* The optimizer is able to deliver rows in group by order so
@@ -3606,14 +3637,30 @@ int sqlite3Select(
       
     } /* endif pGroupBy */
     else {
+      ExprList *pMinMax = 0;
+      u8 flag;
+
+      flag = minMaxQuery(pParse, p);
+      if( flag ){
+        pMinMax = sqlite3ExprListDup(db, p->pEList->a[0].pExpr->pList);
+        if( pMinMax ){
+          pMinMax->a[0].sortOrder = ((flag==ORDERBY_MIN)?0:1);
+          pMinMax->a[0].pExpr->op = TK_COLUMN;
+        }
+      }
+
       /* This case runs if the aggregate has no GROUP BY clause.  The
       ** processing is much simpler since there is only a single row
       ** of output.
       */
       resetAccumulator(pParse, &sAggInfo);
-      pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0);
+      pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, &pMinMax, flag);
       if( pWInfo==0 ) goto select_end;
       updateAccumulator(pParse, &sAggInfo);
+      if( !pMinMax && flag ){
+        sqlite3VdbeAddOp2(v, OP_Goto, 0, pWInfo->iBreak);
+        VdbeComment((v, "%s() by index", (flag==ORDERBY_MIN?"min":"max")));
+      }
       sqlite3WhereEnd(pWInfo);
       finalizeAggFunctions(pParse, &sAggInfo);
       pOrderBy = 0;
@@ -3622,6 +3669,8 @@ int sqlite3Select(
       }
       selectInnerLoop(pParse, p, p->pEList, 0, 0, 0, -1, 
                       pDest, addrEnd, addrEnd, aff);
+
+      sqlite3ExprListDelete(pMinMax);
     }
     sqlite3VdbeResolveLabel(v, addrEnd);
     
index 14d7d08e9f189309e85b55799a06060dc26eddd9..53acb136f3c6a3c71e13b7846b31def2c5958e68 100644 (file)
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.638 2008/01/04 19:10:29 danielk1977 Exp $
+** @(#) $Id: sqliteInt.h,v 1.639 2008/01/05 17:39:30 danielk1977 Exp $
 */
 #ifndef _SQLITEINT_H_
 #define _SQLITEINT_H_
@@ -1229,6 +1229,10 @@ struct WhereLevel {
   sqlite3_index_info *pIdxInfo;  /* Index info for n-th source table */
 };
 
+#define ORDERBY_NORMAL 0
+#define ORDERBY_MIN    1
+#define ORDERBY_MAX    2
+
 /*
 ** The WHERE clause processing routine has two halves.  The
 ** first part does the start of the WHERE loop and the second
@@ -1735,7 +1739,7 @@ int sqlite3IsReadOnly(Parse*, Table*, int);
 void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int);
 void sqlite3DeleteFrom(Parse*, SrcList*, Expr*);
 void sqlite3Update(Parse*, SrcList*, ExprList*, Expr*, int);
-WhereInfo *sqlite3WhereBegin(Parse*, SrcList*, Expr*, ExprList**);
+WhereInfo *sqlite3WhereBegin(Parse*, SrcList*, Expr*, ExprList**, u8);
 void sqlite3WhereEnd(WhereInfo*);
 void sqlite3ExprCodeGetColumn(Vdbe*, Table*, int, int, int);
 int sqlite3ExprCode(Parse*, Expr*, int);
index 0966802d18551230f5d260c223554954bf3aa44d..32402cc31e4b9f7b6cab9f74b89e162eff672d92 100644 (file)
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle UPDATE statements.
 **
-** $Id: update.c,v 1.160 2008/01/05 05:20:10 drh Exp $
+** $Id: update.c,v 1.161 2008/01/05 17:39:30 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 
@@ -339,7 +339,7 @@ void sqlite3Update(
 
   /* Begin the database scan
   */
-  pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0);
+  pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0);
   if( pWInfo==0 ) goto update_cleanup;
 
   /* Remember the rowid of every item to be updated.
index 0ca12237999797adee6ef67422b5fb80012fef43..360e191ee4f10b0ed89bed3861350e54ac1dbae7 100644 (file)
@@ -16,7 +16,7 @@
 ** so is applicable.  Because this module is responsible for selecting
 ** indices, you might also think of this module as the "query optimizer".
 **
-** $Id: where.c,v 1.275 2008/01/05 05:38:21 drh Exp $
+** $Id: where.c,v 1.276 2008/01/05 17:39:30 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 
@@ -1969,7 +1969,8 @@ WhereInfo *sqlite3WhereBegin(
   Parse *pParse,        /* The parser context */
   SrcList *pTabList,    /* A list of all tables to be scanned */
   Expr *pWhere,         /* The WHERE clause */
-  ExprList **ppOrderBy  /* An ORDER BY clause, or NULL */
+  ExprList **ppOrderBy, /* An ORDER BY clause, or NULL */
+  u8 obflag             /* One of ORDERBY_MIN, ORDERBY_MAX or ORDERBY_NORMAL */
 ){
   int i;                     /* Loop counter */
   WhereInfo *pWInfo;         /* Will become the return value of this function */
@@ -1984,6 +1985,7 @@ WhereInfo *sqlite3WhereBegin(
   int iFrom;                      /* First unused FROM clause element */
   int andFlags;              /* AND-ed combination of all wc.a[].flags */
   sqlite3 *db;               /* Database connection */
+  ExprList *pOrderBy = 0;
 
   /* The number of tables in the FROM clause is limited by the number of
   ** bits in a Bitmask 
@@ -1993,6 +1995,10 @@ WhereInfo *sqlite3WhereBegin(
     return 0;
   }
 
+  if( ppOrderBy ){
+    pOrderBy = *ppOrderBy;
+  }
+
   /* Split the WHERE clause into separate subexpressions where each
   ** subexpression is separated by an AND operator.
   */
@@ -2400,6 +2406,7 @@ WhereInfo *sqlite3WhereBegin(
       int testOp;
       int topLimit = (pLevel->flags & WHERE_TOP_LIMIT)!=0;
       int btmLimit = (pLevel->flags & WHERE_BTM_LIMIT)!=0;
+      int isMinQuery = 0;      /* If this is an optimized SELECT min(x) ... */
 
       /* Generate code to evaluate all constraint terms using == or IN
       ** and level the values of those terms on the stack.
@@ -2428,6 +2435,22 @@ WhereInfo *sqlite3WhereBegin(
         SWAP(int, topLimit, btmLimit);
       }
 
+      /* If this loop satisfies a sort order (pOrderBy) request that 
+      ** was passed to this function to implement a "SELECT min(x) ..." 
+      ** query, then the caller will only allow the loop to run for
+      ** a single iteration. This means that the first row returned
+      ** should not have a NULL value stored in 'x'. If column 'x' is
+      ** the first one after the nEq equality constraints in the index,
+      ** this requires some special handling.
+      */
+      if( (obflag==ORDERBY_MIN)
+       && (pLevel->flags&WHERE_ORDERBY)
+       && (pIdx->nColumn>nEq)
+       && (pOrderBy->a[0].pExpr->iColumn==pIdx->aiColumn[nEq])
+      ){
+        isMinQuery = 1;
+      }
+
       /* Generate the termination key.  This is the key value that
       ** will end the search.  There is no termination key if there
       ** are no equality terms and no "X<..." term.
@@ -2452,9 +2475,14 @@ WhereInfo *sqlite3WhereBegin(
         testOp = nEq>0 ? OP_IdxGE : OP_Noop;
         topEq = 1;
       }
-      if( testOp!=OP_Noop ){
+      if( testOp!=OP_Noop || (isMinQuery&&bRev) ){
         int nCol = nEq + topLimit;
         pLevel->iMem = ++pParse->nMem;
+        if( isMinQuery && !topLimit ){
+          nCol++;
+          sqlite3VdbeAddOp2(v, OP_Null, 0, 0);
+          topEq = 0;
+        }
         buildIndexProbe(v, nCol, pIdx);
         if( bRev ){
           int op = topEq ? OP_MoveLe : OP_MoveLt;
@@ -2465,7 +2493,7 @@ WhereInfo *sqlite3WhereBegin(
       }else if( bRev ){
         sqlite3VdbeAddOp2(v, OP_Last, iIdxCur, brk);
       }
-
+   
       /* Generate the start key.  This is the key that defines the lower
       ** bound on the search.  There is no start key if there are no
       ** equality terms and if there is no "X>..." term.  In
@@ -2489,8 +2517,13 @@ WhereInfo *sqlite3WhereBegin(
       }else{
         btmEq = 1;
       }
-      if( nEq>0 || btmLimit ){
+      if( nEq>0 || btmLimit || (isMinQuery&&!bRev) ){
         int nCol = nEq + btmLimit;
+        if( isMinQuery && !btmLimit ){
+          nCol++;
+          sqlite3VdbeAddOp2(v, OP_Null, 0, 0);
+          btmEq = 0;
+        }
         buildIndexProbe(v, nCol, pIdx);
         if( bRev ){
           pLevel->iMem = ++pParse->nMem;
@@ -2538,6 +2571,7 @@ WhereInfo *sqlite3WhereBegin(
       */
       int start;
       int nEq = pLevel->nEq;
+      int isMinQuery = 0;      /* If this is an optimized SELECT min(x) ... */
 
       /* Generate code to evaluate all constraint terms using == or IN
       ** and leave the values of those terms on the stack.
@@ -2545,11 +2579,28 @@ WhereInfo *sqlite3WhereBegin(
       codeAllEqualityTerms(pParse, pLevel, &wc, notReady);
       nxt = pLevel->nxt;
 
-      /* Generate a single key that will be used to both start and terminate
-      ** the search
-      */
-      buildIndexProbe(v, nEq, pIdx);
-      sqlite3VdbeAddOp2(v, OP_Copy, 0, pLevel->iMem);
+      if( (obflag==ORDERBY_MIN)
+       && (pLevel->flags&WHERE_ORDERBY) 
+       && (pIdx->nColumn>nEq)
+       && (pOrderBy->a[0].pExpr->iColumn==pIdx->aiColumn[nEq])
+      ){
+        int h;
+        isMinQuery = 1;
+        for(h=0; h<nEq; h++){
+          sqlite3VdbeAddOp1(v, OP_Copy, 1-nEq);
+        }
+        buildIndexProbe(v, nEq, pIdx);
+        sqlite3VdbeAddOp2(v, OP_Copy, 0, pLevel->iMem);
+        sqlite3VdbeAddOp2(v, OP_Pop, 1, 0);
+        sqlite3VdbeAddOp2(v, OP_Null, 0, 0);
+        buildIndexProbe(v, nEq+1, pIdx);
+      }else{
+        /* Generate a single key that will be used to both start and 
+        ** terminate the search
+        */
+        buildIndexProbe(v, nEq, pIdx);
+        sqlite3VdbeAddOp2(v, OP_Copy, 0, pLevel->iMem);
+      }
 
       /* Generate code (1) to move to the first matching element of the table.
       ** Then generate code (2) that jumps to "nxt" after the cursor is past
@@ -2558,13 +2609,13 @@ WhereInfo *sqlite3WhereBegin(
       ** iteration of the scan to see if the scan has finished. */
       if( bRev ){
         /* Scan in reverse order */
-        sqlite3VdbeAddOp2(v, OP_MoveLe, iIdxCur, nxt);
+        sqlite3VdbeAddOp2(v, (isMinQuery?OP_MoveLt:OP_MoveLe), iIdxCur, nxt);
         start = sqlite3VdbeAddOp2(v, OP_SCopy, pLevel->iMem, 0);
         sqlite3VdbeAddOp2(v, OP_IdxLT, iIdxCur, nxt);
         pLevel->op = OP_Prev;
       }else{
         /* Scan in the forward order */
-        sqlite3VdbeAddOp2(v, OP_MoveGe, iIdxCur, nxt);
+        sqlite3VdbeAddOp2(v, (isMinQuery?OP_MoveGt:OP_MoveGe), iIdxCur, nxt);
         start = sqlite3VdbeAddOp2(v, OP_SCopy, pLevel->iMem, 0);
         sqlite3VdbeAddOp4(v, OP_IdxGE, iIdxCur, nxt, 0, "+", P4_STATIC);
         pLevel->op = OP_Next;
index 7dcd32d7cf44ead668caa34a0b65405dde9444f0..4db4c311b97d55b81c2a9803a8fa0702e71a63a6 100644 (file)
@@ -12,7 +12,7 @@
 # This file implements regression tests for SQLite library.  The
 # focus of this script is page cache subsystem.
 #
-# $Id: collate4.test,v 1.8 2005/04/01 10:47:40 drh Exp $
+# $Id: collate4.test,v 1.9 2008/01/05 17:39:30 danielk1977 Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -591,12 +591,12 @@ do_test collate4-4.3 {
   count {
     SELECT min(a) FROM collate4t1;
   }
-} {10 2}
+} {10 1}
 do_test collate4-4.4 {
   count {
     SELECT max(a) FROM collate4t1;
   }
-} {20 1}
+} {20 0}
 do_test collate4-4.5 {
   # Test that the index with collation type NUMERIC is not used.
   execsql {
index 074f6dff2407ff2c485acb69cd26aec5b88d7552..74c207f14aae247574b1f5f7376b25b54ebf3585 100644 (file)
@@ -13,7 +13,7 @@
 # aggregate min() and max() functions and which are handled as
 # as a special case.
 #
-# $Id: minmax.test,v 1.19 2006/03/26 01:21:23 drh Exp $
+# $Id: minmax.test,v 1.20 2008/01/05 17:39:30 danielk1977 Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -68,14 +68,14 @@ do_test minmax-1.5 {
 } {1}
 do_test minmax-1.6 {
   set sqlite_search_count
-} {2}
+} {1}
 do_test minmax-1.7 {
   set sqlite_search_count 0
   execsql {SELECT max(x) FROM t1}
 } {20}
 do_test minmax-1.8 {
   set sqlite_search_count
-} {1}
+} {0}
 do_test minmax-1.9 {
   set sqlite_search_count 0
   execsql {SELECT max(y) FROM t1}
index a3049408c00fbc9cbd6e7aff024b7bb92b4cd2b1..2f504d499944343973bf4e9670fe4b409ccea4c6 100644 (file)
@@ -15,7 +15,7 @@
 # optimization works right in the presence of descending
 # indices.  Ticket #2514.
 #
-# $Id: minmax2.test,v 1.1 2007/07/18 18:17:12 drh Exp $
+# $Id: minmax2.test,v 1.2 2008/01/05 17:39:30 danielk1977 Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -71,14 +71,14 @@ do_test minmax2-1.5 {
 } {1}
 do_test minmax2-1.6 {
   set sqlite_search_count
-} {2}
+} {1}
 do_test minmax2-1.7 {
   set sqlite_search_count 0
   execsql {SELECT max(x) FROM t1}
 } {20}
 do_test minmax2-1.8 {
   set sqlite_search_count
-} {1}
+} {0}
 do_test minmax2-1.9 {
   set sqlite_search_count 0
   execsql {SELECT max(y) FROM t1}
diff --git a/test/minmax3.test b/test/minmax3.test
new file mode 100644 (file)
index 0000000..1ecef24
--- /dev/null
@@ -0,0 +1,144 @@
+# 2008 January 5
+#
+# 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.
+#
+#***********************************************************************
+# $Id: minmax3.test,v 1.1 2008/01/05 17:39:30 danielk1977 Exp $
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+# Do an SQL statement.  Append the search count to the end of the result.
+#
+proc count sql {
+  set ::sqlite_search_count 0
+  return [concat [execsql $sql] $::sqlite_search_count]
+}
+
+# This procedure sets the value of the file-format in file 'test.db'
+# to $newval. Also, the schema cookie is incremented.
+# 
+proc set_file_format {newval} {
+  set bt [btree_open test.db 10 0]
+  btree_begin_transaction $bt
+  set meta [btree_get_meta $bt]
+  lset meta 2 $newval                    ;# File format
+  lset meta 1 [expr [lindex $meta 1]+1]  ;# Schema cookie
+  eval "btree_update_meta $bt $meta"
+  btree_commit $bt
+  btree_close $bt
+}
+
+# Create the file as file-format 4 (DESC index support). This is 
+# required to exercise a few cases in where.c.
+#
+execsql { select * from sqlite_master }
+set_file_format 4
+
+do_test minmax3-1.0 {
+  execsql {
+    BEGIN;
+    CREATE TABLE t1(x, y, z);
+    INSERT INTO t1 VALUES('1', 'I',   'one');
+    INSERT INTO t1 VALUES('2', 'IV',  'four');
+    INSERT INTO t1 VALUES('2', NULL,  'three');
+    INSERT INTO t1 VALUES('2', 'II',  'two');
+    INSERT INTO t1 VALUES('2', 'V',   'five');
+    INSERT INTO t1 VALUES('3', 'VI',  'six');
+    COMMIT;
+  }
+} {}
+do_test minmax3-1.1.1 {
+  # Linear scan.
+  count { SELECT max(y) FROM t1 WHERE x = '2'; }
+} {V 5}
+do_test minmax3-1.1.2 {
+  # Index optimizes the WHERE x='2' constraint.
+  execsql { CREATE INDEX i1 ON t1(x) }
+  count   { SELECT max(y) FROM t1 WHERE x = '2'; }
+} {V 9}
+do_test minmax3-1.1.3 {
+  # Index optimizes the WHERE x='2' constraint and the MAX(y).
+  execsql { CREATE INDEX i2 ON t1(x,y) }
+  count   { SELECT max(y) FROM t1 WHERE x = '2'; }
+} {V 1}
+do_test minmax3-1.1.4 {
+  # Index optimizes the WHERE x='2' constraint and the MAX(y).
+  execsql { DROP INDEX i2 ; CREATE INDEX i2 ON t1(x, y DESC) }
+  count   { SELECT max(y) FROM t1 WHERE x = '2'; }
+} {V 1}
+do_test minmax3-1.1.5 {
+  count   { SELECT max(y) FROM t1 WHERE x = '2' AND y != 'V'; }
+} {IV 2}
+do_test minmax3-1.1.6 {
+  count   { SELECT max(y) FROM t1 WHERE x = '2' AND y < 'V'; }
+} {IV 1}
+do_test minmax3-1.1.6 {
+  count   { SELECT max(y) FROM t1 WHERE x = '2' AND z != 'five'; }
+} {IV 4}
+
+do_test minmax3-1.2.1 {
+  # Linear scan of t1.
+  execsql { DROP INDEX i1 ; DROP INDEX i2 }
+  count { SELECT min(y) FROM t1 WHERE x = '2'; }
+} {II 5}
+do_test minmax3-1.2.2 {
+  # Index i1 optimizes the WHERE x='2' constraint.
+  execsql { CREATE INDEX i1 ON t1(x) }
+  count   { SELECT min(y) FROM t1 WHERE x = '2'; }
+} {II 9}
+do_test minmax3-1.2.3 {
+  # Index i2 optimizes the WHERE x='2' constraint and the min(y).
+  execsql { CREATE INDEX i2 ON t1(x,y) }
+  count   { SELECT min(y) FROM t1 WHERE x = '2'; }
+} {II 1}
+do_test minmax3-1.2.4 {
+  # Index optimizes the WHERE x='2' constraint and the MAX(y).
+  execsql { DROP INDEX i2 ; CREATE INDEX i2 ON t1(x, y DESC) }
+  count   { SELECT min(y) FROM t1 WHERE x = '2'; }
+} {II 1}
+
+do_test minmax3-1.3.1 {
+  # Linear scan
+  execsql { DROP INDEX i1 ; DROP INDEX i2 }
+  count   { SELECT min(y) FROM t1; }
+} {I 5}
+do_test minmax3-1.3.2 {
+  # Index i1 optimizes the min(y)
+  execsql { CREATE INDEX i1 ON t1(y) }
+  count   { SELECT min(y) FROM t1; }
+} {I 1}
+do_test minmax3-1.3.3 {
+  # Index i1 optimizes the min(y)
+  execsql { DROP INDEX i1 ; CREATE INDEX i1 ON t1(y DESC) }
+  count   { SELECT min(y) FROM t1; }
+} {I 1}
+
+do_test minmax3-1.4.1 {
+  # Linear scan
+  execsql { DROP INDEX i1 }
+  count   { SELECT max(y) FROM t1; }
+} {VI 5}
+do_test minmax3-1.4.2 {
+  # Index i1 optimizes the max(y)
+  execsql { CREATE INDEX i1 ON t1(y) }
+  count   { SELECT max(y) FROM t1; }
+} {VI 0}
+do_test minmax3-1.4.3 {
+  # Index i1 optimizes the max(y)
+  execsql { DROP INDEX i1 ; CREATE INDEX i1 ON t1(y DESC) }
+  execsql   { SELECT y from t1}
+  count   { SELECT max(y) FROM t1; }
+} {VI 0}
+do_test minmax3-1.4.4 {
+  execsql { DROP INDEX i1 }
+} {}
+
+
+finish_test
+