/ Check-in [52e73eec]
Login

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

Overview
Comment:Allow whereShortCut() to use the PRIMARY KEY index of a WITHOUT ROWID table to optimize a vector of "IS" operators in a WHERE clause.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | ota-update
Files: files | file ages | folders
SHA1: 52e73eeca063bb30092ce600068bf487641399a0
User & Date: dan 2015-03-18 20:03:27
References
2015-05-14
09:53
Merge changes from the index-is-operator branch into this one. Drop the partial support added for IS on this branch by [52e73eec]. check-in: 16ab9caf user: dan tags: ota-update
2015-03-25
15:23
Extend [52e73eec] so that the IS optimization may be used on primary keys with more than 3 columns. check-in: 4e8796af user: dan tags: ota-update
Context
2015-03-23
17:10
Fix a broken assert() in the ota module. check-in: 858de8a5 user: dan tags: ota-update
2015-03-18
20:03
Allow whereShortCut() to use the PRIMARY KEY index of a WITHOUT ROWID table to optimize a vector of "IS" operators in a WHERE clause. check-in: 52e73eec user: dan tags: ota-update
19:04
Clarify that OTA is unable to update or delete rows with NULL values in primary key fields. check-in: 2e7c1e0a user: dan tags: ota-update
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/ota/ota1.test.

575
576
577
578
579
580
581


























582
583
584
585
586
587
588
589
  # Test that an OTA database containing no input tables is handled
  # correctly.
  reset_db
  forcedelete ota.db
  do_test $tn3.7 {
    list [catch { run_ota test.db ota.db } msg] $msg
  } {0 SQLITE_DONE}



























  catch { db close }
  eval $destroy_vfs
}


finish_test








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>








575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
  # Test that an OTA database containing no input tables is handled
  # correctly.
  reset_db
  forcedelete ota.db
  do_test $tn3.7 {
    list [catch { run_ota test.db ota.db } msg] $msg
  } {0 SQLITE_DONE}
  
  # Test that OTA can update indexes containing NULL values.
  #
  reset_db
  forcedelete ota.db
  do_execsql_test $tn3.8.1 {
    CREATE TABLE t1(a PRIMARY KEY, b, c);
    CREATE INDEX i1 ON t1(b, c);
    INSERT INTO t1 VALUES(1, 1, NULL);
    INSERT INTO t1 VALUES(2, NULL, 2);
    INSERT INTO t1 VALUES(3, NULL, NULL);

    ATTACH 'ota.db' AS ota;
    CREATE TABLE ota.data_t1(a, b, c, ota_control);
    INSERT INTO data_t1 VALUES(1, NULL, NULL, 1);
    INSERT INTO data_t1 VALUES(3, NULL, NULL, 1);
  } {}

  do_test $tn3.8.2 {
    list [catch { run_ota test.db ota.db } msg] $msg
  } {0 SQLITE_DONE}

  do_execsql_test $tn3.8.3 {
    SELECT * FROM t1
  } {2 {} 2}
  do_execsql_test $tn3.8.4 { PRAGMA integrity_check } {ok}

  catch { db close }
  eval $destroy_vfs
}


finish_test

Changes to src/where.c.

358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
...
411
412
413
414
415
416
417


418
419
420
421
422
423
424
....
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
....
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
....
6108
6109
6110
6111
6112
6113
6114

6115
6116
6117
6118
6119

6120
6121
6122
6123
6124
6125
6126
6127
6128
** "=", "<", ">", "<=", ">=", "IN", and "IS NULL"
*/
static int allowedOp(int op){
  assert( TK_GT>TK_EQ && TK_GT<TK_GE );
  assert( TK_LT>TK_EQ && TK_LT<TK_GE );
  assert( TK_LE>TK_EQ && TK_LE<TK_GE );
  assert( TK_GE==TK_EQ+4 );
  return op==TK_IN || (op>=TK_EQ && op<=TK_GE) || op==TK_ISNULL;
}

