xref: /sqlite-3.40.0/test/bestindex1.test (revision bd24e8fa)
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
14set testdir [file dirname $argv0]
15source $testdir/tester.tcl
16set testprefix bestindex1
17
18ifcapable !vtab {
19  finish_test
20  return
21}
22
23register_tcl_module db
24
25proc 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
48do_execsql_test 1.0 {
49  CREATE VIRTUAL TABLE x1 USING tcl(vtab_command);
50} {}
51
52do_eqp_test 1.1 {
53  SELECT * FROM x1 WHERE a = 'abc'
54} {SCAN x1 VIRTUAL TABLE INDEX 555:eq!}
55
56do_eqp_test 1.2 {
57  SELECT * FROM x1 WHERE a IN ('abc', 'def');
58} {SCAN x1 VIRTUAL TABLE INDEX 555:eq!}
59
60#-------------------------------------------------------------------------
61#
62reset_db
63register_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#
72proc 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
118do_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
126foreach {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#
167set G(collist) [list PrimaryKey flagA columnA]
168set G(cols) [join $G(collist) ,]
169set G(nulls) "NULL"
170
171proc 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
227db close
228forcedelete test.db
229sqlite3 db test.db
230register_tcl_module db
231
232do_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
240do_execsql_test 3.2 {
241  CREATE VIRTUAL TABLE VirtualTableA USING tcl(vtab_command);
242  CREATE VIRTUAL TABLE VirtualTableB USING tcl(vtab_command);
243}
244
245do_execsql_test 3.3 { SELECT primarykey FROM VirtualTableA } {1 2 3 4}
246
247do_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
258do_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#
283proc 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
303do_execsql_test 4.0 {
304  CREATE VIRTUAL TABLE x1 USING tcl(vtab_command);
305} {}
306
307do_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
326do_catchsql_test 5.0 {
327  SELECT * FROM tcl('abc');
328} {1 {wrong number of arguments}}
329
330finish_test
331