]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Fix handling of "x IN (...)" and "x NOT IN (...)" expressions when the set contains...
authordanielk1977 <danielk1977@noemail.net>
Thu, 26 Jun 2008 18:04:03 +0000 (18:04 +0000)
committerdanielk1977 <danielk1977@noemail.net>
Thu, 26 Jun 2008 18:04:03 +0000 (18:04 +0000)
FossilOrigin-Name: d45a97be71fa61ab4a692bd807ab762130f7f5b9

manifest
manifest.uuid
src/expr.c
src/select.c
src/sqliteInt.h
src/vdbe.c
src/where.c
test/in.test
test/incrblob2.test

index 769a500573130e7ef480e080b9c9070d58bb83eb..712f6c42c052775ff61c486d0a1c2c2d7a0e940f 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Document\sthe\srules\sfor\swhen\san\ssqlite3_blob\sobject\sexpires.\s(CVS\s5313)
-D 2008-06-26T15:04:58
+C Fix\shandling\sof\s"x\sIN\s(...)"\sand\s"x\sNOT\sIN\s(...)"\sexpressions\swhen\sthe\sset\scontains\san\sSQL\sNULL\svalue.\s(CVS\s5314)
+D 2008-06-26T18:04:03
 F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0
 F Makefile.in 325dfac0a0dd1cb4d975f1ace6453157892e6042
 F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654
@@ -103,7 +103,7 @@ F src/callback.c 3ba98ae46f60aa7c2c40eac7d18fe5ba9b706b83
 F src/complete.c cb14e06dbe79dee031031f0d9e686ff306afe07c
 F src/date.c e841168e5520bbbb2a1cbcdce7531d8b23017b4d
 F src/delete.c d3fc5987f2eb88f7b9549d58a5dfea079a83fe8b
-F src/expr.c 1a8fbd3dfc531fa8ec51319a0bdef014e28c0fdd
+F src/expr.c edcc8a30453f63fe9eab3a3d714589f44f875d8d
 F src/fault.c 3638519d1e0b82bccfafcb9f5ff491918b28f8e1
 F src/func.c 1e7d9569570134ac0771a00382d9d4b41c4aa052
 F src/global.c 2304cfa3288763bd2fed10caf8c6fbaa2b383f4e
@@ -140,11 +140,11 @@ F src/pragma.c 9a95f5b3708f6d3ddd987eab5f369a19ffcb6795
 F src/prepare.c aba51dad52308e3d9d2074d8ff4e612e7f1cab51
 F src/printf.c 8b063da9dcde26b7c500a01444b718d86f21bc6e
 F src/random.c 5c754319d38abdd6acd74601ee0105504adc508a
-F src/select.c 79f60dc4a7e90bb907c7a2cca42f45276d1ead99
+F src/select.c e27de53be426254e1b75690c9c99482bb1431b79
 F src/shell.c 479807b87f0409289eec4a776cd6ae56d30544b1
 F src/sqlite.h.in 2b9e8de3378101ad8ec8de98e3679ad5c2f39427
 F src/sqlite3ext.h f162a72daef5ebf8b211fe8c0ec96e85d22fbf9b
-F src/sqliteInt.h 5ed69fd1affa577a47ce01baa42ee94f22f45ed7
+F src/sqliteInt.h 969acf22dbe79075e486074a8ffdc1e2fc2b8b1f
 F src/sqliteLimit.h f435e728c6b620ef7312814d660a81f9356eb5c8
 F src/status.c 6cb10377992505bd69f1ca1d75c1240a65f25a58
 F src/table.c 1fa8f8113ac9cbc09ae4801c6d2a7f0af82c5822
@@ -181,7 +181,7 @@ F src/update.c 2d7143b9014e955509cc4f323f9a9584fb898f34
 F src/utf.c 8c94fa10efc78c2568d08d436acc59df4df7191b
 F src/util.c 920d6d5dfdf25f7b85d2093705d8716f9b387e3b
 F src/vacuum.c 14eb21b480924d87e791cd8ab6fb35ac563243ef
-F src/vdbe.c c0daf1d1fb4c3c79805004969d5d036f3d2381f8
+F src/vdbe.c b1528683ff7ca1faf017db5ebedbc403e92d009b
 F src/vdbe.h c46155c221418bea29ee3a749d5950fcf85a70e2
 F src/vdbeInt.h 30535c1d30ba1b5fb58d8f0e1d1261af976558aa
 F src/vdbeapi.c a7c6b8db324cf7eccff32de871dea36aa305c994
