github.com/jdgcs/sqlite3@v1.12.1-0.20210908114423-bc5f96e4dd51/testdata/tcl/bestindex1.test (about)

     1  # 2016-03-01
     2  #
     3  # The author disclaims copyright to this source code.  In place of
     4  # a legal notice, here is a blessing:
     5  #
     6  #    May you do good and not evil.
     7  #    May you find forgiveness for yourself and forgive others.
     8  #    May you share freely, never taking more than you give.
     9  #
    10  #***********************************************************************
    11  # 
    12  #
    13  
    14  set testdir [file dirname $argv0]
    15  source $testdir/tester.tcl
    16  set testprefix bestindex1
    17  
    18  ifcapable !vtab {
    19    finish_test
    20    return
    21  }
    22  
    23  register_tcl_module db
    24  
    25  proc vtab_command {method args} {
    26    switch -- $method {
    27      xConnect {
    28        return "CREATE TABLE t1(a, b, c)"
    29      }
    30  
    31      xBestIndex {
    32        set clist [lindex $args 0]
    33        if {[llength $clist]!=1} { error "unexpected constraint list" }
    34        catch { array unset C }
    35        array set C [lindex $clist 0]
    36        if {$C(usable)} {
    37          return "omit 0 cost 0 rows 1 idxnum 555 idxstr eq!"
    38        } else {
    39          return "cost 1000000 rows 0 idxnum 0 idxstr scan..."
    40        }
    41      }
    42  
    43    }
    44  
    45    return {}
    46  }
    47  
    48  do_execsql_test 1.0 {
    49    CREATE VIRTUAL TABLE x1 USING tcl(vtab_command);
    50  } {}
    51  
    52  do_eqp_test 1.1 {
    53    SELECT * FROM x1 WHERE a = 'abc'
    54  } {SCAN x1 VIRTUAL TABLE INDEX 555:eq!}
    55  
    56  do_eqp_test 1.2 {
    57    SELECT * FROM x1 WHERE a IN ('abc', 'def');
    58  } {SCAN x1 VIRTUAL TABLE INDEX 555:eq!}
    59  
    60  #-------------------------------------------------------------------------
    61  #
    62  reset_db
    63  register_tcl_module db
    64  
    65  # Parameter $mode may be one of:
    66  #
    67  #   "omit" - Implement filtering. Set the omit flag.
    68  #   "use"  - Implement filtering. Use the constraint, but do not set omit.
    69  #   "use2" - Do not implement filtering. Use the constraint anyway.
    70  #
    71  #   
    72  proc t1_vtab {mode method args} {
    73    switch -- $method {
    74      xConnect {
    75        return "CREATE TABLE t1(a, b)"
    76      }
    77  
    78      xBestIndex {
    79        set SQL_FILTER {SELECT * FROM t1x WHERE a='%1%'}
    80        set SQL_SCAN   {SELECT * FROM t1x}
    81  
    82        set clist [lindex $args 0]
    83        set idx 0
    84        for {set idx 0} {$idx < [llength $clist]} {incr idx} {
    85          array unset C
    86          array set C [lindex $clist $idx]
    87          if {$C(column)==0 && $C(op)=="eq" && $C(usable)} {
    88            switch -- $mode {
    89              "omit" {
    90                return [list omit $idx rows 10 cost 10 idxstr $SQL_FILTER]
    91              }
    92              "use" {
    93                return [list use $idx rows 10 cost 10 idxstr $SQL_FILTER]
    94              }
    95              "use2" {
    96                return [list use $idx rows 10 cost 10 idxstr $SQL_SCAN]
    97              }
    98              default {
    99                error "Bad mode - $mode"
   100              }
   101            }
   102          }
   103        }
   104  
   105        return [list idxstr {SELECT * FROM t1x}]
   106      }
   107  
   108      xFilter {
   109        set map [list %1% [lindex $args 2 0]]
   110        set sql [string map $map [lindex $args 1]]
   111        return [list sql $sql]
   112      }
   113    }
   114  
   115    return {}
   116  }
   117  
   118  do_execsql_test 2.1 {
   119    CREATE TABLE t1x(i INTEGER PRIMARY KEY, a, b);
   120    INSERT INTO t1x VALUES(1, 'one', 1);
   121    INSERT INTO t1x VALUES(2, 'two', 2);
   122    INSERT INTO t1x VALUES(3, 'three', 3);
   123    INSERT INTO t1x VALUES(4, 'four', 4);
   124  }
   125  
   126  foreach {tn mode} {
   127    1 use 2 omit 3 use2
   128  } {
   129    do_execsql_test 2.2.$mode.1 "
   130      DROP TABLE IF EXISTS t1;
   131      CREATE VIRTUAL TABLE t1 USING tcl(t1_vtab $mode);
   132    "
   133  
   134    do_execsql_test 2.2.$mode.2 {SELECT * FROM t1} {one 1 two 2 three 3 four 4}
   135    do_execsql_test 2.2.$mode.3 {SELECT rowid FROM t1} {1 2 3 4}
   136    do_execsql_test 2.2.$mode.4 {SELECT rowid FROM t1 WHERE a='two'} {2} 
   137  
   138    do_execsql_test 2.2.$mode.5 {
   139      SELECT rowid FROM t1 WHERE a IN ('one', 'four') ORDER BY +rowid
   140    } {1 4} 
   141  
   142    set plan(use) {
   143      QUERY PLAN
   144      |--SCAN t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x WHERE a='%1%'
   145      `--USE TEMP B-TREE FOR ORDER BY
   146    }
   147    set plan(omit) {
   148      QUERY PLAN
   149      |--SCAN t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x WHERE a='%1%'
   150      `--USE TEMP B-TREE FOR ORDER BY
   151    }
   152    set plan(use2) {
   153      QUERY PLAN
   154      |--SCAN t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x
   155      `--USE TEMP B-TREE FOR ORDER BY
   156    }
   157  
   158    do_eqp_test 2.2.$mode.6 { 
   159      SELECT rowid FROM t1 WHERE a IN ('one', 'four') ORDER BY +rowid
   160    } [string map {"\n  " "\n"} $plan($mode)]
   161  }
   162  
   163  # 2016-04-09.
   164  # Demonstrate a register overwrite problem when using two virtual
   165  # tables where the outer loop uses the IN operator.
   166  #
   167  set G(collist) [list PrimaryKey flagA columnA]
   168  set G(cols) [join $G(collist) ,]
   169  set G(nulls) "NULL"
   170  
   171  proc vtab_command {method args} {
   172    global G
   173  
   174    switch -- $method {
   175      xConnect {
   176        return "CREATE TABLE t1($G(cols))"
   177      }
   178  
   179      xBestIndex {
   180        set clist [lindex $args 0]
   181        #puts $clist
   182        set W [list]
   183        set U [list]
   184  
   185        set i 0
   186        for {set idx 0} {$idx < [llength $clist]} {incr idx} {
   187          array set c [lindex $clist $idx]
   188          if {$c(op)=="eq" && $c(usable)} {
   189            lappend W "[lindex $G(collist) $c(column)] = %$i%"
   190            lappend U use $idx
   191            incr i
   192          }
   193        }
   194  
   195        if {$W==""} {
   196          set sql "SELECT rowid, * FROM t1"
   197        } else {
   198          set sql "SELECT rowid, * FROM t1 WHERE [join $W { AND }]"
   199        }
   200  
   201        return [concat [list idxstr $sql] $U]
   202      }
   203  
   204      xFilter {
   205        foreach {idxnum idxstr vals} $args {}
   206  
   207        set map [list]
   208        for {set i 0} {$i < [llength $vals]} {incr i} {
   209          lappend map "%$i%" 
   210          set v [lindex $vals $i]
   211          if {[string is integer $v]} { 
   212            lappend map $v 
   213          } else {
   214            lappend map "'$v'"
   215          }
   216        }
   217        set sql [string map $map $idxstr]
   218  
   219        #puts "SQL: $sql"
   220        return [list sql $sql]
   221      }
   222    }
   223  
   224    return {}
   225  }
   226  
   227  db close
   228  forcedelete test.db
   229  sqlite3 db test.db
   230  register_tcl_module db
   231  
   232  do_execsql_test 3.1 "
   233    CREATE TABLE t1($G(cols));
   234    INSERT INTO t1 VALUES(1, 0, 'ValueA');
   235    INSERT INTO t1 VALUES(2, 0, 'ValueA');
   236    INSERT INTO t1 VALUES(3, 0, 'ValueB');
   237    INSERT INTO t1 VALUES(4, 0, 'ValueB');
   238  "
   239  
   240  do_execsql_test 3.2 {
   241    CREATE VIRTUAL TABLE VirtualTableA USING tcl(vtab_command);
   242    CREATE VIRTUAL TABLE VirtualTableB USING tcl(vtab_command);
   243  }
   244  
   245  do_execsql_test 3.3 { SELECT primarykey FROM VirtualTableA } {1 2 3 4}
   246  
   247  do_execsql_test 3.4 {
   248    SELECT * FROM 
   249    VirtualTableA a CROSS JOIN VirtualTableB b ON b.PrimaryKey=a.PrimaryKey
   250    WHERE a.ColumnA IN ('ValueA', 'ValueB') AND a.FlagA=0
   251  } {
   252    1 0 ValueA 1 0 ValueA
   253    2 0 ValueA 2 0 ValueA
   254    3 0 ValueB 3 0 ValueB
   255    4 0 ValueB 4 0 ValueB
   256  }
   257  
   258  do_execsql_test 3.5 {
   259    SELECT * FROM 
   260    VirtualTableA a CROSS JOIN VirtualTableB b ON b.PrimaryKey=a.PrimaryKey
   261    WHERE a.FlagA=0 AND a.ColumnA IN ('ValueA', 'ValueB') 
   262  } {
   263    1 0 ValueA 1 0 ValueA
   264    2 0 ValueA 2 0 ValueA
   265    3 0 ValueB 3 0 ValueB
   266    4 0 ValueB 4 0 ValueB
   267  }
   268  
   269  #-------------------------------------------------------------------------
   270  # If there is an IN(..) condition in the WHERE clause of a query on a
   271  # virtual table, the xBestIndex method is first invoked with the IN(...)
   272  # represented by a "usable" SQLITE_INDEX_CONSTRAINT_EQ constraint. If
   273  # the virtual table elects to use the IN(...) constraint, then the 
   274  # xBestIndex method is invoked again, this time with the IN(...) marked
   275  # as "not usable". Depending on the relative costs of the two plans as
   276  # defined by the virtual table implementation, and the cardinality of the
   277  # IN(...) operator, SQLite chooses the most efficient plan. 
   278  #
   279  # At one point the second invocation of xBestIndex() was only being made
   280  # for join queries. The following tests check that this problem has been
   281  # fixed.
   282  #
   283  proc vtab_command {method args} {
   284    switch -- $method {
   285      xConnect {
   286        return "CREATE TABLE t1(a, b, c, d)"
   287      }
   288  
   289      xBestIndex {
   290        set clist [lindex $args 0]
   291        lappend ::bestindex_calls $clist
   292        set ret "cost 1000000 idxnum 555"
   293        for {set i 0} {$i < [llength $clist]} {incr i} {
   294          array set C [lindex $clist $i]
   295          if {$C(usable)} { lappend ret use $i }
   296        }
   297        return $ret
   298      }
   299    }
   300    return {}
   301  }
   302  
   303  do_execsql_test 4.0 {
   304    CREATE VIRTUAL TABLE x1 USING tcl(vtab_command);
   305  } {}
   306  
   307  do_test 4.1 {
   308    set ::bestindex_calls [list]
   309    execsql {
   310      SELECT * FROM x1 WHERE a=? AND b BETWEEN ? AND ? AND c IN (1, 2, 3, 4);
   311    }
   312    set ::bestindex_calls
   313  } [list \
   314      [list {op eq column 0 usable 1} \
   315            {op eq column 2 usable 1} \
   316            {op ge column 1 usable 1} \
   317            {op le column 1 usable 1} \
   318      ] \
   319      [list {op eq column 0 usable 1} \
   320            {op eq column 2 usable 0} \
   321            {op ge column 1 usable 1} \
   322            {op le column 1 usable 1}
   323      ]
   324  ]
   325  
   326  
   327  finish_test