Enhance the OP_Found and OP_NotFound opcodes so that they can accept an
array of registers as an unpacked record in addition to a record built
using OP_MakeRecord. Use this to avoid OP_MakeRecord calls during IN
expression processing.
check-in: b9eab885 user: drh tags: trunk

** for fast set membership tests. In this case an epheremal table must
** be used unless <column> is an INTEGER PRIMARY KEY or an index can
** be found with <column> as its left-most column.
**
** When the b-tree is being used for membership tests, the calling function
** needs to know whether or not the structure contains an SQL NULL
** value in order to correctly evaluate expressions like "X IN (Y, Z)".
** If there is any chance that the (...) might contain a NULL value at
** runtime, then a register is allocated and the register number written
** to *prNotFound. If there is no chance that the (...) contains a
** NULL value, then *prNotFound is left unchanged.
**
** If a register is allocated and its location stored in *prNotFound, then
** its initial value is NULL. If the (...) does not remain constant
** for the duration of the query (i.e. the SELECT within the (...)
** is a correlated subquery) then the value of the allocated register is
** reset to NULL each time the subquery is rerun. This allows the
** caller to use vdbe code equivalent to the following:
**
** if( register==NULL ){
** has_null = <test if data structure contains null>
** register = 1
** }
**
................................................................................
}
sqlite3ExprCachePop(pParse, 1);
return rReg;
}
#endif /* SQLITE_OMIT_SUBQUERY */
#ifndef SQLITE_OMIT_SUBQUERY/*** Generate code for an IN expression.**** x IN (SELECT ...)** x IN (value, value, ...)**** The left-hand side (LHS) is a scalar expression. The right-hand side (RHS)** is an array of zero or more values. The expression is true if the LHS is** contained within the RHS. The value of the expression is unknown (NULL)** if the LHS is NULL or if the LHS is not contained within the RHS and the** RHS contains one or more NULL values.**** This routine generates code will jump to destIfFalse if the LHS is not ** contained within the RHS. If due to NULLs we cannot determine if the LHS** is contained in the RHS then jump to destIfNull. If the LHS is contained** within the RHS then fall through.*/static void sqlite3ExprCodeIN( Parse *pParse, /* Parsing and code generating context */ Expr *pExpr, /* The IN expression */ int destIfFalse, /* Jump here if LHS is not contained in the RHS */ int destIfNull /* Jump here if the results are unknown due to NULLs */){ int rRhsHasNull = 0; /* Register that is true if RHS contains NULL values */ char affinity; /* Comparison affinity to use */ int eType; /* Type of the RHS */ int r1; /* Temporary use register */ Vdbe *v; /* Statement under construction */ /* Compute the RHS. After this step, the table with cursor ** pExpr->iTable will contains the values that make up the RHS. */ v = pParse->pVdbe; assert( v!=0 ); /* OOM detected prior to this routine */ VdbeNoopComment((v, "begin IN expr")); eType = sqlite3FindInIndex(pParse, pExpr, &rRhsHasNull); /* Figure out the affinity to use to create a key from the results ** of the expression. affinityStr stores a static string suitable for ** P4 of OP_MakeRecord. */ affinity = comparisonAffinity(pExpr); /* Code the LHS, the <expr> from "<expr> IN (...)". */ sqlite3ExprCachePush(pParse); r1 = sqlite3GetTempReg(pParse); sqlite3ExprCode(pParse, pExpr->pLeft, r1); sqlite3VdbeAddOp2(v, OP_IsNull, r1, destIfNull); if( eType==IN_INDEX_ROWID ){ /* In this case, the RHS is the ROWID of table b-tree */ sqlite3VdbeAddOp2(v, OP_MustBeInt, r1, destIfFalse); sqlite3VdbeAddOp3(v, OP_NotExists, pExpr->iTable, destIfFalse, r1); }else{ /* In this case, the RHS is an index b-tree. */ int r2; /* Register holding LHS value as a Record */ /* Create a record that can be used for membership testing. */ r2 = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp4(v, OP_MakeRecord, r1, 1, r2, &affinity, 1); /* If the set membership test fails, then the result of the ** "x IN (...)" expression must be either 0 or NULL. If the set ** contains no NULL values, then the result is 0. If the set ** contains one or more NULL values, then the result of the ** expression is also NULL. */ if( rRhsHasNull==0 || destIfFalse==destIfNull ){ /* This branch runs if it is known at compile time that the RHS ** cannot contain NULL values. This happens as the result ** of a "NOT NULL" constraint in the database schema. ** ** Also run this branch if NULL is equivalent to FALSE ** for this particular IN operator. */ sqlite3VdbeAddOp3(v, OP_NotFound, pExpr->iTable, destIfFalse, r2); }else{ /* In this branch, the RHS of the IN might contain a NULL and ** the presence of a NULL on the RHS makes a difference in the ** outcome. */ static const char nullRecord[] = { 0x02, 0x00 }; int j1, j2, j3; /* First check to see if the LHS is contained in the RHS. If so, ** then the presence of NULLs in the RHS does not matter, so jump ** over all of the code that follows. */ j1 = sqlite3VdbeAddOp3(v, OP_Found, pExpr->iTable, 0, r2); /* Here we begin generating code that runs if the LHS is not ** contained within the RHS. Generate additional code that ** tests the RHS for NULLs. If the RHS contains a NULL then ** jump to destIfNull. If there are no NULLs in the RHS then ** jump to destIfFalse. */ j2 = sqlite3VdbeAddOp1(v, OP_NotNull, rRhsHasNull); sqlite3VdbeAddOp4(v, OP_Blob, 2, r2, 0, nullRecord, P4_STATIC); j3 = sqlite3VdbeAddOp3(v, OP_Found, pExpr->iTable, 0, r2); sqlite3VdbeAddOp2(v, OP_Integer, -1, rRhsHasNull); sqlite3VdbeJumpHere(v, j3); sqlite3VdbeAddOp2(v, OP_AddImm, rRhsHasNull, 1); sqlite3VdbeJumpHere(v, j2); /* Jump to the appropriate target depending on whether or not ** the RHS contains a NULL */ sqlite3VdbeAddOp2(v, OP_If, rRhsHasNull, destIfNull); sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfFalse); /* The OP_Found at the top of this branch jumps here when true, ** causing the overall IN expression evaluation to fall through. */ sqlite3VdbeJumpHere(v, j1); } sqlite3ReleaseTempReg(pParse, r2); } sqlite3ReleaseTempReg(pParse, r1); sqlite3ExprCachePop(pParse, 1); VdbeComment((v, "end IN expr"));}#endif /* SQLITE_OMIT_SUBQUERY */
/*
** Duplicate an 8-byte value
*/
static char *dup8bytes(Vdbe *v, const char *in){
char *out = sqlite3DbMallocRaw(sqlite3VdbeDb(v), 8);
if( out ){
memcpy(out, in, 8);
................................................................................
case TK_SELECT: {
testcase( op==TK_EXISTS );
testcase( op==TK_SELECT );
inReg = sqlite3CodeSubselect(pParse, pExpr, 0, 0);
break;
}
case TK_IN: {
int destIfFalse = sqlite3VdbeMakeLabel(v);
int destIfNull = sqlite3VdbeMakeLabel(v);
sqlite3VdbeAddOp2(v, OP_Null, 0, target);
sqlite3ExprCodeIN(pParse, pExpr, destIfFalse, destIfNull);
sqlite3VdbeAddOp2(v, OP_Integer, 1, target);
sqlite3VdbeResolveLabel(v, destIfFalse);
sqlite3VdbeAddOp2(v, OP_AddImm, target, 0);
sqlite3VdbeResolveLabel(v, destIfNull); break; }#endif /* SQLITE_OMIT_SUBQUERY */
/*
** x BETWEEN y AND z
**
** This is equivalent to
**
** x>=y AND x<=z
**
................................................................................
sqlite3VdbeAddOp2(v, op, r1, dest);
testcase( regFree1==0 );
break;
}
case TK_BETWEEN: {
exprCodeBetween(pParse, pExpr, dest, 1, jumpIfNull);
break;
} case TK_IN: { int destIfFalse = sqlite3VdbeMakeLabel(v); int destIfNull = jumpIfNull ? dest : destIfFalse; sqlite3ExprCodeIN(pParse, pExpr, destIfFalse, destIfNull); sqlite3VdbeAddOp2(v, OP_Goto, 0, dest); sqlite3VdbeResolveLabel(v, destIfFalse); break;
}
default: {
r1 = sqlite3ExprCodeTemp(pParse, pExpr, &regFree1);
sqlite3VdbeAddOp3(v, OP_If, r1, dest, jumpIfNull!=0);
testcase( regFree1==0 );
testcase( jumpIfNull==0 );
break;
................................................................................
sqlite3VdbeAddOp2(v, op, r1, dest);
testcase( regFree1==0 );
break;
}
case TK_BETWEEN: {
exprCodeBetween(pParse, pExpr, dest, 0, jumpIfNull);
break;
} case TK_IN: { if( jumpIfNull ){ sqlite3ExprCodeIN(pParse, pExpr, dest, dest); }else{ int destIfNull = sqlite3VdbeMakeLabel(v); sqlite3ExprCodeIN(pParse, pExpr, dest, destIfNull); sqlite3VdbeResolveLabel(v, destIfNull); } break;
}
default: {
r1 = sqlite3ExprCodeTemp(pParse, pExpr, &regFree1);
sqlite3VdbeAddOp3(v, OP_IfNot, r1, dest, jumpIfNull!=0);
testcase( regFree1==0 );
testcase( jumpIfNull==0 );
break;

break;
}
/* Opcode: Found P1 P2 P3 * *
**
** Register P3 holds a blob constructed by MakeRecord. P1 is an index.
** If an entry that matches the value in register p3 exists in P1 then
** jump to P2. If the P3 value does not match any entry in P1
** then fall thru. The P1 cursor is left pointing at the matching entry
** if it exists.**** This instruction is used to implement the IN operator where the** left-hand side is a SELECT statement. P1 may be a true index, or it** may be a temporary index that holds the results of the SELECT** statement. This instruction is also used to implement the** DISTINCT keyword in SELECT statements.**** This instruction checks if index P1 contains a record for which ** the first N serialized values exactly match the N serialized values** in the record in register P3, where N is the total number of values in** the P3 record (the P3 record is a prefix of the P1 record). **** See also: NotFound, IsUnique, NotExists
*/
/* Opcode: NotFound P1 P2 P3 * *
**
** Register P3 holds a blob constructed by MakeRecord. P1 is
** an index. If no entry exists in P1 that matches the blob then jump
** to P2. If an entry does existing, fall through. The cursor is left
** pointing to the entry that matches.
**
** See also: Found, NotExists, IsUnique
*/
case OP_NotFound: /* jump, in3 */
case OP_Found: { /* jump, in3 */
int alreadyExists;
VdbeCursor *pC;
................................................................................
assert( pIn3->flags & MEM_Blob );
ExpandBlob(pIn3);
pIdxKey = sqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z,
aTempRec, sizeof(aTempRec));
if( pIdxKey==0 ){
goto no_mem;
}
if( pOp->opcode==OP_Found ){ pIdxKey->flags |= UNPACKED_PREFIX_MATCH;
}
rc = sqlite3BtreeMovetoUnpacked(pC->pCursor, pIdxKey, 0, 0, &res);
sqlite3VdbeDeleteUnpackedRecord(pIdxKey);
if( rc!=SQLITE_OK ){
break;
}
alreadyExists = (res==0);
pC->deferredMoveto = 0;