/*
** Commute a comparison operator.  Expressions of the form "X op Y"
** are converted into "Y op X".
**
** If left/right precedence rules come into play when determining the
................................................................................
static u16 operatorMask(int op){
  u16 c;
  assert( allowedOp(op) );
  if( op==TK_IN ){
    c = WO_IN;
  }else if( op==TK_ISNULL ){
    c = WO_ISNULL;


  }else{
    assert( (WO_EQ<<(op-TK_EQ)) < 0x7fff );
    c = (u16)(WO_EQ<<(op-TK_EQ));
  }
  assert( op!=TK_ISNULL || c==WO_ISNULL );
  assert( op!=TK_IN || c==WO_IN );
  assert( op!=TK_EQ || c==WO_EQ );
................................................................................
  int iTarget         /* Attempt to leave results in this register */
){
  Expr *pX = pTerm->pExpr;
  Vdbe *v = pParse->pVdbe;
  int iReg;                  /* Register holding results */

  assert( iTarget>0 );
  if( pX->op==TK_EQ ){
    iReg = sqlite3ExprCodeTarget(pParse, pX->pRight, iTarget);
  }else if( pX->op==TK_ISNULL ){
    iReg = iTarget;
    sqlite3VdbeAddOp2(v, OP_Null, 0, iReg);
#ifndef SQLITE_OMIT_SUBQUERY
  }else{
    int eType;
................................................................................
        sqlite3VdbeAddOp2(v, OP_SCopy, r1, regBase+j);
      }
    }
    testcase( pTerm->eOperator & WO_ISNULL );
    testcase( pTerm->eOperator & WO_IN );
    if( (pTerm->eOperator & (WO_ISNULL|WO_IN))==0 ){
      Expr *pRight = pTerm->pExpr->pRight;
      if( sqlite3ExprCanBeNull(pRight) ){
        sqlite3VdbeAddOp2(v, OP_IsNull, regBase+j, pLevel->addrBrk);
        VdbeCoverage(v);
      }
      if( zAff ){
        if( sqlite3CompareAffinity(pRight, zAff[j])==SQLITE_AFF_NONE ){
          zAff[j] = SQLITE_AFF_NONE;
        }
................................................................................
    pLoop->aLTerm[0] = pTerm;
    pLoop->nLTerm = 1;
    pLoop->u.btree.nEq = 1;
    /* TUNING: Cost of a rowid lookup is 10 */
    pLoop->rRun = 33;  /* 33==sqlite3LogEst(10) */
  }else{
    for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){

      assert( pLoop->aLTermSpace==pLoop->aLTerm );
      if( !IsUniqueIndex(pIdx)
       || pIdx->pPartIdxWhere!=0 
       || pIdx->nKeyCol>ArraySize(pLoop->aLTermSpace) 
      ) continue;

      for(j=0; j<pIdx->nKeyCol; j++){
        pTerm = findTerm(pWC, iCur, pIdx->aiColumn[j], 0, WO_EQ, pIdx);
        if( pTerm==0 ) break;
        pLoop->aLTerm[j] = pTerm;
      }
      if( j!=pIdx->nKeyCol ) continue;
      pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_ONEROW|WHERE_INDEXED;
      if( pIdx->isCovering || (pItem->colUsed & ~columnsInIndex(pIdx))==0 ){
        pLoop->wsFlags |= WHERE_IDX_ONLY;







|







 







>
>







 







|







 







|







 







>





>

|







358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
...
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
....
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
....
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
....
6110
6111
6112
6113
6114
6115
6116
6117
6118
6119
6120
6121
6122
6123
6124
6125
6126
6127
6128
6129
6130
6131
6132
** "=", "<", ">", "<=", ">=", "IN", and "IS NULL"
*/
static int allowedOp(int op){
  assert( TK_GT>TK_EQ && TK_GT<TK_GE );
  assert( TK_LT>TK_EQ && TK_LT<TK_GE );
  assert( TK_LE>TK_EQ && TK_LE<TK_GE );
  assert( TK_GE==TK_EQ+4 );
  return op==TK_IN || (op>=TK_EQ && op<=TK_GE) || op==TK_ISNULL || op==TK_IS;
}

/*
** Commute a comparison operator.  Expressions of the form "X op Y"
** are converted into "Y op X".
**
** If left/right precedence rules come into play when determining the
................................................................................
static u16 operatorMask(int op){
  u16 c;
  assert( allowedOp(op) );
  if( op==TK_IN ){
    c = WO_IN;
  }else if( op==TK_ISNULL ){
    c = WO_ISNULL;
  }else if( op==TK_IS ){
    c = WO_IS;
  }else{
    assert( (WO_EQ<<(op-TK_EQ)) < 0x7fff );
    c = (u16)(WO_EQ<<(op-TK_EQ));
  }
  assert( op!=TK_ISNULL || c==WO_ISNULL );
  assert( op!=TK_IN || c==WO_IN );
  assert( op!=TK_EQ || c==WO_EQ );
................................................................................
  int iTarget         /* Attempt to leave results in this register */
){
  Expr *pX = pTerm->pExpr;
  Vdbe *v = pParse->pVdbe;
  int iReg;                  /* Register holding results */

  assert( iTarget>0 );
  if( pX->op==TK_EQ || pX->op==TK_IS ){
    iReg = sqlite3ExprCodeTarget(pParse, pX->pRight, iTarget);
  }else if( pX->op==TK_ISNULL ){
    iReg = iTarget;
    sqlite3VdbeAddOp2(v, OP_Null, 0, iReg);
#ifndef SQLITE_OMIT_SUBQUERY
  }else{
    int eType;
................................................................................
        sqlite3VdbeAddOp2(v, OP_SCopy, r1, regBase+j);
      }
    }
    testcase( pTerm->eOperator & WO_ISNULL );
    testcase( pTerm->eOperator & WO_IN );
    if( (pTerm->eOperator & (WO_ISNULL|WO_IN))==0 ){
      Expr *pRight = pTerm->pExpr->pRight;
      if( sqlite3ExprCanBeNull(pRight) && pTerm->eOperator!=WO_IS ){
        sqlite3VdbeAddOp2(v, OP_IsNull, regBase+j, pLevel->addrBrk);
        VdbeCoverage(v);
      }
      if( zAff ){
        if( sqlite3CompareAffinity(pRight, zAff[j])==SQLITE_AFF_NONE ){
          zAff[j] = SQLITE_AFF_NONE;
        }
................................................................................
    pLoop->aLTerm[0] = pTerm;
    pLoop->nLTerm = 1;
    pLoop->u.btree.nEq = 1;
    /* TUNING: Cost of a rowid lookup is 10 */
    pLoop->rRun = 33;  /* 33==sqlite3LogEst(10) */
  }else{
    for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
      u32 mask = WO_EQ;
      assert( pLoop->aLTermSpace==pLoop->aLTerm );
      if( !IsUniqueIndex(pIdx)
       || pIdx->pPartIdxWhere!=0 
       || pIdx->nKeyCol>ArraySize(pLoop->aLTermSpace) 
      ) continue;
      if( HasRowid(pTab)==0 && IsPrimaryKeyIndex(pIdx) ) mask |= WO_IS;
      for(j=0; j<pIdx->nKeyCol; j++){
        pTerm = findTerm(pWC, iCur, pIdx->aiColumn[j], 0, mask, pIdx);
        if( pTerm==0 ) break;
        pLoop->aLTerm[j] = pTerm;
      }
      if( j!=pIdx->nKeyCol ) continue;
      pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_ONEROW|WHERE_INDEXED;
      if( pIdx->isCovering || (pItem->colUsed & ~columnsInIndex(pIdx))==0 ){
        pLoop->wsFlags |= WHERE_IDX_ONLY;

Changes to src/whereInt.h.

436
437
438
439
440
441
442

443
444
445
446
447
448
449
450
451
#define WO_GE     (WO_EQ<<(TK_GE-TK_EQ))
#define WO_MATCH  0x040
#define WO_ISNULL 0x080
#define WO_OR     0x100       /* Two or more OR-connected terms */
#define WO_AND    0x200       /* Two or more AND-connected terms */
#define WO_EQUIV  0x400       /* Of the form A==B, both columns */
#define WO_NOOP   0x800       /* This term does not restrict search space */


#define WO_ALL    0xfff       /* Mask of all possible WO_* values */
#define WO_SINGLE 0x0ff       /* Mask of all non-compound WO_* values */

/*
** These are definitions of bits in the WhereLoop.wsFlags field.
** The particular combination of bits in each WhereLoop help to
** determine the algorithm that WhereLoop represents.
*/







>

|







436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
#define WO_GE     (WO_EQ<<(TK_GE-TK_EQ))
#define WO_MATCH  0x040
#define WO_ISNULL 0x080
#define WO_OR     0x100       /* Two or more OR-connected terms */
#define WO_AND    0x200       /* Two or more AND-connected terms */
#define WO_EQUIV  0x400       /* Of the form A==B, both columns */
#define WO_NOOP   0x800       /* This term does not restrict search space */
#define WO_IS     0x1000      /* The IS operator */

#define WO_ALL    0x1fff      /* Mask of all possible WO_* values */
#define WO_SINGLE 0x0ff       /* Mask of all non-compound WO_* values */

/*
** These are definitions of bits in the WhereLoop.wsFlags field.
** The particular combination of bits in each WhereLoop help to
** determine the algorithm that WhereLoop represents.
*/

Added test/whereK.test.































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 2015-03-19
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl
set ::testprefix whereK

do_execsql_test 1.0 {
  CREATE TABLE t1(v, w TEXT, x, PRIMARY KEY(w, x)) WITHOUT ROWID;

  INSERT INTO t1 VALUES('a', 1, 1);
  INSERT INTO t1 VALUES('B', 2, 2);
  INSERT INTO t1 VALUES('C', 3, 3);
  INSERT INTO t1 VALUES('d', 4, 4);

  CREATE TABLE t2(v, w TEXT, x, PRIMARY KEY(w, x));

  CREATE TABLE t3(a, b, c);
  CREATE UNIQUE INDEX t3a ON t3(a);
}

do_eqp_test 1.1.1 {
  SELECT v FROM t1 WHERE w IS ? AND x IS ?
} {/SEARCH TABLE t1.*(w=. AND x=.)/}

do_eqp_test 1.1.2 {
  SELECT v FROM t2 WHERE w IS ? AND x IS ?
} {/SCAN TABLE t2/}

do_execsql_test 1.2.1 { SELECT v FROM t1 WHERE w IS 1   AND x IS 1 }   {a}
do_execsql_test 1.2.2 { SELECT v FROM t1 WHERE w IS '2' AND x IS 2 }   {B}
do_execsql_test 1.2.3 { SELECT v FROM t1 WHERE w IS 3   AND x IS '3' } {}
do_execsql_test 1.2.4 { SELECT v FROM t1 WHERE w IS '4' AND x IS '4' } {}
  

# IS constraints may only be used if all fields of the index are 
# constrained by either "IS" or "=".
#
do_eqp_test 1.3.1 {
    SELECT v FROM t1 WHERE w IS ?
} {/SCAN TABLE t1/}
do_eqp_test 1.3.2 {
    SELECT v FROM t1 WHERE w IS ? AND x > ?
} {/SCAN TABLE t1/}

do_execsql_test 1.4.1 { SELECT v FROM t1 WHERE w IS 3   AND x = 3 } {C}
do_execsql_test 1.4.2 { SELECT v FROM t1 WHERE w = '4' AND x IS 4 } {d}

do_eqp_test 1.5.1 { SELECT b FROM t3 WHERE a IS ? } {/SCAN TABLE/}
do_eqp_test 1.5.2 { SELECT b FROM t3 WHERE a = ? } {/SEARCH TABLE/}

finish_test