/ Check-in [eca35871]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Merge trunk fixes for "x IN (?)" handling.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | orderby-planning
Files: files | file ages | folders
SHA1: eca35871c34374ca9189c7c9b6d490ac3c30357f
User & Date: drh 2014-03-20 20:56:49
Context
2014-03-21
15:24
New test case for block-sorting. check-in: e70cfa28 user: drh tags: orderby-planning
2014-03-20
20:56
Merge trunk fixes for "x IN (?)" handling. check-in: eca35871 user: drh tags: orderby-planning
19:04
Remove a testcase() that is now always true due to the "x IN (?)" optimization. Add an ALWAYS() around a conditional in the parser that cannot be false. check-in: d5a1530b user: drh tags: trunk
2014-03-19
23:42
Merge the vdbesort.c optimization from trunk. check-in: e4bfffb9 user: drh tags: orderby-planning
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/expr.c.

    29     29   ** SELECT * FROM t1 WHERE a;
    30     30   ** SELECT a AS b FROM t1 WHERE b;
    31     31   ** SELECT * FROM t1 WHERE (select a from t1);
    32     32   */
    33     33   char sqlite3ExprAffinity(Expr *pExpr){
    34     34     int op;
    35     35     pExpr = sqlite3ExprSkipCollate(pExpr);
           36  +  if( pExpr->flags & EP_Generic ) return SQLITE_AFF_NONE;
    36     37     op = pExpr->op;
    37     38     if( op==TK_SELECT ){
    38     39       assert( pExpr->flags&EP_xIsSelect );
    39     40       return sqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr);
    40     41     }
    41     42   #ifndef SQLITE_OMIT_CAST
    42     43     if( op==TK_CAST ){
................................................................................
    61     62   ** Set the collating sequence for expression pExpr to be the collating
    62     63   ** sequence named by pToken.   Return a pointer to a new Expr node that
    63     64   ** implements the COLLATE operator.
    64     65   **
    65     66   ** If a memory allocation error occurs, that fact is recorded in pParse->db
    66     67   ** and the pExpr parameter is returned unchanged.
    67     68   */
    68         -Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr *pExpr, Token *pCollName){
           69  +Expr *sqlite3ExprAddCollateToken(
           70  +  Parse *pParse,           /* Parsing context */
           71  +  Expr *pExpr,             /* Add the "COLLATE" clause to this expression */
           72  +  const Token *pCollName   /* Name of collating sequence */
           73  +){
    69     74     if( pCollName->n>0 ){
    70     75       Expr *pNew = sqlite3ExprAlloc(pParse->db, TK_COLLATE, pCollName, 1);
    71     76       if( pNew ){
    72     77         pNew->pLeft = pExpr;
    73     78         pNew->flags |= EP_Collate|EP_Skip;
    74     79         pExpr = pNew;
    75     80       }
................................................................................
   114    119   */
   115    120   CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){
   116    121     sqlite3 *db = pParse->db;
   117    122     CollSeq *pColl = 0;
   118    123     Expr *p = pExpr;
   119    124     while( p ){
   120    125       int op = p->op;
          126  +    if( p->flags & EP_Generic ) break;
   121    127       if( op==TK_CAST || op==TK_UPLUS ){
   122    128         p = p->pLeft;
   123    129         continue;
   124    130       }
   125    131       if( op==TK_COLLATE || (op==TK_REGISTER && p->op2==TK_COLLATE) ){
   126    132         pColl = sqlite3GetCollSeq(pParse, ENC(db), 0, p->u.zToken);
   127    133         break;
................................................................................
  1624   1630       u32 savedNQueryLoop = pParse->nQueryLoop;
  1625   1631       int rMayHaveNull = 0;
  1626   1632       eType = IN_INDEX_EPH;
  1627   1633       if( prNotFound ){
  1628   1634         *prNotFound = rMayHaveNull = ++pParse->nMem;
  1629   1635         sqlite3VdbeAddOp2(v, OP_Null, 0, *prNotFound);
  1630   1636       }else{
  1631         -      testcase( pParse->nQueryLoop>0 );
  1632   1637         pParse->nQueryLoop = 0;
  1633   1638         if( pX->pLeft->iColumn<0 && !ExprHasProperty(pX, EP_xIsSelect) ){
  1634   1639           eType = IN_INDEX_ROWID;
  1635   1640         }
  1636   1641       }
  1637   1642       sqlite3CodeSubselect(pParse, pX, rMayHaveNull, eType==IN_INDEX_ROWID);
  1638   1643       pParse->nQueryLoop = savedNQueryLoop;

Changes to src/parse.y.

  1016   1016         **      expr1 NOT IN ()
  1017   1017         **
  1018   1018         ** simplify to constants 0 (false) and 1 (true), respectively,
  1019   1019         ** regardless of the value of expr1.
  1020   1020         */
  1021   1021         A.pExpr = sqlite3PExpr(pParse, TK_INTEGER, 0, 0, &sqlite3IntTokens[N]);
  1022   1022         sqlite3ExprDelete(pParse->db, X.pExpr);
         1023  +    }else if( Y->nExpr==1 ){
         1024  +      /* Expressions of the form:
         1025  +      **
         1026  +      **      expr1 IN (?1)
         1027  +      **      expr1 NOT IN (?2)
         1028  +      **
         1029  +      ** with exactly one value on the RHS can be simplified to something
         1030  +      ** like this:
         1031  +      **
         1032  +      **      expr1 == ?1
         1033  +      **      expr1 <> ?2
         1034  +      **
         1035  +      ** But, the RHS of the == or <> is marked with the EP_Generic flag
         1036  +      ** so that it may not contribute to the computation of comparison
         1037  +      ** affinity or the collating sequence to use for comparison.  Otherwise,
         1038  +      ** the semantics would be subtly different from IN or NOT IN.
         1039  +      */
         1040  +      Expr *pRHS = Y->a[0].pExpr;
         1041  +      Y->a[0].pExpr = 0;
         1042  +      sqlite3ExprListDelete(pParse->db, Y);
         1043  +      /* pRHS cannot be NULL because a malloc error would have been detected
         1044  +      ** before now and control would have never reached this point */
         1045  +      if( ALWAYS(pRHS) ){
         1046  +        pRHS->flags &= ~EP_Collate;
         1047  +        pRHS->flags |= EP_Generic;
         1048  +      }
         1049  +      A.pExpr = sqlite3PExpr(pParse, N ? TK_NE : TK_EQ, X.pExpr, pRHS, 0);
  1023   1050       }else{
  1024   1051         A.pExpr = sqlite3PExpr(pParse, TK_IN, X.pExpr, 0, 0);
  1025   1052         if( A.pExpr ){
  1026   1053           A.pExpr->x.pList = Y;
  1027   1054           sqlite3ExprSetHeight(pParse, A.pExpr);
  1028   1055         }else{
  1029   1056           sqlite3ExprListDelete(pParse->db, Y);

Changes to src/shell.c.

  1191   1191   static void explain_data_prepare(struct callback_data *p, sqlite3_stmt *pSql){
  1192   1192     const char *zSql;               /* The text of the SQL statement */
  1193   1193     const char *z;                  /* Used to check if this is an EXPLAIN */
  1194   1194     int *abYield = 0;               /* True if op is an OP_Yield */
  1195   1195     int nAlloc = 0;                 /* Allocated size of p->aiIndent[], abYield */
  1196   1196     int iOp;                        /* Index of operation in p->aiIndent[] */
  1197   1197   
  1198         -  const char *azNext[] = { "Next", "Prev", "VPrev", "VNext", "SorterNext", 0 };
         1198  +  const char *azNext[] = { "Next", "Prev", "VPrev", "VNext", "SorterNext",
         1199  +                           "NextIfOpen", "PrevIfOpen", 0 };
  1199   1200     const char *azYield[] = { "Yield", "SeekLt", "SeekGt", "RowSetRead", "Rewind", 0 };
  1200   1201     const char *azGoto[] = { "Goto", 0 };
  1201   1202   
  1202   1203     /* Try to figure out if this is really an EXPLAIN statement. If this
  1203   1204     ** cannot be verified, return early.  */
  1204   1205     zSql = sqlite3_sql(pSql);
  1205   1206     if( zSql==0 ) return;

Changes to src/sqliteInt.h.

  1889   1889   #define EP_Agg       0x000002 /* Contains one or more aggregate functions */
  1890   1890   #define EP_Resolved  0x000004 /* IDs have been resolved to COLUMNs */
  1891   1891   #define EP_Error     0x000008 /* Expression contains one or more errors */
  1892   1892   #define EP_Distinct  0x000010 /* Aggregate function with DISTINCT keyword */
  1893   1893   #define EP_VarSelect 0x000020 /* pSelect is correlated, not constant */
  1894   1894   #define EP_DblQuoted 0x000040 /* token.z was originally in "..." */
  1895   1895   #define EP_InfixFunc 0x000080 /* True for an infix function: LIKE, GLOB, etc */
  1896         -#define EP_Collate   0x000100 /* Tree contains a TK_COLLATE opeartor */
  1897         -      /* unused      0x000200 */
         1896  +#define EP_Collate   0x000100 /* Tree contains a TK_COLLATE operator */
         1897  +#define EP_Generic   0x000200 /* Ignore COLLATE or affinity on this tree */
  1898   1898   #define EP_IntValue  0x000400 /* Integer value contained in u.iValue */
  1899   1899   #define EP_xIsSelect 0x000800 /* x.pSelect is valid (otherwise x.pList is) */
  1900   1900   #define EP_Skip      0x001000 /* COLLATE, AS, or UNLIKELY */
  1901   1901   #define EP_Reduced   0x002000 /* Expr struct EXPR_REDUCEDSIZE bytes only */
  1902   1902   #define EP_TokenOnly 0x004000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */
  1903   1903   #define EP_Static    0x008000 /* Held in memory not obtained from malloc() */
  1904   1904   #define EP_MemToken  0x010000 /* Need to sqlite3DbFree() Expr.zToken */
................................................................................
  3286   3286   #endif
  3287   3287   
  3288   3288   const char *sqlite3ErrStr(int);
  3289   3289   int sqlite3ReadSchema(Parse *pParse);
  3290   3290   CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char*,int);
  3291   3291   CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName);
  3292   3292   CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr);
  3293         -Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr*, Token*);
         3293  +Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr*, const Token*);
  3294   3294   Expr *sqlite3ExprAddCollateString(Parse*,Expr*,const char*);
  3295   3295   Expr *sqlite3ExprSkipCollate(Expr*);
  3296   3296   int sqlite3CheckCollSeq(Parse *, CollSeq *);
  3297   3297   int sqlite3CheckObjectName(Parse *, const char *);
  3298   3298   void sqlite3VdbeSetChanges(sqlite3 *, int);
  3299   3299   int sqlite3AddInt64(i64*,i64);
  3300   3300   int sqlite3SubInt64(i64*,i64);

Changes to src/vdbesort.c.

  1006   1006           **
  1007   1007           ** Alternatively, if pIter2 contains the smaller of the two values,
  1008   1008           ** set aTree[i] to its index and update pIter1. If vdbeSorterCompare()
  1009   1009           ** was actually called above, then pSorter->pUnpacked now contains
  1010   1010           ** a value equivalent to pIter2. So set pKey2 to NULL to prevent
  1011   1011           ** vdbeSorterCompare() from decoding pIter2 again.  */
  1012   1012           if( iRes<=0 ){
  1013         -          pSorter->aTree[i] = (pIter1 - pSorter->aIter);
         1013  +          pSorter->aTree[i] = (int)(pIter1 - pSorter->aIter);
  1014   1014             pIter2 = &pSorter->aIter[ pSorter->aTree[i ^ 0x0001] ];
  1015   1015             pKey2 = pIter2->aKey;
  1016   1016           }else{
  1017   1017             if( pIter1->pFile ) pKey2 = 0;
  1018         -          pSorter->aTree[i] = (pIter2 - pSorter->aIter);
         1018  +          pSorter->aTree[i] = (int)(pIter2 - pSorter->aIter);
  1019   1019             pIter1 = &pSorter->aIter[ pSorter->aTree[i ^ 0x0001] ];
  1020   1020           }
  1021   1021   
  1022   1022         }
  1023   1023         *pbEof = (pSorter->aIter[pSorter->aTree[1]].pFile==0);
  1024   1024       }
  1025   1025     }else{

Changes to src/where.c.

  3207   3207         pLevel->op = OP_Noop;
  3208   3208       }else if( bRev ){
  3209   3209         pLevel->op = OP_Prev;
  3210   3210       }else{
  3211   3211         pLevel->op = OP_Next;
  3212   3212       }
  3213   3213       pLevel->p1 = iIdxCur;
  3214         -    assert( (WHERE_UNQ_WANTED>>16)==1 );
  3215         -    pLevel->p3 = (pLoop->wsFlags>>16)&1;
         3214  +    pLevel->p3 = (pLoop->wsFlags&WHERE_UNQ_WANTED)!=0 ? 1:0;
  3216   3215       if( (pLoop->wsFlags & WHERE_CONSTRAINT)==0 ){
  3217   3216         pLevel->p5 = SQLITE_STMTSTATUS_FULLSCAN_STEP;
  3218   3217       }else{
  3219   3218         assert( pLevel->p5==0 );
  3220   3219       }
  3221   3220     }else
  3222   3221   