@@ -190,7 +190,7 @@ F src/vdbeblob.c 9345f6dcd675fdcfdb537d2d2f487542d9ea136a
 F src/vdbefifo.c c46dae1194e4277bf007144d7e5b0c0b1c24f136
 F src/vdbemem.c a39a822e6ae61c4cab4a512df4a315888b206911
 F src/vtab.c 2096c03ec5540a43c8c73a8f43407dfd3549a982
-F src/where.c 767db25b4b92a5e0a6f1b75ba40abf377b65a212
+F src/where.c 6a22ceb86d774c4428ac53196f721bb14e6cbdfa
 F tclinstaller.tcl 4356d9d94d2b5ed5e68f9f0c80c4df3048dd7617
 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
 F test/all.test ed6849e7a425620d5d4388409f3b15901b5bc2e7
@@ -335,11 +335,11 @@ F test/fuzz_common.tcl ff4bc2dfc465f6878f8e2d819620914365382731
 F test/fuzz_malloc.test 166b58dfd77cc04f6afeeaef0cfc1087abf134d1
 F test/hook.test e17d4ed2843ba4ef9b234aa63e6f056bf88f9a19
 F test/icu.test e6bfae7f625c88fd14df6f540fe835bdfc1e4329
-F test/in.test ca4ea8ac1077f5221055fcb66969892612632ef7
+F test/in.test b35fc31caf26647cc4243c3af8ad29e45ed8776f
 F test/in2.test b1f447f4f0f67e9f83ff931e7e2e30873f9ea055
 F test/in3.test dc62b080ed79898121c61c91118b4d1e111f1438
 F test/incrblob.test 4455fffd08b2f9418a9257e18b135d72273eff3e
-F test/incrblob2.test a1db17c1f70bdbf9cae8415fb500df511569306a
+F test/incrblob2.test c9aad1e11f7726d8c49e66f2a1ecc2d04f9f6861
 F test/incrblob_err.test a3e3d9442d2993e8a449e791db4001d11a2f683f
 F test/incrvacuum.test 1a2b0bddc76629afeb41e3d8ea3e4563982d16b9
 F test/incrvacuum2.test 46ef65f377e3937cfd1ba66e818309dab46f590d
@@ -594,7 +594,7 @@ F tool/speedtest16.c c8a9c793df96db7e4933f0852abb7a03d48f2e81
 F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 1dbced29de5f59ba2ebf877edcadf171540374d1
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
-P 1e3b8308021107d983d2152663f62b369cca091d
-R 5972f13e602e13f1fa109add837cf301
-U drh
-Z 6e75d867f7857f56f8d906b33d9926f0
+P e1de2287fd9b067f687cb8b427786b878af6c5b7
+R 01ffd027189ad04179892df9936c6090
+U danielk1977
+Z c4dcbe6b5c0897a85aa2b72a88c7084f
index b2d89f7a25343e545be38987cd8585b3144b4ced..4cd97bfb7c06eeb2d28c8e3ad4b3bda194ca00a8 100644 (file)
@@ -1 +1 @@
-e1de2287fd9b067f687cb8b427786b878af6c5b7
\ No newline at end of file
+d45a97be71fa61ab4a692bd807ab762130f7f5b9
\ No newline at end of file
index 877469eafd04246e670538e194a624e9b475b53c..96836ed682fd54e71301ae1f92e7753baabe5a94 100644 (file)
@@ -12,7 +12,7 @@
 ** This file contains routines used for analyzing expressions and
 ** for generating VDBE code that evaluates expressions in SQLite.
 **
-** $Id: expr.c,v 1.376 2008/06/24 12:46:31 drh Exp $
+** $Id: expr.c,v 1.377 2008/06/26 18:04:03 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -1673,22 +1673,46 @@ static int isCandidateForInOpt(Select *p){
 **
 **     SELECT <column> FROM <table>
 **
-** If the mustBeUnique parameter is false, the structure will be used 
-** 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.
-**
-** If mustBeUnique is true, then the structure will be used to iterate
+** If prNotFound parameter is 0, then the structure will be used to iterate
 ** through the set members, skipping any duplicates. In this case an
 ** epheremal table must be used unless the selected <column> is guaranteed
 ** to be unique - either because it is an INTEGER PRIMARY KEY or it
 ** is unique by virtue of a constraint or implicit index.
+**
+** If the prNotFound parameter is not 0, then the structure will be used 
+** 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 structure is being used for set membership tests, the user
+** 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 structure may 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 structure 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 structure does not remain constant
+** for the duration of the query (i.e. the set is a correlated sub-select), 
+** the value of the allocated register is reset to NULL each time the 
+** structure is repopulated. 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
+**   }
+**
+** in order to avoid running the <test if data structure contains null>
+** test more often than is necessary.
 */
 #ifndef SQLITE_OMIT_SUBQUERY
