1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +03:00

Scan the table backwards if there is an ORDER BY ... DESC clause that can

be satisfied by an index. (CVS 795)

FossilOrigin-Name: c7a3487981de0ed5b43ea3ff4d46ab4437068dca
This commit is contained in:
drh
2002-12-04 20:01:06 +00:00
parent 2dcc9aa2a8
commit c045ec51be
5 changed files with 135 additions and 47 deletions

View File

@ -11,7 +11,7 @@
*************************************************************************
** Internal interface definitions for SQLite.
**
** @(#) $Id: sqliteInt.h,v 1.149 2002/11/20 11:55:19 drh Exp $
** @(#) $Id: sqliteInt.h,v 1.150 2002/12/04 20:01:06 drh Exp $
*/
#include "config.h"
#include "sqlite.h"
@ -614,6 +614,7 @@ struct WhereLevel {
int iLeftJoin; /* Memory cell used to implement LEFT OUTER JOIN */
int top; /* First instruction of interior of the loop */
int inOp, inP1, inP2;/* Opcode used to implement an IN operator */
int bRev; /* Do the scan in the reverse direction */
};
/*

View File

@ -36,7 +36,7 @@
** in this file for details. If in doubt, do not deviate from existing
** commenting and indentation practices when changing or adding code.
**
** $Id: vdbe.c,v 1.185 2002/12/02 04:25:21 drh Exp $
** $Id: vdbe.c,v 1.186 2002/12/04 20:01:06 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
@ -3354,8 +3354,19 @@ case OP_Close: {
** If there are no records greater than the key and P2 is not zero,
** then an immediate jump to P2 is made.
**
** See also: Found, NotFound, Distinct
** See also: Found, NotFound, Distinct, MoveLt
*/
/* Opcode: MoveLt P1 P2 *
**
** Pop the top of the stack and use its value as a key. Reposition
** cursor P1 so that it points to the entry with the largest key that is
** less than the key popped from the stack.
** If there are no records less than than the key and P2
** is not zero then an immediate jump to P2 is made.
**
** See also: MoveTo
*/
case OP_MoveLt:
case OP_MoveTo: {
int i = pOp->p1;
int tos = p->tos;
@ -3363,7 +3374,7 @@ case OP_MoveTo: {
VERIFY( if( tos<0 ) goto not_enough_stack; )
if( i>=0 && i<p->nCursor && (pC = &p->aCsr[i])->pCursor!=0 ){
int res;
int res, oc;
if( aStack[tos].flags & STK_Int ){
int iKey = intToKey(aStack[tos].i);
sqliteBtreeMoveto(pC->pCursor, (char*)&iKey, sizeof(int), &res);
@ -3376,12 +3387,19 @@ case OP_MoveTo: {
}
pC->nullRow = 0;
sqlite_search_count++;
if( res<0 ){
oc = pOp->opcode;
if( oc==OP_MoveTo && res<0 ){
sqliteBtreeNext(pC->pCursor, &res);
pC->recnoIsValid = 0;
if( res && pOp->p2>0 ){
pc = pOp->p2 - 1;
}
}else if( oc==OP_MoveLt && res>=0 ){
sqliteBtreePrevious(pC->pCursor, &res);
pC->recnoIsValid = 0;
if( res && pOp->p2>0 ){
pc = pOp->p2 - 1;
}
}
}
POPSTACK;
@ -4026,7 +4044,17 @@ case OP_Rewind: {
** table or index. If there are no more key/value pairs then fall through
** to the following instruction. But if the cursor advance was successful,
** jump immediately to P2.
**
** See also: Prev
*/
/* Opcode: Prev P1 P2 *
**
** Back up cursor P1 so that it points to the previous key/data pair in its
** table or index. If there is no previous key/value pairs then fall through
** to the following instruction. But if the cursor backup was successful,
** jump immediately to P2.
*/
case OP_Prev:
case OP_Next: {
int i = pOp->p1;
BtCursor *pCrsr;
@ -4036,7 +4064,8 @@ case OP_Next: {
if( p->aCsr[i].nullRow ){
res = 1;
}else{
rc = sqliteBtreeNext(pCrsr, &res);
rc = pOp->opcode==OP_Next ? sqliteBtreeNext(pCrsr, &res) :
sqliteBtreePrevious(pCrsr, &res);
p->aCsr[i].nullRow = res;
}
if( res==0 ){
@ -4164,6 +4193,15 @@ case OP_IdxRecno: {
** then jump to P2. Otherwise fall through to the next instruction.
** In either case, the stack is popped once.
*/
/* Opcode: IdxLT P1 P2 *
**
** Compare the top of the stack against the key on the index entry that
** cursor P1 is currently pointing to. Ignore the last 4 bytes of the
** index entry. If the index entry is less than the top of the stack
** then jump to P2. Otherwise fall through to the next instruction.
** In either case, the stack is popped once.
*/
case OP_IdxLT:
case OP_IdxGT:
case OP_IdxGE: {
int i= pOp->p1;
@ -4178,7 +4216,9 @@ case OP_IdxGE: {
if( rc!=SQLITE_OK ){
break;
}
if( pOp->opcode==OP_IdxGE ){
if( pOp->opcode==OP_IdxLT ){
res = -res;
}else if( pOp->opcode==OP_IdxGE ){
res++;
}
if( res>0 ){

View File

@ -13,7 +13,7 @@
** the WHERE clause of SQL statements. Also found here are subroutines
** to generate VDBE code to evaluate expressions.
**
** $Id: where.c,v 1.67 2002/12/03 02:22:52 drh Exp $
** $Id: where.c,v 1.68 2002/12/04 20:01:06 drh Exp $
*/
#include "sqliteInt.h"
@ -169,18 +169,22 @@ static Index *findSortingIndex(
Table *pTab, /* The table to be sorted */
int base, /* Cursor number for pTab */
ExprList *pOrderBy, /* The ORDER BY clause */
Index *pPreferredIdx /* Use this index, if possible and not NULL */
Index *pPreferredIdx, /* Use this index, if possible and not NULL */
int *pbRev /* Set to 1 if ORDER BY is DESC */
){
int i;
Index *pMatch;
Index *pIdx;
int sortOrder;
assert( pOrderBy!=0 );
assert( pOrderBy->nExpr>0 );
sortOrder = pOrderBy->a[0].sortOrder & SQLITE_SO_DIRMASK;
for(i=0; i<pOrderBy->nExpr; i++){
Expr *p;
if( (pOrderBy->a[i].sortOrder & SQLITE_SO_DIRMASK)!=SQLITE_SO_ASC ){
/* Indices can only be used for ascending sort order */
if( (pOrderBy->a[i].sortOrder & SQLITE_SO_DIRMASK)!=sortOrder ){
/* Indices can only be used if all ORDER BY terms are either
** DESC or ASC. Indices cannot be used on a mixture. */
return 0;
}
if( (pOrderBy->a[i].sortOrder & SQLITE_SO_TYPEMASK)!=SQLITE_SO_UNK ){
@ -194,7 +198,7 @@ static Index *findSortingIndex(
return 0;
}
}
/* If we get this far, it means the ORDER BY clause consists only of
** ascending columns in the left-most table of the FROM clause. Now
** check for a matching index.
@ -210,6 +214,9 @@ static Index *findSortingIndex(
if( pIdx==pPreferredIdx ) break;
}
}
if( pMatch && pbRev ){
*pbRev = sortOrder==SQLITE_SO_DESC;
}
return pMatch;
}
@ -468,17 +475,18 @@ WhereInfo *sqliteWhereBegin(
**
** The best index is determined as follows. For each of the
** left-most terms that is fixed by an equality operator, add
** 4 to the score. The right-most term of the index may be
** 8 to the score. The right-most term of the index may be
** constrained by an inequality. Add 1 if for an "x<..." constraint
** and add 2 for an "x>..." constraint. Chose the index that
** gives the best score.
**
** This scoring system is designed so that the score can later be
** used to determine how the index is used. If the score&3 is 0
** used to determine how the index is used. If the score&7 is 0
** then all constraints are equalities. If score&1 is not 0 then
** there is an inequality used as a termination key. (ex: "x<...")
** If score&2 is not 0 then there is an inequality used as the
** start key. (ex: "x>...");
** start key. (ex: "x>..."). A score or 4 is the special case
** of an IN operator constraint. (ex: "x IN ...").
**
** The IN operator (as in "<expr> IN (...)") is treated the same as
** an equality comparison except that it can only be used on the
@ -562,15 +570,19 @@ WhereInfo *sqliteWhereBegin(
}
}
}
/* The following loop ends with nEq set to the number of columns
** on the left of the index with == constraints.
*/
for(nEq=0; nEq<pIdx->nColumn; nEq++){
m = (1<<(nEq+1))-1;
if( (m & eqMask)!=m ) break;
}
score = nEq*4;
score = nEq*8; /* Base score is 8 times number of == constraints */
m = 1<<nEq;
if( m & ltMask ) score++;
if( m & gtMask ) score+=2;
if( score==0 && inMask ) score = 4;
if( m & ltMask ) score++; /* Increase score for a < constraint */
if( m & gtMask ) score+=2; /* Increase score for a > constraint */
if( score==0 && inMask ) score = 4; /* Default score for IN constraint */
if( score>bestScore ){
pBestIdx = pIdx;
bestScore = score;
@ -578,6 +590,7 @@ WhereInfo *sqliteWhereBegin(
}
pWInfo->a[i].pIdx = pBestIdx;
pWInfo->a[i].score = bestScore;
pWInfo->a[i].bRev = 0;
loopMask |= 1<<idx;
if( pBestIdx ){
pWInfo->a[i].iCur = pParse->nTab++;
@ -592,15 +605,14 @@ WhereInfo *sqliteWhereBegin(
Index *pSortIdx;
Index *pIdx;
Table *pTab;
int bRev = 0;
pTab = pTabList->a[0].pTab;
pIdx = pWInfo->a[0].pIdx;
if( pIdx && pWInfo->a[0].score==4 ){
/* If there is already an index on the left-most column and it is
** an equality index, then either sorting is not helpful, or the
** index is an IN operator, in which case the index does not give
** the correct sort order. Either way, pretend that no suitable
** index is found.
/* If there is already an IN index on the left-most table,
** it will not give the correct sort order.
** So, pretend that no suitable index is found.
*/
pSortIdx = 0;
}else if( iDirectEq[0]>=0 || iDirectLt[0]>=0 || iDirectGt[0]>=0 ){
@ -609,7 +621,7 @@ WhereInfo *sqliteWhereBegin(
*/
pSortIdx = 0;
}else{
pSortIdx = findSortingIndex(pTab, base, *ppOrderBy, pIdx);
pSortIdx = findSortingIndex(pTab, base, *ppOrderBy, pIdx, &bRev);
}
if( pSortIdx && (pIdx==0 || pIdx==pSortIdx) ){
if( pIdx==0 ){
@ -617,6 +629,7 @@ WhereInfo *sqliteWhereBegin(
pWInfo->a[0].iCur = pParse->nTab++;
pWInfo->peakNTab = pParse->nTab;
}
pWInfo->a[0].bRev = bRev;
*ppOrderBy = 0;
}
}
@ -708,7 +721,7 @@ WhereInfo *sqliteWhereBegin(
*/
int start;
int testOp;
int nColumn = pLevel->score/4;
int nColumn = (pLevel->score+4)/8;
brk = pLevel->brk = sqliteVdbeMakeLabel(v);
for(j=0; j<nColumn; j++){
for(k=0; k<nExpr; k++){
@ -756,7 +769,7 @@ WhereInfo *sqliteWhereBegin(
cont = pLevel->cont = sqliteVdbeMakeLabel(v);
sqliteVdbeAddOp(v, OP_MakeKey, nColumn, 0);
sqliteAddIdxKeyType(v, pIdx);
if( nColumn==pIdx->nColumn ){
if( nColumn==pIdx->nColumn || pLevel->bRev ){
sqliteVdbeAddOp(v, OP_MemStore, pLevel->iMem, 0);
testOp = OP_IdxGT;
}else{
@ -765,17 +778,28 @@ WhereInfo *sqliteWhereBegin(
sqliteVdbeAddOp(v, OP_MemStore, pLevel->iMem, 1);
testOp = OP_IdxGE;
}
sqliteVdbeAddOp(v, OP_MoveTo, pLevel->iCur, brk);
start = sqliteVdbeAddOp(v, OP_MemLoad, pLevel->iMem, 0);
sqliteVdbeAddOp(v, testOp, pLevel->iCur, brk);
sqliteVdbeAddOp(v, OP_IdxRecno, pLevel->iCur, 0);
if( pLevel->bRev ){
/* Scan in reverse order */
sqliteVdbeAddOp(v, OP_IncrKey, 0, 0);
sqliteVdbeAddOp(v, OP_MoveLt, pLevel->iCur, brk);
start = sqliteVdbeAddOp(v, OP_MemLoad, pLevel->iMem, 0);
sqliteVdbeAddOp(v, OP_IdxLT, pLevel->iCur, brk);
sqliteVdbeAddOp(v, OP_IdxRecno, pLevel->iCur, 0);
pLevel->op = OP_Prev;
}else{
/* Scan in the forward order */
sqliteVdbeAddOp(v, OP_MoveTo, pLevel->iCur, brk);
start = sqliteVdbeAddOp(v, OP_MemLoad, pLevel->iMem, 0);
sqliteVdbeAddOp(v, testOp, pLevel->iCur, brk);
sqliteVdbeAddOp(v, OP_IdxRecno, pLevel->iCur, 0);
pLevel->op = OP_Next;
}
if( i==pTabList->nSrc-1 && pushKey ){
haveKey = 1;
}else{
sqliteVdbeAddOp(v, OP_MoveTo, base+idx, 0);
haveKey = 0;
}
pLevel->op = OP_Next;
pLevel->p1 = pLevel->iCur;
pLevel->p2 = start;
}else if( i<ARRAYSIZE(iDirectLt) && (iDirectLt[i]>=0 || iDirectGt[i]>=0) ){
@ -862,7 +886,7 @@ WhereInfo *sqliteWhereBegin(
** to force the output order to conform to an ORDER BY.
*/
int score = pLevel->score;
int nEqColumn = score/4;
int nEqColumn = score/8;
int start;
int leFlag, geFlag;
int testOp;
@ -901,9 +925,17 @@ WhereInfo *sqliteWhereBegin(
sqliteVdbeAddOp(v, OP_Dup, nEqColumn-1, 0);
}
/* Labels for the beginning and end of the loop
*/
cont = pLevel->cont = sqliteVdbeMakeLabel(v);
brk = pLevel->brk = sqliteVdbeMakeLabel(v);
/* 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.
**
** 2002-Dec-04: On a reverse-order scan, the so-called "termination"
** key computed here really ends up being the start key.
*/
if( (score & 1)!=0 ){
for(k=0; k<nExpr; k++){
@ -942,7 +974,13 @@ WhereInfo *sqliteWhereBegin(
if( leFlag ){
sqliteVdbeAddOp(v, OP_IncrKey, 0, 0);
}
sqliteVdbeAddOp(v, OP_MemStore, pLevel->iMem, 1);
if( pLevel->bRev ){
sqliteVdbeAddOp(v, OP_MoveLt, pLevel->iCur, brk);
}else{
sqliteVdbeAddOp(v, OP_MemStore, pLevel->iMem, 1);
}
}else if( pLevel->bRev ){
sqliteVdbeAddOp(v, OP_Last, pLevel->iCur, brk);
}
/* Generate the start key. This is the key that defines the lower
@ -950,6 +988,9 @@ WhereInfo *sqliteWhereBegin(
** equality terms and if there is no "X>..." term. In
** that case, generate a "Rewind" instruction in place of the
** start key search.
**
** 2002-Dec-04: In the case of a reverse-order search, the so-called
** "start" key really ends up being used as the termination key.
*/
if( (score & 2)!=0 ){
for(k=0; k<nExpr; k++){
@ -979,15 +1020,21 @@ WhereInfo *sqliteWhereBegin(
}else{
geFlag = 1;
}
brk = pLevel->brk = sqliteVdbeMakeLabel(v);
cont = pLevel->cont = sqliteVdbeMakeLabel(v);
if( nEqColumn>0 || (score&2)!=0 ){
sqliteVdbeAddOp(v, OP_MakeKey, nEqColumn + ((score&2)!=0), 0);
sqliteAddIdxKeyType(v, pIdx);
if( !geFlag ){
sqliteVdbeAddOp(v, OP_IncrKey, 0, 0);
}
sqliteVdbeAddOp(v, OP_MoveTo, pLevel->iCur, brk);
if( pLevel->bRev ){
pLevel->iMem = pParse->nMem++;
sqliteVdbeAddOp(v, OP_MemStore, pLevel->iMem, 1);
testOp = OP_IdxLT;
}else{
sqliteVdbeAddOp(v, OP_MoveTo, pLevel->iCur, brk);
}
}else if( pLevel->bRev ){
testOp = OP_Noop;
}else{
sqliteVdbeAddOp(v, OP_Rewind, pLevel->iCur, brk);
}
@ -1011,7 +1058,7 @@ WhereInfo *sqliteWhereBegin(
/* Record the instruction used to terminate the loop.
*/
pLevel->op = OP_Next;
pLevel->op = pLevel->bRev ? OP_Prev : OP_Next;
pLevel->p1 = pLevel->iCur;
pLevel->p2 = start;
}