................................................................................
  4009   4008         if( ExprHasProperty(pExpr, EP_xIsSelect) ){
  4010   4009           /* "x IN (SELECT ...)":  TUNING: the SELECT returns 25 rows */
  4011   4010           nIn = 46;  assert( 46==sqlite3LogEst(25) );
  4012   4011         }else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){
  4013   4012           /* "x IN (value, value, ...)" */
  4014   4013           nIn = sqlite3LogEst(pExpr->x.pList->nExpr);
  4015   4014         }
         4015  +      assert( nIn>0 );  /* RHS always has 2 or more terms...  The parser
         4016  +                        ** changes "x IN (?)" into "x=?". */
  4016   4017         pNew->rRun += nIn;
  4017   4018         pNew->u.btree.nEq++;
  4018   4019         pNew->nOut = nRowEst + nInMul + nIn;
  4019   4020       }else if( pTerm->eOperator & (WO_EQ) ){
  4020   4021         assert(
  4021   4022           (pNew->wsFlags & (WHERE_COLUMN_NULL|WHERE_COLUMN_IN|WHERE_SKIPSCAN))!=0
  4022   4023           || nInMul==0

Changes to test/in4.test.

   154    154   } {}
   155    155   do_test in4-3.11 {
   156    156     execsql { SELECT * FROM t3 WHERE x IN (1, 2) OR y IN ()}
   157    157   } {1 1 1}
   158    158   do_test in4-3.12 {
   159    159     execsql { SELECT * FROM t3 WHERE x IN (1, 2) AND y IN ()}
   160    160   } {}
          161  +
          162  +# Tests for "... IN (?)" and "... NOT IN (?)".  In other words, tests
          163  +# for when the RHS of IN is a single expression.  This should work the
          164  +# same as the == and <> operators.
          165  +#
          166  +do_execsql_test in4-3.21 {
          167  +  SELECT * FROM t3 WHERE x=10 AND y IN (10);
          168  +} {10 10 10}
          169  +do_execsql_test in4-3.22 {
          170  +  SELECT * FROM t3 WHERE x IN (10) AND y=10;
          171  +} {10 10 10}
          172  +do_execsql_test in4-3.23 {
          173  +  SELECT * FROM t3 WHERE x IN (10) AND y IN (10);
          174  +} {10 10 10}
          175  +do_execsql_test in4-3.24 {
          176  +  SELECT * FROM t3 WHERE x=1 AND y NOT IN (10);
          177  +} {1 1 1}
          178  +do_execsql_test in4-3.25 {
          179  +  SELECT * FROM t3 WHERE x  NOT IN (10) AND y=1;
          180  +} {1 1 1}
          181  +do_execsql_test in4-3.26 {
          182  +  SELECT * FROM t3 WHERE x NOT IN (10) AND y NOT IN (10);
          183  +} {1 1 1}
          184  +
          185  +# The query planner recognizes that "x IN (?)" only generates a
          186  +# single match and can use this information to optimize-out ORDER BY
          187  +# clauses.
          188  +#
          189  +do_execsql_test in4-3.31 {
          190  +  DROP INDEX t3i1;
          191  +  CREATE UNIQUE INDEX t3xy ON t3(x,y);
          192  +
          193  +  SELECT *, '|' FROM t3 A, t3 B
          194  +   WHERE A.x=10 AND A.y IN (10)
          195  +     AND B.x=1 AND B.y IN (1);
          196  +} {10 10 10 1 1 1 |}
          197  +do_execsql_test in4-3.32 {
          198  +  EXPLAIN QUERY PLAN
          199  +  SELECT *, '|' FROM t3 A, t3 B
          200  +   WHERE A.x=10 AND A.y IN (10)
          201  +     AND B.x=1 AND B.y IN (1);
          202  +} {~/B-TREE/}  ;# No separate sorting pass
          203  +do_execsql_test in4-3.33 {
          204  +  SELECT *, '|' FROM t3 A, t3 B
          205  +   WHERE A.x IN (10) AND A.y=10
          206  +     AND B.x IN (1) AND B.y=1;
          207  +} {10 10 10 1 1 1 |}
          208  +do_execsql_test in4-3.34 {
          209  +  EXPLAIN QUERY PLAN
          210  +  SELECT *, '|' FROM t3 A, t3 B
          211  +   WHERE A.x IN (10) AND A.y=10
          212  +     AND B.x IN (1) AND B.y=1;
          213  +} {~/B-TREE/}  ;# No separate sorting pass
          214  +
          215  +# An expression of the form "x IN (?,?)" creates an ephemeral table to
          216  +# hold the list of values on the RHS.  But "x IN (?)" does not create
          217  +# an ephemeral table.
          218  +#
          219  +do_execsql_test in4-3.41 {
          220  +  SELECT * FROM t3 WHERE x IN (10,11);
          221  +} {10 10 10}
          222  +do_execsql_test in4-3.42 {
          223  +  EXPLAIN
          224  +  SELECT * FROM t3 WHERE x IN (10,11);
          225  +} {/OpenEphemeral/}
          226  +do_execsql_test in4-3.43 {
          227  +  SELECT * FROM t3 WHERE x IN (10);
          228  +} {10 10 10}
          229  +do_execsql_test in4-3.44 {
          230  +  EXPLAIN
          231  +  SELECT * FROM t3 WHERE x IN (10);
          232  +} {~/OpenEphemeral/}
          233  +do_execsql_test in4-3.45 {
          234  +  SELECT * FROM t3 WHERE x NOT IN (10,11);
          235  +} {1 1 1}
          236  +do_execsql_test in4-3.46 {
          237  +  EXPLAIN
          238  +  SELECT * FROM t3 WHERE x NOT IN (10,11);
          239  +} {/OpenEphemeral/}
          240  +do_execsql_test in4-3.47 {
          241  +  SELECT * FROM t3 WHERE x NOT IN (10);
          242  +} {1 1 1}
          243  +do_execsql_test in4-3.48 {
          244  +  EXPLAIN
          245  +  SELECT * FROM t3 WHERE x NOT IN (10);
          246  +} {~/OpenEphemeral/}
          247  +
          248  +# Make sure that when "x IN (?)" is converted into "x==?" that collating
          249  +# sequence and affinity computations do not get messed up.
          250  +#
          251  +do_execsql_test in4-4.1 {
          252  +  CREATE TABLE t4a(a TEXT, b TEXT COLLATE nocase, c);
          253  +  INSERT INTO t4a VALUES('ABC','abc',1);
          254  +  INSERT INTO t4a VALUES('def','xyz',2);
          255  +  INSERT INTO t4a VALUES('ghi','ghi',3);
          256  +  SELECT c FROM t4a WHERE a=b ORDER BY c;
          257  +} {3}
          258  +do_execsql_test in4-4.2 {
          259  +  SELECT c FROM t4a WHERE b=a ORDER BY c;
          260  +} {1 3}
          261  +do_execsql_test in4-4.3 {
          262  +  SELECT c FROM t4a WHERE (a||'')=b ORDER BY c;
          263  +} {1 3}
          264  +do_execsql_test in4-4.4 {
          265  +  SELECT c FROM t4a WHERE (a||'')=(b||'') ORDER BY c;
          266  +} {3}
          267  +do_execsql_test in4-4.5 {
          268  +  SELECT c FROM t4a WHERE a IN (b) ORDER BY c;
          269  +} {3}
          270  +do_execsql_test in4-4.6 {
          271  +  SELECT c FROM t4a WHERE (a||'') IN (b) ORDER BY c;
          272  +} {3}
          273  +
          274  +
          275  +do_execsql_test in4-4.11 {
          276  +  CREATE TABLE t4b(a TEXT, b NUMERIC, c);
          277  +  INSERT INTO t4b VALUES('1.0',1,4);
          278  +  SELECT c FROM t4b WHERE a=b;
          279  +} {4}
          280  +do_execsql_test in4-4.12 {
          281  +  SELECT c FROM t4b WHERE b=a;
          282  +} {4}
          283  +do_execsql_test in4-4.13 {
          284  +  SELECT c FROM t4b WHERE +a=b;
          285  +} {4}
          286  +do_execsql_test in4-4.14 {
          287  +  SELECT c FROM t4b WHERE a=+b;
          288  +} {}
          289  +do_execsql_test in4-4.15 {
          290  +  SELECT c FROM t4b WHERE +b=a;
          291  +} {}
          292  +do_execsql_test in4-4.16 {
          293  +  SELECT c FROM t4b WHERE b=+a;
          294  +} {4}
          295  +do_execsql_test in4-4.17 {
          296  +  SELECT c FROM t4b WHERE a IN (b);
          297  +} {}
          298  +do_execsql_test in4-4.18 {
          299  +  SELECT c FROM t4b WHERE b IN (a);
          300  +} {4}
          301  +do_execsql_test in4-4.19 {
          302  +  SELECT c FROM t4b WHERE +b IN (a);
          303  +} {}
          304  +
          305  +do_execsql_test in4-5.1 {
          306  +  CREATE TABLE t5(c INTEGER PRIMARY KEY, d TEXT COLLATE nocase);
          307  +  INSERT INTO t5 VALUES(17, 'fuzz');
          308  +  SELECT 1 FROM t5 WHERE 'fuzz' IN (d);  -- match
          309  +  SELECT 2 FROM t5 WHERE 'FUZZ' IN (d);  -- no match
          310  +  SELECT 3 FROM t5 WHERE d IN ('fuzz');  -- match
          311  +  SELECT 4 FROM t5 WHERE d IN ('FUZZ');  -- match
          312  +} {1 3 4}
          313  +
          314  +# An expression of the form "x IN (y)" can be used as "x=y" by the
          315  +# query planner when computing transitive constraints or to run the
          316  +# query using an index on y.
          317  +#
          318  +do_execsql_test in4-6.1 {
          319  +  CREATE TABLE t6a(a INTEGER PRIMARY KEY, b);
          320  +  INSERT INTO t6a VALUES(1,2),(3,4),(5,6);
          321  +  CREATE TABLE t6b(c INTEGER PRIMARY KEY, d);
          322  +  INSERT INTO t6b VALUES(4,44),(5,55),(6,66);
          323  +
          324  +  SELECT * FROM t6a, t6b WHERE a=3 AND b IN (c);
          325  +} {3 4 4 44}
          326  +do_execsql_test in4-6.1-eqp {
          327  +  EXPLAIN QUERY PLAN
          328  +  SELECT * FROM t6a, t6b WHERE a=3 AND b IN (c);
          329  +} {~/SCAN/}
          330  +do_execsql_test in4-6.2 {
          331  +  SELECT * FROM t6a, t6b WHERE a=3 AND c IN (b);
          332  +} {3 4 4 44}
          333  +do_execsql_test in4-6.2-eqp {
          334  +  EXPLAIN QUERY PLAN
          335  +  SELECT * FROM t6a, t6b WHERE a=3 AND c IN (b);
          336  +} {~/SCAN/}
          337  +
   161    338   
   162    339   finish_test