]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Factor out the IN operator code generation into a subroutine. Use this
authordrh <drh@noemail.net>
Thu, 12 Nov 2009 17:52:24 +0000 (17:52 +0000)
committerdrh <drh@noemail.net>
Thu, 12 Nov 2009 17:52:24 +0000 (17:52 +0000)
subroutine to implement both logic and branching versions of the IN operator.

FossilOrigin-Name: fcff5b7e2d059ffb8d21df57d0c9648bd876e813

manifest
manifest.uuid
src/expr.c
src/vdbe.c

index 6c8b3a1433af98f68b5f7f0ebede58dba8d97fff..3e281bcb1640d5ef69dc84b0f7b1266988939466 100644 (file)
--- 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-----
index 6690415674ce2a1481506bb1c8fa10c0b4be8caf..d91a3471ce7c9a9541ebd1696aea0b445ae85aee 100644 (file)
@@ -1 +1 @@
-5735f60b23460e7677b9c982a26bc13b0f4ed02b
\ No newline at end of file
+fcff5b7e2d059ffb8d21df57d0c9648bd876e813
\ No newline at end of file
index b16657c610827ac863925209d1941f2a17d76aea..8ff43381b689a557bc939ae8fa919db7c53c258d 100644 (file)
@@ -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 <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
 */
@@ -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 <expr> from "<expr> 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, &regFree1);
       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, &regFree1);
       sqlite3VdbeAddOp3(v, OP_IfNot, r1, dest, jumpIfNull!=0);
index d0421354dbfdcbaf6abcd2378c9fd3b856cfca96..758e07973c61e39aa5d622cd8fd59812a2978627 100644 (file)
@@ -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 ){