Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Add test cases for skip-scan. Enhance "do_test" so that if the expected result is of the form "/*..*/" or "~/*..*/" it treats the expected result as a glob pattern rather than as a regular expression. Fix a bug in ANALYZE result loading associated with WITHOUT ROWID tables. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | skip-scan |
Files: | files | file ages | folders |
SHA1: |
d3e6e9b2a74074c05429d3c341c23525 |
User & Date: | drh 2013-11-13 15:32:15.331 |
Context
2013-11-13
| ||
16:58 | Improve the way that skip-scan loops are constructued. Add test cases. Improved the scoring of skip-scan loops. (check-in: 5e75ab9388 user: drh tags: skip-scan) | |
15:32 | Add test cases for skip-scan. Enhance "do_test" so that if the expected result is of the form "/*..*/" or "~/*..*/" it treats the expected result as a glob pattern rather than as a regular expression. Fix a bug in ANALYZE result loading associated with WITHOUT ROWID tables. (check-in: d3e6e9b2a7 user: drh tags: skip-scan) | |
12:27 | Add the ability to use an index even if the left-most columns of the index are unconstrainted, provided that the left-most columns have few distinct values. (check-in: 27dd5993d1 user: drh tags: skip-scan) | |
Changes
Changes to src/analyze.c.
︙ | ︙ | |||
1424 1425 1426 1427 1428 1429 1430 | if( argv==0 || argv[0]==0 || argv[2]==0 ){ return 0; } pTable = sqlite3FindTable(pInfo->db, argv[0], pInfo->zDatabase); if( pTable==0 ){ return 0; } | | | > > | | 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 | if( argv==0 || argv[0]==0 || argv[2]==0 ){ return 0; } pTable = sqlite3FindTable(pInfo->db, argv[0], pInfo->zDatabase); if( pTable==0 ){ return 0; } if( argv[1]==0 ){ pIndex = 0; }else if( sqlite3_stricmp(argv[0],argv[1])==0 ){ pIndex = sqlite3PrimaryKeyIndex(pTable); }else{ pIndex = sqlite3FindIndex(pInfo->db, argv[1], pInfo->zDatabase); } z = argv[2]; if( pIndex ){ decodeIntArray((char*)z, pIndex->nKeyCol+1, pIndex->aiRowEst, pIndex); if( pIndex->pPartIdxWhere==0 ) pTable->nRowEst = pIndex->aiRowEst[0]; }else{ |
︙ | ︙ |
Added test/skipscan1.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | # 2013-11-13 # # 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. # #*********************************************************************** # # This file implements tests of the "skip-scan" query strategy. # set testdir [file dirname $argv0] source $testdir/tester.tcl do_execsql_test skipscan1-1.1 { CREATE TABLE t1(a TEXT, b INT, c INT, d INT); CREATE INDEX t1abc ON t1(a,b,c); INSERT INTO t1 VALUES('abc',123,4,5); INSERT INTO t1 VALUES('abc',234,5,6); INSERT INTO t1 VALUES('abc',234,6,7); INSERT INTO t1 VALUES('abc',345,7,8); INSERT INTO t1 VALUES('def',567,8,9); INSERT INTO t1 VALUES('def',345,9,10); INSERT INTO t1 VALUES('bcd',100,6,11); /* Fake the sqlite_stat1 table so that the query planner believes ** the table contains thousands of rows and that the first few ** columns are not selective. */ ANALYZE; DELETE FROM sqlite_stat1; INSERT INTO sqlite_stat1 VALUES('t1','t1abc','10000 5000 2000 10'); ANALYZE sqlite_master; } {} # Simple queries that leave the first one or two columns of the # index unconstrainted. # do_execsql_test skipscan1-1.2 { SELECT a,b,c,d,'|' FROM t1 WHERE b=345 ORDER BY a; } {abc 345 7 8 | def 345 9 10 |} do_execsql_test skipscan1-1.2eqp { EXPLAIN QUERY PLAN SELECT a,b,c,d,'|' FROM t1 WHERE b=345 ORDER BY a; } {/* USING INDEX t1abc (ANY(a) AND b=?)*/} do_execsql_test skipscan1-1.2sort { EXPLAIN QUERY PLAN SELECT a,b,c,d,'|' FROM t1 WHERE b=345 ORDER BY a; } {~/*ORDER BY*/} do_execsql_test skipscan1-1.3 { SELECT a,b,c,d,'|' FROM t1 WHERE b=345 ORDER BY a DESC; } {def 345 9 10 | abc 345 7 8 |} do_execsql_test skipscan1-1.3eqp { EXPLAIN QUERY PLAN SELECT a,b,c,d,'|' FROM t1 WHERE b=345 ORDER BY a; } {/* USING INDEX t1abc (ANY(a) AND b=?)*/} do_execsql_test skipscan1-1.3sort { EXPLAIN QUERY PLAN SELECT a,b,c,d,'|' FROM t1 WHERE b=345 ORDER BY a; } {~/*ORDER BY*/} do_execsql_test skipscan1-1.4 { SELECT a,b,c,d,'|' FROM t1 WHERE c=6 ORDER BY a, b, c; } {abc 234 6 7 | bcd 100 6 11 |} do_execsql_test skipscan1-1.4eqp { EXPLAIN QUERY PLAN SELECT a,b,c,d,'|' FROM t1 WHERE c=6 ORDER BY a, b, c; } {/* USING INDEX t1abc (ANY(a) AND ANY(b) AND c=?)*/} do_execsql_test skipscan1-1.4sort { EXPLAIN QUERY PLAN SELECT a,b,c,d,'|' FROM t1 WHERE c=6 ORDER BY a, b, c; } {~/*ORDER BY*/} # Joins # do_execsql_test skipscan1-1.5 { CREATE TABLE t1j(x TEXT, y INTEGER); INSERT INTO t1j VALUES('one',1),('six',6),('ninty-nine',99); SELECT x, a, b, c, d, '|' FROM t1j, t1 WHERE c=y ORDER BY +a; } {six abc 234 6 7 | six bcd 100 6 11 |} do_execsql_test skipscan1-1.5eqp { EXPLAIN QUERY PLAN SELECT x, a, b, c, d, '|' FROM t1j, t1 WHERE c=y ORDER BY +a; } {/* INDEX t1abc (ANY(a) AND ANY(b) AND c=?)*/} do_execsql_test skipscan1-1.6 { SELECT x, a, b, c, d, '|' FROM t1j LEFT JOIN t1 ON c=y ORDER BY +y, +a; } {one {} {} {} {} | six abc 234 6 7 | six bcd 100 6 11 | ninty-nine {} {} {} {} |} do_execsql_test skipscan1-1.6eqp { EXPLAIN QUERY PLAN SELECT x, a, b, c, d, '|' FROM t1j LEFT JOIN t1 ON c=y ORDER BY +y, +a; } {/* INDEX t1abc (ANY(a) AND ANY(b) AND c=?)*/} do_execsql_test skipscan1-2.1 { CREATE TABLE t2(a TEXT, b INT, c INT, d INT, PRIMARY KEY(a,b,c)); INSERT INTO t2 SELECT * FROM t1; /* Fake the sqlite_stat1 table so that the query planner believes ** the table contains thousands of rows and that the first few ** columns are not selective. */ ANALYZE; UPDATE sqlite_stat1 SET stat='10000 5000 2000 10' WHERE idx NOT NULL; ANALYZE sqlite_master; } {} do_execsql_test skipscan1-2.2 { SELECT a,b,c,d,'|' FROM t2 WHERE b=345 ORDER BY a; } {abc 345 7 8 | def 345 9 10 |} do_execsql_test skipscan1-2.2eqp { EXPLAIN QUERY PLAN SELECT a,b,c,d,'|' FROM t2 WHERE b=345 ORDER BY a; } {/* USING INDEX sqlite_autoindex_t2_1 (ANY(a) AND b=?)*/} do_execsql_test skipscan1-2.2sort { EXPLAIN QUERY PLAN SELECT a,b,c,d,'|' FROM t2 WHERE b=345 ORDER BY a; } {~/*ORDER BY*/} do_execsql_test skipscan1-3.1 { CREATE TABLE t3(a TEXT, b INT, c INT, d INT, PRIMARY KEY(a,b,c)) WITHOUT ROWID; INSERT INTO t3 SELECT * FROM t1; /* Fake the sqlite_stat1 table so that the query planner believes ** the table contains thousands of rows and that the first few ** columns are not selective. */ ANALYZE; UPDATE sqlite_stat1 SET stat='10000 5000 2000 10' WHERE idx NOT NULL; ANALYZE sqlite_master; } {} do_execsql_test skipscan1-3.2 { SELECT a,b,c,d,'|' FROM t3 WHERE b=345 ORDER BY a; } {abc 345 7 8 | def 345 9 10 |} do_execsql_test skipscan1-3.2eqp { EXPLAIN QUERY PLAN SELECT a,b,c,d,'|' FROM t3 WHERE b=345 ORDER BY a; } {/* INDEX sqlite_autoindex_t3_1 (ANY(a) AND b=?)*/} do_execsql_test skipscan1-3.2sort { EXPLAIN QUERY PLAN SELECT a,b,c,d,'|' FROM t3 WHERE b=345 ORDER BY a; } {~/*ORDER BY*/} finish_test |
Changes to test/tester.tcl.
︙ | ︙ | |||
612 613 614 615 616 617 618 | fail_test $name } else { if {[regexp {^~?/.*/$} $expected]} { # "expected" is of the form "/PATTERN/" then the result if correct if # regular expression PATTERN matches the result. "~/PATTERN/" means # the regular expression must not match. if {[string index $expected 0]=="~"} { | > > > > > | > > | > > > > > | | > | 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 | fail_test $name } else { if {[regexp {^~?/.*/$} $expected]} { # "expected" is of the form "/PATTERN/" then the result if correct if # regular expression PATTERN matches the result. "~/PATTERN/" means # the regular expression must not match. if {[string index $expected 0]=="~"} { set re [string range $expected 2 end-1] if {[string index $re 0]=="*"} { # If the regular expression begins with * then treat it as a glob instead set ok [string match $re $result] } else { set re [string map {# {[-0-9.]+}} $re] set ok [regexp $re $result] } set ok [expr {!$ok}] } else { set re [string range $expected 1 end-1] if {[string index $re 0]=="*"} { # If the regular expression begins with * then treat it as a glob instead set ok [string match $re $result] } else { set re [string map {# {[-0-9.]+}} $re] set ok [regexp $re $result] } } } elseif {[regexp {^~?\*.*\*$} $expected]} { # "expected" is of the form "*GLOB*" then the result if correct if # glob pattern GLOB matches the result. "~/GLOB/" means # the glob must not match. if {[string index $expected 0]=="~"} { set e [string range $expected 1 end] |
︙ | ︙ |