From: danielk1977 Date: Sat, 5 Jan 2008 17:39:29 +0000 (+0000) Subject: First pass at optimizing max()/min() as described in #2853. Some refinements to come... X-Git-Tag: version-3.5.5~77 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a9d1ccb9b038c164d6220b429c42418bf1a0c73a;p=thirdparty%2Fsqlite.git First pass at optimizing max()/min() as described in #2853. Some refinements to come. (CVS 4687) FossilOrigin-Name: c449e04f1870b1ff726c95c0bf1c6c6a22ca588a --- diff --git a/manifest b/manifest index 88780f56b9..b2c79f6734 100644 --- 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 diff --git a/manifest.uuid b/manifest.uuid index 6f2823bcce..74d5a25e93 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -66396d2f0289e36b5fc0af5078c08d1b17f342ae \ No newline at end of file +c449e04f1870b1ff726c95c0bf1c6c6a22ca588a \ No newline at end of file diff --git a/src/delete.c b/src/delete.c index 3fce444144..c87bf47ab0 100644 --- a/src/delete.c +++ b/src/delete.c @@ -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. diff --git a/src/select.c b/src/select.c index 4ccaf79799..445f08a2b0 100644 --- a/src/select.c +++ b/src/select.c @@ -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); diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 14d7d08e9f..53acb136f3 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -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); diff --git a/src/update.c b/src/update.c index 0966802d18..32402cc31e 100644 --- a/src/update.c +++ b/src/update.c @@ -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. diff --git a/src/where.c b/src/where.c index 0ca1223799..360e191ee4 100644 --- a/src/where.c +++ b/src/where.c @@ -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; hiMem); + 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; diff --git a/test/collate4.test b/test/collate4.test index 7dcd32d7cf..4db4c311b9 100644 --- a/test/collate4.test +++ b/test/collate4.test @@ -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 { diff --git a/test/minmax.test b/test/minmax.test index 074f6dff24..74c207f14a 100644 --- a/test/minmax.test +++ b/test/minmax.test @@ -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} diff --git a/test/minmax2.test b/test/minmax2.test index a3049408c0..2f504d4999 100644 --- a/test/minmax2.test +++ b/test/minmax2.test @@ -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 index 0000000000..1ecef24cf1 --- /dev/null +++ b/test/minmax3.test @@ -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 +