From: drh Date: Thu, 12 Nov 2009 17:52:24 +0000 (+0000) Subject: Factor out the IN operator code generation into a subroutine. Use this X-Git-Tag: fts3-refactor~13 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e3365e6c3762a6bb09f58f371a06797c3cd5e391;p=thirdparty%2Fsqlite.git Factor out the IN operator code generation into a subroutine. Use this subroutine to implement both logic and branching versions of the IN operator. FossilOrigin-Name: fcff5b7e2d059ffb8d21df57d0c9648bd876e813 --- diff --git a/manifest b/manifest index 6c8b3a1433..3e281bcb16 100644 --- a/manifest +++ b/manifest @@ -1,8 +1,8 @@ -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 -C Factor\sout\sthe\scode\sgenerator\sfor\sBETWEEN\sinto\sa\ssubroutine. -D 2009-11-12T13:32:23 +C Factor\sout\sthe\sIN\soperator\scode\sgeneration\sinto\sa\ssubroutine.\s\sUse\sthis\nsubroutine\sto\simplement\sboth\slogic\sand\sbranching\sversions\sof\sthe\sIN\soperator. +D 2009-11-12T17:52:24 F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0 F Makefile.in 53f3dfa49f28ab5b80cb083fb7c9051e596bcfa1 F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654 @@ -117,7 +117,7 @@ F src/callback.c 908f3e0172c3d4058f4ca0acd42c637c52e9669f F src/complete.c 77016e5a2766cf8b84381397152d2c99ea4b218f F src/date.c a79c0a8f219370b972e320741f995a3bef9df33f F src/delete.c ec04635d152debdab70d4b30c5516b59282075ea -F src/expr.c 64330769c242615f037290c2dcdfded8bb53b537 +F src/expr.c 98925d79ea6a0a13a2ce46369482ae2fdb2fa616 F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb F src/fkey.c 41219cba186bcf0a053e42327dfa23aaba4f834a F src/func.c bf54e1202cbfb28bf4b1fd9b58899009ae76716f @@ -210,7 +210,7 @@ F src/update.c 8efeb09822886e33c265dd96d29a3d865ea6dcf2 F src/utf.c dad16adcc0c35ef2437dca125a4b07419d361052 F src/util.c ad4f03079ba0fe83590d1cc9197e8e4844e38592 F src/vacuum.c 03309a08d549f9389cc3a3589afd4fadbdaf0679 -F src/vdbe.c 393a6766807b0b335f9f1bdebb41ba08d138e5ef +F src/vdbe.c 7c0238174a2400eb4c578978ae164c025aef0872 F src/vdbe.h 7b06b21f76e349bc2b7e18599550d79bf943c940 F src/vdbeInt.h 59c65e7b810836b9e946acee45c7b3c02b967d1b F src/vdbeapi.c 17680ab7a75ec938c5ba039a6c87489d01faf2cb @@ -771,14 +771,14 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f -P d7b3801dc7fad1b002f892fb5d82047ebff98369 -R 8f2a35eead76fc902fb56cbc9c8e7046 +P 5735f60b23460e7677b9c982a26bc13b0f4ed02b +R cd9746f4aa3a1481551f5a560f35dd38 U drh -Z ae99e07341da03fa353722abb2f83700 +Z 81c5b2ffa7a845f506af8b1b8ae99199 -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) -iD8DBQFK/A5roxKgR168RlERAnPLAJ0Sxm4g2oTIdPyR4JvF7/DwSMTZ7ACdHf/0 -GxIVBC/bE7L6MXpVSt2rKxI= -=OMih +iD8DBQFK/EtboxKgR168RlERAkKeAJ4iV9MT2c84lYVox/EKQ+FokU0xYgCfa68+ +e8jT2Mgzw0vJyZcF2wVoLY8= +=fBGP -----END PGP SIGNATURE----- diff --git a/manifest.uuid b/manifest.uuid index 6690415674..d91a3471ce 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5735f60b23460e7677b9c982a26bc13b0f4ed02b \ No newline at end of file +fcff5b7e2d059ffb8d21df57d0c9648bd876e813 \ No newline at end of file diff --git a/src/expr.c b/src/expr.c index b16657c610..8ff43381b6 100644 --- a/src/expr.c +++ b/src/expr.c @@ -1345,16 +1345,16 @@ static int isCandidateForInOpt(Select *p){ ** 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 a chance that the b-tree might contain a NULL value at +** 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 b-tree contains a +** 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 b-tree does not remain constant -** for the duration of the query (i.e. the SELECT that generates the b-tree +** 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 b-tree is repopulated. This allows the +** reset to NULL each time the subquery is rerun. This allows the ** caller to use vdbe code equivalent to the following: ** ** if( register==NULL ){ @@ -1694,6 +1694,136 @@ int sqlite3CodeSubselect( } #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 from " 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 */ @@ -2472,95 +2602,19 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ break; } case TK_IN: { - int rNotFound = 0; - int rMayHaveNull = 0; - int j2, j3, j4, j5; - char affinity; - int eType; - - VdbeNoopComment((v, "begin IN expr r%d", target)); - eType = sqlite3FindInIndex(pParse, pExpr, &rMayHaveNull); - if( rMayHaveNull ){ - rNotFound = ++pParse->nMem; - } - - /* 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 from " IN (...)". The temporary table - ** pExpr->iTable contains the values that make up the (...) set. - */ - sqlite3ExprCachePush(pParse); - sqlite3ExprCode(pParse, pExpr->pLeft, target); - j2 = sqlite3VdbeAddOp1(v, OP_IsNull, target); - if( eType==IN_INDEX_ROWID ){ - j3 = sqlite3VdbeAddOp1(v, OP_MustBeInt, target); - j4 = sqlite3VdbeAddOp3(v, OP_NotExists, pExpr->iTable, 0, target); - sqlite3VdbeAddOp2(v, OP_Integer, 1, target); - j5 = sqlite3VdbeAddOp0(v, OP_Goto); - sqlite3VdbeJumpHere(v, j3); - sqlite3VdbeJumpHere(v, j4); - sqlite3VdbeAddOp2(v, OP_Integer, 0, target); - }else{ - r2 = regFree2 = sqlite3GetTempReg(pParse); - - /* Create a record and test for set membership. If the set contains - ** the value, then jump to the end of the test code. The target - ** register still contains the true (1) value written to it earlier. - */ - sqlite3VdbeAddOp4(v, OP_MakeRecord, target, 1, r2, &affinity, 1); - sqlite3VdbeAddOp2(v, OP_Integer, 1, target); - j5 = sqlite3VdbeAddOp3(v, OP_Found, pExpr->iTable, 0, r2); - - /* 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( rNotFound==0 ){ - /* This branch runs if it is known at compile time (now) that - ** the set contains no NULL values. This happens as the result - ** of a "NOT NULL" constraint in the database schema. No need - ** to test the data structure at runtime in this case. - */ - sqlite3VdbeAddOp2(v, OP_Integer, 0, target); - }else{ - /* This block populates the rNotFound register with either NULL - ** or 0 (an integer value). If the data structure contains one - ** or more NULLs, then set rNotFound to NULL. Otherwise, set it - ** to 0. If register rMayHaveNull is already set to some value - ** other than NULL, then the test has already been run and - ** rNotFound is already populated. - */ - static const char nullRecord[] = { 0x02, 0x00 }; - j3 = sqlite3VdbeAddOp1(v, OP_NotNull, rMayHaveNull); - sqlite3VdbeAddOp2(v, OP_Null, 0, rNotFound); - sqlite3VdbeAddOp4(v, OP_Blob, 2, rMayHaveNull, 0, - nullRecord, P4_STATIC); - j4 = sqlite3VdbeAddOp3(v, OP_Found, pExpr->iTable, 0, rMayHaveNull); - sqlite3VdbeAddOp2(v, OP_Integer, 0, rNotFound); - sqlite3VdbeJumpHere(v, j4); - sqlite3VdbeJumpHere(v, j3); - - /* Copy the value of register rNotFound (which is either NULL or 0) - ** into the target register. This will be the result of the - ** expression. - */ - sqlite3VdbeAddOp2(v, OP_Copy, rNotFound, target); - } - } - sqlite3VdbeJumpHere(v, j2); - sqlite3VdbeJumpHere(v, j5); - sqlite3ExprCachePop(pParse, 1); - VdbeComment((v, "end IN expr r%d", target)); + 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 +#endif /* SQLITE_OMIT_SUBQUERY */ + + /* ** x BETWEEN y AND z ** @@ -3149,6 +3203,14 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ 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, ®Free1); sqlite3VdbeAddOp3(v, OP_If, r1, dest, jumpIfNull!=0); @@ -3280,6 +3342,16 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ 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, ®Free1); sqlite3VdbeAddOp3(v, OP_IfNot, r1, dest, jumpIfNull!=0); diff --git a/src/vdbe.c b/src/vdbe.c index d0421354db..758e07973c 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -3357,30 +3357,16 @@ case OP_Seek: { /* in2 */ /* 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 +** If P3 is a prefix of any entry in P1 then a jump is made to P2 and +** P1 is left pointing at the matching entry. */ /* 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. +** an index. If P3 is not the prefix of any entry in P1 then a jump +** is made to P2. If P1 does contain an entry whose prefix matches +** P3 then control falls through to the next instruction and P1 is +** left pointing at the matching entry. ** ** See also: Found, NotExists, IsUnique */ @@ -3410,9 +3396,7 @@ case OP_Found: { /* jump, in3 */ if( pIdxKey==0 ){ goto no_mem; } - if( pOp->opcode==OP_Found ){ - pIdxKey->flags |= UNPACKED_PREFIX_MATCH; - } + pIdxKey->flags |= UNPACKED_PREFIX_MATCH; rc = sqlite3BtreeMovetoUnpacked(pC->pCursor, pIdxKey, 0, 0, &res); sqlite3VdbeDeleteUnpackedRecord(pIdxKey); if( rc!=SQLITE_OK ){