1# 2020-01-29 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 13set testdir [file dirname $argv0] 14source $testdir/tester.tcl 15set testprefix bestindex8 16 17ifcapable !vtab { 18 finish_test 19 return 20} 21 22register_tcl_module db 23 24proc vtab_command {src method args} { 25 switch -- $method { 26 xConnect { 27 return "CREATE TABLE xxx(a, b)" 28 } 29 30 xBestIndex { 31 set hdl [lindex $args 0] 32 set clist [$hdl constraints] 33 set orderby [$hdl orderby] 34 lappend ::lBestIndexDistinct [$hdl distinct] 35 36 #puts "ORDERBY: $orderby" 37 set iCons 0 38 set ret [list] 39 foreach cons $clist { 40 catch { array unset C } 41 array set C $cons 42 if {$C(usable)} { 43 lappend ret use $iCons 44 } 45 incr iCons 46 } 47 if {$orderby=="{column 0 desc 0} {column 1 desc 0}" 48 || $orderby=="{column 0 desc 0}" 49 } { 50 lappend ret orderby 1 51 lappend ret idxnum 1 52 #puts "ORDER-BY-CONSUMED" 53 } 54 return $ret 55 } 56 57 xFilter { 58 set idxnum [lindex $args 0] 59 if {$idxnum} { 60 return [list sql "SELECT rowid, a, b FROM $src order by 2, 3"] 61 } 62 return [list sql "SELECT rowid, a, b FROM $src"] 63 } 64 65 } 66 67 return {} 68} 69 70do_execsql_test 1.0 { 71 CREATE TABLE t1(a, b); 72 CREATE INDEX i1 ON t1(a, b); 73 INSERT INTO t1 VALUES('a', 'b'), ('c', 'd'); 74 INSERT INTO t1 VALUES('a', 'b'), ('c', 'd'); 75 CREATE VIRTUAL TABLE vt1 USING tcl(vtab_command t1); 76 77 CREATE TABLE t0(c0); 78 INSERT INTO t0(c0) VALUES (1), (0); 79} 80 81foreach {tn sql bDistinct idxinsert res} { 82 1 "SELECT a, b FROM vt1" 0 0 {a b c d a b c d} 83 2 "SELECT DISTINCT a, b FROM vt1" 2 1 {a b c d} 84 3 "SELECT DISTINCT a FROM vt1" 2 1 {a c} 85 4 "SELECT DISTINCT b FROM vt1" 2 1 {b d} 86 5 "SELECT DISTINCT b FROM vt1 ORDER BY a" 0 1 {b d} 87 6 "SELECT DISTINCT t0.c0 FROM vt1, t0 ORDER BY vt1.a" 0 1 {1 0} 88 7 "SELECT DISTINCT a, b FROM vt1 ORDER BY a, b" 1 0 {a b c d} 89 8 "SELECT DISTINCT a, b FROM vt1 ORDER BY a" 0 1 {a b c d} 90 9 "SELECT DISTINCT a FROM vt1 ORDER BY a, b" 0 1 {a c} 91 92 10 "SELECT DISTINCT a, b FROM vt1 WHERE b='b'" 2 1 {a b} 93 11 "SELECT DISTINCT a, b FROM vt1 WHERE +b='b'" 2 1 {a b} 94} { 95 set ::lBestIndexDistinct "" 96if {$tn==10} breakpoint 97 do_execsql_test 1.$tn.1 $sql $res 98 do_test 1.$tn.2 { 99 set ::lBestIndexDistinct 100 } $bDistinct 101 do_test 1.$tn.3 { 102 expr {[lsearch [execsql "explain $sql"] IdxInsert]>=0} 103 } $idxinsert 104} 105 106#------------------------------------------------------------------------- 107reset_db 108register_tcl_module db 109 110proc vtab_command {src method args} { 111 switch -- $method { 112 xConnect { 113 return "CREATE TABLE xxx(a, b)" 114 } 115 116 xBestIndex { 117 set hdl [lindex $args 0] 118 set ret [list] 119 120 set iCons 0 121 foreach cons [$hdl constraints] { 122 array set C $cons 123 if {($C(op)=="limit" || $C(op)=="offset") && $C(usable)} { 124 lappend ret use $iCons 125 } 126 incr iCons 127 } 128 129 return $ret 130 } 131 132 xFilter { 133 lappend ::lFilterArgs [lindex $args 2] 134 return [list sql "SELECT rowid, a, b FROM $src"] 135 } 136 137 } 138 139 return {} 140} 141 142do_execsql_test 2.0 { 143 CREATE TABLE t1(a, b); 144 CREATE INDEX i1 ON t1(a, b); 145 CREATE VIRTUAL TABLE vt1 USING tcl(vtab_command t1); 146} 147 148do_test 2.1 { 149 set ::lFilterArgs [list] 150 execsql { SELECT * FROM vt1 LIMIT 10 } 151 set ::lFilterArgs 152} {10} 153 154do_test 2.2 { 155 set ::lFilterArgs [list] 156 execsql { SELECT * FROM vt1 LIMIT 5 OFFSET 50 } 157 set ::lFilterArgs 158} {{5 50}} 159 160do_test 2.3 { 161 set ::lFilterArgs [list] 162 execsql { SELECT * FROM vt1 ORDER BY a, b LIMIT 1 OFFSET 1 } 163 set ::lFilterArgs 164} {{1 1}} 165 166do_test 2.4 { 167 set ::lFilterArgs [list] 168 execsql { SELECT * FROM vt1 ORDER BY a, +b LIMIT 1 OFFSET 1 } 169 set ::lFilterArgs 170} {{}} 171 172#------------------------------------------------------------------------- 173reset_db 174register_tcl_module db 175 176proc vtab_command {src method args} { 177 switch -- $method { 178 xConnect { 179 return "CREATE TABLE xxx(a, b)" 180 } 181 182 xBestIndex { 183 set hdl [lindex $args 0] 184 set lCons [$hdl constraints] 185 186 set ret [list] 187 for {set i 0} {$i < [llength $lCons]} {incr i} { 188 array set C [lindex $lCons $i] 189 if {$C(usable)} { 190 lappend ret use $i 191 $hdl in $i 1 192 } 193 } 194 return $ret 195 } 196 197 xFilter { 198 set lArg [lindex $args 2] 199 lappend ::lFilterArg {*}$lArg 200 return [list sql "SELECT rowid, a, b FROM $src"] 201 } 202 203 } 204 205 return {} 206} 207 208do_execsql_test 3.0 { 209 CREATE TABLE t1(a, b); 210 CREATE INDEX i1 ON t1(a, b); 211 CREATE VIRTUAL TABLE vt1 USING tcl(vtab_command t1); 212} 213 214foreach {tn sql lfa} { 215 1 "SELECT * FROM vt1 WHERE b IN (10, 20, 30)" {{10 20 30}} 216 2 "SELECT * FROM vt1 WHERE b IN ('abc', 'def')" {{abc def}} 217 3 "SELECT * FROM vt1 WHERE a IS NULL AND b IN ('abc', 'def')" {{} {abc def}} 218 4 "SELECT * FROM vt1 WHERE a IN (1,2,3) AND b IN ('abc', 'def')" 219 {{1 2 3} {abc def}} 220 221 5 "SELECT * FROM vt1 222 WHERE a IN (SELECT 1 UNION SELECT 2) AND b IN ('abc', 'def')" 223 {{1 2} {abc def}} 224 225 6 "SELECT * FROM vt1 226 WHERE b IN ('abc', 'def') AND a IN (SELECT 1 UNION SELECT 2)" 227 {{abc def} {1 2}} 228} { 229 do_test 3.$tn { 230 set ::lFilterArg [list] 231 execsql $sql 232 set ::lFilterArg 233 } $lfa 234} 235 236#explain_i { SELECT * FROM vt1 WHERE b IN (10, 20, 30) } 237 238#------------------------------------------------------------------------- 239reset_db 240register_tcl_module db 241 242proc vtab_command {src method args} { 243 switch -- $method { 244 xConnect { 245 return "CREATE TABLE xxx(a, b, c)" 246 } 247 248 xBestIndex { 249 set hdl [lindex $args 0] 250 set lCons [$hdl constraints] 251 252 set ret [list] 253 for {set i 0} {$i < [llength $lCons]} {incr i} { 254 lappend ::lBestIndexRhs [$hdl rhs_value $i -] 255 } 256 return $ret 257 } 258 259 xFilter { 260 return [list sql "SELECT rowid, a, b, c FROM $src"] 261 } 262 263 } 264 265 return {} 266} 267 268do_execsql_test 4.0 { 269 CREATE TABLE t1(a, b, c); 270 CREATE VIRTUAL TABLE vt1 USING tcl(vtab_command t1); 271} 272 273foreach {tn sql lbir} { 274 1 "SELECT * FROM vt1 WHERE b = 10" {10} 275 2 "SELECT * FROM vt1 WHERE a = 'abc' AND b < 30" {abc 30} 276 3 "SELECT * FROM vt1 WHERE a = 'abc' AND b < 30+2" {abc -} 277 4 "SELECT * FROM vt1 WHERE a IN (1,2,3) AND b < 30+2" {- -} 278 5 "SELECT * FROM vt1 WHERE a IS 111 AND b < 30+2" {111 -} 279} { 280 do_test 4.$tn { 281 set ::lBestIndexRhs [list] 282 execsql $sql 283 set ::lBestIndexRhs 284 } $lbir 285} 286 287#------------------------------------------------------------------------- 288reset_db 289db cache size 0 290register_tcl_module db 291 292set ::vtab_handle_in 1 293proc vtab_command {src method args} { 294 switch -- $method { 295 xConnect { 296 return "CREATE TABLE xxx(a, b, c)" 297 } 298 299 xBestIndex { 300 set lCols [list a b c] 301 302 set hdl [lindex $args 0] 303 set lCons [$hdl constraints] 304 set lOrder [$hdl order] 305 306 set L "" 307 set O "" 308 set W [list] 309 set a 0 310 for {set i 0} {$i < [llength $lCons]} {incr i} { 311 array set C [lindex $lCons $i] 312 if {$C(usable)} { 313 if { $C(op)=="eq" } { 314 set bIn 0 315 if {$::vtab_handle_in} { set bIn [$hdl in $i 1] } 316 if {$bIn} { 317 lappend W "[lindex $lCols $C(column)] IN (%I$a%)" 318 } else { 319 lappend W "[lindex $lCols $C(column)] = %$a%" 320 } 321 lappend ret omit $i 322 } 323 if { $C(op)=="limit" } { set L " LIMIT %$a%" ; lappend ret use $i } 324 if { $C(op)=="offset" } { set O " OFFSET %$a%" ; lappend ret use $i } 325 incr a 326 } 327 } 328 329 set order "" 330 set selectlist "rowid, a, b, c" 331 if {[llength $lOrder]} { 332 array set sl [list] 333 set lO [list] 334 foreach s $lOrder { 335 array set C $s 336 set ad "" 337 if {$C(desc)} { set ad " DESC" } 338 lappend lO "[lindex $lCols $C(column)]$ad" 339 set sl($C(column)) 1 340 } 341 if {[$hdl distinct]==2} { 342 set selectlist "DISTINCT 0" 343 foreach i {0 1 2} { 344 if {[info exists sl($i)]} { 345 append selectlist ", [lindex $lCols $i]" 346 } else { 347 append selectlist ", 0" 348 } 349 } 350 } else { 351 set order " ORDER BY [join $lO ,]" 352 } 353 } 354 355 set where "" 356 if {[llength $W]} { set where " WHERE [join $W { AND }]" } 357 set sql "SELECT $selectlist FROM $src$where$order$L$O" 358 359 lappend ret idxStr $sql 360 return $ret 361 } 362 363 xFilter { 364 foreach {idxnum idxstr lArg} $args {} 365 set ii 0 366 set sql $idxstr 367 foreach a $lArg { 368 set sql [string map [list %$ii% $a] $sql] 369 set sql [string map [list %I$ii% [join $a ,]] $sql] 370 incr ii 371 } 372 lappend ::lFilterSql $sql 373 374 if {[regexp {OFFSET (.*)$} $sql -> off]} { 375 set real_sql " 376 WITH c(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM c WHERE i<$off ) 377 SELECT 0,0,0,0 FROM c 378 UNION ALL SELECT * FROM ( 379 $sql 380 ) 381 " 382 } else { 383 set real_sql $sql 384 } 385 386 return [list sql $real_sql] 387 } 388 389 } 390 391 return {} 392} 393 394do_execsql_test 5.0 { 395 CREATE TABLE t1(a, b, c); 396 CREATE VIRTUAL TABLE vt1 USING tcl(vtab_command t1); 397 INSERT INTO t1 VALUES(1, 2, 3); 398 INSERT INTO t1 VALUES(2, 3, 4); 399 INSERT INTO t1 VALUES(3, 4, 5); 400 INSERT INTO t1 VALUES(1, 5, 6); 401 INSERT INTO t1 VALUES(2, 6, 7); 402 INSERT INTO t1 VALUES(3, 7, 8); 403 INSERT INTO t1 VALUES(1, 8, 9); 404 INSERT INTO t1 VALUES(2, 9, 0); 405} 406 407proc do_vtab_test {tn sql vtsql {res {}}} { 408 set ::lFilterSql [list] 409 uplevel [list do_execsql_test $tn.1 $sql $res] 410 uplevel [list do_test $tn.2 {set ::lFilterSql} [list {*}$vtsql]] 411} 412 413do_vtab_test 5.1.1 { 414 SELECT DISTINCT a FROM vt1 415} { 416 {SELECT DISTINCT 0, a, 0, 0 FROM t1} 417} {1 2 3} 418 419do_vtab_test 5.1.2 { 420 SELECT DISTINCT a FROM vt1 ORDER BY a 421} { 422 {SELECT rowid, a, b, c FROM t1 ORDER BY a} 423} {1 2 3} 424 425do_vtab_test 5.1.3 { 426 SELECT DISTINCT a FROM vt1 WHERE c IN (4,5,6,7,8) 427} { 428 {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c IN (4,5,6,7,8)} 429} {2 3 1} 430 431set ::vtab_handle_in 0 432do_vtab_test 5.1.4 { 433 SELECT DISTINCT a FROM vt1 WHERE c IN (4,5,6,7,8) 434} { 435 {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c = 4} 436 {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c = 5} 437 {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c = 6} 438 {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c = 7} 439 {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c = 8} 440} {2 3 1} 441 442set ::vtab_handle_in 1 443do_vtab_test 5.1.5a { 444 SELECT a, b, c FROM vt1 WHERE c IN (4,5,6,7,8) LIMIT 2 OFFSET 2 445} { 446 {SELECT rowid, a, b, c FROM t1 WHERE c IN (4,5,6,7,8) LIMIT 2 OFFSET 2} 447} {1 5 6 2 6 7} 448 449set ::vtab_handle_in 0 450do_vtab_test 5.1.5b { 451 SELECT a, b, c FROM vt1 WHERE c IN (4,5,6,7,8) LIMIT 2 OFFSET 2 452} { 453 {SELECT rowid, a, b, c FROM t1 WHERE c = 4} 454 {SELECT rowid, a, b, c FROM t1 WHERE c = 5} 455 {SELECT rowid, a, b, c FROM t1 WHERE c = 6} 456 {SELECT rowid, a, b, c FROM t1 WHERE c = 7} 457} {1 5 6 2 6 7} 458set ::vtab_handle_in 1 459 460finish_test 461