# 2016-03-01 # # 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 bestindex1 ifcapable !vtab { finish_test return } register_tcl_module db proc vtab_command {method args} { switch -- $method { xConnect { return "CREATE TABLE t1(a, b, c)" } xBestIndex { set hdl [lindex $args 0] set clist [$hdl constraints] set orderby [$hdl orderby] if {[llength $clist]!=1} { error "unexpected constraint list" } catch { array unset C } array set C [lindex $clist 0] if {$C(usable)} { return "omit 0 cost 0 rows 1 idxnum 555 idxstr eq!" } else { return "cost 1000000 rows 0 idxnum 0 idxstr scan..." } } } return {} } do_execsql_test 1.0 { CREATE VIRTUAL TABLE x1 USING tcl(vtab_command); } {} do_eqp_test 1.1 { SELECT * FROM x1 WHERE a = 'abc' } {SCAN x1 VIRTUAL TABLE INDEX 555:eq!} do_eqp_test 1.2 { SELECT * FROM x1 WHERE a IN ('abc', 'def'); } {SCAN x1 VIRTUAL TABLE INDEX 555:eq!} #------------------------------------------------------------------------- # reset_db register_tcl_module db # Parameter $mode may be one of: # # "omit" - Implement filtering. Set the omit flag. # "use" - Implement filtering. Use the constraint, but do not set omit. # "use2" - Do not implement filtering. Use the constraint anyway. # # proc t1_vtab {mode method args} { switch -- $method { xConnect { return "CREATE TABLE t1(a, b)" } xBestIndex { set hdl [lindex $args 0] set clist [$hdl constraints] set orderby [$hdl orderby] set SQL_FILTER {SELECT * FROM t1x WHERE a='%1%'} set SQL_SCAN {SELECT * FROM t1x} set idx 0 for {set idx 0} {$idx < [llength $clist]} {incr idx} { array unset C array set C [lindex $clist $idx] if {$C(column)==0 && $C(op)=="eq" && $C(usable)} { switch -- $mode { "omit" { return [list omit $idx rows 10 cost 10 idxstr $SQL_FILTER] } "use" { return [list use $idx rows 10 cost 10 idxstr $SQL_FILTER] } "use2" { return [list use $idx rows 10 cost 10 idxstr $SQL_SCAN] } default { error "Bad mode - $mode" } } } } return [list idxstr {SELECT * FROM t1x}] } xFilter { set map [list %1% [lindex $args 2 0]] set sql [string map $map [lindex $args 1]] return [list sql $sql] } } return {} } do_execsql_test 2.1 { CREATE TABLE t1x(i INTEGER PRIMARY KEY, a, b); INSERT INTO t1x VALUES(1, 'one', 1); INSERT INTO t1x VALUES(2, 'two', 2); INSERT INTO t1x VALUES(3, 'three', 3); INSERT INTO t1x VALUES(4, 'four', 4); } foreach {tn mode} { 1 use 2 omit 3 use2 } { do_execsql_test 2.2.$mode.1 " DROP TABLE IF EXISTS t1; CREATE VIRTUAL TABLE t1 USING tcl(t1_vtab $mode); " do_execsql_test 2.2.$mode.2 {SELECT * FROM t1} {one 1 two 2 three 3 four 4} do_execsql_test 2.2.$mode.3 {SELECT rowid FROM t1} {1 2 3 4} do_execsql_test 2.2.$mode.4 {SELECT rowid FROM t1 WHERE a='two'} {2} do_execsql_test 2.2.$mode.5 { SELECT rowid FROM t1 WHERE a IN ('one', 'four') ORDER BY +rowid } {1 4} set plan(use) { QUERY PLAN |--SCAN t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x WHERE a='%1%' `--USE TEMP B-TREE FOR ORDER BY } set plan(omit) { QUERY PLAN |--SCAN t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x WHERE a='%1%' `--USE TEMP B-TREE FOR ORDER BY } set plan(use2) { QUERY PLAN |--SCAN t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x `--USE TEMP B-TREE FOR ORDER BY } do_eqp_test 2.2.$mode.6 { SELECT rowid FROM t1 WHERE a IN ('one', 'four') ORDER BY +rowid } [string map {"\n " "\n"} $plan($mode)] } # 2016-04-09. # Demonstrate a register overwrite problem when using two virtual # tables where the outer loop uses the IN operator. # set G(collist) [list PrimaryKey flagA columnA] set G(cols) [join $G(collist) ,] set G(nulls) "NULL" proc vtab_command {method args} { global G switch -- $method { xConnect { return "CREATE TABLE t1($G(cols))" } xBestIndex { set hdl [lindex $args 0] set clist [$hdl constraints] set orderby [$hdl orderby] #puts $clist set W [list] set U [list] set i 0 for {set idx 0} {$idx < [llength $clist]} {incr idx} { array set c [lindex $clist $idx] if {$c(op)=="eq" && $c(usable)} { lappend W "[lindex $G(collist) $c(column)] = %$i%" lappend U use $idx incr i } } if {$W==""} { set sql "SELECT rowid, * FROM t1" } else { set sql "SELECT rowid, * FROM t1 WHERE [join $W { AND }]" } return [concat [list idxstr $sql] $U] } xFilter { foreach {idxnum idxstr vals} $args {} set map [list] for {set i 0} {$i < [llength $vals]} {incr i} { lappend map "%$i%" set v [lindex $vals $i] if {[string is integer $v]} { lappend map $v } else { lappend map "'$v'" } } set sql [string map $map $idxstr] #puts "SQL: $sql" return [list sql $sql] } } return {} } db close forcedelete test.db sqlite3 db test.db register_tcl_module db do_execsql_test 3.1 " CREATE TABLE t1($G(cols)); INSERT INTO t1 VALUES(1, 0, 'ValueA'); INSERT INTO t1 VALUES(2, 0, 'ValueA'); INSERT INTO t1 VALUES(3, 0, 'ValueB'); INSERT INTO t1 VALUES(4, 0, 'ValueB'); " do_execsql_test 3.2 { CREATE VIRTUAL TABLE VirtualTableA USING tcl(vtab_command); CREATE VIRTUAL TABLE VirtualTableB USING tcl(vtab_command); } do_execsql_test 3.3 { SELECT primarykey FROM VirtualTableA } {1 2 3 4} do_execsql_test 3.4 { SELECT * FROM VirtualTableA a CROSS JOIN VirtualTableB b ON b.PrimaryKey=a.PrimaryKey WHERE a.ColumnA IN ('ValueA', 'ValueB') AND a.FlagA=0 } { 1 0 ValueA 1 0 ValueA 2 0 ValueA 2 0 ValueA 3 0 ValueB 3 0 ValueB 4 0 ValueB 4 0 ValueB } do_execsql_test 3.5 { SELECT * FROM VirtualTableA a CROSS JOIN VirtualTableB b ON b.PrimaryKey=a.PrimaryKey WHERE a.FlagA=0 AND a.ColumnA IN ('ValueA', 'ValueB') } { 1 0 ValueA 1 0 ValueA 2 0 ValueA 2 0 ValueA 3 0 ValueB 3 0 ValueB 4 0 ValueB 4 0 ValueB } #------------------------------------------------------------------------- # If there is an IN(..) condition in the WHERE clause of a query on a # virtual table, the xBestIndex method is first invoked with the IN(...) # represented by a "usable" SQLITE_INDEX_CONSTRAINT_EQ constraint. If # the virtual table elects to use the IN(...) constraint, then the # xBestIndex method is invoked again, this time with the IN(...) marked # as "not usable". Depending on the relative costs of the two plans as # defined by the virtual table implementation, and the cardinality of the # IN(...) operator, SQLite chooses the most efficient plan. # # At one point the second invocation of xBestIndex() was only being made # for join queries. The following tests check that this problem has been # fixed. # proc vtab_command {method args} { switch -- $method { xConnect { return "CREATE TABLE t1(a, b, c, d)" } xBestIndex { set hdl [lindex $args 0] set clist [$hdl constraints] set orderby [$hdl orderby] lappend ::bestindex_calls $clist set ret "cost 1000000 idxnum 555" for {set i 0} {$i < [llength $clist]} {incr i} { array set C [lindex $clist $i] if {$C(usable)} { lappend ret use $i } } return $ret } } return {} } do_execsql_test 4.0 { CREATE VIRTUAL TABLE x1 USING tcl(vtab_command); } {} do_test 4.1 { set ::bestindex_calls [list] execsql { SELECT * FROM x1 WHERE a=? AND b BETWEEN ? AND ? AND c IN (1, 2, 3, 4); } set ::bestindex_calls } [list \ [list {op eq column 0 usable 1} \ {op eq column 2 usable 1} \ {op ge column 1 usable 1} \ {op le column 1 usable 1} \ ] \ [list {op eq column 0 usable 1} \ {op eq column 2 usable 0} \ {op ge column 1 usable 1} \ {op le column 1 usable 1} ] ] do_catchsql_test 5.0 { SELECT * FROM tcl('abc'); } {1 {wrong number of arguments}} finish_test