-int sqlite3FindInIndex(Parse *pParse, Expr *pX, int mustBeUnique){
+int sqlite3FindInIndex(Parse *pParse, Expr *pX, int *prNotFound){
   Select *p;
   int eType = 0;
   int iTab = pParse->nTab++;
+  int mustBeUnique = !prNotFound;
 
   /* The follwing if(...) expression is true if the SELECT is of the 
   ** simple form:
@@ -1764,13 +1788,20 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, int mustBeUnique){
           eType = IN_INDEX_INDEX;
 
           sqlite3VdbeJumpHere(v, iAddr);
+          if( prNotFound && !pTab->aCol[iCol].notNull ){
+            *prNotFound = ++pParse->nMem;
+          }
         }
       }
     }
   }
 
   if( eType==0 ){
-    sqlite3CodeSubselect(pParse, pX);
+    int rMayHaveNull = 0;
+    if( prNotFound ){
+      *prNotFound = rMayHaveNull = ++pParse->nMem;
+    }
+    sqlite3CodeSubselect(pParse, pX, rMayHaveNull);
     eType = IN_INDEX_EPH;
   }else{
     pX->iTable = iTab;
@@ -1792,7 +1823,7 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, int mustBeUnique){
 ** operator or subquery.
 */
 #ifndef SQLITE_OMIT_SUBQUERY
-void sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
+void sqlite3CodeSubselect(Parse *pParse, Expr *pExpr, int rMayHaveNull){
   int testAddr = 0;                       /* One-time test address */
   Vdbe *v = sqlite3GetVdbe(pParse);
   if( v==0 ) return;
@@ -1821,6 +1852,10 @@ void sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
       KeyInfo keyInfo;
       int addr;        /* Address of OP_OpenEphemeral instruction */
 
+      if( rMayHaveNull ){
+        sqlite3VdbeAddOp2(v, OP_Null, 0, rMayHaveNull);
+      }
+
       affinity = sqlite3ExprAffinity(pExpr->pLeft);
 
       /* Whether this is an 'x IN(SELECT...)' or an 'x IN(<exprlist>)'
@@ -2535,17 +2570,22 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
       testcase( op==TK_EXISTS );
       testcase( op==TK_SELECT );
       if( pExpr->iColumn==0 ){
-        sqlite3CodeSubselect(pParse, pExpr);
+        sqlite3CodeSubselect(pParse, pExpr, 0);
       }
       inReg = pExpr->iColumn;
       break;
     }
     case TK_IN: {
+      int rNotFound = 0;
+      int rMayHaveNull = 0;
       int j1, j2, j3, j4, j5;
       char affinity;
       int eType;
 
-      eType = sqlite3FindInIndex(pParse, pExpr, 0);
+      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
@@ -2570,13 +2610,55 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int 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, r1, 1, r2, &affinity, 1);
         sqlite3ExprCacheAffinityChange(pParse, r1, 1);
         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.
+          */
+          j3 = sqlite3VdbeAddOp1(v, OP_NotNull, rMayHaveNull);
+          sqlite3VdbeAddOp2(v, OP_Null, 0, rNotFound);
+          sqlite3VdbeAddOp2(v, OP_Integer, 1, rMayHaveNull);
+         sqlite3VdbeAddOp4(v, OP_MakeRecord, rNotFound, 1, r2, 0, 1);
+          j4 = sqlite3VdbeAddOp3(v, OP_Found, pExpr->iTable, 0, r2);
+          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);
+        }
       }
-      sqlite3VdbeAddOp2(v, OP_AddImm, target, -1);
       sqlite3VdbeJumpHere(v, j2);
       sqlite3VdbeJumpHere(v, j5);
       break;
index 7045c32b26314176a77112b096166b7d5c74bae9..0f937226ef89a9c1ac4b49120e1b8de7565f3fe1 100644 (file)
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle SELECT statements in SQLite.
 **
