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