Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bc3f784cd2 | |||
| 2ed99def5b | |||
| e53b4f9774 | |||
| a6ed5a4f39 | |||
| 7853002c71 | |||
| 280db65e2c | |||
| 2abf90096f | |||
| 990b6ceccf | |||
| e01b9281fc | |||
| 79610f5d09 | |||
| 0824ccf29b | |||
| 81beb6d115 | |||
| 0e0f5edbb5 | |||
| c2309693b9 | |||
| a4e61024d7 | |||
| 96d43a05ec | |||
| cd84474ece | |||
| 958151b005 | |||
| 2b78dd2359 | |||
| e7c3aca601 | |||
| 65e67ed1b2 | |||
| 8e0b8e0688 | |||
| d8ac297233 | |||
| 53761f4d52 | |||
| e86573fa51 | |||
| fbf3bdcdbc | |||
| b5c39ac0c9 | |||
| ba22f84cc2 | |||
| 85a18faff9 | |||
| 94c67eca89 | |||
| 1447950438 | |||
| 850493ac1f | |||
| b12dfd0125 | |||
| 1c93844dd3 | |||
| 94975e27a3 | |||
| 47dd4867bc | |||
| 3e6ac1643c | |||
| 02e4f27146 | |||
| dbbf8e4eb7 | |||
| 336bfe06b9 | |||
| a494ca8d12 | |||
| ff3b534b9f | |||
| c45bf341af | |||
| 323f7d3fc6 | |||
| 09c480aba1 | |||
| 1b8b839a8d | |||
| ef5de04b4e | |||
| e7dd68c2d4 |
@@ -0,0 +1,68 @@
|
||||
## SQLite Expert Extension
|
||||
|
||||
This folder contains code for a simple system to propose useful indexes
|
||||
given a database and a set of SQL queries. It works as follows:
|
||||
|
||||
1. The user database schema is copied to a temporary database.
|
||||
|
||||
1. All SQL queries are prepared against the temporary database. The
|
||||
**sqlite3\_whereinfo\_hook()** API is used to record information regarding
|
||||
the WHERE and ORDER BY clauses attached to each query.
|
||||
|
||||
1. The information gathered in step 2 is used to create (possibly a large
|
||||
number of) candidate indexes.
|
||||
|
||||
1. The SQL queries are prepared a second time. If the planner uses any
|
||||
of the indexes created in step 3, they are recommended to the user.
|
||||
|
||||
No ANALYZE data is available to the planner in step 4 above. This can lead to sub-optimal results.
|
||||
|
||||
This extension requires that SQLite be built with the
|
||||
SQLITE\_ENABLE\_WHEREINFO\_HOOK pre-processor symbol defined.
|
||||
|
||||
# C API
|
||||
|
||||
The SQLite expert C API is defined in sqlite3expert.h. Most uses will proceed
|
||||
as follows:
|
||||
|
||||
1. An sqlite3expert object is created by calling **sqlite3\_expert\_new()**.
|
||||
A database handle opened by the user is passed as an argument.
|
||||
|
||||
1. The sqlite3expert object is configured with one or more SQL statements
|
||||
by making one or more calls to **sqlite3\_expert\_sql()**. Each call may
|
||||
specify a single SQL statement, or multiple statements separated by
|
||||
semi-colons.
|
||||
|
||||
1. **sqlite3\_expert\_analyze()** is called to run the analysis.
|
||||
|
||||
1. One or more calls are made to **sqlite3\_expert\_report()** to extract
|
||||
components of the results of the analysis.
|
||||
|
||||
1. **sqlite3\_expert\_destroy()** is called to free all resources.
|
||||
|
||||
Refer to comments in sqlite3expert.h for further details.
|
||||
|
||||
# sqlite3_expert application
|
||||
|
||||
The file "expert.c" contains the code for a command line application that
|
||||
uses the API described above. It can be compiled with (for example):
|
||||
|
||||
<pre>
|
||||
gcc -O2 -DSQLITE_ENABLE_WHEREINFO_HOOK sqlite3.c expert.c sqlite3expert.c -o sqlite3_expert
|
||||
</pre>
|
||||
|
||||
Assuming the database is "test.db", it can then be run to analyze a single query:
|
||||
|
||||
<pre>
|
||||
./sqlite3_expert -sql <sql-query> test.db
|
||||
</pre>
|
||||
|
||||
Or an entire text file worth of queries with:
|
||||
|
||||
<pre>
|
||||
./sqlite3_expert -file <text-file> test.db
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
** 2017 April 07
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
*************************************************************************
|
||||
*/
|
||||
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "sqlite3expert.h"
|
||||
|
||||
|
||||
static void option_requires_argument(const char *zOpt){
|
||||
fprintf(stderr, "Option requires an argument: %s\n", zOpt);
|
||||
exit(-3);
|
||||
}
|
||||
|
||||
static int option_integer_arg(const char *zVal){
|
||||
return atoi(zVal);
|
||||
}
|
||||
|
||||
static void usage(char **argv){
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "Usage %s ?OPTIONS? DATABASE\n", argv[0]);
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "Options are:\n");
|
||||
fprintf(stderr, " -sql SQL (analyze SQL statements passed as argument)\n");
|
||||
fprintf(stderr, " -file FILE (read SQL statements from file FILE)\n");
|
||||
fprintf(stderr, " -verbose LEVEL (integer verbosity level. default 1)\n");
|
||||
fprintf(stderr, " -sample PERCENT (percent of db to sample. default 100)\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
static int readSqlFromFile(sqlite3expert *p, const char *zFile, char **pzErr){
|
||||
FILE *in = fopen(zFile, "rb");
|
||||
long nIn;
|
||||
size_t nRead;
|
||||
char *pBuf;
|
||||
int rc;
|
||||
if( in==0 ){
|
||||
*pzErr = sqlite3_mprintf("failed to open file %s\n", zFile);
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
fseek(in, 0, SEEK_END);
|
||||
nIn = ftell(in);
|
||||
rewind(in);
|
||||
pBuf = sqlite3_malloc64( nIn+1 );
|
||||
nRead = fread(pBuf, nIn, 1, in);
|
||||
fclose(in);
|
||||
if( nRead!=1 ){
|
||||
sqlite3_free(pBuf);
|
||||
*pzErr = sqlite3_mprintf("failed to read file %s\n", zFile);
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
pBuf[nIn] = 0;
|
||||
rc = sqlite3_expert_sql(p, pBuf, pzErr);
|
||||
sqlite3_free(pBuf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv){
|
||||
const char *zDb;
|
||||
int rc = 0;
|
||||
char *zErr = 0;
|
||||
int i;
|
||||
int iVerbose = 1; /* -verbose option */
|
||||
|
||||
sqlite3 *db = 0;
|
||||
sqlite3expert *p = 0;
|
||||
|
||||
if( argc<2 ) usage(argv);
|
||||
zDb = argv[argc-1];
|
||||
rc = sqlite3_open(zDb, &db);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stderr, "Cannot open db file: %s - %s\n", zDb, sqlite3_errmsg(db));
|
||||
exit(-2);
|
||||
}
|
||||
|
||||
p = sqlite3_expert_new(db, &zErr);
|
||||
if( p==0 ){
|
||||
fprintf(stderr, "Cannot run analysis: %s\n", zErr);
|
||||
rc = 1;
|
||||
}else{
|
||||
for(i=1; i<(argc-1); i++){
|
||||
char *zArg = argv[i];
|
||||
int nArg = strlen(zArg);
|
||||
if( nArg>=2 && 0==sqlite3_strnicmp(zArg, "-file", nArg) ){
|
||||
if( ++i==(argc-1) ) option_requires_argument("-file");
|
||||
rc = readSqlFromFile(p, argv[i], &zErr);
|
||||
}
|
||||
|
||||
else if( nArg>=3 && 0==sqlite3_strnicmp(zArg, "-sql", nArg) ){
|
||||
if( ++i==(argc-1) ) option_requires_argument("-sql");
|
||||
rc = sqlite3_expert_sql(p, argv[i], &zErr);
|
||||
}
|
||||
|
||||
else if( nArg>=3 && 0==sqlite3_strnicmp(zArg, "-sample", nArg) ){
|
||||
int iSample;
|
||||
if( ++i==(argc-1) ) option_requires_argument("-sample");
|
||||
iSample = option_integer_arg(argv[i]);
|
||||
sqlite3_expert_config(p, EXPERT_CONFIG_SAMPLE, iSample);
|
||||
}
|
||||
|
||||
else if( nArg>=2 && 0==sqlite3_strnicmp(zArg, "-verbose", nArg) ){
|
||||
if( ++i==(argc-1) ) option_requires_argument("-verbose");
|
||||
iVerbose = option_integer_arg(argv[i]);
|
||||
}
|
||||
|
||||
else{
|
||||
usage(argv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_expert_analyze(p, &zErr);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
int nQuery = sqlite3_expert_count(p);
|
||||
if( iVerbose>0 ){
|
||||
const char *zCand = sqlite3_expert_report(p,0,EXPERT_REPORT_CANDIDATES);
|
||||
fprintf(stdout, "-- Candidates -------------------------------\n");
|
||||
fprintf(stdout, "%s\n", zCand);
|
||||
}
|
||||
for(i=0; i<nQuery; i++){
|
||||
const char *zSql = sqlite3_expert_report(p, i, EXPERT_REPORT_SQL);
|
||||
const char *zIdx = sqlite3_expert_report(p, i, EXPERT_REPORT_INDEXES);
|
||||
const char *zEQP = sqlite3_expert_report(p, i, EXPERT_REPORT_PLAN);
|
||||
if( zIdx==0 ) zIdx = "(no new indexes)\n";
|
||||
if( iVerbose>0 ){
|
||||
fprintf(stdout, "-- Query %d ----------------------------------\n",i+1);
|
||||
fprintf(stdout, "%s\n\n", zSql);
|
||||
}
|
||||
fprintf(stdout, "%s\n%s\n", zIdx, zEQP);
|
||||
}
|
||||
}else{
|
||||
fprintf(stderr, "Error: %s\n", zErr ? zErr : "?");
|
||||
}
|
||||
|
||||
sqlite3_expert_destroy(p);
|
||||
sqlite3_free(zErr);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
# 2009 Nov 11
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# The focus of this file is testing the CLI shell tool. Specifically,
|
||||
# the ".recommend" command.
|
||||
#
|
||||
#
|
||||
|
||||
# Test plan:
|
||||
#
|
||||
#
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
set testprefix expert1
|
||||
|
||||
if {$tcl_platform(platform)=="windows"} {
|
||||
set CMD "sqlite3_expert.exe"
|
||||
} else {
|
||||
set CMD ".././sqlite3_expert"
|
||||
}
|
||||
|
||||
proc squish {txt} {
|
||||
regsub -all {[[:space:]]+} $txt { }
|
||||
}
|
||||
|
||||
proc do_setup_rec_test {tn setup sql res} {
|
||||
reset_db
|
||||
db eval $setup
|
||||
uplevel [list do_rec_test $tn $sql $res]
|
||||
}
|
||||
|
||||
foreach {tn setup} {
|
||||
1 {
|
||||
if {![file executable $CMD]} { continue }
|
||||
|
||||
proc do_rec_test {tn sql res} {
|
||||
set res [squish [string trim $res]]
|
||||
set tst [subst -nocommands {
|
||||
squish [string trim [exec $::CMD -verbose 0 -sql {$sql;} test.db]]
|
||||
}]
|
||||
uplevel [list do_test $tn $tst $res]
|
||||
}
|
||||
}
|
||||
2 {
|
||||
if {[info commands sqlite3_expert_new]==""} { continue }
|
||||
|
||||
proc do_rec_test {tn sql res} {
|
||||
set expert [sqlite3_expert_new db]
|
||||
$expert sql $sql
|
||||
$expert analyze
|
||||
|
||||
set result [list]
|
||||
for {set i 0} {$i < [$expert count]} {incr i} {
|
||||
set idx [string trim [$expert report $i indexes]]
|
||||
if {$idx==""} {set idx "(no new indexes)"}
|
||||
lappend result $idx
|
||||
lappend result [string trim [$expert report $i plan]]
|
||||
}
|
||||
|
||||
$expert destroy
|
||||
|
||||
set tst [subst -nocommands {set {} [squish [join {$result}]]}]
|
||||
uplevel [list do_test $tn $tst [string trim [squish $res]]]
|
||||
}
|
||||
}
|
||||
} {
|
||||
|
||||
eval $setup
|
||||
|
||||
|
||||
do_setup_rec_test $tn.1 { CREATE TABLE t1(a, b, c) } {
|
||||
SELECT * FROM t1
|
||||
} {
|
||||
(no new indexes)
|
||||
0|0|0|SCAN TABLE t1
|
||||
}
|
||||
|
||||
do_setup_rec_test $tn.2 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
} {
|
||||
SELECT * FROM t1 WHERE b>?;
|
||||
} {
|
||||
CREATE INDEX t1_idx_00000062 ON t1(b);
|
||||
0|0|0|SEARCH TABLE t1 USING INDEX t1_idx_00000062 (b>?)
|
||||
}
|
||||
|
||||
do_setup_rec_test $tn.3 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
} {
|
||||
SELECT * FROM t1 WHERE b COLLATE nocase BETWEEN ? AND ?
|
||||
} {
|
||||
CREATE INDEX t1_idx_3e094c27 ON t1(b COLLATE NOCASE);
|
||||
0|0|0|SEARCH TABLE t1 USING INDEX t1_idx_3e094c27 (b>? AND b<?)
|
||||
}
|
||||
|
||||
do_setup_rec_test $tn.4 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
} {
|
||||
SELECT a FROM t1 ORDER BY b;
|
||||
} {
|
||||
CREATE INDEX t1_idx_00000062 ON t1(b);
|
||||
0|0|0|SCAN TABLE t1 USING INDEX t1_idx_00000062
|
||||
}
|
||||
|
||||
do_setup_rec_test $tn.5 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
} {
|
||||
SELECT a FROM t1 WHERE a=? ORDER BY b;
|
||||
} {
|
||||
CREATE INDEX t1_idx_000123a7 ON t1(a, b);
|
||||
0|0|0|SEARCH TABLE t1 USING COVERING INDEX t1_idx_000123a7 (a=?)
|
||||
}
|
||||
|
||||
do_setup_rec_test $tn.6 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
} {
|
||||
SELECT min(a) FROM t1
|
||||
} {
|
||||
CREATE INDEX t1_idx_00000061 ON t1(a);
|
||||
0|0|0|SEARCH TABLE t1 USING COVERING INDEX t1_idx_00000061
|
||||
}
|
||||
|
||||
do_setup_rec_test $tn.7 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
} {
|
||||
SELECT * FROM t1 ORDER BY a, b, c;
|
||||
} {
|
||||
CREATE INDEX t1_idx_033e95fe ON t1(a, b, c);
|
||||
0|0|0|SCAN TABLE t1 USING COVERING INDEX t1_idx_033e95fe
|
||||
}
|
||||
|
||||
#do_setup_rec_test $tn.1.8 {
|
||||
# CREATE TABLE t1(a, b, c);
|
||||
#} {
|
||||
# SELECT * FROM t1 ORDER BY a ASC, b COLLATE nocase DESC, c ASC;
|
||||
#} {
|
||||
# CREATE INDEX t1_idx_5be6e222 ON t1(a, b COLLATE NOCASE DESC, c);
|
||||
# 0|0|0|SCAN TABLE t1 USING COVERING INDEX t1_idx_5be6e222
|
||||
#}
|
||||
|
||||
do_setup_rec_test $tn.8.1 {
|
||||
CREATE TABLE t1(a COLLATE NOCase, b, c);
|
||||
} {
|
||||
SELECT * FROM t1 WHERE a=?
|
||||
} {
|
||||
CREATE INDEX t1_idx_00000061 ON t1(a);
|
||||
0|0|0|SEARCH TABLE t1 USING INDEX t1_idx_00000061 (a=?)
|
||||
}
|
||||
do_setup_rec_test $tn.8.2 {
|
||||
CREATE TABLE t1(a, b COLLATE nocase, c);
|
||||
} {
|
||||
SELECT * FROM t1 ORDER BY a ASC, b DESC, c ASC;
|
||||
} {
|
||||
CREATE INDEX t1_idx_5cb97285 ON t1(a, b DESC, c);
|
||||
0|0|0|SCAN TABLE t1 USING COVERING INDEX t1_idx_5cb97285
|
||||
}
|
||||
|
||||
|
||||
# Tables with names that require quotes.
|
||||
#
|
||||
do_setup_rec_test $tn.9.1 {
|
||||
CREATE TABLE "t t"(a, b, c);
|
||||
} {
|
||||
SELECT * FROM "t t" WHERE a=?
|
||||
} {
|
||||
CREATE INDEX 't t_idx_00000061' ON 't t'(a);
|
||||
0|0|0|SEARCH TABLE t t USING INDEX t t_idx_00000061 (a=?)
|
||||
}
|
||||
|
||||
do_setup_rec_test $tn.9.2 {
|
||||
CREATE TABLE "t t"(a, b, c);
|
||||
} {
|
||||
SELECT * FROM "t t" WHERE b BETWEEN ? AND ?
|
||||
} {
|
||||
CREATE INDEX 't t_idx_00000062' ON 't t'(b);
|
||||
0|0|0|SEARCH TABLE t t USING INDEX t t_idx_00000062 (b>? AND b<?)
|
||||
}
|
||||
|
||||
# Columns with names that require quotes.
|
||||
#
|
||||
do_setup_rec_test $tn.10.1 {
|
||||
CREATE TABLE t3(a, "b b", c);
|
||||
} {
|
||||
SELECT * FROM t3 WHERE "b b" = ?
|
||||
} {
|
||||
CREATE INDEX t3_idx_00050c52 ON t3('b b');
|
||||
0|0|0|SEARCH TABLE t3 USING INDEX t3_idx_00050c52 (b b=?)
|
||||
}
|
||||
|
||||
do_setup_rec_test $tn.10.2 {
|
||||
CREATE TABLE t3(a, "b b", c);
|
||||
} {
|
||||
SELECT * FROM t3 ORDER BY "b b"
|
||||
} {
|
||||
CREATE INDEX t3_idx_00050c52 ON t3('b b');
|
||||
0|0|0|SCAN TABLE t3 USING INDEX t3_idx_00050c52
|
||||
}
|
||||
|
||||
# Transitive constraints
|
||||
#
|
||||
do_setup_rec_test $tn.11.1 {
|
||||
CREATE TABLE t5(a, b);
|
||||
CREATE TABLE t6(c, d);
|
||||
} {
|
||||
SELECT * FROM t5, t6 WHERE a=? AND b=c AND c=?
|
||||
} {
|
||||
CREATE INDEX t5_idx_000123a7 ON t5(a, b);
|
||||
CREATE INDEX t6_idx_00000063 ON t6(c);
|
||||
0|0|1|SEARCH TABLE t6 USING INDEX t6_idx_00000063 (c=?)
|
||||
0|1|0|SEARCH TABLE t5 USING COVERING INDEX t5_idx_000123a7 (a=? AND b=?)
|
||||
}
|
||||
|
||||
# OR terms.
|
||||
#
|
||||
do_setup_rec_test $tn.12.1 {
|
||||
CREATE TABLE t7(a, b);
|
||||
} {
|
||||
SELECT * FROM t7 WHERE a=? OR b=?
|
||||
} {
|
||||
CREATE INDEX t7_idx_00000062 ON t7(b);
|
||||
CREATE INDEX t7_idx_00000061 ON t7(a);
|
||||
0|0|0|SEARCH TABLE t7 USING INDEX t7_idx_00000061 (a=?)
|
||||
0|0|0|SEARCH TABLE t7 USING INDEX t7_idx_00000062 (b=?)
|
||||
}
|
||||
|
||||
# rowid terms.
|
||||
#
|
||||
do_setup_rec_test $tn.13.1 {
|
||||
CREATE TABLE t8(a, b);
|
||||
} {
|
||||
SELECT * FROM t8 WHERE rowid=?
|
||||
} {
|
||||
(no new indexes)
|
||||
0|0|0|SEARCH TABLE t8 USING INTEGER PRIMARY KEY (rowid=?)
|
||||
}
|
||||
do_setup_rec_test $tn.13.2 {
|
||||
CREATE TABLE t8(a, b);
|
||||
} {
|
||||
SELECT * FROM t8 ORDER BY rowid
|
||||
} {
|
||||
(no new indexes)
|
||||
0|0|0|SCAN TABLE t8
|
||||
}
|
||||
do_setup_rec_test $tn.13.3 {
|
||||
CREATE TABLE t8(a, b);
|
||||
} {
|
||||
SELECT * FROM t8 WHERE a=? ORDER BY rowid
|
||||
} {
|
||||
CREATE INDEX t8_idx_00000061 ON t8(a);
|
||||
0|0|0|SEARCH TABLE t8 USING INDEX t8_idx_00000061 (a=?)
|
||||
}
|
||||
|
||||
# Triggers
|
||||
#
|
||||
do_setup_rec_test $tn.14 {
|
||||
CREATE TABLE t9(a, b, c);
|
||||
CREATE TABLE t10(a, b, c);
|
||||
CREATE TRIGGER t9t AFTER INSERT ON t9 BEGIN
|
||||
UPDATE t10 SET a=new.a WHERE b = new.b;
|
||||
END;
|
||||
} {
|
||||
INSERT INTO t9 VALUES(?, ?, ?);
|
||||
} {
|
||||
CREATE INDEX t10_idx_00000062 ON t10(b);
|
||||
0|0|0|SEARCH TABLE t10 USING INDEX t10_idx_00000062 (b=?)
|
||||
}
|
||||
|
||||
do_setup_rec_test $tn.15 {
|
||||
CREATE TABLE t1(a, b);
|
||||
CREATE TABLE t2(c, d);
|
||||
|
||||
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t1 SELECT (i-1)/50, (i-1)/20 FROM s;
|
||||
|
||||
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t2 SELECT (i-1)/20, (i-1)/5 FROM s;
|
||||
} {
|
||||
SELECT * FROM t2, t1 WHERE b=? AND d=? AND t2.rowid=t1.rowid
|
||||
} {
|
||||
CREATE INDEX t2_idx_00000064 ON t2(d);
|
||||
0|0|0|SEARCH TABLE t2 USING INDEX t2_idx_00000064 (d=?)
|
||||
0|1|1|SEARCH TABLE t1 USING INTEGER PRIMARY KEY (rowid=?)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
proc do_candidates_test {tn sql res} {
|
||||
set res [squish [string trim $res]]
|
||||
|
||||
set expert [sqlite3_expert_new db]
|
||||
$expert sql $sql
|
||||
$expert analyze
|
||||
|
||||
set candidates [squish [string trim [$expert report 0 candidates]]]
|
||||
$expert destroy
|
||||
|
||||
uplevel [list do_test $tn [list set {} $candidates] $res]
|
||||
}
|
||||
|
||||
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
CREATE TABLE t2(c, d);
|
||||
|
||||
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t1 SELECT (i-1)/50, (i-1)/20 FROM s;
|
||||
|
||||
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t2 SELECT (i-1)/20, (i-1)/5 FROM s;
|
||||
}
|
||||
do_candidates_test 3.1 {
|
||||
SELECT * FROM t1,t2 WHERE (b=? OR a=?) AND (c=? OR d=?)
|
||||
} {
|
||||
CREATE INDEX t1_idx_00000062 ON t1(b); -- stat1: 100 20
|
||||
CREATE INDEX t1_idx_00000061 ON t1(a); -- stat1: 100 50
|
||||
CREATE INDEX t2_idx_00000063 ON t2(c); -- stat1: 100 20
|
||||
CREATE INDEX t2_idx_00000064 ON t2(d); -- stat1: 100 5
|
||||
}
|
||||
|
||||
do_candidates_test 3.2 {
|
||||
SELECT * FROM t1,t2 WHERE a=? AND b=? AND c=? AND d=?
|
||||
} {
|
||||
CREATE INDEX t1_idx_000123a7 ON t1(a, b); -- stat1: 100 50 17
|
||||
CREATE INDEX t2_idx_0001295b ON t2(c, d); -- stat1: 100 20 5
|
||||
}
|
||||
|
||||
do_execsql_test 3.2 {
|
||||
CREATE INDEX t1_idx_00000061 ON t1(a); -- stat1: 100 50
|
||||
CREATE INDEX t1_idx_00000062 ON t1(b); -- stat1: 100 20
|
||||
CREATE INDEX t1_idx_000123a7 ON t1(a, b); -- stat1: 100 50 16
|
||||
|
||||
CREATE INDEX t2_idx_00000063 ON t2(c); -- stat1: 100 20
|
||||
CREATE INDEX t2_idx_00000064 ON t2(d); -- stat1: 100 5
|
||||
CREATE INDEX t2_idx_0001295b ON t2(c, d); -- stat1: 100 20 5
|
||||
|
||||
ANALYZE;
|
||||
SELECT * FROM sqlite_stat1 ORDER BY 1, 2;
|
||||
} {
|
||||
t1 t1_idx_00000061 {100 50}
|
||||
t1 t1_idx_00000062 {100 20}
|
||||
t1 t1_idx_000123a7 {100 50 17}
|
||||
t2 t2_idx_00000063 {100 20}
|
||||
t2 t2_idx_00000064 {100 5}
|
||||
t2 t2_idx_0001295b {100 20 5}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
** 2017 April 07
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
*************************************************************************
|
||||
*/
|
||||
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
typedef struct sqlite3expert sqlite3expert;
|
||||
|
||||
/*
|
||||
** Create a new sqlite3expert object.
|
||||
**
|
||||
** If successful, a pointer to the new object is returned and (*pzErr) set
|
||||
** to NULL. Or, if an error occurs, NULL is returned and (*pzErr) set to
|
||||
** an English-language error message. In this case it is the responsibility
|
||||
** of the caller to eventually free the error message buffer using
|
||||
** sqlite3_free().
|
||||
*/
|
||||
sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErr);
|
||||
|
||||
/*
|
||||
** Configure an sqlite3expert object.
|
||||
**
|
||||
** EXPERT_CONFIG_SAMPLE:
|
||||
** By default, sqlite3_expert_analyze() generates sqlite_stat1 data for
|
||||
** each candidate index. This involves scanning and sorting the entire
|
||||
** contents of each user database table once for each candidate index
|
||||
** associated with the table. For large databases, this can be
|
||||
** prohibitively slow. This option allows the sqlite3expert object to
|
||||
** be configured so that sqlite_stat1 data is instead generated based on a
|
||||
** subset of each table, or so that no sqlite_stat1 data is used at all.
|
||||
**
|
||||
** A single integer argument is passed to this option. If the value is less
|
||||
** than or equal to zero, then no sqlite_stat1 data is generated or used by
|
||||
** the analysis - indexes are recommended based on the database schema only.
|
||||
** Or, if the value is 100 or greater, complete sqlite_stat1 data is
|
||||
** generated for each candidate index (this is the default). Finally, if the
|
||||
** value falls between 0 and 100, then it represents the percentage of user
|
||||
** table rows that should be considered when generating sqlite_stat1 data.
|
||||
**
|
||||
** Examples:
|
||||
**
|
||||
** // Do not generate any sqlite_stat1 data
|
||||
** sqlite3_expert_config(pExpert, EXPERT_CONFIG_SAMPLE, 0);
|
||||
**
|
||||
** // Generate sqlite_stat1 data based on 10% of the rows in each table.
|
||||
** sqlite3_expert_config(pExpert, EXPERT_CONFIG_SAMPLE, 10);
|
||||
*/
|
||||
int sqlite3_expert_config(sqlite3expert *p, int op, ...);
|
||||
|
||||
#define EXPERT_CONFIG_SAMPLE 1 /* int */
|
||||
|
||||
/*
|
||||
** Specify zero or more SQL statements to be included in the analysis.
|
||||
**
|
||||
** Buffer zSql must contain zero or more complete SQL statements. This
|
||||
** function parses all statements contained in the buffer and adds them
|
||||
** to the internal list of statements to analyze. If successful, SQLITE_OK
|
||||
** is returned and (*pzErr) set to NULL. Or, if an error occurs - for example
|
||||
** due to a error in the SQL - an SQLite error code is returned and (*pzErr)
|
||||
** may be set to point to an English language error message. In this case
|
||||
** the caller is responsible for eventually freeing the error message buffer
|
||||
** using sqlite3_free().
|
||||
**
|
||||
** If an error does occur while processing one of the statements in the
|
||||
** buffer passed as the second argument, none of the statements in the
|
||||
** buffer are added to the analysis.
|
||||
**
|
||||
** This function must be called before sqlite3_expert_analyze(). If a call
|
||||
** to this function is made on an sqlite3expert object that has already
|
||||
** been passed to sqlite3_expert_analyze() SQLITE_MISUSE is returned
|
||||
** immediately and no statements are added to the analysis.
|
||||
*/
|
||||
int sqlite3_expert_sql(
|
||||
sqlite3expert *p, /* From a successful sqlite3_expert_new() */
|
||||
const char *zSql, /* SQL statement(s) to add */
|
||||
char **pzErr /* OUT: Error message (if any) */
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
** This function is called after the sqlite3expert object has been configured
|
||||
** with all SQL statements using sqlite3_expert_sql() to actually perform
|
||||
** the analysis. Once this function has been called, it is not possible to
|
||||
** add further SQL statements to the analysis.
|
||||
**
|
||||
** If successful, SQLITE_OK is returned and (*pzErr) is set to NULL. Or, if
|
||||
** an error occurs, an SQLite error code is returned and (*pzErr) set to
|
||||
** point to a buffer containing an English language error message. In this
|
||||
** case it is the responsibility of the caller to eventually free the buffer
|
||||
** using sqlite3_free().
|
||||
**
|
||||
** If an error does occur within this function, the sqlite3expert object
|
||||
** is no longer useful for any purpose. At that point it is no longer
|
||||
** possible to add further SQL statements to the object or to re-attempt
|
||||
** the analysis. The sqlite3expert object must still be freed using a call
|
||||
** sqlite3_expert_destroy().
|
||||
*/
|
||||
int sqlite3_expert_analyze(sqlite3expert *p, char **pzErr);
|
||||
|
||||
/*
|
||||
** Return the total number of statements loaded using sqlite3_expert_sql().
|
||||
** The total number of SQL statements may be different from the total number
|
||||
** to calls to sqlite3_expert_sql().
|
||||
*/
|
||||
int sqlite3_expert_count(sqlite3expert*);
|
||||
|
||||
/*
|
||||
** Return a component of the report.
|
||||
**
|
||||
** This function is called after sqlite3_expert_analyze() to extract the
|
||||
** results of the analysis. Each call to this function returns either a
|
||||
** NULL pointer or a pointer to a buffer containing a nul-terminated string.
|
||||
** The value passed as the third argument must be one of the EXPERT_REPORT_*
|
||||
** #define constants defined below.
|
||||
**
|
||||
** For some EXPERT_REPORT_* parameters, the buffer returned contains
|
||||
** information relating to a specific SQL statement. In these cases that
|
||||
** SQL statement is identified by the value passed as the second argument.
|
||||
** SQL statements are numbered from 0 in the order in which they are parsed.
|
||||
** If an out-of-range value (less than zero or equal to or greater than the
|
||||
** value returned by sqlite3_expert_count()) is passed as the second argument
|
||||
** along with such an EXPERT_REPORT_* parameter, NULL is always returned.
|
||||
**
|
||||
** EXPERT_REPORT_SQL:
|
||||
** Return the text of SQL statement iStmt.
|
||||
**
|
||||
** EXPERT_REPORT_INDEXES:
|
||||
** Return a buffer containing the CREATE INDEX statements for all recommended
|
||||
** indexes for statement iStmt. If there are no new recommeded indexes, NULL
|
||||
** is returned.
|
||||
**
|
||||
** EXPERT_REPORT_PLAN:
|
||||
** Return a buffer containing the EXPLAIN QUERY PLAN output for SQL query
|
||||
** iStmt after the proposed indexes have been added to the database schema.
|
||||
**
|
||||
** EXPERT_REPORT_CANDIDATES:
|
||||
** Return a pointer to a buffer containing the CREATE INDEX statements
|
||||
** for all indexes that were tested (for all SQL statements). The iStmt
|
||||
** parameter is ignored for EXPERT_REPORT_CANDIDATES calls.
|
||||
*/
|
||||
const char *sqlite3_expert_report(sqlite3expert*, int iStmt, int eReport);
|
||||
|
||||
/*
|
||||
** Values for the third argument passed to sqlite3_expert_report().
|
||||
*/
|
||||
#define EXPERT_REPORT_SQL 1
|
||||
#define EXPERT_REPORT_INDEXES 2
|
||||
#define EXPERT_REPORT_PLAN 3
|
||||
#define EXPERT_REPORT_CANDIDATES 4
|
||||
|
||||
/*
|
||||
** Free an (sqlite3expert*) handle and all associated resources. There
|
||||
** should be one call to this function for each successful call to
|
||||
** sqlite3-expert_new().
|
||||
*/
|
||||
void sqlite3_expert_destroy(sqlite3expert*);
|
||||
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
** 2017 April 07
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
*************************************************************************
|
||||
*/
|
||||
|
||||
#if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_WHEREINFO_HOOK)
|
||||
|
||||
#include "sqlite3expert.h"
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(INCLUDE_SQLITE_TCL_H)
|
||||
# include "sqlite_tcl.h"
|
||||
#else
|
||||
# include "tcl.h"
|
||||
# ifndef SQLITE_TCLAPI
|
||||
# define SQLITE_TCLAPI
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Extract an sqlite3* db handle from the object passed as the second
|
||||
** argument. If successful, set *pDb to point to the db handle and return
|
||||
** TCL_OK. Otherwise, return TCL_ERROR.
|
||||
*/
|
||||
static int dbHandleFromObj(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite3 **pDb){
|
||||
Tcl_CmdInfo info;
|
||||
if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(pObj), &info) ){
|
||||
Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(pObj), 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
*pDb = *(sqlite3 **)info.objClientData;
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Tclcmd: $expert sql SQL
|
||||
** $expert analyze
|
||||
** $expert count
|
||||
** $expert report STMT EREPORT
|
||||
** $expert destroy
|
||||
*/
|
||||
static int SQLITE_TCLAPI testExpertCmd(
|
||||
void *clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
sqlite3expert *pExpert = (sqlite3expert*)clientData;
|
||||
struct Subcmd {
|
||||
const char *zSub;
|
||||
int nArg;
|
||||
const char *zMsg;
|
||||
} aSub[] = {
|
||||
{ "sql", 1, "TABLE", }, /* 0 */
|
||||
{ "analyze", 0, "", }, /* 1 */
|
||||
{ "count", 0, "", }, /* 2 */
|
||||
{ "report", 2, "STMT EREPORT", }, /* 3 */
|
||||
{ "destroy", 0, "", }, /* 4 */
|
||||
{ 0 }
|
||||
};
|
||||
int iSub;
|
||||
int rc = TCL_OK;
|
||||
char *zErr = 0;
|
||||
|
||||
if( objc<2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
rc = Tcl_GetIndexFromObjStruct(interp,
|
||||
objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
|
||||
);
|
||||
if( rc!=TCL_OK ) return rc;
|
||||
if( objc!=2+aSub[iSub].nArg ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
switch( iSub ){
|
||||
case 0: { /* sql */
|
||||
char *zArg = Tcl_GetString(objv[2]);
|
||||
rc = sqlite3_expert_sql(pExpert, zArg, &zErr);
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: { /* analyze */
|
||||
rc = sqlite3_expert_analyze(pExpert, &zErr);
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: { /* count */
|
||||
int n = sqlite3_expert_count(pExpert);
|
||||
Tcl_SetObjResult(interp, Tcl_NewIntObj(n));
|
||||
break;
|
||||
}
|
||||
|
||||
case 3: { /* report */
|
||||
const char *aEnum[] = {
|
||||
"sql", "indexes", "plan", "candidates", 0
|
||||
};
|
||||
int iEnum;
|
||||
int iStmt;
|
||||
const char *zReport;
|
||||
|
||||
if( Tcl_GetIntFromObj(interp, objv[2], &iStmt)
|
||||
|| Tcl_GetIndexFromObj(interp, objv[3], aEnum, "report", 0, &iEnum)
|
||||
){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
assert( EXPERT_REPORT_SQL==1 );
|
||||
assert( EXPERT_REPORT_INDEXES==2 );
|
||||
assert( EXPERT_REPORT_PLAN==3 );
|
||||
assert( EXPERT_REPORT_CANDIDATES==4 );
|
||||
zReport = sqlite3_expert_report(pExpert, iStmt, 1+iEnum);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(zReport, -1));
|
||||
break;
|
||||
}
|
||||
|
||||
default: /* destroy */
|
||||
assert( iSub==4 );
|
||||
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
|
||||
break;
|
||||
}
|
||||
|
||||
if( rc!=TCL_OK ){
|
||||
if( zErr ){
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1));
|
||||
}else{
|
||||
extern const char *sqlite3ErrName(int);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
|
||||
}
|
||||
}
|
||||
sqlite3_free(zErr);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void SQLITE_TCLAPI testExpertDel(void *clientData){
|
||||
sqlite3expert *pExpert = (sqlite3expert*)clientData;
|
||||
sqlite3_expert_destroy(pExpert);
|
||||
}
|
||||
|
||||
/*
|
||||
** sqlite3_expert_new DB
|
||||
*/
|
||||
static int SQLITE_TCLAPI test_sqlite3_expert_new(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
static int iCmd = 0;
|
||||
sqlite3 *db;
|
||||
char *zCmd = 0;
|
||||
char *zErr = 0;
|
||||
sqlite3expert *pExpert;
|
||||
int rc = TCL_OK;
|
||||
|
||||
if( objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( dbHandleFromObj(interp, objv[1], &db) ){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
zCmd = sqlite3_mprintf("sqlite3expert%d", ++iCmd);
|
||||
if( zCmd==0 ){
|
||||
Tcl_AppendResult(interp, "out of memory", (char*)0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
pExpert = sqlite3_expert_new(db, &zErr);
|
||||
if( pExpert==0 ){
|
||||
Tcl_AppendResult(interp, zErr, (char*)0);
|
||||
rc = TCL_ERROR;
|
||||
}else{
|
||||
void *p = (void*)pExpert;
|
||||
Tcl_CreateObjCommand(interp, zCmd, testExpertCmd, p, testExpertDel);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(zCmd, -1));
|
||||
}
|
||||
|
||||
sqlite3_free(zCmd);
|
||||
sqlite3_free(zErr);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int TestExpert_Init(Tcl_Interp *interp){
|
||||
struct Cmd {
|
||||
const char *zCmd;
|
||||
Tcl_ObjCmdProc *xProc;
|
||||
} aCmd[] = {
|
||||
{ "sqlite3_expert_new", test_sqlite3_expert_new },
|
||||
};
|
||||
int i;
|
||||
|
||||
for(i=0; i<sizeof(aCmd)/sizeof(struct Cmd); i++){
|
||||
struct Cmd *p = &aCmd[i];
|
||||
Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, 0, 0);
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
#else /* defined(SQLITE_TEST) && defined(SQLITE_ENABLE_WHEREINFO_HOOK) */
|
||||
int TestExpert_Init(Tcl_Interp *interp){
|
||||
return TCL_OK;
|
||||
}
|
||||
#endif
|
||||
@@ -274,6 +274,8 @@ SRC += \
|
||||
# Source code to the test files.
|
||||
#
|
||||
TESTSRC = \
|
||||
$(TOP)/ext/expert/sqlite3expert.c \
|
||||
$(TOP)/ext/expert/test_expert.c \
|
||||
$(TOP)/ext/fts3/fts3_term.c \
|
||||
$(TOP)/ext/fts3/fts3_test.c \
|
||||
$(TOP)/ext/rbu/test_rbu.c \
|
||||
@@ -761,6 +763,23 @@ sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl
|
||||
sqlite3_analyzer$(EXE): sqlite3_analyzer.c
|
||||
$(TCCX) $(TCL_FLAGS) sqlite3_analyzer.c -o $@ $(LIBTCL) $(THREADLIB)
|
||||
|
||||
sqlite3_expert$(EXE): $(TOP)/ext/expert/sqlite3expert.h $(TOP)/ext/expert/sqlite3expert.c $(TOP)/ext/expert/expert.c sqlite3.c
|
||||
$(TCCX) -DSQLITE_ENABLE_WHEREINFO_HOOK $(TOP)/ext/expert/sqlite3expert.h $(TOP)/ext/expert/sqlite3expert.c $(TOP)/ext/expert/expert.c sqlite3.c -o sqlite3_expert $(THREADLIB)
|
||||
|
||||
sqlite3_schemalint.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/schemalint.tcl
|
||||
echo "#define TCLSH 2" > $@
|
||||
echo "#define SQLITE_ENABLE_DBSTAT_VTAB 1" >> $@
|
||||
cat sqlite3.c $(TOP)/src/tclsqlite.c >> $@
|
||||
echo "static const char *tclsh_main_loop(void){" >> $@
|
||||
echo "static const char *zMainloop = " >> $@
|
||||
tclsh $(TOP)/tool/tostr.tcl $(TOP)/tool/schemalint.tcl >> $@
|
||||
echo "; return zMainloop; }" >> $@
|
||||
|
||||
sqlite3_schemalint$(EXE): $(TESTSRC) sqlite3_schemalint.c
|
||||
$(TCCX) $(TCL_FLAGS) $(TESTFIXTURE_FLAGS) \
|
||||
sqlite3_schemalint.c $(TESTSRC) \
|
||||
-o sqlite3_schemalint$(EXE) $(LIBTCL) $(THREADLIB)
|
||||
|
||||
dbdump$(EXE): $(TOP)/ext/misc/dbdump.c sqlite3.o
|
||||
$(TCCX) -DDBDUMP_STANDALONE -o dbdump$(EXE) \
|
||||
$(TOP)/ext/misc/dbdump.c sqlite3.o $(THREADLIB)
|
||||
@@ -965,6 +984,7 @@ clean:
|
||||
rm -f sqlite3rc.h
|
||||
rm -f shell.c sqlite3ext.h
|
||||
rm -f sqlite3_analyzer sqlite3_analyzer.exe sqlite3_analyzer.c
|
||||
rm -f sqlite3_expert sqlite3_expert.exe
|
||||
rm -f sqlite-*-output.vsix
|
||||
rm -f mptester mptester.exe
|
||||
rm -f fuzzershell fuzzershell.exe
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
C In\sthe\sskip-ahead-distinct\soptimization,\sfix\sa\sbug\sin\sthe\slogic\sthat\sdetermines\nwhen\sto\sinvoke\sthe\soptimization\sbased\son\ssqlite_stat1\sstatistics.
|
||||
D 2017-04-15T11:53:47.342
|
||||
C Speed\sthis\sbranch\sup\sa\sbit\sby\sfiltering\sbefore\sthe\svirtual\stable\slayer\swhen\nsampling\suser\sdata.
|
||||
D 2017-04-20T16:43:32.927
|
||||
F Makefile.in 1cc758ce3374a32425e4d130c2fe7b026b20de5b8843243de75f087c0a2661fb
|
||||
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
|
||||
F Makefile.msc 6a8c838220f7c00820e1fc0ac1bccaaa8e5676067e1dbfa1bafa7a4ffecf8ae6
|
||||
@@ -40,6 +40,12 @@ F ext/README.md fd5f78013b0a2bc6f0067afb19e6ad040e89a10179b4f6f03eee58fac5f169bd
|
||||
F ext/async/README.txt e12275968f6fde133a80e04387d0e839b0c51f91
|
||||
F ext/async/sqlite3async.c 0f3070cc3f5ede78f2b9361fb3b629ce200d7d74
|
||||
F ext/async/sqlite3async.h f489b080af7e72aec0e1ee6f1d98ab6cf2e4dcef
|
||||
F ext/expert/README.md 9f15075ec5ad772808eff55ef044c31140fd1146aa0a3c47eafd155e71851b01
|
||||
F ext/expert/expert.c 33842ef151d84c5f8000f9c7b938998c6b999eaef7ce1f4eeb0df8ffe6739496
|
||||
F ext/expert/expert1.test 1033e43071b69dc2f4e88fbf03fc7f18846c9865cac14f28c80f581437f09acb
|
||||
F ext/expert/sqlite3expert.c fde366d8c1b1970b2c18196ca2e64d01c2106bd9431c371a26e8d5b79f37f90b
|
||||
F ext/expert/sqlite3expert.h af6354f8ee5c9e025024e63fec3bd640a802afcc3099a44d804752cf0791d811
|
||||
F ext/expert/test_expert.c b01a5115f9444a9b416582c985138f5dfdb279848ce8b7452be383530be27f01
|
||||
F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e
|
||||
F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b
|
||||
F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea
|
||||
@@ -325,7 +331,7 @@ F ext/userauth/userauth.c 3410be31283abba70255d71fd24734e017a4497f
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
|
||||
F main.mk 9abb506e717887d57f754bae139b85c1a06d6f2ac25b589f3e792e310567f278
|
||||
F main.mk ef818c7b1bb21f657e3bfb363cc7167264d688ca404a666e6ddda6029e94c43b
|
||||
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
|
||||
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
|
||||
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
|
||||
@@ -347,7 +353,7 @@ F src/btmutex.c 0e9ce2d56159b89b9bc8e197e023ee11e39ff8ca
|
||||
F src/btree.c 24ae5472bd0b53b4130ecdda389deb621af721d1fcb50890b878102b00bd10fa
|
||||
F src/btree.h 80f518c0788be6cec8d9f8e13bd8e380df299d2b5e4ac340dc887b0642647cfc
|
||||
F src/btreeInt.h a392d353104b4add58b4a59cb185f5d5693dde832c565b77d8d4c343ed98f610
|
||||
F src/build.c 4026a9c554b233e50c5e9ad46963e676cf54dd2306d952aa1eaa07a1bc9ce14f
|
||||
F src/build.c 3fd46781483b527ee18508e7854e87e60a259211bb9bbf16b6fafaf08a043a64
|
||||
F src/callback.c 2e76147783386374bf01b227f752c81ec872d730
|
||||
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
|
||||
F src/ctime.c 47d91a25ad8f199a71a5b1b7b169d6dd0d6e98c5719eca801568798743d1161c
|
||||
@@ -366,7 +372,7 @@ F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
|
||||
F src/insert.c d4bb3a135948553d18cf992f76f7ed7b18aa0327f250607b5a6671e55d9947d5
|
||||
F src/legacy.c e88ed13c2d531decde75d42c2e35623fb9ce3cb0
|
||||
F src/loadext.c a72909474dadce771d3669bf84bf689424f6f87d471fee898589c3ef9b2acfd9
|
||||
F src/main.c 158326243c5ddc8b98a1e983fa488650cf76d760
|
||||
F src/main.c ffb658429483c0d1c604014c631871f64b8db19666d8b70f75c7512d003ac1ad
|
||||
F src/malloc.c e20bb2b48abec52d3faf01cce12e8b4f95973755fafec98d45162dfdab111978
|
||||
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
|
||||
F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de
|
||||
@@ -394,23 +400,23 @@ F src/parse.y 48b03113704ee8bd78ee6996d81de7fbee22e105
|
||||
F src/pcache.c 62835bed959e2914edd26afadfecce29ece0e870
|
||||
F src/pcache.h 2cedcd8407eb23017d92790b112186886e179490
|
||||
F src/pcache1.c 1195a21fe28e223e024f900b2011e80df53793f0356a24caace4188b098540dc
|
||||
F src/pragma.c 220474f113cade6a6b5bacd3e3a65eca339acbee804128bc5db13a9cad55fba5
|
||||
F src/pragma.c 150821702fc90694b46c3432c1402fc970a4c5b8595cb13c21aeb568f9a78fc3
|
||||
F src/pragma.h 37a1311d0388db480388d7ec09054f7103045eff20d4971f8a433b77f40b9921
|
||||
F src/prepare.c b1140c3d0cf59bc85ace00ce363153041b424b7a
|
||||
F src/prepare.c 7c46b5c7be9e19a1bf87777f0b7f9fb257b5ff9856c46de49f2354acfbeb4c86
|
||||
F src/printf.c 8757834f1b54dae512fb25eb1acc8e94a0d15dd2290b58f2563f65973265adb2
|
||||
F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
|
||||
F src/resolve.c 3e518b962d932a997fae373366880fc028c75706
|
||||
F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
|
||||
F src/select.c 4588dcfb0fa430012247a209ba08e17904dd32ec7690e9cb6c85e0ef012b0518
|
||||
F src/shell.c 70f4957b988572315e97c56941fdc81fd35907fee36b7b2e7be5ec4c7e9d065d
|
||||
F src/sqlite.h.in 40233103e3e4e10f8a63523498d0259d232e42aba478e2d3fb914799185aced6
|
||||
F src/sqlite.h.in 900a07463a87be50b9954817f4c24a0660b4c4ddc1bfe83dedea484c6ac98425
|
||||
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
|
||||
F src/sqlite3ext.h 58fd0676d3111d02e62e5a35992a7d3da5d3f88753acc174f2d37b774fbbdd28
|
||||
F src/sqliteInt.h 9affb53bb405dcea1d86e85198ebaf6232a684cc2b2af6b3c181869f1c8f3e93
|
||||
F src/sqliteInt.h 0e520ab49f019221dd5a17b6e4006523ce4f33d88b20bcf9115d11952a487c39
|
||||
F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b
|
||||
F src/status.c a9e66593dfb28a9e746cba7153f84d49c1ddc4b1
|
||||
F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34
|
||||
F src/tclsqlite.c 6c2151b6d8d98e183a04466d40df8889c0574d79
|
||||
F src/tclsqlite.c 2e0f7f63de8329526fbcb14fa5261d3574b2a06dd330f4df680120a3e6156133
|
||||
F src/test1.c 8a98191a1da8e100f77cdb5cc716df67d405028d
|
||||
F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5
|
||||
F src/test3.c d03f5b5da9a2410b7a91c64b0d3306ed28ab6fee
|
||||
@@ -463,16 +469,16 @@ F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
|
||||
F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c
|
||||
F src/tokenize.c 1003d6d90c6783206c711f0a9397656fa5b055209f4d092caa43bb3bf5215db5
|
||||
F src/treeview.c b92d57c1ac59f4a3f6b189506921a2b48098f6f4d6afd0b715bc2815ef6af092
|
||||
F src/trigger.c c9f0810043b265724fdb1bdd466894f984dfc182
|
||||
F src/trigger.c 134b8e7b61317ab7b2a2dd12eb1b9aa2e23ac5bc4a05e63e35b3609b6b30a7c0
|
||||
F src/update.c c443935c652af9365e033f756550b5032d02e1b06eb2cb890ed7511ae0c051dc
|
||||
F src/utf.c 699001c79f28e48e9bcdf8a463da029ea660540c
|
||||
F src/util.c ca8440ede81e155d15cff7c101654f60b55a9ae6
|
||||
F src/vacuum.c 1fe4555cd8c9b263afb85b5b4ee3a4a4181ad569
|
||||
F src/vdbe.c 808fda3d50f544120d27c731449b524b4ec8f8b0f734b228831078f0ba53ecb9
|
||||
F src/vdbe.c 314f0a70ddc29e63f131dfbe6f53c277875d3028cdf5654a709e21cab39fe0c9
|
||||
F src/vdbe.h f7d1456e28875c2dcb964056589b5b7149ab7edf39edeca801596a39bb3d3848
|
||||
F src/vdbeInt.h c070bc5c8b913bda0ceaa995cd4d939ded5e4fc96cf7c3c1c602d41b871f8ade
|
||||
F src/vdbeapi.c 5b08d82592bcff4470601fe78aaabebd50837860
|
||||
F src/vdbeaux.c 6b3f6ce909e206d4c918988b13b7fa687e92b4471d137e0f2a37edac80ec60be
|
||||
F src/vdbeaux.c 526b617ac6b5e167a6bd581e067f1ee1dbcb06e7802cff46b76fb1c02ed7d34e
|
||||
F src/vdbeblob.c 359891617358deefc85bef7bcf787fa6b77facb9
|
||||
F src/vdbemem.c 3122f5a21064198c10ee1b4686937aab27d5395712d9af905b7fa1affc47a453
|
||||
F src/vdbesort.c e72fe02a2121386ba767ede8942e9450878b8fc873abf3d1b6824485f092570c
|
||||
@@ -482,7 +488,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
|
||||
F src/wal.c 40c543f0a2195d1b0dc88ef12142bea690009344
|
||||
F src/wal.h 06b2a0b599cc0f53ea97f497cf8c6b758c999f71
|
||||
F src/walker.c b71a992b413b3a022572eccf29ef4b4890223791
|
||||
F src/where.c 10ae856aa4bcf7c6be39b6b53422fc94e713ab8a19383b4cf525bc05ae70d872
|
||||
F src/where.c d22a2ae7f823f5405e5d00c3397204ce1272d53bc3f2fc0c516315f143eb1a24
|
||||
F src/whereInt.h 2a4b634d63ce488b46d4b0da8f2eaa8f9aeab202bc25ef76f007de5e3fba1f20
|
||||
F src/wherecode.c 943e32e9dccd0af802e0683ae11071c8bd808364e5908a5fb66758bd404c8681
|
||||
F src/whereexpr.c e913aaa7b73ffcce66abcea5f197e2c538d48b5df78d0b7bba8ff4d73cc2e745
|
||||
@@ -1029,7 +1035,7 @@ F test/parser1.test 391b9bf9a229547a129c61ac345ed1a6f5eb1854
|
||||
F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
|
||||
F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
|
||||
F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
|
||||
F test/permutations.test af720e7d139e7e5417341d0f0eef2b911c0b067852138dc2f5b6a451b5725118
|
||||
F test/permutations.test 9c0da2079fa37e7509957c9efbbdc282dea4ed0e732d19e6f216d53ae431a67d
|
||||
F test/pragma.test 1e94755164a3a3264cd39836de4bebcb7809e5f8
|
||||
F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f
|
||||
F test/pragma3.test 14c12bc5352b1e100e0b6b44f371053a81ccf8ed
|
||||
@@ -1525,6 +1531,7 @@ F tool/replace.tcl 60f91e8dd06ab81f74d213ecbd9c9945f32ac048
|
||||
F tool/restore_jrnl.tcl 6957a34f8f1f0f8285e07536225ec3b292a9024a
|
||||
F tool/rollback-test.c 9fc98427d1e23e84429d7e6d07d9094fbdec65a5
|
||||
F tool/run-speed-test.sh f95d19fd669b68c4c38b6b475242841d47c66076
|
||||
F tool/schemalint.tcl 49690356702d6cac07e2bb1790eac73862e92926
|
||||
F tool/showdb.c e6bc9dba233bf1b57ca0a525a2bba762db4e223de84990739db3f09c46151b1e
|
||||
F tool/showjournal.c 5bad7ae8784a43d2b270d953060423b8bd480818
|
||||
F tool/showlocks.c 9920bcc64f58378ff1118caead34147201f48c68
|
||||
@@ -1572,8 +1579,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P d78355c85f49e139f1ac1a660563865350f0e442652b7bc50b684398f81cc602 e50fd48969f99bc988389c53ff46714603b1d11de12c71b55c00cbee037f073c
|
||||
R a0fc0755acaae22ae06336777dc36757
|
||||
T +closed e50fd48969f99bc988389c53ff46714603b1d11de12c71b55c00cbee037f073c
|
||||
U drh
|
||||
Z 7aacb0cf945619cb618007e65cb171f4
|
||||
P c62e358243d96cb38a7ce2aa679fc640b62bf46080eab4bd5fc2acf5997d6cd5
|
||||
R 73307a63b82b2606f65f28fc989fd3ae
|
||||
U dan
|
||||
Z b05e3391f67ce73794363163fdd05070
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
89f9e4363aa19f306e55f749c442eae2f8994f6a47c65e645a79b308b450d5e5
|
||||
8e57c31340dd9ffc457da63c5996fb1b573f8154f864ec2b52c15f399906ac8b
|
||||
+6
-6
@@ -479,7 +479,7 @@ void sqlite3UnlinkAndDeleteIndex(sqlite3 *db, int iDb, const char *zIdxName){
|
||||
}
|
||||
freeIndex(db, pIndex);
|
||||
}
|
||||
db->flags |= SQLITE_InternChanges;
|
||||
db->bInternChanges = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -551,7 +551,7 @@ void sqlite3ResetAllSchemasOfConnection(sqlite3 *db){
|
||||
sqlite3SchemaClear(pDb->pSchema);
|
||||
}
|
||||
}
|
||||
db->flags &= ~SQLITE_InternChanges;
|
||||
db->bInternChanges = 0;
|
||||
sqlite3VtabUnlockList(db);
|
||||
sqlite3BtreeLeaveAll(db);
|
||||
sqlite3CollapseDatabaseArray(db);
|
||||
@@ -561,7 +561,7 @@ void sqlite3ResetAllSchemasOfConnection(sqlite3 *db){
|
||||
** This routine is called when a commit occurs.
|
||||
*/
|
||||
void sqlite3CommitInternalChanges(sqlite3 *db){
|
||||
db->flags &= ~SQLITE_InternChanges;
|
||||
db->bInternChanges = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -665,7 +665,7 @@ void sqlite3UnlinkAndDeleteTable(sqlite3 *db, int iDb, const char *zTabName){
|
||||
pDb = &db->aDb[iDb];
|
||||
p = sqlite3HashInsert(&pDb->pSchema->tblHash, zTabName, 0);
|
||||
sqlite3DeleteTable(db, p);
|
||||
db->flags |= SQLITE_InternChanges;
|
||||
db->bInternChanges = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2051,7 +2051,7 @@ void sqlite3EndTable(
|
||||
return;
|
||||
}
|
||||
pParse->pNewTable = 0;
|
||||
db->flags |= SQLITE_InternChanges;
|
||||
db->bInternChanges = 1;
|
||||
|
||||
#ifndef SQLITE_OMIT_ALTERTABLE
|
||||
if( !p->pSelect ){
|
||||
@@ -3320,7 +3320,7 @@ void sqlite3CreateIndex(
|
||||
sqlite3OomFault(db);
|
||||
goto exit_create_index;
|
||||
}
|
||||
db->flags |= SQLITE_InternChanges;
|
||||
db->bInternChanges = 1;
|
||||
if( pTblName!=0 ){
|
||||
pIndex->tnum = db->init.newTnum;
|
||||
}
|
||||
|
||||
+3
-2
@@ -811,6 +811,7 @@ int sqlite3_db_config(sqlite3 *db, int op, ...){
|
||||
{ SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, SQLITE_Fts3Tokenizer },
|
||||
{ SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, SQLITE_LoadExtension },
|
||||
{ SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, SQLITE_NoCkptOnClose },
|
||||
{ SQLITE_DBCONFIG_FULL_EQP, SQLITE_FullEQP },
|
||||
};
|
||||
unsigned int i;
|
||||
rc = SQLITE_ERROR; /* IMP: R-42790-23372 */
|
||||
@@ -1252,7 +1253,7 @@ void sqlite3RollbackAll(sqlite3 *db, int tripCode){
|
||||
** the database rollback and schema reset, which can cause false
|
||||
** corruption reports in some cases. */
|
||||
sqlite3BtreeEnterAll(db);
|
||||
schemaChange = (db->flags & SQLITE_InternChanges)!=0 && db->init.busy==0;
|
||||
schemaChange = db->bInternChanges && db->init.busy==0;
|
||||
|
||||
for(i=0; i<db->nDb; i++){
|
||||
Btree *p = db->aDb[i].pBt;
|
||||
@@ -1266,7 +1267,7 @@ void sqlite3RollbackAll(sqlite3 *db, int tripCode){
|
||||
sqlite3VtabRollback(db);
|
||||
sqlite3EndBenignMalloc();
|
||||
|
||||
if( (db->flags&SQLITE_InternChanges)!=0 && db->init.busy==0 ){
|
||||
if( db->bInternChanges && db->init.busy==0 ){
|
||||
sqlite3ExpirePreparedStatements(db);
|
||||
sqlite3ResetAllSchemasOfConnection(db);
|
||||
}
|
||||
|
||||
@@ -1076,6 +1076,7 @@ void sqlite3Pragma(
|
||||
** type: Column declaration type.
|
||||
** notnull: True if 'NOT NULL' is part of column declaration
|
||||
** dflt_value: The default value for the column, if any.
|
||||
** pk: Non-zero for PK fields.
|
||||
*/
|
||||
case PragTyp_TABLE_INFO: if( zRight ){
|
||||
Table *pTab;
|
||||
|
||||
+1
-1
@@ -354,7 +354,7 @@ error_out:
|
||||
*/
|
||||
int sqlite3Init(sqlite3 *db, char **pzErrMsg){
|
||||
int i, rc;
|
||||
int commit_internal = !(db->flags&SQLITE_InternChanges);
|
||||
int commit_internal = db->bInternChanges==0;
|
||||
|
||||
assert( sqlite3_mutex_held(db->mutex) );
|
||||
assert( sqlite3BtreeHoldsMutex(db->aDb[0].pBt) );
|
||||
|
||||
@@ -2007,6 +2007,16 @@ struct sqlite3_mem_methods {
|
||||
** have been disabled - 0 if they are not disabled, 1 if they are.
|
||||
** </dd>
|
||||
**
|
||||
** <dt>SQLITE_DBCONFIG_FULL_EQP</dt>
|
||||
** <dd> By default, the output of EXPLAIN QUERY PLAN commands does not
|
||||
** include output for any operations performed by trigger programs. This
|
||||
** option is used to set or clear (the default) a flag that governs this
|
||||
** behavior. The first parameter passed to this operation is an integer -
|
||||
** non-zero to enable output for trigger programs, or zero to disable it.
|
||||
** The second parameter is a pointer to an integer into which is written
|
||||
** 0 or 1 to indicate whether output-for-triggers has been disabled - 0 if
|
||||
** it is not disabled, 1 if it is.
|
||||
** </dd>
|
||||
** </dl>
|
||||
*/
|
||||
#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */
|
||||
@@ -2016,6 +2026,7 @@ struct sqlite3_mem_methods {
|
||||
#define SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004 /* int int* */
|
||||
#define SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005 /* int int* */
|
||||
#define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006 /* int int* */
|
||||
#define SQLITE_DBCONFIG_FULL_EQP 1007 /* int int* */
|
||||
|
||||
|
||||
/*
|
||||
@@ -8037,6 +8048,22 @@ int sqlite3_vtab_config(sqlite3*, int op, ...);
|
||||
*/
|
||||
int sqlite3_vtab_on_conflict(sqlite3 *);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Determine The Collation For a Virtual Table Constraint
|
||||
**
|
||||
** This function may only be called from within a call to the [xBestIndex]
|
||||
** method of a [virtual table implementation].
|
||||
**
|
||||
** The first argument must be the database handle with which the virtual
|
||||
** table is associated (the one passed to the [xConnect] or [xCreate] method
|
||||
** to create the sqlite3_vtab object. The second argument must be an index
|
||||
** into the aConstraint[] array belonging to the sqlite3_index_info structure
|
||||
** passed to xBestIndex. This function returns a pointer to a buffer
|
||||
** containing the name of the collation sequence for the corresponding
|
||||
** constraint.
|
||||
*/
|
||||
SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3*, int);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Conflict resolution modes
|
||||
** KEYWORDS: {conflict resolution mode}
|
||||
|
||||
+3
-1
@@ -1334,6 +1334,7 @@ struct sqlite3 {
|
||||
u8 mTrace; /* zero or more SQLITE_TRACE flags */
|
||||
u8 skipBtreeMutex; /* True if no shared-cache backends */
|
||||
u8 nSqlExec; /* Number of pending OP_SqlExec opcodes */
|
||||
u8 bInternChanges; /* There are uncommited schema changes */
|
||||
int nextPagesize; /* Pagesize after VACUUM if >0 */
|
||||
u32 magic; /* Magic number for detect library misuse */
|
||||
int nChange; /* Value returned by sqlite3_changes() */
|
||||
@@ -1399,6 +1400,7 @@ struct sqlite3 {
|
||||
VtabCtx *pVtabCtx; /* Context for active vtab connect/create */
|
||||
VTable **aVTrans; /* Virtual tables with open transactions */
|
||||
VTable *pDisconnect; /* Disconnect these in next sqlite3_prepare() */
|
||||
void *pBestIndexCtx; /* For sqlite3_vtab_collation() */
|
||||
#endif
|
||||
Hash aFunc; /* Hash table of connection functions */
|
||||
Hash aCollSeq; /* All collating sequences */
|
||||
@@ -1448,7 +1450,7 @@ struct sqlite3 {
|
||||
** SQLITE_CacheSpill == PAGER_CACHE_SPILL
|
||||
*/
|
||||
#define SQLITE_VdbeTrace 0x00000001 /* True to trace VDBE execution */
|
||||
#define SQLITE_InternChanges 0x00000002 /* Uncommitted Hash table changes */
|
||||
#define SQLITE_FullEQP 0x00000002 /* Include triggers in EQP output */
|
||||
#define SQLITE_FullColNames 0x00000004 /* Show full column names on SELECT */
|
||||
#define SQLITE_FullFSync 0x00000008 /* Use full fsync on the backend */
|
||||
#define SQLITE_CkptFullFSync 0x00000010 /* Use full fsync for checkpoint */
|
||||
|
||||
@@ -4129,6 +4129,7 @@ static void init_all(Tcl_Interp *interp){
|
||||
#if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
|
||||
extern int TestSession_Init(Tcl_Interp*);
|
||||
#endif
|
||||
extern int TestExpert_Init(Tcl_Interp*);
|
||||
extern int Fts5tcl_Init(Tcl_Interp *);
|
||||
extern int SqliteRbu_Init(Tcl_Interp*);
|
||||
extern int Sqlitetesttcl_Init(Tcl_Interp*);
|
||||
@@ -4177,6 +4178,7 @@ static void init_all(Tcl_Interp *interp){
|
||||
#if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
|
||||
TestSession_Init(interp);
|
||||
#endif
|
||||
TestExpert_Init(interp);
|
||||
Fts5tcl_Init(interp);
|
||||
SqliteRbu_Init(interp);
|
||||
Sqlitetesttcl_Init(interp);
|
||||
|
||||
+1
-1
@@ -584,7 +584,7 @@ void sqlite3UnlinkAndDeleteTrigger(sqlite3 *db, int iDb, const char *zName){
|
||||
*pp = (*pp)->pNext;
|
||||
}
|
||||
sqlite3DeleteTrigger(db, pTrigger);
|
||||
db->flags |= SQLITE_InternChanges;
|
||||
db->bInternChanges = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -3037,7 +3037,7 @@ case OP_Savepoint: {
|
||||
int isSchemaChange;
|
||||
iSavepoint = db->nSavepoint - iSavepoint - 1;
|
||||
if( p1==SAVEPOINT_ROLLBACK ){
|
||||
isSchemaChange = (db->flags & SQLITE_InternChanges)!=0;
|
||||
isSchemaChange = db->bInternChanges;
|
||||
for(ii=0; ii<db->nDb; ii++){
|
||||
rc = sqlite3BtreeTripAllCursors(db->aDb[ii].pBt,
|
||||
SQLITE_ABORT_ROLLBACK,
|
||||
@@ -3056,7 +3056,7 @@ case OP_Savepoint: {
|
||||
if( isSchemaChange ){
|
||||
sqlite3ExpirePreparedStatements(db);
|
||||
sqlite3ResetAllSchemasOfConnection(db);
|
||||
db->flags = (db->flags | SQLITE_InternChanges);
|
||||
db->bInternChanges = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3336,7 +3336,7 @@ case OP_SetCookie: {
|
||||
if( pOp->p2==BTREE_SCHEMA_VERSION ){
|
||||
/* When the schema cookie changes, record the new cookie internally */
|
||||
pDb->pSchema->schema_cookie = pOp->p3;
|
||||
db->flags |= SQLITE_InternChanges;
|
||||
db->bInternChanges = 1;
|
||||
}else if( pOp->p2==BTREE_FILE_FORMAT ){
|
||||
/* Record changes in the file format */
|
||||
pDb->pSchema->file_format = pOp->p3;
|
||||
|
||||
+97
-88
@@ -1611,6 +1611,8 @@ int sqlite3VdbeList(
|
||||
int i; /* Loop counter */
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
Mem *pMem = &p->aMem[1]; /* First Mem of result set */
|
||||
int bFull = (p->explain==1 || (db->flags & SQLITE_FullEQP));
|
||||
Op *pOp;
|
||||
|
||||
assert( p->explain );
|
||||
assert( p->magic==VDBE_MAGIC_RUN );
|
||||
@@ -1638,7 +1640,7 @@ int sqlite3VdbeList(
|
||||
** encountered, but p->pc will eventually catch up to nRow.
|
||||
*/
|
||||
nRow = p->nOp;
|
||||
if( p->explain==1 ){
|
||||
if( bFull ){
|
||||
/* The first 8 memory cells are used for the result set. So we will
|
||||
** commandeer the 9th cell to use as storage for an array of pointers
|
||||
** to trigger subprograms. The VDBE is guaranteed to have at least 9
|
||||
@@ -1658,17 +1660,11 @@ int sqlite3VdbeList(
|
||||
|
||||
do{
|
||||
i = p->pc++;
|
||||
}while( i<nRow && p->explain==2 && p->aOp[i].opcode!=OP_Explain );
|
||||
if( i>=nRow ){
|
||||
p->rc = SQLITE_OK;
|
||||
rc = SQLITE_DONE;
|
||||
}else if( db->u1.isInterrupted ){
|
||||
p->rc = SQLITE_INTERRUPT;
|
||||
rc = SQLITE_ERROR;
|
||||
sqlite3VdbeError(p, sqlite3ErrStr(p->rc));
|
||||
}else{
|
||||
char *zP4;
|
||||
Op *pOp;
|
||||
if( i>=nRow ){
|
||||
p->rc = SQLITE_OK;
|
||||
rc = SQLITE_DONE;
|
||||
break;
|
||||
}
|
||||
if( i<p->nOp ){
|
||||
/* The output line number is small enough that we are still in the
|
||||
** main program. */
|
||||
@@ -1683,94 +1679,107 @@ int sqlite3VdbeList(
|
||||
}
|
||||
pOp = &apSub[j]->aOp[i];
|
||||
}
|
||||
if( p->explain==1 ){
|
||||
pMem->flags = MEM_Int;
|
||||
pMem->u.i = i; /* Program counter */
|
||||
pMem++;
|
||||
|
||||
pMem->flags = MEM_Static|MEM_Str|MEM_Term;
|
||||
pMem->z = (char*)sqlite3OpcodeName(pOp->opcode); /* Opcode */
|
||||
assert( pMem->z!=0 );
|
||||
pMem->n = sqlite3Strlen30(pMem->z);
|
||||
pMem->enc = SQLITE_UTF8;
|
||||
pMem++;
|
||||
|
||||
/* When an OP_Program opcode is encounter (the only opcode that has
|
||||
** a P4_SUBPROGRAM argument), expand the size of the array of subprograms
|
||||
** kept in p->aMem[9].z to hold the new program - assuming this subprogram
|
||||
** has not already been seen.
|
||||
*/
|
||||
if( pOp->p4type==P4_SUBPROGRAM ){
|
||||
int nByte = (nSub+1)*sizeof(SubProgram*);
|
||||
int j;
|
||||
for(j=0; j<nSub; j++){
|
||||
if( apSub[j]==pOp->p4.pProgram ) break;
|
||||
}
|
||||
if( j==nSub && SQLITE_OK==sqlite3VdbeMemGrow(pSub, nByte, nSub!=0) ){
|
||||
apSub = (SubProgram **)pSub->z;
|
||||
apSub[nSub++] = pOp->p4.pProgram;
|
||||
pSub->flags |= MEM_Blob;
|
||||
pSub->n = nSub*sizeof(SubProgram*);
|
||||
}
|
||||
/* When an OP_Program opcode is encounter (the only opcode that has
|
||||
** a P4_SUBPROGRAM argument), expand the size of the array of subprograms
|
||||
** kept in p->aMem[9].z to hold the new program - assuming this subprogram
|
||||
** has not already been seen.
|
||||
*/
|
||||
if( bFull && pOp->p4type==P4_SUBPROGRAM ){
|
||||
int nByte = (nSub+1)*sizeof(SubProgram*);
|
||||
int j;
|
||||
for(j=0; j<nSub; j++){
|
||||
if( apSub[j]==pOp->p4.pProgram ) break;
|
||||
}
|
||||
if( j==nSub ){
|
||||
rc = sqlite3VdbeMemGrow(pSub, nByte, nSub!=0);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
apSub = (SubProgram **)pSub->z;
|
||||
apSub[nSub++] = pOp->p4.pProgram;
|
||||
pSub->flags |= MEM_Blob;
|
||||
pSub->n = nSub*sizeof(SubProgram*);
|
||||
nRow += pOp->p4.pProgram->nOp;
|
||||
}
|
||||
}
|
||||
}while( p->explain==2 && pOp->opcode!=OP_Explain );
|
||||
|
||||
pMem->flags = MEM_Int;
|
||||
pMem->u.i = pOp->p1; /* P1 */
|
||||
pMem++;
|
||||
|
||||
pMem->flags = MEM_Int;
|
||||
pMem->u.i = pOp->p2; /* P2 */
|
||||
pMem++;
|
||||
|
||||
pMem->flags = MEM_Int;
|
||||
pMem->u.i = pOp->p3; /* P3 */
|
||||
pMem++;
|
||||
|
||||
if( sqlite3VdbeMemClearAndResize(pMem, 100) ){ /* P4 */
|
||||
assert( p->db->mallocFailed );
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
pMem->flags = MEM_Str|MEM_Term;
|
||||
zP4 = displayP4(pOp, pMem->z, pMem->szMalloc);
|
||||
if( zP4!=pMem->z ){
|
||||
pMem->n = 0;
|
||||
sqlite3VdbeMemSetStr(pMem, zP4, -1, SQLITE_UTF8, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
if( db->u1.isInterrupted ){
|
||||
p->rc = SQLITE_INTERRUPT;
|
||||
rc = SQLITE_ERROR;
|
||||
sqlite3VdbeError(p, sqlite3ErrStr(p->rc));
|
||||
}else{
|
||||
assert( pMem->z!=0 );
|
||||
pMem->n = sqlite3Strlen30(pMem->z);
|
||||
pMem->enc = SQLITE_UTF8;
|
||||
}
|
||||
pMem++;
|
||||
|
||||
if( p->explain==1 ){
|
||||
if( sqlite3VdbeMemClearAndResize(pMem, 4) ){
|
||||
assert( p->db->mallocFailed );
|
||||
return SQLITE_ERROR;
|
||||
char *zP4;
|
||||
if( p->explain==1 ){
|
||||
pMem->flags = MEM_Int;
|
||||
pMem->u.i = i; /* Program counter */
|
||||
pMem++;
|
||||
|
||||
pMem->flags = MEM_Static|MEM_Str|MEM_Term;
|
||||
pMem->z = (char*)sqlite3OpcodeName(pOp->opcode); /* Opcode */
|
||||
assert( pMem->z!=0 );
|
||||
pMem->n = sqlite3Strlen30(pMem->z);
|
||||
pMem->enc = SQLITE_UTF8;
|
||||
pMem++;
|
||||
}
|
||||
pMem->flags = MEM_Str|MEM_Term;
|
||||
pMem->n = 2;
|
||||
sqlite3_snprintf(3, pMem->z, "%.2x", pOp->p5); /* P5 */
|
||||
pMem->enc = SQLITE_UTF8;
|
||||
|
||||
pMem->flags = MEM_Int;
|
||||
pMem->u.i = pOp->p1; /* P1 */
|
||||
pMem++;
|
||||
|
||||
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
|
||||
if( sqlite3VdbeMemClearAndResize(pMem, 500) ){
|
||||
|
||||
pMem->flags = MEM_Int;
|
||||
pMem->u.i = pOp->p2; /* P2 */
|
||||
pMem++;
|
||||
|
||||
pMem->flags = MEM_Int;
|
||||
pMem->u.i = pOp->p3; /* P3 */
|
||||
pMem++;
|
||||
|
||||
if( sqlite3VdbeMemClearAndResize(pMem, 100) ){ /* P4 */
|
||||
assert( p->db->mallocFailed );
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
pMem->flags = MEM_Str|MEM_Term;
|
||||
pMem->n = displayComment(pOp, zP4, pMem->z, 500);
|
||||
pMem->enc = SQLITE_UTF8;
|
||||
#else
|
||||
pMem->flags = MEM_Null; /* Comment */
|
||||
#endif
|
||||
}
|
||||
zP4 = displayP4(pOp, pMem->z, pMem->szMalloc);
|
||||
if( zP4!=pMem->z ){
|
||||
pMem->n = 0;
|
||||
sqlite3VdbeMemSetStr(pMem, zP4, -1, SQLITE_UTF8, 0);
|
||||
}else{
|
||||
assert( pMem->z!=0 );
|
||||
pMem->n = sqlite3Strlen30(pMem->z);
|
||||
pMem->enc = SQLITE_UTF8;
|
||||
}
|
||||
pMem++;
|
||||
|
||||
p->nResColumn = 8 - 4*(p->explain-1);
|
||||
p->pResultSet = &p->aMem[1];
|
||||
p->rc = SQLITE_OK;
|
||||
rc = SQLITE_ROW;
|
||||
if( p->explain==1 ){
|
||||
if( sqlite3VdbeMemClearAndResize(pMem, 4) ){
|
||||
assert( p->db->mallocFailed );
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
pMem->flags = MEM_Str|MEM_Term;
|
||||
pMem->n = 2;
|
||||
sqlite3_snprintf(3, pMem->z, "%.2x", pOp->p5); /* P5 */
|
||||
pMem->enc = SQLITE_UTF8;
|
||||
pMem++;
|
||||
|
||||
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
|
||||
if( sqlite3VdbeMemClearAndResize(pMem, 500) ){
|
||||
assert( p->db->mallocFailed );
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
pMem->flags = MEM_Str|MEM_Term;
|
||||
pMem->n = displayComment(pOp, zP4, pMem->z, 500);
|
||||
pMem->enc = SQLITE_UTF8;
|
||||
#else
|
||||
pMem->flags = MEM_Null; /* Comment */
|
||||
#endif
|
||||
}
|
||||
|
||||
p->nResColumn = 8 - 4*(p->explain-1);
|
||||
p->pResultSet = &p->aMem[1];
|
||||
p->rc = SQLITE_OK;
|
||||
rc = SQLITE_ROW;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
+40
-1
@@ -885,7 +885,8 @@ static sqlite3_index_info *allocateIndexInfo(
|
||||
*/
|
||||
pIdxInfo = sqlite3DbMallocZero(pParse->db, sizeof(*pIdxInfo)
|
||||
+ (sizeof(*pIdxCons) + sizeof(*pUsage))*nTerm
|
||||
+ sizeof(*pIdxOrderBy)*nOrderBy );
|
||||
+ sizeof(*pIdxOrderBy)*nOrderBy
|
||||
);
|
||||
if( pIdxInfo==0 ){
|
||||
sqlite3ErrorMsg(pParse, "out of memory");
|
||||
return 0;
|
||||
@@ -3116,6 +3117,35 @@ static int whereLoopAddVirtualOne(
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Context object used to pass information from whereLoopAddVirtual()
|
||||
** to sqlite3_vtab_collation().
|
||||
*/
|
||||
struct BestIndexCtx {
|
||||
WhereClause *pWC;
|
||||
sqlite3_index_info *pIdxInfo;
|
||||
Parse *pParse;
|
||||
};
|
||||
|
||||
/*
|
||||
** If this function is invoked from within an xBestIndex() callback, it
|
||||
** returns a pointer to a buffer containing the name of the collation
|
||||
** sequence associated with element iCons of the sqlite3_index_info.aConstraint
|
||||
** array. Or, if iCons is out of range or there is no active xBestIndex
|
||||
** call, return NULL.
|
||||
*/
|
||||
const char *sqlite3_vtab_collation(sqlite3 *db, int iCons){
|
||||
struct BestIndexCtx *p = (struct BestIndexCtx*)db->pBestIndexCtx;
|
||||
const char *zRet = 0;
|
||||
if( p && iCons>=0 && iCons<p->pIdxInfo->nConstraint ){
|
||||
int iTerm = p->pIdxInfo->aConstraint[iCons].iTermOffset;
|
||||
Expr *pX = p->pWC->a[iTerm].pExpr;
|
||||
CollSeq *pC = sqlite3BinaryCompareCollSeq(p->pParse,pX->pLeft,pX->pRight);
|
||||
zRet = (pC ? pC->zName : "BINARY");
|
||||
}
|
||||
return zRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Add all WhereLoop objects for a table of the join identified by
|
||||
** pBuilder->pNew->iTab. That table is guaranteed to be a virtual table.
|
||||
@@ -3157,6 +3187,8 @@ static int whereLoopAddVirtual(
|
||||
WhereLoop *pNew;
|
||||
Bitmask mBest; /* Tables used by best possible plan */
|
||||
u16 mNoOmit;
|
||||
struct BestIndexCtx bic;
|
||||
void *pSaved;
|
||||
|
||||
assert( (mPrereq & mUnusable)==0 );
|
||||
pWInfo = pBuilder->pWInfo;
|
||||
@@ -3178,6 +3210,12 @@ static int whereLoopAddVirtual(
|
||||
return SQLITE_NOMEM_BKPT;
|
||||
}
|
||||
|
||||
bic.pWC = pWC;
|
||||
bic.pIdxInfo = p;
|
||||
bic.pParse = pParse;
|
||||
pSaved = pParse->db->pBestIndexCtx;
|
||||
pParse->db->pBestIndexCtx = (void*)&bic;
|
||||
|
||||
/* First call xBestIndex() with all constraints usable. */
|
||||
WHERETRACE(0x40, (" VirtualOne: all usable\n"));
|
||||
rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn);
|
||||
@@ -3254,6 +3292,7 @@ static int whereLoopAddVirtual(
|
||||
|
||||
if( p->needToFreeIdxStr ) sqlite3_free(p->idxStr);
|
||||
sqlite3DbFreeNN(pParse->db, p);
|
||||
pParse->db->pBestIndexCtx = pSaved;
|
||||
return rc;
|
||||
}
|
||||
#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
||||
|
||||
@@ -89,6 +89,7 @@ foreach f [glob $testdir/*.test] { lappend alltests [file tail $f] }
|
||||
foreach f [glob -nocomplain \
|
||||
$testdir/../ext/rtree/*.test \
|
||||
$testdir/../ext/fts5/test/*.test \
|
||||
$testdir/../ext/expert/*.test \
|
||||
] {
|
||||
lappend alltests $f
|
||||
}
|
||||
|
||||
@@ -0,0 +1,594 @@
|
||||
if {[catch {
|
||||
|
||||
set ::VERBOSE 0
|
||||
|
||||
proc usage {} {
|
||||
puts stderr "Usage: $::argv0 ?SWITCHES? DATABASE/SCHEMA"
|
||||
puts stderr " Switches are:"
|
||||
puts stderr " -select SQL (recommend indexes for SQL statement)"
|
||||
puts stderr " -verbose (increase verbosity of output)"
|
||||
puts stderr " -test (run internal tests and then exit)"
|
||||
puts stderr ""
|
||||
exit
|
||||
}
|
||||
|
||||
# Return the quoted version of identfier $id. Quotes are only added if
|
||||
# they are required by SQLite.
|
||||
#
|
||||
# This command currently assumes that quotes are required if the
|
||||
# identifier contains any ASCII-range characters that are not
|
||||
# alpha-numeric or underscores.
|
||||
#
|
||||
proc quote {id} {
|
||||
if {[requires_quote $id]} {
|
||||
set x [string map {\" \"\"} $id]
|
||||
return "\"$x\""
|
||||
}
|
||||
return $id
|
||||
}
|
||||
proc requires_quote {id} {
|
||||
foreach c [split $id {}] {
|
||||
if {[string is alnum $c]==0 && $c!="_"} {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
# The argument passed to this command is a Tcl list of identifiers. The
|
||||
# value returned is the same list, except with each item quoted and the
|
||||
# elements comma-separated.
|
||||
#
|
||||
proc list_to_sql {L} {
|
||||
set ret [list]
|
||||
foreach l $L {
|
||||
lappend ret [quote $l]
|
||||
}
|
||||
join $ret ", "
|
||||
}
|
||||
|
||||
proc readfile {zFile} {
|
||||
set fd [open $zFile]
|
||||
set data [read $fd]
|
||||
close $fd
|
||||
return $data
|
||||
}
|
||||
|
||||
proc process_cmdline_args {ctxvar argv} {
|
||||
upvar $ctxvar G
|
||||
set nArg [llength $argv]
|
||||
set G(database) [lindex $argv end]
|
||||
|
||||
for {set i 0} {$i < [llength $argv]-1} {incr i} {
|
||||
set k [lindex $argv $i]
|
||||
switch -- $k {
|
||||
-select {
|
||||
incr i
|
||||
if {$i>=[llength $argv]-1} usage
|
||||
set zSelect [lindex $argv $i]
|
||||
if {[file readable $zSelect]} {
|
||||
lappend G(lSelect) [readfile $zSelect]
|
||||
} else {
|
||||
lappend G(lSelect) $zSelect
|
||||
}
|
||||
}
|
||||
-verbose {
|
||||
set ::VERBOSE 1
|
||||
}
|
||||
-test {
|
||||
sqlidx_internal_tests
|
||||
}
|
||||
default {
|
||||
usage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if {$G(database)=="-test"} {
|
||||
sqlidx_internal_tests
|
||||
}
|
||||
}
|
||||
|
||||
proc open_database {ctxvar} {
|
||||
upvar $ctxvar G
|
||||
sqlite3 db ""
|
||||
|
||||
# Check if the "database" file is really an SQLite database. If so, copy
|
||||
# it into the temp db just opened. Otherwise, assume that it is an SQL
|
||||
# schema and execute it directly.
|
||||
set fd [open $G(database)]
|
||||
set hdr [read $fd 16]
|
||||
if {$hdr == "SQLite format 3\000"} {
|
||||
close $fd
|
||||
sqlite3 db2 $G(database)
|
||||
sqlite3_backup B db main db2 main
|
||||
B step 2000000000
|
||||
set rc [B finish]
|
||||
db2 close
|
||||
if {$rc != "SQLITE_OK"} { error "Failed to load database $G(database)" }
|
||||
} else {
|
||||
append hdr [read $fd]
|
||||
db eval $hdr
|
||||
close $fd
|
||||
}
|
||||
}
|
||||
|
||||
proc analyze_selects {ctxvar} {
|
||||
upvar $ctxvar G
|
||||
set G(trace) ""
|
||||
|
||||
# Collect a line of xTrace output for each loop in the set of SELECT
|
||||
# statements.
|
||||
proc xTrace {zMsg} {
|
||||
upvar G G
|
||||
lappend G(trace) $zMsg
|
||||
}
|
||||
db trace xTrace
|
||||
foreach s $G(lSelect) {
|
||||
set stmt [sqlite3_prepare_v2 db $s -1 dummy]
|
||||
set rc [sqlite3_finalize $stmt]
|
||||
if {$rc!="SQLITE_OK"} {
|
||||
error "Failed to compile SQL: [sqlite3_errmsg db]"
|
||||
}
|
||||
}
|
||||
|
||||
db trace ""
|
||||
if {$::VERBOSE} {
|
||||
foreach t $G(trace) { puts "trace: $t" }
|
||||
}
|
||||
|
||||
# puts $G(trace)
|
||||
}
|
||||
|
||||
# The argument is a list of the form:
|
||||
#
|
||||
# key1 {value1.1 value1.2} key2 {value2.1 value 2.2...}
|
||||
#
|
||||
# Values lists may be of any length greater than zero. This function returns
|
||||
# a list of lists created by pivoting on each values list. i.e. a list
|
||||
# consisting of the elements:
|
||||
#
|
||||
# {{key1 value1.1} {key2 value2.1}}
|
||||
# {{key1 value1.2} {key2 value2.1}}
|
||||
# {{key1 value1.1} {key2 value2.2}}
|
||||
# {{key1 value1.2} {key2 value2.2}}
|
||||
#
|
||||
proc expand_eq_list {L} {
|
||||
set ll [list {}]
|
||||
for {set i 0} {$i < [llength $L]} {incr i 2} {
|
||||
set key [lindex $L $i]
|
||||
set new [list]
|
||||
foreach piv [lindex $L $i+1] {
|
||||
foreach l $ll {
|
||||
lappend new [concat $l [list [list $key $piv]]]
|
||||
}
|
||||
}
|
||||
set ll $new
|
||||
}
|
||||
|
||||
return $ll
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Formulate a CREATE INDEX statement that creates an index on table $tname.
|
||||
#
|
||||
proc eqset_to_index {ctxvar aCollVar tname eqset {range {}}} {
|
||||
upvar $ctxvar G
|
||||
upvar $aCollVar aColl
|
||||
|
||||
set rangeset [list]
|
||||
foreach e [lsort $eqset] {
|
||||
lappend rangeset [lindex $e 0] [lindex $e 1] ASC
|
||||
}
|
||||
set rangeset [concat $rangeset $range]
|
||||
|
||||
set lCols [list]
|
||||
set idxname $tname
|
||||
|
||||
foreach {c collate dir} $rangeset {
|
||||
append idxname "_$c"
|
||||
set coldef [quote $c]
|
||||
|
||||
if {[string compare -nocase $collate $aColl($c)]!=0} {
|
||||
append idxname [string tolower $collate]
|
||||
append coldef " COLLATE [quote $collate]"
|
||||
}
|
||||
|
||||
if {$dir=="DESC"} {
|
||||
append coldef " DESC"
|
||||
append idxname "desc"
|
||||
}
|
||||
lappend lCols $coldef
|
||||
}
|
||||
|
||||
set create_index "CREATE INDEX [quote $idxname] ON [quote $tname]("
|
||||
append create_index [join $lCols ", "]
|
||||
append create_index ");"
|
||||
|
||||
set G(trial.$idxname) $create_index
|
||||
}
|
||||
|
||||
proc expand_or_cons {L} {
|
||||
set lRet [list [list]]
|
||||
foreach elem $L {
|
||||
set type [lindex $elem 0]
|
||||
if {$type=="eq" || $type=="range"} {
|
||||
set lNew [list]
|
||||
for {set i 0} {$i < [llength $lRet]} {incr i} {
|
||||
lappend lNew [concat [lindex $lRet $i] [list $elem]]
|
||||
}
|
||||
set lRet $lNew
|
||||
} elseif {$type=="or"} {
|
||||
set lNew [list]
|
||||
foreach branch [lrange $elem 1 end] {
|
||||
foreach b [expand_or_cons $branch] {
|
||||
for {set i 0} {$i < [llength $lRet]} {incr i} {
|
||||
lappend lNew [concat [lindex $lRet $i] $b]
|
||||
}
|
||||
}
|
||||
}
|
||||
set lRet $lNew
|
||||
}
|
||||
}
|
||||
return $lRet
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Argument $tname is the name of a table in the main database opened by
|
||||
# database handle [db]. $arrayvar is the name of an array variable in the
|
||||
# caller's context. This command populates the array with an entry mapping
|
||||
# from column name to default collation sequence for each column of table
|
||||
# $tname. For example, if a table is declared:
|
||||
#
|
||||
# CREATE TABLE t1(a COLLATE nocase, b, c COLLATE binary)
|
||||
#
|
||||
# the mapping is populated with:
|
||||
#
|
||||
# map(a) -> "nocase"
|
||||
# map(b) -> "binary"
|
||||
# map(c) -> "binary"
|
||||
#
|
||||
proc sqlidx_get_coll_map {tname arrayvar} {
|
||||
upvar $arrayvar aColl
|
||||
set colnames [list]
|
||||
set qname [quote $tname]
|
||||
db eval "PRAGMA table_info = $qname" x { lappend colnames $x(name) }
|
||||
db eval "CREATE INDEX schemalint_test ON ${qname}([list_to_sql $colnames])"
|
||||
db eval "PRAGMA index_xinfo = schemalint_test" x {
|
||||
set aColl($x(name)) $x(coll)
|
||||
}
|
||||
db eval "DROP INDEX schemalint_test"
|
||||
}
|
||||
|
||||
proc find_trial_indexes {ctxvar} {
|
||||
upvar $ctxvar G
|
||||
foreach t $G(trace) {
|
||||
set tname [lindex $t 0]
|
||||
catch { array unset mask }
|
||||
|
||||
# Invoke "PRAGMA table_info" on the table. Use the results to create
|
||||
# an array mapping from column name to collation sequence. Store the
|
||||
# array in local variable aColl.
|
||||
#
|
||||
sqlidx_get_coll_map $tname aColl
|
||||
|
||||
set orderby [list]
|
||||
if {[lindex $t end 0]=="orderby"} {
|
||||
set orderby [lrange [lindex $t end] 1 end]
|
||||
}
|
||||
|
||||
foreach lCons [expand_or_cons [lrange $t 2 end]] {
|
||||
|
||||
# Populate the array mask() so that it contains an entry for each
|
||||
# combination of prerequisite scans that may lead to distinct sets
|
||||
# of constraints being usable.
|
||||
#
|
||||
catch { array unset mask }
|
||||
set mask(0) 1
|
||||
foreach a $lCons {
|
||||
set type [lindex $a 0]
|
||||
if {$type=="eq" || $type=="range"} {
|
||||
set m [lindex $a 3]
|
||||
foreach k [array names mask] { set mask([expr ($k & $m)]) 1 }
|
||||
set mask($m) 1
|
||||
}
|
||||
}
|
||||
|
||||
# Loop once for each distinct prerequisite scan mask identified in
|
||||
# the previous block.
|
||||
#
|
||||
foreach k [array names mask] {
|
||||
|
||||
# Identify the constraints available for prerequisite mask $k. For
|
||||
# each == constraint, set an entry in the eq() array as follows:
|
||||
#
|
||||
# set eq(<col>) <collation>
|
||||
#
|
||||
# If there is more than one == constraint for a column, and they use
|
||||
# different collation sequences, <collation> is replaced with a list
|
||||
# of the possible collation sequences. For example, for:
|
||||
#
|
||||
# SELECT * FROM t1 WHERE a=? COLLATE BINARY AND a=? COLLATE NOCASE
|
||||
#
|
||||
# Set the following entry in the eq() array:
|
||||
#
|
||||
# set eq(a) {binary nocase}
|
||||
#
|
||||
# For each range constraint found an entry is appended to the $ranges
|
||||
# list. The entry is itself a list of the form {<col> <collation>}.
|
||||
#
|
||||
catch {array unset eq}
|
||||
set ranges [list]
|
||||
foreach a $lCons {
|
||||
set type [lindex $a 0]
|
||||
if {$type=="eq" || $type=="range"} {
|
||||
foreach {type col collate m} $a {
|
||||
if {($m & $k)==$m} {
|
||||
if {$type=="eq"} {
|
||||
lappend eq($col) $collate
|
||||
} else {
|
||||
lappend ranges [list $col $collate ASC]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
set ranges [lsort -unique $ranges]
|
||||
if {$orderby != ""} {
|
||||
lappend ranges $orderby
|
||||
}
|
||||
|
||||
foreach eqset [expand_eq_list [array get eq]] {
|
||||
if {$eqset != ""} {
|
||||
eqset_to_index G aColl $tname $eqset
|
||||
}
|
||||
|
||||
foreach r $ranges {
|
||||
set tail [list]
|
||||
foreach {c collate dir} $r {
|
||||
set bSeen 0
|
||||
foreach e $eqset {
|
||||
if {[lindex $e 0] == $c} {
|
||||
set bSeen 1
|
||||
break
|
||||
}
|
||||
}
|
||||
if {$bSeen==0} { lappend tail {*}$r }
|
||||
}
|
||||
if {[llength $tail]} {
|
||||
eqset_to_index G aColl $tname $eqset $r
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if {$::VERBOSE} {
|
||||
foreach k [array names G trial.*] { puts "index: $G($k)" }
|
||||
}
|
||||
}
|
||||
|
||||
proc run_trials {ctxvar} {
|
||||
upvar $ctxvar G
|
||||
set ret [list]
|
||||
|
||||
foreach k [array names G trial.*] {
|
||||
set idxname [lindex [split $k .] 1]
|
||||
db eval $G($k)
|
||||
set pgno [db one {SELECT rootpage FROM sqlite_master WHERE name = $idxname}]
|
||||
set IDX($pgno) $idxname
|
||||
}
|
||||
db eval ANALYZE
|
||||
|
||||
catch { array unset used }
|
||||
foreach s $G(lSelect) {
|
||||
db eval "EXPLAIN $s" x {
|
||||
if {($x(opcode)=="OpenRead" || $x(opcode)=="ReopenIdx")} {
|
||||
if {[info exists IDX($x(p2))]} { set used($IDX($x(p2))) 1 }
|
||||
}
|
||||
}
|
||||
foreach idx [array names used] {
|
||||
lappend ret $G(trial.$idx)
|
||||
}
|
||||
}
|
||||
|
||||
set ret
|
||||
}
|
||||
|
||||
proc sqlidx_init_context {varname} {
|
||||
upvar $varname G
|
||||
set G(lSelect) [list] ;# List of SELECT statements to analyze
|
||||
set G(database) "" ;# Name of database or SQL schema file
|
||||
set G(trace) [list] ;# List of data from xTrace()
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# The following is test code only.
|
||||
#
|
||||
proc sqlidx_one_test {tn schema select expected} {
|
||||
# if {$tn!=2} return
|
||||
sqlidx_init_context C
|
||||
|
||||
sqlite3 db ""
|
||||
db collate "a b c" [list string compare]
|
||||
db eval $schema
|
||||
lappend C(lSelect) $select
|
||||
analyze_selects C
|
||||
find_trial_indexes C
|
||||
|
||||
set idxlist [run_trials C]
|
||||
if {$idxlist != [list {*}$expected]} {
|
||||
puts stderr "Test $tn failed"
|
||||
puts stderr "Expected: $expected"
|
||||
puts stderr "Got: $idxlist"
|
||||
exit -1
|
||||
}
|
||||
|
||||
db close
|
||||
|
||||
upvar nTest nTest
|
||||
incr nTest
|
||||
}
|
||||
|
||||
proc sqlidx_internal_tests {} {
|
||||
set nTest 0
|
||||
|
||||
|
||||
# No indexes for a query with no constraints.
|
||||
sqlidx_one_test 0 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
} {
|
||||
SELECT * FROM t1;
|
||||
} {
|
||||
}
|
||||
|
||||
sqlidx_one_test 1 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
CREATE TABLE t2(x, y, z);
|
||||
} {
|
||||
SELECT a FROM t1, t2 WHERE a=? AND x=c
|
||||
} {
|
||||
{CREATE INDEX t2_x ON t2(x);}
|
||||
{CREATE INDEX t1_a_c ON t1(a, c);}
|
||||
}
|
||||
|
||||
sqlidx_one_test 2 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
} {
|
||||
SELECT * FROM t1 WHERE b>?;
|
||||
} {
|
||||
{CREATE INDEX t1_b ON t1(b);}
|
||||
}
|
||||
|
||||
sqlidx_one_test 3 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
} {
|
||||
SELECT * FROM t1 WHERE b COLLATE nocase BETWEEN ? AND ?
|
||||
} {
|
||||
{CREATE INDEX t1_bnocase ON t1(b COLLATE NOCASE);}
|
||||
}
|
||||
|
||||
sqlidx_one_test 4 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
} {
|
||||
SELECT a FROM t1 ORDER BY b;
|
||||
} {
|
||||
{CREATE INDEX t1_b ON t1(b);}
|
||||
}
|
||||
|
||||
sqlidx_one_test 5 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
} {
|
||||
SELECT a FROM t1 WHERE a=? ORDER BY b;
|
||||
} {
|
||||
{CREATE INDEX t1_a_b ON t1(a, b);}
|
||||
}
|
||||
|
||||
sqlidx_one_test 5 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
} {
|
||||
SELECT min(a) FROM t1
|
||||
} {
|
||||
{CREATE INDEX t1_a ON t1(a);}
|
||||
}
|
||||
|
||||
sqlidx_one_test 6 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
} {
|
||||
SELECT * FROM t1 ORDER BY a ASC, b COLLATE nocase DESC, c ASC;
|
||||
} {
|
||||
{CREATE INDEX t1_a_bnocasedesc_c ON t1(a, b COLLATE NOCASE DESC, c);}
|
||||
}
|
||||
|
||||
sqlidx_one_test 7 {
|
||||
CREATE TABLE t1(a COLLATE NOCase, b, c);
|
||||
} {
|
||||
SELECT * FROM t1 WHERE a=?
|
||||
} {
|
||||
{CREATE INDEX t1_a ON t1(a);}
|
||||
}
|
||||
|
||||
# Tables with names that require quotes.
|
||||
#
|
||||
sqlidx_one_test 8.1 {
|
||||
CREATE TABLE "t t"(a, b, c);
|
||||
} {
|
||||
SELECT * FROM "t t" WHERE a=?
|
||||
} {
|
||||
{CREATE INDEX "t t_a" ON "t t"(a);}
|
||||
}
|
||||
sqlidx_one_test 8.2 {
|
||||
CREATE TABLE "t t"(a, b, c);
|
||||
} {
|
||||
SELECT * FROM "t t" WHERE b BETWEEN ? AND ?
|
||||
} {
|
||||
{CREATE INDEX "t t_b" ON "t t"(b);}
|
||||
}
|
||||
|
||||
# Columns with names that require quotes.
|
||||
#
|
||||
sqlidx_one_test 9.1 {
|
||||
CREATE TABLE t3(a, "b b", c);
|
||||
} {
|
||||
SELECT * FROM t3 WHERE "b b" = ?
|
||||
} {
|
||||
{CREATE INDEX "t3_b b" ON t3("b b");}
|
||||
}
|
||||
sqlidx_one_test 9.2 {
|
||||
CREATE TABLE t3(a, "b b", c);
|
||||
} {
|
||||
SELECT * FROM t3 ORDER BY "b b"
|
||||
} {
|
||||
{CREATE INDEX "t3_b b" ON t3("b b");}
|
||||
}
|
||||
|
||||
# Collations with names that require quotes.
|
||||
#
|
||||
sqlidx_one_test 10.1 {
|
||||
CREATE TABLE t4(a, b, c);
|
||||
} {
|
||||
SELECT * FROM t4 ORDER BY c COLLATE "a b c"
|
||||
} {
|
||||
{CREATE INDEX "t4_ca b c" ON t4(c COLLATE "a b c");}
|
||||
}
|
||||
sqlidx_one_test 10.2 {
|
||||
CREATE TABLE t4(a, b, c);
|
||||
} {
|
||||
SELECT * FROM t4 WHERE c = ? COLLATE "a b c"
|
||||
} {
|
||||
{CREATE INDEX "t4_ca b c" ON t4(c COLLATE "a b c");}
|
||||
}
|
||||
|
||||
# Transitive constraints
|
||||
#
|
||||
sqlidx_one_test 11.1 {
|
||||
CREATE TABLE t5(a, b);
|
||||
CREATE TABLE t6(c, d);
|
||||
} {
|
||||
SELECT * FROM t5, t6 WHERE a=? AND b=c AND c=?
|
||||
} {
|
||||
{CREATE INDEX t6_c ON t6(c);}
|
||||
{CREATE INDEX t5_a_b ON t5(a, b);}
|
||||
}
|
||||
|
||||
puts "All $nTest tests passed"
|
||||
exit
|
||||
}
|
||||
# End of internal test code.
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
if {[info exists ::argv0]==0} { set ::argv0 [info nameofexec] }
|
||||
if {[info exists ::argv]==0} usage
|
||||
sqlidx_init_context D
|
||||
process_cmdline_args D $::argv
|
||||
open_database D
|
||||
analyze_selects D
|
||||
find_trial_indexes D
|
||||
foreach idx [run_trials D] { puts $idx }
|
||||
|
||||
} err]} {
|
||||
puts "ERROR: $err"
|
||||
puts $errorInfo
|
||||
exit 1
|
||||
}
|
||||
Reference in New Issue
Block a user