-** $Id: select.c,v 1.436 2008/06/25 00:12:41 drh Exp $
+** $Id: select.c,v 1.437 2008/06/26 18:04:03 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 
@@ -655,7 +655,6 @@ static void selectInnerLoop(
       int addr2;
 
       assert( nColumn==1 );
-      addr2 = sqlite3VdbeAddOp1(v, OP_IsNull, regResult);
       p->affinity = sqlite3CompareAffinity(pEList->a[0].pExpr, pDest->affinity);
       if( pOrderBy ){
         /* At first glance you would think we could optimize out the
@@ -670,7 +669,6 @@ static void selectInnerLoop(
         sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1);
         sqlite3ReleaseTempReg(pParse, r1);
       }
-      sqlite3VdbeJumpHere(v, addr2);
       break;
     }
 
@@ -828,13 +826,10 @@ static void generateSortTail(
     }
 #ifndef SQLITE_OMIT_SUBQUERY
     case SRT_Set: {
-      int j1;
       assert( nColumn==1 );
-      j1 = sqlite3VdbeAddOp1(v, OP_IsNull, regRow);
       sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, 1, regRowid, &p->affinity, 1);
       sqlite3ExprCacheAffinityChange(pParse, regRow, 1);
       sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, regRowid);
-      sqlite3VdbeJumpHere(v, j1);
       break;
     }
     case SRT_Mem: {
@@ -2391,7 +2386,6 @@ static int generateOutputSubroutine(
     case SRT_Set: {
       int addr2, r1;
       assert( pIn->nMem==1 );
-      addr2 = sqlite3VdbeAddOp1(v, OP_IsNull, pIn->iMem);
       p->affinity = 
          sqlite3CompareAffinity(p->pEList->a[0].pExpr, pDest->affinity);
       r1 = sqlite3GetTempReg(pParse);
@@ -2399,7 +2393,6 @@ static int generateOutputSubroutine(
       sqlite3ExprCacheAffinityChange(pParse, pIn->iMem, 1);
       sqlite3VdbeAddOp2(v, OP_IdxInsert, pDest->iParm, r1);
       sqlite3ReleaseTempReg(pParse, r1);
-      sqlite3VdbeJumpHere(v, addr2);
       break;
     }
 
@@ -3577,7 +3570,7 @@ void sqlite3SelectMask(Parse *pParse, Select *p, u32 mask){
 **
 **     SRT_Mem         Store first result in memory cell pDest->iParm
 **
-**     SRT_Set         Store non-null results as keys of table pDest->iParm. 
+**     SRT_Set         Store results as keys of table pDest->iParm. 
 **                     Apply the affinity pDest->affinity before storing them.
 **
 **     SRT_Union       Store results as a key in a temporary table pDest->iParm.
index 7757f6915bd9cb91df293fe2b8dc1abdad7d8ec4..2d6581761498af2c3b741b33eab351b5ca15d42e 100644 (file)
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.731 2008/06/26 10:54:12 danielk1977 Exp $
+** @(#) $Id: sqliteInt.h,v 1.732 2008/06/26 18:04:03 danielk1977 Exp $
 */
 #ifndef _SQLITEINT_H_
 #define _SQLITEINT_H_
@@ -1435,7 +1435,7 @@ struct Select {
 
 #define SRT_Callback     5  /* Invoke a callback with each row of result */
 #define SRT_Mem          6  /* Store result in a memory cell */
-#define SRT_Set          7  /* Store non-null results as keys in an index */
+#define SRT_Set          7  /* Store results as keys in an index */
 #define SRT_Table        8  /* Store result as data with an automatic rowid */
 #define SRT_EphemTab     9  /* Create transient tab and store like SRT_Table */
 #define SRT_Coroutine   10  /* Generate a single row of result */
@@ -2118,7 +2118,7 @@ void sqlite3AlterRenameTable(Parse*, SrcList*, Token*);
 int sqlite3GetToken(const unsigned char *, int *);
 void sqlite3NestedParse(Parse*, const char*, ...);
 void sqlite3ExpirePreparedStatements(sqlite3*);
-void sqlite3CodeSubselect(Parse *, Expr *);
+void sqlite3CodeSubselect(Parse *, Expr *, int);
 int sqlite3SelectResolve(Parse *, Select *, NameContext *);
 void sqlite3ColumnDefault(Vdbe *, Table *, int);
 void sqlite3AlterFinishAddColumn(Parse *, Token *);
@@ -2224,7 +2224,7 @@ CollSeq *sqlite3BinaryCompareCollSeq(Parse *, Expr *, Expr *);
 #define IN_INDEX_ROWID           1
 #define IN_INDEX_EPH             2
 #define IN_INDEX_INDEX           3
-int sqlite3FindInIndex(Parse *, Expr *, int);
+int sqlite3FindInIndex(Parse *, Expr *, int*);
 
 #ifdef SQLITE_ENABLE_ATOMIC_WRITE
   int sqlite3JournalOpen(sqlite3_vfs *, const char *, sqlite3_file *, int, int);
index 20207343d4883937611a339b1dbd643c86f7bb89..79e5e33da53cf3452eed3d3d2f2ef73e85b1b9e4 100644 (file)
@@ -43,7 +43,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.755 2008/06/25 00:12:41 drh Exp $
+** $Id: vdbe.c,v 1.756 2008/06/26 18:04:03 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -1428,7 +1428,7 @@ case OP_ShiftRight: {           /* same as TK_RSHIFT, in1, in2, out3 */
 
 /* Opcode: AddImm  P1 P2 * * *
 ** 
-** Add the constant P2 the value in register P1.
+** Add the constant P2 to the value in register P1.
 ** The result is always an integer.
 **
 ** To force any register to be an integer, just add 0.
index a5ba55f0db254aace60e911b6ac6afd0339076e7..009dd4848dd3e7de918267b38d5bc7d9191b78c8 100644 (file)
@@ -16,7 +16,7 @@
 ** so is applicable.  Because this module is responsible for selecting
 ** indices, you might also think of this module as the "query optimizer".
 **
-** $Id: where.c,v 1.310 2008/06/25 02:47:57 drh Exp $
+** $Id: where.c,v 1.311 2008/06/26 18:04:03 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 
@@ -1787,7 +1787,7 @@ static int codeEqualityTerm(
 
     assert( pX->op==TK_IN );
     iReg = iTarget;
-    eType = sqlite3FindInIndex(pParse, pX, 1);
+    eType = sqlite3FindInIndex(pParse, pX, 0);
     iTab = pX->iTable;
     sqlite3VdbeAddOp2(v, OP_Rewind, iTab, 0);
     VdbeComment((v, "%.*s", pX->span.n, pX->span.z));
index 2f7201cc5bbdeba40f0e1cc8fa47056fe596d6ce..b86417216104568ee43c4459a798e92ea2cddf5d 100644 (file)
@@ -11,7 +11,7 @@
 # This file implements regression tests for SQLite library.  The
 # focus of this file is testing the IN and BETWEEN operator.
 #
-# $Id: in.test,v 1.20 2008/06/24 12:46:31 drh Exp $
+# $Id: in.test,v 1.21 2008/06/26 18:04:03 danielk1977 Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -429,4 +429,150 @@ do_test in-12.9 {
   }
 } {1 {SELECTs to the left and right of INTERSECT do not have the same number of result columns}}
 
+
+#------------------------------------------------------------------------
+# The following tests check that NULL is handled correctly when it 
+# appears as part of a set of values on the right-hand side of an
+# IN or NOT IN operator.
+#
+# When it appears in such a set, NULL is handled as an "unknown value".
+# If, because of the unknown value in the set, the result of the expression 
+# cannot be determined, then it itself evaluates to NULL.
+#
+
+# Warm body test to demonstrate the principles being tested:
+#
+do_test in-13.1 {
+  db nullvalue "null"
+  execsql { SELECT 
+    1 IN (NULL, 1, 2),     -- The value 1 is a member of the set, return true.
+    3 IN (NULL, 1, 2),     -- Ambiguous, return NULL.
+    1 NOT IN (NULL, 1, 2), -- The value 1 is a member of the set, return false.
+    3 NOT IN (NULL, 1, 2)  -- Ambiguous, return NULL.
+  }
+} {1 null 0 null}
+
+do_test in-13.2 {
+  execsql { 
+    CREATE TABLE t7(a, b, c NOT NULL);
+    INSERT INTO t7 VALUES(1,    1, 1);
+    INSERT INTO t7 VALUES(2,    2, 2);
+    INSERT INTO t7 VALUES(3,    3, 3);
+    INSERT INTO t7 VALUES(NULL, 4, 4);
+    INSERT INTO t7 VALUES(NULL, 5, 5);
+  }
+} {}
+
+do_test in-13.3 {
+  execsql { SELECT 2 IN (SELECT a FROM t7) }
+} {1}
+do_test in-13.4 {
+  execsql { SELECT 6 IN (SELECT a FROM t7) }
+} {null}
+
+do_test in-13.5 {
+  execsql { SELECT 2 IN (SELECT b FROM t7) }
+} {1}
+do_test in-13.6 {
+  execsql { SELECT 6 IN (SELECT b FROM t7) }
+} {0}
+
+do_test in-13.7 {
+  execsql { SELECT 2 IN (SELECT c FROM t7) }
+} {1}
+do_test in-13.8 {
+  execsql { SELECT 6 IN (SELECT c FROM t7) }
+} {0}
+
+do_test in-13.9 {
+  execsql {
+    SELECT
+      2 NOT IN (SELECT a FROM t7),
+      6 NOT IN (SELECT a FROM t7),
+      2 NOT IN (SELECT b FROM t7),
+      6 NOT IN (SELECT b FROM t7),
+      2 NOT IN (SELECT c FROM t7),
+      6 NOT IN (SELECT c FROM t7)
+  } 
+} {0 null 0 1 0 1}
+
+do_test in-13.10 {
+  execsql { 
+    SELECT b IN (
+      SELECT inside.a 
+      FROM t7 AS inside 
+      WHERE inside.b BETWEEN outside.b+1 AND outside.b+2
+    )
+    FROM t7 AS outside ORDER BY b;
+  }
+} {0 null null null 0}
+
+do_test in-13.11 {
+  execsql {
+    SELECT b NOT IN (
+      SELECT inside.a 
+      FROM t7 AS inside 
+      WHERE inside.b BETWEEN outside.b+1 AND outside.b+2
+    )
+    FROM t7 AS outside ORDER BY b;
+  }
+} {1 null null null 1}
+
+do_test in-13.12 {
+  execsql {
+    CREATE INDEX i1 ON t7(a);
+    CREATE INDEX i2 ON t7(b);
+    CREATE INDEX i3 ON t7(c);
+  }
+  execsql {
+    SELECT
+      2 IN (SELECT a FROM t7),
+      6 IN (SELECT a FROM t7),
+      2 IN (SELECT b FROM t7),
+      6 IN (SELECT b FROM t7),
+      2 IN (SELECT c FROM t7),
+      6 IN (SELECT c FROM t7)
+  } 
+} {1 null 1 0 1 0}
+
+do_test in-13.13 {
+  execsql {
+    SELECT
+      2 NOT IN (SELECT a FROM t7),
+      6 NOT IN (SELECT a FROM t7),
+      2 NOT IN (SELECT b FROM t7),
+      6 NOT IN (SELECT b FROM t7),
+      2 NOT IN (SELECT c FROM t7),
+      6 NOT IN (SELECT c FROM t7)
+  } 
+} {0 null 0 1 0 1}
+
+do_test in-13.14 {
+  execsql {
+    BEGIN TRANSACTION;
+    CREATE TABLE a(id INTEGER);
+    INSERT INTO a VALUES(1);
+    INSERT INTO a VALUES(2);
+    INSERT INTO a VALUES(3);
+    CREATE TABLE b(id INTEGER);
+    INSERT INTO b VALUES(NULL);
+    INSERT INTO b VALUES(3);
+    INSERT INTO b VALUES(4);
+    INSERT INTO b VALUES(5);
+    COMMIT;
+    SELECT * FROM a WHERE id NOT IN (SELECT id FROM b);
+  }
+} {}
+do_test in-13.14 {
+  execsql {
+    CREATE INDEX i5 ON b(id);
+    SELECT * FROM a WHERE id NOT IN (SELECT id FROM b);
+  }
+} {}
+
+
+do_test in-13.X {
+  db nullvalue ""
+} {}
+
 finish_test
index 0f2346d03230dc0246ab0624668aad1a2826a052..7757930b498768eaab9f3105fc36d18b3628c51b 100644 (file)
@@ -12,7 +12,7 @@
 # Test that it is possible to have two open blob handles on a single
 # blob object.
 #
-# $Id: incrblob2.test,v 1.6 2008/06/25 14:31:53 drh Exp $
+# $Id: incrblob2.test,v 1.7 2008/06/26 18:04:03 danielk1977 Exp $
 #
 
 set testdir [file dirname $argv0]
@@ -353,4 +353,11 @@ do_test incrblob2-7.5 {
   close $h
 } {}
 
+#do_test incrblob2-8.5 {
+  #execsql BEGIN
+  #db eval {SELECT * FROM t2} {
+  #execsql "DROP TABLE t2"
+  #}
+#} {}
+
 finish_test