mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-30 19:03:16 +03:00
Add optimizations for the IN operator in WHERE clauses. This is a partial
implementation of enhancement #63. Still need to add test cases. (CVS 610) FossilOrigin-Name: 8481e841ebdeabe07bf780246bda1aa053eb60b7
This commit is contained in:
@ -12,7 +12,7 @@
|
||||
** This is the header file for the generic hash-table implemenation
|
||||
** used in SQLite.
|
||||
**
|
||||
** $Id: hash.h,v 1.4 2002/02/23 23:45:45 drh Exp $
|
||||
** $Id: hash.h,v 1.5 2002/06/08 23:25:09 drh Exp $
|
||||
*/
|
||||
#ifndef _SQLITE_HASH_H_
|
||||
#define _SQLITE_HASH_H_
|
||||
@ -99,6 +99,7 @@ void sqliteHashClear(Hash*);
|
||||
#define sqliteHashNext(E) ((E)->next)
|
||||
#define sqliteHashData(E) ((E)->data)
|
||||
#define sqliteHashKey(E) ((E)->pKey)
|
||||
#define sqliteHashKeysize(E) ((E)->nKey)
|
||||
|
||||
/*
|
||||
** Number of entries in a hash table
|
||||
|
@ -11,7 +11,7 @@
|
||||
*************************************************************************
|
||||
** Internal interface definitions for SQLite.
|
||||
**
|
||||
** @(#) $Id: sqliteInt.h,v 1.120 2002/06/06 18:54:41 drh Exp $
|
||||
** @(#) $Id: sqliteInt.h,v 1.121 2002/06/08 23:25:09 drh Exp $
|
||||
*/
|
||||
#include "sqlite.h"
|
||||
#include "hash.h"
|
||||
@ -411,7 +411,8 @@ struct Token {
|
||||
struct Expr {
|
||||
int op; /* Operation performed by this node */
|
||||
Expr *pLeft, *pRight; /* Left and right subnodes */
|
||||
ExprList *pList; /* A list of expressions used as a function argument */
|
||||
ExprList *pList; /* A list of expressions used as function arguments
|
||||
** or in "<expr> IN (<expr-list)" */
|
||||
Token token; /* An operand token */
|
||||
Token span; /* Complete text of the expression */
|
||||
int iTable, iColumn; /* When op==TK_COLUMN, then this expr node means the
|
||||
@ -419,7 +420,8 @@ struct Expr {
|
||||
** op==TK_FUNCTION, iColumn holds the function id */
|
||||
int iAgg; /* When op==TK_COLUMN and pParse->useAgg==TRUE, pull
|
||||
** result from the iAgg-th element of the aggregator */
|
||||
Select *pSelect; /* When the expression is a sub-select */
|
||||
Select *pSelect; /* When the expression is a sub-select. Also the
|
||||
** right side of "<expr> IN (<select>)" */
|
||||
};
|
||||
|
||||
/*
|
||||
@ -508,6 +510,7 @@ struct WhereLevel {
|
||||
int op, p1, p2; /* Opcode used to terminate the loop */
|
||||
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 */
|
||||
};
|
||||
|
||||
/*
|
||||
|
79
src/vdbe.c
79
src/vdbe.c
@ -30,7 +30,7 @@
|
||||
** But other routines are also provided to help in building up
|
||||
** a program instruction by instruction.
|
||||
**
|
||||
** $Id: vdbe.c,v 1.153 2002/06/06 23:16:06 drh Exp $
|
||||
** $Id: vdbe.c,v 1.154 2002/06/08 23:25:09 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <ctype.h>
|
||||
@ -198,6 +198,7 @@ struct AggElem {
|
||||
typedef struct Set Set;
|
||||
struct Set {
|
||||
Hash hash; /* A set is just a hash table */
|
||||
HashElem *prev; /* Previously accessed hash elemen */
|
||||
};
|
||||
|
||||
/*
|
||||
@ -1066,19 +1067,19 @@ static char *zOpName[] = { 0,
|
||||
"FileOpen", "FileRead", "FileColumn", "AggReset",
|
||||
"AggFocus", "AggNext", "AggSet", "AggGet",
|
||||
"AggFunc", "AggInit", "AggPush", "AggPop",
|
||||
"SetInsert", "SetFound", "SetNotFound", "MakeRecord",
|
||||
"MakeKey", "MakeIdxKey", "IncrKey", "Goto",
|
||||
"If", "IfNot", "Halt", "ColumnCount",
|
||||
"ColumnName", "Callback", "NullCallback", "Integer",
|
||||
"String", "Pop", "Dup", "Pull",
|
||||
"Push", "MustBeInt", "Add", "AddImm",
|
||||
"Subtract", "Multiply", "Divide", "Remainder",
|
||||
"BitAnd", "BitOr", "BitNot", "ShiftLeft",
|
||||
"ShiftRight", "AbsValue", "Eq", "Ne",
|
||||
"Lt", "Le", "Gt", "Ge",
|
||||
"IsNull", "NotNull", "Negative", "And",
|
||||
"Or", "Not", "Concat", "Noop",
|
||||
"Function", "Limit",
|
||||
"SetInsert", "SetFound", "SetNotFound", "SetFirst",
|
||||
"SetNext", "MakeRecord", "MakeKey", "MakeIdxKey",
|
||||
"IncrKey", "Goto", "If", "IfNot",
|
||||
"Halt", "ColumnCount", "ColumnName", "Callback",
|
||||
"NullCallback", "Integer", "String", "Pop",
|
||||
"Dup", "Pull", "Push", "MustBeInt",
|
||||
"Add", "AddImm", "Subtract", "Multiply",
|
||||
"Divide", "Remainder", "BitAnd", "BitOr",
|
||||
"BitNot", "ShiftLeft", "ShiftRight", "AbsValue",
|
||||
"Eq", "Ne", "Lt", "Le",
|
||||
"Gt", "Ge", "IsNull", "NotNull",
|
||||
"Negative", "And", "Or", "Not",
|
||||
"Concat", "Noop", "Function", "Limit",
|
||||
};
|
||||
|
||||
/*
|
||||
@ -1957,7 +1958,7 @@ case OP_AddImm: {
|
||||
/* Opcode: MustBeInt * P2 *
|
||||
**
|
||||
** Force the top of the stack to be an integer. If the top of the
|
||||
** stack is not an integer and cannot be comverted into an integer
|
||||
** stack is not an integer and cannot be converted into an integer
|
||||
** with out data loss, then jump immediately to P2, or if P2==0
|
||||
** raise an SQLITE_MISMATCH exception.
|
||||
*/
|
||||
@ -3018,7 +3019,7 @@ case OP_MoveTo: {
|
||||
** This operation is similar to NotFound except that this operation
|
||||
** does not pop the key from the stack.
|
||||
**
|
||||
** See also: Found, NotFound, MoveTo
|
||||
** See also: Found, NotFound, MoveTo, IsUnique, NotExists
|
||||
*/
|
||||
/* Opcode: Found P1 P2 *
|
||||
**
|
||||
@ -3027,7 +3028,7 @@ case OP_MoveTo: {
|
||||
** does not exist, then fall thru. The cursor is left pointing
|
||||
** to the record if it exists. The key is popped from the stack.
|
||||
**
|
||||
** See also: Distinct, NotFound, MoveTo
|
||||
** See also: Distinct, NotFound, MoveTo, IsUnique, NotExists
|
||||
*/
|
||||
/* Opcode: NotFound P1 P2 *
|
||||
**
|
||||
@ -3084,7 +3085,7 @@ case OP_Found: {
|
||||
** number for that entry is pushed onto the stack and control
|
||||
** falls through to the next instruction.
|
||||
**
|
||||
** See also: Distinct, NotFound, NotExists
|
||||
** See also: Distinct, NotFound, NotExists, Found
|
||||
*/
|
||||
case OP_IsUnique: {
|
||||
int i = pOp->p1;
|
||||
@ -3167,7 +3168,7 @@ case OP_IsUnique: {
|
||||
** operation assumes the key is an integer and NotFound assumes it
|
||||
** is a string.
|
||||
**
|
||||
** See also: Distinct, Found, MoveTo, NotExists
|
||||
** See also: Distinct, Found, MoveTo, NotFound, IsUnique
|
||||
*/
|
||||
case OP_NotExists: {
|
||||
int i = pOp->p1;
|
||||
@ -4775,6 +4776,46 @@ case OP_SetNotFound: {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Opcode: SetFirst P1 P2 *
|
||||
**
|
||||
** Read the first element from set P1 and push it onto the stack. If the
|
||||
** set is empty, push nothing and jump immediately to P2. This opcode is
|
||||
** used in combination with OP_SetNext to loop over all elements of a set.
|
||||
*/
|
||||
/* Opcode: SetNext P1 P2 *
|
||||
**
|
||||
** Read the next element from set P1 and push it onto the stack. If there
|
||||
** are no more elements in the set, do not do the push and fall through.
|
||||
** Otherwise, jump to P2 after pushing the next set element.
|
||||
*/
|
||||
case OP_SetFirst:
|
||||
case OP_SetNext: {
|
||||
Set *pSet;
|
||||
int tos;
|
||||
VERIFY( if( pOp->p1<0 || pOp->p1>=p->nSet ) goto bad_instruction; )
|
||||
pSet = &p->aSet[pOp->p1];
|
||||
if( pOp->opcode==OP_SetFirst ){
|
||||
pSet->prev = sqliteHashFirst(&pSet->hash);
|
||||
if( pSet->prev==0 ){
|
||||
pc = pOp->p2 - 1;
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
VERIFY( if( pSet->prev==0 ) goto bad_instruction; )
|
||||
pSet->prev = sqliteHashNext(pSet->prev);
|
||||
if( pSet->prev==0 ){
|
||||
break;
|
||||
}else{
|
||||
pc = pOp->p2 - 1;
|
||||
}
|
||||
}
|
||||
tos = ++p->tos;
|
||||
VERIFY( if( NeedStack(p, p->tos) ) goto no_mem; )
|
||||
zStack[tos] = sqliteHashKey(pSet->prev);
|
||||
aStack[tos].n = sqliteHashKeysize(pSet->prev);
|
||||
aStack[tos].flags = STK_Str | STK_Static;
|
||||
break;
|
||||
}
|
||||
|
||||
/* An other opcode is illegal...
|
||||
*/
|
||||
|
100
src/vdbe.h
100
src/vdbe.h
@ -15,7 +15,7 @@
|
||||
** or VDBE. The VDBE implements an abstract machine that runs a
|
||||
** simple program to access and modify the underlying database.
|
||||
**
|
||||
** $Id: vdbe.h,v 1.53 2002/05/26 20:54:34 drh Exp $
|
||||
** $Id: vdbe.h,v 1.54 2002/06/08 23:25:09 drh Exp $
|
||||
*/
|
||||
#ifndef _SQLITE_VDBE_H_
|
||||
#define _SQLITE_VDBE_H_
|
||||
@ -149,62 +149,64 @@ typedef struct VdbeOp VdbeOp;
|
||||
#define OP_SetInsert 69
|
||||
#define OP_SetFound 70
|
||||
#define OP_SetNotFound 71
|
||||
#define OP_SetFirst 72
|
||||
#define OP_SetNext 73
|
||||
|
||||
#define OP_MakeRecord 72
|
||||
#define OP_MakeKey 73
|
||||
#define OP_MakeIdxKey 74
|
||||
#define OP_IncrKey 75
|
||||
#define OP_MakeRecord 74
|
||||
#define OP_MakeKey 75
|
||||
#define OP_MakeIdxKey 76
|
||||
#define OP_IncrKey 77
|
||||
|
||||
#define OP_Goto 76
|
||||
#define OP_If 77
|
||||
#define OP_IfNot 78
|
||||
#define OP_Halt 79
|
||||
#define OP_Goto 78
|
||||
#define OP_If 79
|
||||
#define OP_IfNot 80
|
||||
#define OP_Halt 81
|
||||
|
||||
#define OP_ColumnCount 80
|
||||
#define OP_ColumnName 81
|
||||
#define OP_Callback 82
|
||||
#define OP_NullCallback 83
|
||||
#define OP_ColumnCount 82
|
||||
#define OP_ColumnName 83
|
||||
#define OP_Callback 84
|
||||
#define OP_NullCallback 85
|
||||
|
||||
#define OP_Integer 84
|
||||
#define OP_String 85
|
||||
#define OP_Pop 86
|
||||
#define OP_Dup 87
|
||||
#define OP_Pull 88
|
||||
#define OP_Push 89
|
||||
#define OP_MustBeInt 90
|
||||
#define OP_Integer 86
|
||||
#define OP_String 87
|
||||
#define OP_Pop 88
|
||||
#define OP_Dup 89
|
||||
#define OP_Pull 90
|
||||
#define OP_Push 91
|
||||
#define OP_MustBeInt 92
|
||||
|
||||
#define OP_Add 91
|
||||
#define OP_AddImm 92
|
||||
#define OP_Subtract 93
|
||||
#define OP_Multiply 94
|
||||
#define OP_Divide 95
|
||||
#define OP_Remainder 96
|
||||
#define OP_BitAnd 97
|
||||
#define OP_BitOr 98
|
||||
#define OP_BitNot 99
|
||||
#define OP_ShiftLeft 100
|
||||
#define OP_ShiftRight 101
|
||||
#define OP_AbsValue 102
|
||||
#define OP_Eq 103
|
||||
#define OP_Ne 104
|
||||
#define OP_Lt 105
|
||||
#define OP_Le 106
|
||||
#define OP_Gt 107
|
||||
#define OP_Ge 108
|
||||
#define OP_IsNull 109
|
||||
#define OP_NotNull 110
|
||||
#define OP_Negative 111
|
||||
#define OP_And 112
|
||||
#define OP_Or 113
|
||||
#define OP_Not 114
|
||||
#define OP_Concat 115
|
||||
#define OP_Noop 116
|
||||
#define OP_Function 117
|
||||
#define OP_Add 93
|
||||
#define OP_AddImm 94
|
||||
#define OP_Subtract 95
|
||||
#define OP_Multiply 96
|
||||
#define OP_Divide 97
|
||||
#define OP_Remainder 98
|
||||
#define OP_BitAnd 99
|
||||
#define OP_BitOr 100
|
||||
#define OP_BitNot 101
|
||||
#define OP_ShiftLeft 102
|
||||
#define OP_ShiftRight 103
|
||||
#define OP_AbsValue 104
|
||||
#define OP_Eq 105
|
||||
#define OP_Ne 106
|
||||
#define OP_Lt 107
|
||||
#define OP_Le 108
|
||||
#define OP_Gt 109
|
||||
#define OP_Ge 110
|
||||
#define OP_IsNull 111
|
||||
#define OP_NotNull 112
|
||||
#define OP_Negative 113
|
||||
#define OP_And 114
|
||||
#define OP_Or 115
|
||||
#define OP_Not 116
|
||||
#define OP_Concat 117
|
||||
#define OP_Noop 118
|
||||
#define OP_Function 119
|
||||
|
||||
#define OP_Limit 118
|
||||
#define OP_Limit 120
|
||||
|
||||
|
||||
#define OP_MAX 118
|
||||
#define OP_MAX 120
|
||||
|
||||
/*
|
||||
** Prototypes for the VDBE interface. See comments on the implementation
|
||||
|
84
src/where.c
84
src/where.c
@ -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.48 2002/05/26 20:54:34 drh Exp $
|
||||
** $Id: where.c,v 1.49 2002/06/08 23:25:10 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@ -112,6 +112,7 @@ static int allowedOp(int op){
|
||||
case TK_GT:
|
||||
case TK_GE:
|
||||
case TK_EQ:
|
||||
case TK_IN:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
@ -136,7 +137,7 @@ static void exprAnalyze(int base, ExprInfo *pInfo){
|
||||
pInfo->idxLeft = -1;
|
||||
pInfo->idxRight = -1;
|
||||
if( allowedOp(pExpr->op) && (pInfo->prereqRight & pInfo->prereqLeft)==0 ){
|
||||
if( pExpr->pRight->op==TK_COLUMN ){
|
||||
if( pExpr->pRight && pExpr->pRight->op==TK_COLUMN ){
|
||||
pInfo->idxRight = pExpr->pRight->iTable - base;
|
||||
pInfo->indexable = 1;
|
||||
}
|
||||
@ -288,6 +289,7 @@ WhereInfo *sqliteWhereBegin(
|
||||
if( aExpr[j].idxLeft==idx && aExpr[j].p->pLeft->iColumn<0
|
||||
&& (aExpr[j].prereqRight & loopMask)==aExpr[j].prereqRight ){
|
||||
switch( aExpr[j].p->op ){
|
||||
case TK_IN:
|
||||
case TK_EQ: iDirectEq[i] = j; break;
|
||||
case TK_LE:
|
||||
case TK_LT: iDirectLt[i] = j; break;
|
||||
@ -329,6 +331,9 @@ WhereInfo *sqliteWhereBegin(
|
||||
** 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>...");
|
||||
**
|
||||
** The IN operator as in "<expr> IN (...)" is treated the same as
|
||||
** an equality comparison.
|
||||
*/
|
||||
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
|
||||
int eqMask = 0; /* Index columns covered by an x=... constraint */
|
||||
@ -346,6 +351,7 @@ WhereInfo *sqliteWhereBegin(
|
||||
for(k=0; k<pIdx->nColumn; k++){
|
||||
if( pIdx->aiColumn[k]==iColumn ){
|
||||
switch( aExpr[j].p->op ){
|
||||
case TK_IN:
|
||||
case TK_EQ: {
|
||||
eqMask |= 1<<k;
|
||||
break;
|
||||
@ -467,6 +473,7 @@ WhereInfo *sqliteWhereBegin(
|
||||
}
|
||||
|
||||
pIdx = pLevel->pIdx;
|
||||
pLevel->inOp = OP_Noop;
|
||||
if( i<ARRAYSIZE(iDirectEq) && iDirectEq[i]>=0 ){
|
||||
/* Case 1: We can directly reference a single row using an
|
||||
** equality comparison against the ROWID field.
|
||||
@ -475,30 +482,31 @@ WhereInfo *sqliteWhereBegin(
|
||||
assert( k<nExpr );
|
||||
assert( aExpr[k].p!=0 );
|
||||
assert( aExpr[k].idxLeft==idx || aExpr[k].idxRight==idx );
|
||||
brk = pLevel->brk = sqliteVdbeMakeLabel(v);
|
||||
if( aExpr[k].idxLeft==idx ){
|
||||
sqliteExprCode(pParse, aExpr[k].p->pRight);
|
||||
Expr *pX = aExpr[k].p;
|
||||
if( pX->op!=TK_IN ){
|
||||
sqliteExprCode(pParse, aExpr[k].p->pRight);
|
||||
}else if( pX->pList ){
|
||||
sqliteVdbeAddOp(v, OP_SetFirst, pX->iTable, brk);
|
||||
pLevel->inOp = OP_SetNext;
|
||||
pLevel->inP1 = pX->iTable;
|
||||
pLevel->inP2 = sqliteVdbeCurrentAddr(v);
|
||||
}else{
|
||||
assert( pX->pSelect );
|
||||
sqliteVdbeAddOp(v, OP_Rewind, pX->iTable, brk);
|
||||
sqliteVdbeAddOp(v, OP_KeyAsData, pX->iTable, 1);
|
||||
pLevel->inP2 = sqliteVdbeAddOp(v, OP_FullKey, pX->iTable, 0);
|
||||
pLevel->inOp = OP_Next;
|
||||
pLevel->inP1 = pX->iTable;
|
||||
}
|
||||
}else{
|
||||
sqliteExprCode(pParse, aExpr[k].p->pLeft);
|
||||
}
|
||||
aExpr[k].p = 0;
|
||||
brk = pLevel->brk = sqliteVdbeMakeLabel(v);
|
||||
cont = pLevel->cont = brk;
|
||||
cont = pLevel->cont = sqliteVdbeMakeLabel(v);
|
||||
sqliteVdbeAddOp(v, OP_MustBeInt, 0, brk);
|
||||
if( i==pTabList->nSrc-1 && pushKey ){
|
||||
/* Note: The OP_Dup below will cause the recno to be left on the
|
||||
** stack if the record does not exists and the OP_NotExists jump is
|
||||
** taken. This violates a general rule of the VDBE that you should
|
||||
** never leave values on the stack in order to avoid a stack overflow.
|
||||
** But in this case, the OP_Dup will never happen inside of a loop,
|
||||
** because the pushKey flag is only true for UPDATE and DELETE, not
|
||||
** for SELECT, and nested loops only occur on a SELECT.
|
||||
** So it is safe to leave the recno on the stack.
|
||||
*/
|
||||
haveKey = 1;
|
||||
sqliteVdbeAddOp(v, OP_Dup, 0, 0);
|
||||
}else{
|
||||
haveKey = 0;
|
||||
}
|
||||
haveKey = 0;
|
||||
sqliteVdbeAddOp(v, OP_NotExists, base+idx, brk);
|
||||
pLevel->op = OP_Noop;
|
||||
}else if( pIdx!=0 && pLevel->score%4==0 ){
|
||||
@ -507,17 +515,37 @@ WhereInfo *sqliteWhereBegin(
|
||||
int start;
|
||||
int testOp;
|
||||
int nColumn = pLevel->score/4;
|
||||
brk = pLevel->brk = sqliteVdbeMakeLabel(v);
|
||||
for(j=0; j<nColumn; j++){
|
||||
for(k=0; k<nExpr; k++){
|
||||
if( aExpr[k].p==0 ) continue;
|
||||
Expr *pX = aExpr[k].p;
|
||||
if( pX==0 ) continue;
|
||||
if( aExpr[k].idxLeft==idx
|
||||
&& aExpr[k].p->op==TK_EQ
|
||||
&& (aExpr[k].prereqRight & loopMask)==aExpr[k].prereqRight
|
||||
&& aExpr[k].p->pLeft->iColumn==pIdx->aiColumn[j]
|
||||
&& pX->pLeft->iColumn==pIdx->aiColumn[j]
|
||||
){
|
||||
sqliteExprCode(pParse, aExpr[k].p->pRight);
|
||||
aExpr[k].p = 0;
|
||||
break;
|
||||
if( pX->op==TK_EQ ){
|
||||
sqliteExprCode(pParse, pX->pRight);
|
||||
aExpr[k].p = 0;
|
||||
break;
|
||||
}
|
||||
if( pX->op==TK_IN && nColumn==1 ){
|
||||
if( pX->pList ){
|
||||
sqliteVdbeAddOp(v, OP_SetFirst, pX->iTable, brk);
|
||||
pLevel->inOp = OP_SetNext;
|
||||
pLevel->inP1 = pX->iTable;
|
||||
pLevel->inP2 = sqliteVdbeCurrentAddr(v);
|
||||
}else{
|
||||
assert( pX->pSelect );
|
||||
sqliteVdbeAddOp(v, OP_Rewind, pX->iTable, brk);
|
||||
sqliteVdbeAddOp(v, OP_KeyAsData, pX->iTable, 1);
|
||||
pLevel->inP2 = sqliteVdbeAddOp(v, OP_FullKey, pX->iTable, 0);
|
||||
pLevel->inOp = OP_Next;
|
||||
pLevel->inP1 = pX->iTable;
|
||||
}
|
||||
aExpr[k].p = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( aExpr[k].idxRight==idx
|
||||
&& aExpr[k].p->op==TK_EQ
|
||||
@ -531,7 +559,6 @@ WhereInfo *sqliteWhereBegin(
|
||||
}
|
||||
}
|
||||
pLevel->iMem = pParse->nMem++;
|
||||
brk = pLevel->brk = sqliteVdbeMakeLabel(v);
|
||||
cont = pLevel->cont = sqliteVdbeMakeLabel(v);
|
||||
sqliteVdbeAddOp(v, OP_MakeKey, nColumn, 0);
|
||||
if( nColumn==pIdx->nColumn ){
|
||||
@ -834,6 +861,9 @@ void sqliteWhereEnd(WhereInfo *pWInfo){
|
||||
sqliteVdbeAddOp(v, pLevel->op, pLevel->p1, pLevel->p2);
|
||||
}
|
||||
sqliteVdbeResolveLabel(v, pLevel->brk);
|
||||
if( pLevel->inOp!=OP_Noop ){
|
||||
sqliteVdbeAddOp(v, pLevel->inOp, pLevel->inP1, pLevel->inP2);
|
||||
}
|
||||
if( pLevel->iLeftJoin ){
|
||||
int addr;
|
||||
addr = sqliteVdbeAddOp(v, OP_MemLoad, pLevel->iLeftJoin, 0);
|
||||
|
Reference in New Issue
Block a user