Index: Makefile.in
==================================================================
--- Makefile.in
+++ Makefile.in
@@ -185,11 +185,11 @@
notify.lo opcodes.lo os.lo os_unix.lo os_win.lo \
pager.lo parse.lo pcache.lo pcache1.lo pragma.lo prepare.lo printf.lo \
random.lo resolve.lo rowset.lo rtree.lo \
sqlite3session.lo select.lo sqlite3rbu.lo status.lo stmt.lo \
table.lo threads.lo tokenize.lo treeview.lo trigger.lo \
- update.lo util.lo vacuum.lo \
+ update.lo upsert.lo util.lo vacuum.lo \
vdbe.lo vdbeapi.lo vdbeaux.lo vdbeblob.lo vdbemem.lo vdbesort.lo \
vdbetrace.lo wal.lo walker.lo where.lo wherecode.lo whereexpr.lo \
utf.lo vtab.lo
# Object files for the amalgamation.
@@ -282,10 +282,11 @@
$(TOP)/src/tokenize.c \
$(TOP)/src/treeview.c \
$(TOP)/src/trigger.c \
$(TOP)/src/utf.c \
$(TOP)/src/update.c \
+ $(TOP)/src/upsert.c \
$(TOP)/src/util.c \
$(TOP)/src/vacuum.c \
$(TOP)/src/vdbe.c \
$(TOP)/src/vdbe.h \
$(TOP)/src/vdbeapi.c \
@@ -925,10 +926,13 @@
$(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/trigger.c
update.lo: $(TOP)/src/update.c $(HDR)
$(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/update.c
+upsert.lo: $(TOP)/src/upsert.c $(HDR)
+ $(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/upsert.c
+
utf.lo: $(TOP)/src/utf.c $(HDR)
$(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/utf.c
util.lo: $(TOP)/src/util.c $(HDR)
$(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/util.c
Index: Makefile.msc
==================================================================
--- Makefile.msc
+++ Makefile.msc
@@ -1191,11 +1191,11 @@
notify.lo opcodes.lo os.lo os_unix.lo os_win.lo \
pager.lo pcache.lo pcache1.lo pragma.lo prepare.lo printf.lo \
random.lo resolve.lo rowset.lo rtree.lo \
sqlite3session.lo select.lo sqlite3rbu.lo status.lo \
table.lo threads.lo tokenize.lo treeview.lo trigger.lo \
- update.lo util.lo vacuum.lo \
+ update.lo upsert.lo util.lo vacuum.lo \
vdbeapi.lo vdbeaux.lo vdbeblob.lo vdbemem.lo vdbesort.lo \
vdbetrace.lo wal.lo walker.lo where.lo wherecode.lo whereexpr.lo \
utf.lo vtab.lo
# <>
@@ -1290,10 +1290,11 @@
$(TOP)\src\tokenize.c \
$(TOP)\src\treeview.c \
$(TOP)\src\trigger.c \
$(TOP)\src\utf.c \
$(TOP)\src\update.c \
+ $(TOP)\src\upsert.c \
$(TOP)\src\util.c \
$(TOP)\src\vacuum.c \
$(TOP)\src\vdbe.c \
$(TOP)\src\vdbeapi.c \
$(TOP)\src\vdbeaux.c \
@@ -1671,11 +1672,11 @@
# <>
sqlite3.def: libsqlite3.lib
echo EXPORTS > sqlite3.def
dumpbin /all libsqlite3.lib \
- | $(TCLSH_CMD) $(TOP)\tool\replace.tcl include "^\s+1 _?(sqlite3(?:session|changeset|changegroup)?_[^@]*)(?:@\d+)?$$" \1 \
+ | $(TCLSH_CMD) $(TOP)\tool\replace.tcl include "^\s+1 _?(sqlite3(?:session|changeset|changegroup|rebaser)?_[^@]*)(?:@\d+)?$$" \1 \
| sort >> sqlite3.def
# <>
$(SQLITE3EXE): shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLITE3H)
$(LTLINK) $(SHELL_COMPILE_OPTS) $(READLINE_FLAGS) shell.c $(SHELL_CORE_SRC) \
@@ -1992,10 +1993,13 @@
$(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\trigger.c
update.lo: $(TOP)\src\update.c $(HDR)
$(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\update.c
+upsert.lo: $(TOP)\src\upsert.c $(HDR)
+ $(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\upsert.c
+
utf.lo: $(TOP)\src\utf.c $(HDR)
$(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\utf.c
util.lo: $(TOP)\src\util.c $(HDR)
$(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\util.c
Index: VERSION
==================================================================
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
-3.23.1
+3.24.0
Index: autoconf/Makefile.am
==================================================================
--- autoconf/Makefile.am
+++ autoconf/Makefile.am
@@ -1,7 +1,7 @@
-AM_CFLAGS = @THREADSAFE_FLAGS@ @DYNAMIC_EXTENSION_FLAGS@ @FTS5_FLAGS@ @JSON1_FLAGS@ @ZLIB_FLAGS@ @SESSION_FLAGS@ -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE
+AM_CFLAGS = @THREADSAFE_FLAGS@ @DYNAMIC_EXTENSION_FLAGS@ @FTS5_FLAGS@ @JSON1_FLAGS@ @ZLIB_FLAGS@ @SESSION_FLAGS@ -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE @DEBUG_FLAGS@
lib_LTLIBRARIES = libsqlite3.la
libsqlite3_la_SOURCES = sqlite3.c
libsqlite3_la_LDFLAGS = -no-undefined -version-info 8:6:8
Index: autoconf/Makefile.msc
==================================================================
--- autoconf/Makefile.msc
+++ autoconf/Makefile.msc
@@ -964,11 +964,11 @@
$(CSC) /target:exe $(TOP)\Replace.cs
sqlite3.def: Replace.exe $(LIBOBJ)
echo EXPORTS > sqlite3.def
dumpbin /all $(LIBOBJ) \
- | .\Replace.exe "^\s+/EXPORT:_?(sqlite3(?:session|changeset|changegroup)?_[^@,]*)(?:@\d+|,DATA)?$$" $$1 true \
+ | .\Replace.exe "^\s+/EXPORT:_?(sqlite3(?:session|changeset|changegroup|rebaser)?_[^@,]*)(?:@\d+|,DATA)?$$" $$1 true \
| sort >> sqlite3.def
$(SQLITE3EXE): shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLITE3H)
$(LTLINK) $(SHELL_COMPILE_OPTS) $(READLINE_FLAGS) shell.c $(SHELL_CORE_SRC) \
/link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS)
Index: autoconf/configure.ac
==================================================================
--- autoconf/configure.ac
+++ autoconf/configure.ac
@@ -113,12 +113,12 @@
#-----------------------------------------------------------------------
# --enable-fts5
#
AC_ARG_ENABLE(fts5, [AS_HELP_STRING(
- [--enable-fts5], [include fts5 support [default=no]])],
- [], [enable_fts5=no])
+ [--enable-fts5], [include fts5 support [default=yes]])],
+ [], [enable_fts5=yes])
if test x"$enable_fts5" = "xyes"; then
AC_SEARCH_LIBS(log, m)
FTS5_FLAGS=-DSQLITE_ENABLE_FTS5
fi
AC_SUBST(FTS5_FLAGS)
@@ -126,12 +126,12 @@
#-----------------------------------------------------------------------
# --enable-json1
#
AC_ARG_ENABLE(json1, [AS_HELP_STRING(
- [--enable-json1], [include json1 support [default=no]])],
- [], [enable_json1=no])
+ [--enable-json1], [include json1 support [default=yes]])],
+ [], [enable_json1=yes])
if test x"$enable_json1" = "xyes"; then
JSON1_FLAGS=-DSQLITE_ENABLE_JSON1
fi
AC_SUBST(JSON1_FLAGS)
#-----------------------------------------------------------------------
@@ -145,10 +145,22 @@
if test x"$enable_session" = "xyes"; then
SESSION_FLAGS="-DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK"
fi
AC_SUBST(SESSION_FLAGS)
#-----------------------------------------------------------------------
+
+#-----------------------------------------------------------------------
+# --enable-debug
+#
+AC_ARG_ENABLE(debug, [AS_HELP_STRING(
+ [--enable-debug], [build with debugging features enabled [default=no]])],
+ [], [enable_session=no])
+if test x"$enable_debug" = "xyes"; then
+ DEBUG_FLAGS="-DSQLITE_DEBUG -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE"
+fi
+AC_SUBST(DEBUG_FLAGS)
+#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# --enable-static-shell
#
AC_ARG_ENABLE(static-shell, [AS_HELP_STRING(
Index: configure
==================================================================
--- configure
+++ configure
@@ -1,8 +1,8 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for sqlite 3.23.1.
+# Generated by GNU Autoconf 2.69 for sqlite 3.24.0.
#
#
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
#
#
@@ -724,12 +724,12 @@
MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='sqlite'
PACKAGE_TARNAME='sqlite'
-PACKAGE_VERSION='3.23.1'
-PACKAGE_STRING='sqlite 3.23.1'
+PACKAGE_VERSION='3.24.0'
+PACKAGE_STRING='sqlite 3.24.0'
PACKAGE_BUGREPORT=''
PACKAGE_URL=''
# Factoring default headers for most tests.
ac_includes_default="\
@@ -1463,11 +1463,11 @@
#
if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures sqlite 3.23.1 to adapt to many kinds of systems.
+\`configure' configures sqlite 3.24.0 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
To assign environment variables (e.g., CC, CFLAGS...), specify them as
VAR=VALUE. See below for descriptions of some of the useful variables.
@@ -1528,11 +1528,11 @@
_ACEOF
fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of sqlite 3.23.1:";;
+ short | recursive ) echo "Configuration of sqlite 3.24.0:";;
esac
cat <<\_ACEOF
Optional Features:
--disable-option-checking ignore unrecognized --enable/--with options
@@ -1653,11 +1653,11 @@
fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-sqlite configure 3.23.1
+sqlite configure 3.24.0
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
This configure script is free software; the Free Software Foundation
gives unlimited permission to copy, distribute and modify it.
@@ -2072,11 +2072,11 @@
} # ac_fn_c_check_header_mongrel
cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by sqlite $as_me 3.23.1, which was
+It was created by sqlite $as_me 3.24.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
_ACEOF
@@ -12240,11 +12240,11 @@
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# Save the log message, to keep $0 and so on meaningful, and to
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by sqlite $as_me 3.23.1, which was
+This file was extended by sqlite $as_me 3.24.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
CONFIG_HEADERS = $CONFIG_HEADERS
CONFIG_LINKS = $CONFIG_LINKS
@@ -12306,11 +12306,11 @@
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-sqlite config.status 3.23.1
+sqlite config.status 3.24.0
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
Copyright (C) 2012 Free Software Foundation, Inc.
This config.status script is free software; the Free Software Foundation
Index: doc/lemon.html
==================================================================
--- doc/lemon.html
+++ doc/lemon.html
@@ -97,10 +97,13 @@
-Dname
Define C preprocessor macro name. This macro is usable by
"%ifdef" and
"%ifndef" lines
in the grammar file.
@@ -677,10 +680,34 @@
Then the Parse() function generated will have an 4th parameter
of type "MyStruct*" and all action routines will have access to
a variable named "pAbc" that is the value of the 4th parameter
in the most recent call to Parse().
+The %extra_context directive works the same except that it
+is passed in on the ParseAlloc() or ParseInit() routines instead of
+on Parse().
+
+
+
The %extra_context directive
+
+The %extra_context directive instructs Lemon to add a 2th parameter
+to the parameter list of the ParseAlloc() and ParseInif() functions. Lemon
+doesn't do anything itself with these extra argument, but it does
+store the value make it available to C-code action routines, destructors,
+and so forth. For example, if the grammar file contains:
+
+
+ %extra_context { MyStruct *pAbc }
+
+
+Then the ParseAlloc() and ParseInit() functions will have an 2th parameter
+of type "MyStruct*" and all action routines will have access to
+a variable named "pAbc" that is the value of that 2th parameter.
+
+The %extra_argument directive works the same except that it
+is passed in on the Parse() routine instead of on ParseAlloc()/ParseInit().
+
The %fallback directive
The %fallback directive specifies an alternative meaning for one
or more tokens. The alternative meaning is tried if the original token
Index: ext/expert/expert1.test
==================================================================
--- ext/expert/expert1.test
+++ ext/expert/expert1.test
@@ -93,65 +93,65 @@
do_setup_rec_test $tn.1 { CREATE TABLE t1(a, b, c) } {
SELECT * FROM t1
} {
(no new indexes)
- 0|0|0|SCAN TABLE t1
+ 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>?)
+ 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)
+ 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
+ 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=?)
+ 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
+ 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
+ SCAN TABLE t1 USING COVERING INDEX t1_idx_033e95fe
}
#do_setup_rec_test $tn.1.8 {
# CREATE TABLE t1(a, b, c);
#} {
@@ -165,19 +165,19 @@
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=?)
+ 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
+ SCAN TABLE t1 USING COVERING INDEX t1_idx_5cb97285
}
# Tables with names that require quotes.
#
@@ -185,20 +185,20 @@
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=?)
+ 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)
+ 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 {
@@ -205,20 +205,20 @@
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=?)
+ 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
+ SCAN TABLE t3 USING INDEX t3_idx_00050c52
}
# Transitive constraints
#
do_setup_rec_test $tn.11.1 {
@@ -227,12 +227,12 @@
} {
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=?)
+ SEARCH TABLE t6 USING INDEX t6_idx_00000063 (c=?)
+ SEARCH TABLE t5 USING COVERING INDEX t5_idx_000123a7 (a=? AND b=?)
}
# OR terms.
#
do_setup_rec_test $tn.12.1 {
@@ -240,12 +240,13 @@
} {
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=?)
+ MULTI-INDEX OR
+ SEARCH TABLE t7 USING INDEX t7_idx_00000061 (a=?)
+ SEARCH TABLE t7 USING INDEX t7_idx_00000062 (b=?)
}
# rowid terms.
#
do_setup_rec_test $tn.13.1 {
@@ -252,27 +253,27 @@
CREATE TABLE t8(a, b);
} {
SELECT * FROM t8 WHERE rowid=?
} {
(no new indexes)
- 0|0|0|SEARCH TABLE t8 USING INTEGER PRIMARY KEY (rowid=?)
+ 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
+ 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=?)
+ SEARCH TABLE t8 USING INDEX t8_idx_00000061 (a=?)
}
# Triggers
#
do_setup_rec_test $tn.14 {
@@ -283,11 +284,12 @@
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=?)
+ -- TRIGGER t9t
+ 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);
@@ -299,21 +301,21 @@
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=?)
+ SEARCH TABLE t2 USING INDEX t2_idx_00000064 (d=?)
+ SEARCH TABLE t1 USING INTEGER PRIMARY KEY (rowid=?)
}
do_setup_rec_test $tn.16 {
CREATE TABLE t1(a, b);
} {
SELECT * FROM t1 WHERE b IS NOT NULL;
} {
(no new indexes)
- 0|0|0|SCAN TABLE t1
+ SCAN TABLE t1
}
}
proc do_candidates_test {tn sql res} {
@@ -377,6 +379,5 @@
t2 t2_idx_0001295b {100 20 5}
}
finish_test
-
Index: ext/expert/sqlite3expert.c
==================================================================
--- ext/expert/sqlite3expert.c
+++ ext/expert/sqlite3expert.c
@@ -1121,13 +1121,13 @@
idxHashClear(&hIdx);
rc = idxPrintfPrepareStmt(dbm, &pExplain, pzErr,
"EXPLAIN QUERY PLAN %s", pStmt->zSql
);
while( rc==SQLITE_OK && sqlite3_step(pExplain)==SQLITE_ROW ){
- int iSelectid = sqlite3_column_int(pExplain, 0);
- int iOrder = sqlite3_column_int(pExplain, 1);
- int iFrom = sqlite3_column_int(pExplain, 2);
+ /* int iId = sqlite3_column_int(pExplain, 0); */
+ /* int iParent = sqlite3_column_int(pExplain, 1); */
+ /* int iNotUsed = sqlite3_column_int(pExplain, 2); */
const char *zDetail = (const char*)sqlite3_column_text(pExplain, 3);
int nDetail = STRLEN(zDetail);
int i;
for(i=0; izEQP = idxAppendText(&rc, pStmt->zEQP, "%d|%d|%d|%s\n",
- iSelectid, iOrder, iFrom, zDetail
- );
+ pStmt->zEQP = idxAppendText(&rc, pStmt->zEQP, "%s\n", zDetail);
}
for(pEntry=hIdx.pFirst; pEntry; pEntry=pEntry->pNext){
pStmt->zIdx = idxAppendText(&rc, pStmt->zIdx, "%s;\n", pEntry->zKey);
}
Index: ext/fts3/fts3.c
==================================================================
--- ext/fts3/fts3.c
+++ ext/fts3/fts3.c
@@ -3961,11 +3961,11 @@
}
}
#ifdef SQLITE_TEST
if( rc==SQLITE_OK ){
- rc = sqlite3Fts3ExprInitTestInterface(db);
+ rc = sqlite3Fts3ExprInitTestInterface(db, pHash);
}
#endif
/* Create the virtual table wrapper around the hash-table and overload
** the four scalar functions. If this is successful, register the
Index: ext/fts3/fts3Int.h
==================================================================
--- ext/fts3/fts3Int.h
+++ ext/fts3/fts3Int.h
@@ -582,11 +582,11 @@
int sqlite3Fts3ExprParse(sqlite3_tokenizer *, int,
char **, int, int, int, const char *, int, Fts3Expr **, char **
);
void sqlite3Fts3ExprFree(Fts3Expr *);
#ifdef SQLITE_TEST
-int sqlite3Fts3ExprInitTestInterface(sqlite3 *db);
+int sqlite3Fts3ExprInitTestInterface(sqlite3 *db, Fts3Hash*);
int sqlite3Fts3InitTerm(sqlite3 *db);
#endif
int sqlite3Fts3OpenTokenizer(sqlite3_tokenizer *, int, const char *, int,
sqlite3_tokenizer_cursor **
Index: ext/fts3/fts3_expr.c
==================================================================
--- ext/fts3/fts3_expr.c
+++ ext/fts3/fts3_expr.c
@@ -1106,38 +1106,10 @@
#ifdef SQLITE_TEST
#include
-/*
-** Function to query the hash-table of tokenizers (see README.tokenizers).
-*/
-static int queryTestTokenizer(
- sqlite3 *db,
- const char *zName,
- const sqlite3_tokenizer_module **pp
-){
- int rc;
- sqlite3_stmt *pStmt;
- const char zSql[] = "SELECT fts3_tokenizer(?)";
-
- *pp = 0;
- rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
- if( rc!=SQLITE_OK ){
- return rc;
- }
-
- sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC);
- if( SQLITE_ROW==sqlite3_step(pStmt) ){
- if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB ){
- memcpy((void *)pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp));
- }
- }
-
- return sqlite3_finalize(pStmt);
-}
-
/*
** Return a pointer to a buffer containing a text representation of the
** expression passed as the first argument. The buffer is obtained from
** sqlite3_malloc(). It is the responsibility of the caller to use
** sqlite3_free() to release the memory. If an OOM condition is encountered,
@@ -1201,51 +1173,47 @@
** of a column of the fts3 table that the query expression may refer to.
** For example:
**
** SELECT fts3_exprtest('simple', 'Bill col2:Bloggs', 'col1', 'col2');
*/
-static void fts3ExprTest(
+static void fts3ExprTestCommon(
+ int bRebalance,
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
- sqlite3_tokenizer_module const *pModule = 0;
sqlite3_tokenizer *pTokenizer = 0;
int rc;
char **azCol = 0;
const char *zExpr;
int nExpr;
int nCol;
int ii;
Fts3Expr *pExpr;
char *zBuf = 0;
- sqlite3 *db = sqlite3_context_db_handle(context);
+ Fts3Hash *pHash = (Fts3Hash*)sqlite3_user_data(context);
+ const char *zTokenizer = 0;
+ char *zErr = 0;
if( argc<3 ){
sqlite3_result_error(context,
"Usage: fts3_exprtest(tokenizer, expr, col1, ...", -1
);
return;
}
- rc = queryTestTokenizer(db,
- (const char *)sqlite3_value_text(argv[0]), &pModule);
- if( rc==SQLITE_NOMEM ){
- sqlite3_result_error_nomem(context);
- goto exprtest_out;
- }else if( !pModule ){
- sqlite3_result_error(context, "No such tokenizer module", -1);
- goto exprtest_out;
- }
-
- rc = pModule->xCreate(0, 0, &pTokenizer);
- assert( rc==SQLITE_NOMEM || rc==SQLITE_OK );
- if( rc==SQLITE_NOMEM ){
- sqlite3_result_error_nomem(context);
- goto exprtest_out;
- }
- pTokenizer->pModule = pModule;
+ zTokenizer = (const char*)sqlite3_value_text(argv[0]);
+ rc = sqlite3Fts3InitTokenizer(pHash, zTokenizer, &pTokenizer, &zErr);
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_NOMEM ){
+ sqlite3_result_error_nomem(context);
+ }else{
+ sqlite3_result_error(context, zErr, -1);
+ }
+ sqlite3_free(zErr);
+ return;
+ }
zExpr = (const char *)sqlite3_value_text(argv[1]);
nExpr = sqlite3_value_bytes(argv[1]);
nCol = argc-2;
azCol = (char **)sqlite3_malloc(nCol*sizeof(char *));
@@ -1255,11 +1223,11 @@
}
for(ii=0; iixDestroy(pTokenizer);
+ if( pTokenizer ){
+ rc = pTokenizer->pModule->xDestroy(pTokenizer);
}
sqlite3_free(azCol);
}
+
+static void fts3ExprTest(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ fts3ExprTestCommon(0, context, argc, argv);
+}
+static void fts3ExprTestRebalance(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ fts3ExprTestCommon(1, context, argc, argv);
+}
/*
** Register the query expression parser test function fts3_exprtest()
** with database connection db.
*/
-int sqlite3Fts3ExprInitTestInterface(sqlite3* db){
+int sqlite3Fts3ExprInitTestInterface(sqlite3 *db, Fts3Hash *pHash){
int rc = sqlite3_create_function(
- db, "fts3_exprtest", -1, SQLITE_UTF8, 0, fts3ExprTest, 0, 0
+ db, "fts3_exprtest", -1, SQLITE_UTF8, (void*)pHash, fts3ExprTest, 0, 0
);
if( rc==SQLITE_OK ){
rc = sqlite3_create_function(db, "fts3_exprtest_rebalance",
- -1, SQLITE_UTF8, (void *)1, fts3ExprTest, 0, 0
+ -1, SQLITE_UTF8, (void*)pHash, fts3ExprTestRebalance, 0, 0
);
}
return rc;
}
#endif
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */
Index: ext/fts5/test/fts5plan.test
==================================================================
--- ext/fts5/test/fts5plan.test
+++ ext/fts5/test/fts5plan.test
@@ -27,40 +27,39 @@
}
do_eqp_test 1.1 {
SELECT * FROM t1, f1 WHERE f1 MATCH t1.x
} {
- 0 0 0 {SCAN TABLE t1}
- 0 1 1 {SCAN TABLE f1 VIRTUAL TABLE INDEX 65537:}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--SCAN TABLE f1 VIRTUAL TABLE INDEX 65537:
}
do_eqp_test 1.2 {
SELECT * FROM t1, f1 WHERE f1 > t1.x
} {
- 0 0 1 {SCAN TABLE f1 VIRTUAL TABLE INDEX 0:}
- 0 1 0 {SCAN TABLE t1}
+ QUERY PLAN
+ |--SCAN TABLE f1 VIRTUAL TABLE INDEX 0:
+ `--SCAN TABLE t1
}
do_eqp_test 1.3 {
SELECT * FROM f1 WHERE f1 MATCH ? ORDER BY ff
} {
- 0 0 0 {SCAN TABLE f1 VIRTUAL TABLE INDEX 65537:}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SCAN TABLE f1 VIRTUAL TABLE INDEX 65537:
+ `--USE TEMP B-TREE FOR ORDER BY
}
do_eqp_test 1.4 {
SELECT * FROM f1 ORDER BY rank
} {
- 0 0 0 {SCAN TABLE f1 VIRTUAL TABLE INDEX 0:}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SCAN TABLE f1 VIRTUAL TABLE INDEX 0:
+ `--USE TEMP B-TREE FOR ORDER BY
}
do_eqp_test 1.5 {
SELECT * FROM f1 WHERE rank MATCH ?
-} {
- 0 0 0 {SCAN TABLE f1 VIRTUAL TABLE INDEX 2:}
-}
-
-
-
+} {SCAN TABLE f1 VIRTUAL TABLE INDEX 2:}
finish_test
Index: ext/misc/closure.c
==================================================================
--- ext/misc/closure.c
+++ ext/misc/closure.c
@@ -824,21 +824,16 @@
sqlite3_index_info *pIdxInfo /* Information about the query */
){
int iPlan = 0;
int i;
int idx = 1;
- int seenMatch = 0;
const struct sqlite3_index_constraint *pConstraint;
closure_vtab *pVtab = (closure_vtab*)pTab;
double rCost = 10000000.0;
pConstraint = pIdxInfo->aConstraint;
for(i=0; inConstraint; i++, pConstraint++){
- if( pConstraint->iColumn==CLOSURE_COL_ROOT
- && pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){
- seenMatch = 1;
- }
if( pConstraint->usable==0 ) continue;
if( (iPlan & 1)==0
&& pConstraint->iColumn==CLOSURE_COL_ROOT
&& pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ
){
@@ -890,19 +885,30 @@
){
/* All of tablename, idcolumn, and parentcolumn must be specified
** in either the CREATE VIRTUAL TABLE or in the WHERE clause constraints
** or else the result is an empty set. */
iPlan = 0;
+ }
+ if( (iPlan&1)==0 ){
+ /* If there is no usable "root=?" term, then set the index-type to 0.
+ ** Also clear any argvIndex variables already set. This is necessary
+ ** to prevent the core from throwing an "xBestIndex malfunction error"
+ ** error (because the argvIndex values are not contiguously assigned
+ ** starting from 1). */
+ rCost *= 1e30;
+ for(i=0; inConstraint; i++, pConstraint++){
+ pIdxInfo->aConstraintUsage[i].argvIndex = 0;
+ }
+ iPlan = 0;
}
pIdxInfo->idxNum = iPlan;
if( pIdxInfo->nOrderBy==1
&& pIdxInfo->aOrderBy[0].iColumn==CLOSURE_COL_ID
&& pIdxInfo->aOrderBy[0].desc==0
){
pIdxInfo->orderByConsumed = 1;
}
- if( seenMatch && (iPlan&1)==0 ) rCost *= 1e30;
pIdxInfo->estimatedCost = rCost;
return SQLITE_OK;
}
Index: ext/misc/completion.c
==================================================================
--- ext/misc/completion.c
+++ ext/misc/completion.c
@@ -60,10 +60,11 @@
sqlite3 *db; /* Database connection for this cursor */
int nPrefix, nLine; /* Number of bytes in zPrefix and zLine */
char *zPrefix; /* The prefix for the word we want to complete */
char *zLine; /* The whole that we want to complete */
const char *zCurrentRow; /* Current output row */
+ int szRow; /* Length of the zCurrentRow string */
sqlite3_stmt *pStmt; /* Current statement */
sqlite3_int64 iRowid; /* The rowid */
int ePhase; /* Current phase */
int j; /* inter-phase counter */
};
@@ -172,36 +173,10 @@
completionCursorReset((completion_cursor*)cur);
sqlite3_free(cur);
return SQLITE_OK;
}
-/*
-** All SQL keywords understood by SQLite
-*/
-static const char *completionKwrds[] = {
- "ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ANALYZE", "AND", "AS",
- "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "BY",
- "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT",
- "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT_DATE",
- "CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT", "DEFERRABLE",
- "DEFERRED", "DELETE", "DESC", "DETACH", "DISTINCT", "DROP", "EACH",
- "ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUSIVE", "EXISTS", "EXPLAIN",
- "FAIL", "FOR", "FOREIGN", "FROM", "FULL", "GLOB", "GROUP", "HAVING", "IF",
- "IGNORE", "IMMEDIATE", "IN", "INDEX", "INDEXED", "INITIALLY", "INNER",
- "INSERT", "INSTEAD", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY",
- "LEFT", "LIKE", "LIMIT", "MATCH", "NATURAL", "NO", "NOT", "NOTNULL",
- "NULL", "OF", "OFFSET", "ON", "OR", "ORDER", "OUTER", "PLAN", "PRAGMA",
- "PRIMARY", "QUERY", "RAISE", "RECURSIVE", "REFERENCES", "REGEXP",
- "REINDEX", "RELEASE", "RENAME", "REPLACE", "RESTRICT", "RIGHT",
- "ROLLBACK", "ROW", "SAVEPOINT", "SELECT", "SET", "TABLE", "TEMP",
- "TEMPORARY", "THEN", "TO", "TRANSACTION", "TRIGGER", "UNION", "UNIQUE",
- "UPDATE", "USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", "WHERE",
- "WITH", "WITHOUT",
-};
-#define completionKwCount \
- (int)(sizeof(completionKwrds)/sizeof(completionKwrds[0]))
-
/*
** Advance a completion_cursor to its next row of output.
**
** The ->ePhase, ->j, and ->pStmt fields of the completion_cursor object
** record the current state of the scan. This routine sets ->zCurrentRow
@@ -220,15 +195,15 @@
int iCol = -1; /* If >=0, step pCur->pStmt and use the i-th column */
pCur->iRowid++;
while( pCur->ePhase!=COMPLETION_EOF ){
switch( pCur->ePhase ){
case COMPLETION_KEYWORDS: {
- if( pCur->j >= completionKwCount ){
+ if( pCur->j >= sqlite3_keyword_count() ){
pCur->zCurrentRow = 0;
pCur->ePhase = COMPLETION_DATABASES;
}else{
- pCur->zCurrentRow = completionKwrds[pCur->j++];
+ sqlite3_keyword_name(pCur->j++, &pCur->zCurrentRow, &pCur->szRow);
}
iCol = -1;
break;
}
case COMPLETION_DATABASES: {
@@ -296,20 +271,23 @@
if( pCur->zCurrentRow==0 ) continue;
}else{
if( sqlite3_step(pCur->pStmt)==SQLITE_ROW ){
/* Extract the next row of content */
pCur->zCurrentRow = (const char*)sqlite3_column_text(pCur->pStmt, iCol);
+ pCur->szRow = sqlite3_column_bytes(pCur->pStmt, iCol);
}else{
/* When all rows are finished, advance to the next phase */
sqlite3_finalize(pCur->pStmt);
pCur->pStmt = 0;
pCur->ePhase = eNextPhase;
continue;
}
}
if( pCur->nPrefix==0 ) break;
- if( sqlite3_strnicmp(pCur->zPrefix, pCur->zCurrentRow, pCur->nPrefix)==0 ){
+ if( pCur->nPrefix<=pCur->szRow
+ && sqlite3_strnicmp(pCur->zPrefix, pCur->zCurrentRow, pCur->nPrefix)==0
+ ){
break;
}
}
return SQLITE_OK;
@@ -325,11 +303,11 @@
int i /* Which column to return */
){
completion_cursor *pCur = (completion_cursor*)cur;
switch( i ){
case COMPLETION_COLUMN_CANDIDATE: {
- sqlite3_result_text(ctx, pCur->zCurrentRow, -1, SQLITE_TRANSIENT);
+ sqlite3_result_text(ctx, pCur->zCurrentRow, pCur->szRow,SQLITE_TRANSIENT);
break;
}
case COMPLETION_COLUMN_PREFIX: {
sqlite3_result_text(ctx, pCur->zPrefix, -1, SQLITE_TRANSIENT);
break;
Index: ext/misc/csv.c
==================================================================
--- ext/misc/csv.c
+++ ext/misc/csv.c
@@ -130,10 +130,11 @@
csv_errmsg(p, "out of memory");
return 1;
}
p->in = fopen(zFilename, "rb");
if( p->in==0 ){
+ sqlite3_free(p->zIn);
csv_reader_reset(p);
csv_errmsg(p, "cannot open '%s' for reading", zFilename);
return 1;
}
}else{
Index: ext/misc/dbdump.c
==================================================================
--- ext/misc/dbdump.c
+++ ext/misc/dbdump.c
@@ -139,49 +139,16 @@
** that quoting is required.
**
** Return '"' if quoting is required. Return 0 if no quoting is required.
*/
static char quoteChar(const char *zName){
- /* All SQLite keywords, in alphabetical order */
- static const char *azKeywords[] = {
- "ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ANALYZE", "AND", "AS",
- "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "BY",
- "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT",
- "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT_DATE",
- "CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT", "DEFERRABLE",
- "DEFERRED", "DELETE", "DESC", "DETACH", "DISTINCT", "DROP", "EACH",
- "ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUSIVE", "EXISTS", "EXPLAIN",
- "FAIL", "FOR", "FOREIGN", "FROM", "FULL", "GLOB", "GROUP", "HAVING", "IF",
- "IGNORE", "IMMEDIATE", "IN", "INDEX", "INDEXED", "INITIALLY", "INNER",
- "INSERT", "INSTEAD", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY",
- "LEFT", "LIKE", "LIMIT", "MATCH", "NATURAL", "NO", "NOT", "NOTNULL",
- "NULL", "OF", "OFFSET", "ON", "OR", "ORDER", "OUTER", "PLAN", "PRAGMA",
- "PRIMARY", "QUERY", "RAISE", "RECURSIVE", "REFERENCES", "REGEXP",
- "REINDEX", "RELEASE", "RENAME", "REPLACE", "RESTRICT", "RIGHT",
- "ROLLBACK", "ROW", "SAVEPOINT", "SELECT", "SET", "TABLE", "TEMP",
- "TEMPORARY", "THEN", "TO", "TRANSACTION", "TRIGGER", "UNION", "UNIQUE",
- "UPDATE", "USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", "WHERE",
- "WITH", "WITHOUT",
- };
- int i, lwr, upr, mid, c;
+ int i;
if( !isalpha((unsigned char)zName[0]) && zName[0]!='_' ) return '"';
for(i=0; zName[i]; i++){
if( !isalnum((unsigned char)zName[i]) && zName[i]!='_' ) return '"';
}
- lwr = 0;
- upr = sizeof(azKeywords)/sizeof(azKeywords[0]) - 1;
- while( lwr<=upr ){
- mid = (lwr+upr)/2;
- c = sqlite3_stricmp(azKeywords[mid], zName);
- if( c==0 ) return '"';
- if( c<0 ){
- lwr = mid+1;
- }else{
- upr = mid-1;
- }
- }
- return 0;
+ return sqlite3_keyword_check(zName, i) ? '"' : 0;
}
/*
** Release memory previously allocated by tableColumnList().
ADDED ext/misc/templatevtab.c
Index: ext/misc/templatevtab.c
==================================================================
--- /dev/null
+++ ext/misc/templatevtab.c
@@ -0,0 +1,267 @@
+/*
+** 2018-04-19
+**
+** 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 a template virtual-table.
+** Developers can make a copy of this file as a baseline for writing
+** new virtual tables and/or table-valued functions.
+**
+** Steps for writing a new virtual table implementation:
+**
+** (1) Make a copy of this file. Perhaps call it "mynewvtab.c"
+**
+** (2) Replace this header comment with something appropriate for
+** the new virtual table
+**
+** (3) Change every occurrence of "templatevtab" to some other string
+** appropriate for the new virtual table. Ideally, the new string
+** should be the basename of the source file: "mynewvtab". Also
+** globally change "TEMPLATEVTAB" to "MYNEWVTAB".
+**
+** (4) Run a test compilation to make sure the unmodified virtual
+** table works.
+**
+** (5) Begin making incremental changes, testing as you go, to evolve
+** the new virtual table to do what you want it to do.
+**
+** This template is minimal, in the sense that it uses only the required
+** methods on the sqlite3_module object. As a result, templatevtab is
+** a read-only and eponymous-only table. Those limitation can be removed
+** by adding new methods.
+**
+** This template implements an eponymous-only virtual table with a rowid and
+** two columns named "a" and "b". The table as 10 rows with fixed integer
+** values. Usage example:
+**
+** SELECT rowid, a, b FROM templatevtab;
+*/
+#if !defined(SQLITEINT_H)
+#include "sqlite3ext.h"
+#endif
+SQLITE_EXTENSION_INIT1
+#include
+#include
+
+/* templatevtab_vtab is a subclass of sqlite3_vtab which is
+** underlying representation of the virtual table
+*/
+typedef struct templatevtab_vtab templatevtab_vtab;
+struct templatevtab_vtab {
+ sqlite3_vtab base; /* Base class - must be first */
+ /* Add new fields here, as necessary */
+};
+
+/* templatevtab_cursor is a subclass of sqlite3_vtab_cursor which will
+** serve as the underlying representation of a cursor that scans
+** over rows of the result
+*/
+typedef struct templatevtab_cursor templatevtab_cursor;
+struct templatevtab_cursor {
+ sqlite3_vtab_cursor base; /* Base class - must be first */
+ /* Insert new fields here. For this templatevtab we only keep track
+ ** of the rowid */
+ sqlite3_int64 iRowid; /* The rowid */
+};
+
+/*
+** The templatevtabConnect() method is invoked to create a new
+** template virtual table.
+**
+** Think of this routine as the constructor for templatevtab_vtab objects.
+**
+** All this routine needs to do is:
+**
+** (1) Allocate the templatevtab_vtab object and initialize all fields.
+**
+** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the
+** result set of queries against the virtual table will look like.
+*/
+static int templatevtabConnect(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ templatevtab_vtab *pNew;
+ int rc;
+
+ rc = sqlite3_declare_vtab(db,
+ "CREATE TABLE x(a,b)"
+ );
+ /* For convenience, define symbolic names for the index to each column. */
+#define TEMPLATEVTAB_A 0
+#define TEMPLATEVTAB_B 1
+ if( rc==SQLITE_OK ){
+ pNew = sqlite3_malloc( sizeof(*pNew) );
+ *ppVtab = (sqlite3_vtab*)pNew;
+ if( pNew==0 ) return SQLITE_NOMEM;
+ memset(pNew, 0, sizeof(*pNew));
+ }
+ return rc;
+}
+
+/*
+** This method is the destructor for templatevtab_vtab objects.
+*/
+static int templatevtabDisconnect(sqlite3_vtab *pVtab){
+ templatevtab_vtab *p = (templatevtab_vtab*)pVtab;
+ sqlite3_free(p);
+ return SQLITE_OK;
+}
+
+/*
+** Constructor for a new templatevtab_cursor object.
+*/
+static int templatevtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
+ templatevtab_cursor *pCur;
+ pCur = sqlite3_malloc( sizeof(*pCur) );
+ if( pCur==0 ) return SQLITE_NOMEM;
+ memset(pCur, 0, sizeof(*pCur));
+ *ppCursor = &pCur->base;
+ return SQLITE_OK;
+}
+
+/*
+** Destructor for a templatevtab_cursor.
+*/
+static int templatevtabClose(sqlite3_vtab_cursor *cur){
+ templatevtab_cursor *pCur = (templatevtab_cursor*)cur;
+ sqlite3_free(pCur);
+ return SQLITE_OK;
+}
+
+
+/*
+** Advance a templatevtab_cursor to its next row of output.
+*/
+static int templatevtabNext(sqlite3_vtab_cursor *cur){
+ templatevtab_cursor *pCur = (templatevtab_cursor*)cur;
+ pCur->iRowid++;
+ return SQLITE_OK;
+}
+
+/*
+** Return values of columns for the row at which the templatevtab_cursor
+** is currently pointing.
+*/
+static int templatevtabColumn(
+ sqlite3_vtab_cursor *cur, /* The cursor */
+ sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
+ int i /* Which column to return */
+){
+ templatevtab_cursor *pCur = (templatevtab_cursor*)cur;
+ switch( i ){
+ case TEMPLATEVTAB_A:
+ sqlite3_result_int(ctx, 1000 + pCur->iRowid);
+ break;
+ default:
+ assert( i==TEMPLATEVTAB_B );
+ sqlite3_result_int(ctx, 2000 + pCur->iRowid);
+ break;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Return the rowid for the current row. In this implementation, the
+** rowid is the same as the output value.
+*/
+static int templatevtabRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+ templatevtab_cursor *pCur = (templatevtab_cursor*)cur;
+ *pRowid = pCur->iRowid;
+ return SQLITE_OK;
+}
+
+/*
+** Return TRUE if the cursor has been moved off of the last
+** row of output.
+*/
+static int templatevtabEof(sqlite3_vtab_cursor *cur){
+ templatevtab_cursor *pCur = (templatevtab_cursor*)cur;
+ return pCur->iRowid>=10;
+}
+
+/*
+** This method is called to "rewind" the templatevtab_cursor object back
+** to the first row of output. This method is always called at least
+** once prior to any call to templatevtabColumn() or templatevtabRowid() or
+** templatevtabEof().
+*/
+static int templatevtabFilter(
+ sqlite3_vtab_cursor *pVtabCursor,
+ int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv
+){
+ templatevtab_cursor *pCur = (templatevtab_cursor *)pVtabCursor;
+ pCur->iRowid = 1;
+ return SQLITE_OK;
+}
+
+/*
+** SQLite will invoke this method one or more times while planning a query
+** that uses the virtual table. This routine needs to create
+** a query plan for each invocation and compute an estimated cost for that
+** plan.
+*/
+static int templatevtabBestIndex(
+ sqlite3_vtab *tab,
+ sqlite3_index_info *pIdxInfo
+){
+ pIdxInfo->estimatedCost = (double)10;
+ pIdxInfo->estimatedRows = 10;
+ return SQLITE_OK;
+}
+
+/*
+** This following structure defines all the methods for the
+** virtual table.
+*/
+static sqlite3_module templatevtabModule = {
+ /* iVersion */ 0,
+ /* xCreate */ 0,
+ /* xConnect */ templatevtabConnect,
+ /* xBestIndex */ templatevtabBestIndex,
+ /* xDisconnect */ templatevtabDisconnect,
+ /* xDestroy */ 0,
+ /* xOpen */ templatevtabOpen,
+ /* xClose */ templatevtabClose,
+ /* xFilter */ templatevtabFilter,
+ /* xNext */ templatevtabNext,
+ /* xEof */ templatevtabEof,
+ /* xColumn */ templatevtabColumn,
+ /* xRowid */ templatevtabRowid,
+ /* xUpdate */ 0,
+ /* xBegin */ 0,
+ /* xSync */ 0,
+ /* xCommit */ 0,
+ /* xRollback */ 0,
+ /* xFindMethod */ 0,
+ /* xRename */ 0,
+ /* xSavepoint */ 0,
+ /* xRelease */ 0,
+ /* xRollbackTo */ 0
+};
+
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_templatevtab_init(
+ sqlite3 *db,
+ char **pzErrMsg,
+ const sqlite3_api_routines *pApi
+){
+ int rc = SQLITE_OK;
+ SQLITE_EXTENSION_INIT2(pApi);
+ rc = sqlite3_create_module(db, "templatevtab", &templatevtabModule, 0);
+ return rc;
+}
Index: ext/rbu/rbu.c
==================================================================
--- ext/rbu/rbu.c
+++ ext/rbu/rbu.c
@@ -27,11 +27,13 @@
"Usage: %s ?OPTIONS? TARGET-DB RBU-DB\n"
"\n"
"Where options are:\n"
"\n"
" -step NSTEP\n"
+" -statstep NSTATSTEP\n"
" -vacuum\n"
+" -presql SQL\n"
"\n"
" If the -vacuum switch is not present, argument RBU-DB must be an RBU\n"
" database containing an update suitable for target database TARGET-DB.\n"
" Or, if -vacuum is specified, then TARGET-DB is a database to vacuum using\n"
" RBU, and RBU-DB is used as the state database for the vacuum (refer to\n"
@@ -77,11 +79,13 @@
const char *zRbu; /* Database containing RBU */
char zBuf[200]; /* Buffer for printf() */
char *zErrmsg; /* Error message, if any */
sqlite3rbu *pRbu; /* RBU handle */
int nStep = 0; /* Maximum number of step() calls */
+ int nStatStep = 0; /* Report stats after this many step calls */
int bVacuum = 0;
+ const char *zPreSql = 0;
int rc;
sqlite3_int64 nProgress = 0;
int nArgc = argc-2;
if( argc<3 ) usage(argv[0]);
@@ -88,13 +92,22 @@
for(i=1; i1 && nArg<=8 && 0==memcmp(zArg, "-vacuum", nArg) ){
bVacuum = 1;
+ }else if( nArg>1 && nArg<=7
+ && 0==memcmp(zArg, "-presql", nArg) && i1 && nArg<=5 && 0==memcmp(zArg, "-step", nArg) && i1 && nArg<=9
+ && 0==memcmp(zArg, "-statstep", nArg) && i0 && (i % nStatStep)==0 ){
+ sqlite3_int64 nUsed;
+ sqlite3_int64 nHighwater;
+ sqlite3_status64(SQLITE_STATUS_MEMORY_USED, &nUsed, &nHighwater, 0);
+ fprintf(stdout, "memory used=%lld highwater=%lld", nUsed, nHighwater);
+ if( bVacuum==0 ){
+ int one;
+ int two;
+ sqlite3rbu_bp_progress(pRbu, &one, &two);
+ fprintf(stdout, " progress=%d/%d\n", one, two);
+ }else{
+ fprintf(stdout, "\n");
+ }
+ fflush(stdout);
+ }
+ }
+ nProgress = sqlite3rbu_progress(pRbu);
+ rc = sqlite3rbu_close(pRbu, &zErrmsg);
+ }
/* Let the user know what happened. */
switch( rc ){
case SQLITE_OK:
sqlite3_snprintf(sizeof(zBuf), zBuf,
Index: ext/rbu/rbu1.test
==================================================================
--- ext/rbu/rbu1.test
+++ ext/rbu/rbu1.test
@@ -137,10 +137,11 @@
eval $create_vfs
foreach {tn2 cmd} {
1 run_rbu
2 step_rbu 3 step_rbu_uri 4 step_rbu_state
+ 5 step_rbu_legacy
} {
foreach {tn schema} {
1 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
}
Index: ext/rbu/rbu_common.tcl
==================================================================
--- ext/rbu/rbu_common.tcl
+++ ext/rbu/rbu_common.tcl
@@ -67,10 +67,26 @@
rbu close
if {$rc != "SQLITE_OK"} break
}
set rc
}
+
+proc step_rbu_legacy {target rbu} {
+ while 1 {
+ sqlite3rbu rbu $target $rbu
+ set state [rbu state]
+ check_prestep_state $target $state
+ set rc [rbu step]
+ check_poststep_state $rc $target $state
+ rbu close
+ if {$rc != "SQLITE_OK"} break
+ sqlite3 tmpdb $rbu
+ tmpdb eval { DELETE FROM rbu_state WHERE k==10 }
+ tmpdb close
+ }
+ set rc
+}
proc do_rbu_vacuum_test {tn step} {
forcedelete state.db
uplevel [list do_test $tn.1 {
if {$step==0} { sqlite3rbu_vacuum rbu test.db state.db }
ADDED ext/rbu/rbusplit.test
Index: ext/rbu/rbusplit.test
==================================================================
--- /dev/null
+++ ext/rbu/rbusplit.test
@@ -0,0 +1,95 @@
+# 2018 April 28
+#
+# 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.
+#
+#***********************************************************************
+#
+#
+
+source [file join [file dirname [info script]] rbu_common.tcl]
+set ::testprefix rbusplit
+
+db close
+sqlite3_shutdown
+sqlite3_config_uri 1
+
+autoinstall_test_functions
+
+proc build_db {db} {
+ $db eval {
+ CREATE TABLE t1(a PRIMARY KEY, b, c);
+ CREATE TABLE t2(a PRIMARY KEY, b, c);
+
+ CREATE INDEX t1c ON t1(c);
+ }
+}
+
+proc build_rbu {filename} {
+ forcedelete $filename
+ sqlite3 dbRbu $filename
+ dbRbu eval {
+ CREATE TABLE data0_t1(a, b, c, rbu_control);
+ CREATE TABLE data1_t1(a, b, c, rbu_control);
+ CREATE TABLE data2_t1(a, b, c, rbu_control);
+ CREATE TABLE data3_t1(a, b, c, rbu_control);
+
+ CREATE TABLE data_t2(a, b, c, rbu_control);
+
+ INSERT INTO data0_t1 VALUES(1, 1, 1, 0);
+ INSERT INTO data0_t1 VALUES(2, 2, 2, 0);
+ INSERT INTO data0_t1 VALUES(3, 3, 3, 0);
+ INSERT INTO data0_t1 VALUES(4, 4, 4, 0);
+ INSERT INTO data1_t1 VALUES(5, 5, 5, 0);
+ INSERT INTO data1_t1 VALUES(6, 6, 6, 0);
+ INSERT INTO data1_t1 VALUES(7, 7, 7, 0);
+ INSERT INTO data1_t1 VALUES(8, 8, 8, 0);
+ INSERT INTO data3_t1 VALUES(9, 9, 9, 0);
+
+ INSERT INTO data_t2 VALUES(1, 1, 1, 0);
+ INSERT INTO data_t2 VALUES(2, 2, 2, 0);
+ INSERT INTO data_t2 VALUES(3, 3, 3, 0);
+ INSERT INTO data_t2 VALUES(4, 4, 4, 0);
+ INSERT INTO data_t2 VALUES(5, 5, 5, 0);
+ INSERT INTO data_t2 VALUES(6, 6, 6, 0);
+ INSERT INTO data_t2 VALUES(7, 7, 7, 0);
+ INSERT INTO data_t2 VALUES(8, 8, 8, 0);
+ INSERT INTO data_t2 VALUES(9, 9, 9, 0);
+ }
+
+ dbRbu close
+}
+
+foreach {tn cmd} {
+ 1 run_rbu
+ 2 step_rbu
+} {
+ reset_db
+ build_db db
+ build_rbu testrbu.db
+
+ do_test 1.$tn.1 {
+ $cmd test.db testrbu.db
+ } {SQLITE_DONE}
+ do_execsql_test 1.$tn.1 {
+ SELECT * FROM t1;
+ } {
+ 1 1 1 2 2 2 3 3 3 4 4 4
+ 5 5 5 6 6 6 7 7 7 8 8 8
+ 9 9 9
+ }
+ do_execsql_test 1.$tn.2 {
+ SELECT * FROM t2;
+ } {
+ 1 1 1 2 2 2 3 3 3 4 4 4
+ 5 5 5 6 6 6 7 7 7 8 8 8
+ 9 9 9
+ }
+}
+
+finish_test
+
Index: ext/rbu/sqlite3rbu.c
==================================================================
--- ext/rbu/sqlite3rbu.c
+++ ext/rbu/sqlite3rbu.c
@@ -151,10 +151,14 @@
** Valid if STAGE==1. The current change-counter cookie value in the
** target db file.
**
** RBU_STATE_OALSZ:
** Valid if STAGE==1. The size in bytes of the *-oal file.
+**
+** RBU_STATE_DATATBL:
+** Only valid if STAGE==1. The RBU database name of the table
+** currently being read.
*/
#define RBU_STATE_STAGE 1
#define RBU_STATE_TBL 2
#define RBU_STATE_IDX 3
#define RBU_STATE_ROW 4
@@ -161,10 +165,11 @@
#define RBU_STATE_PROGRESS 5
#define RBU_STATE_CKPT 6
#define RBU_STATE_COOKIE 7
#define RBU_STATE_OALSZ 8
#define RBU_STATE_PHASEONESTEP 9
+#define RBU_STATE_DATATBL 10
#define RBU_STAGE_OAL 1
#define RBU_STAGE_MOVE 2
#define RBU_STAGE_CAPTURE 3
#define RBU_STAGE_CKPT 4
@@ -203,10 +208,11 @@
** A structure to store values read from the rbu_state table in memory.
*/
struct RbuState {
int eStage;
char *zTbl;
+ char *zDataTbl;
char *zIdx;
i64 iWalCksum;
int nRow;
i64 nProgress;
u32 iCookie;
@@ -2266,10 +2272,11 @@
** Free an RbuState object allocated by rbuLoadState().
*/
static void rbuFreeState(RbuState *p){
if( p ){
sqlite3_free(p->zTbl);
+ sqlite3_free(p->zDataTbl);
sqlite3_free(p->zIdx);
sqlite3_free(p);
}
}
@@ -2335,10 +2342,14 @@
break;
case RBU_STATE_PHASEONESTEP:
pRet->nPhaseOneStep = sqlite3_column_int64(pStmt, 1);
break;
+
+ case RBU_STATE_DATATBL:
+ pRet->zDataTbl = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc);
+ break;
default:
rc = SQLITE_CORRUPT;
break;
}
@@ -3110,21 +3121,23 @@
"(%d, %d), "
"(%d, %d), "
"(%d, %lld), "
"(%d, %lld), "
"(%d, %lld), "
- "(%d, %lld) ",
+ "(%d, %lld), "
+ "(%d, %Q) ",
p->zStateDb,
RBU_STATE_STAGE, eStage,
RBU_STATE_TBL, p->objiter.zTbl,
RBU_STATE_IDX, p->objiter.zIdx,
RBU_STATE_ROW, p->nStep,
RBU_STATE_PROGRESS, p->nProgress,
RBU_STATE_CKPT, p->iWalCksum,
RBU_STATE_COOKIE, (i64)pFd->iCookie,
RBU_STATE_OALSZ, p->iOalSz,
- RBU_STATE_PHASEONESTEP, p->nPhaseOneStep
+ RBU_STATE_PHASEONESTEP, p->nPhaseOneStep,
+ RBU_STATE_DATATBL, p->objiter.zDataTbl
)
);
assert( pInsert==0 || rc==SQLITE_OK );
if( rc==SQLITE_OK ){
@@ -3376,11 +3389,12 @@
RbuObjIter *pIter = &p->objiter;
int rc = SQLITE_OK;
while( rc==SQLITE_OK && pIter->zTbl && (pIter->bCleanup
|| rbuStrCompare(pIter->zIdx, pState->zIdx)
- || rbuStrCompare(pIter->zTbl, pState->zTbl)
+ || (pState->zDataTbl==0 && rbuStrCompare(pIter->zTbl, pState->zTbl))
+ || (pState->zDataTbl && rbuStrCompare(pIter->zDataTbl, pState->zDataTbl))
)){
rc = rbuObjIterNext(p, pIter);
}
if( rc==SQLITE_OK && !pIter->zTbl ){
Index: ext/rtree/rtree6.test
==================================================================
--- ext/rtree/rtree6.test
+++ ext/rtree/rtree6.test
@@ -72,46 +72,52 @@
} {C0}
do_eqp_test rtree6.2.1 {
SELECT * FROM t1,t2 WHERE k=+ii AND x1<10
} {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 2:C0}
- 0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)}
+ QUERY PLAN
+ |--SCAN TABLE t1 VIRTUAL TABLE INDEX 2:C0
+ `--SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)
}
do_eqp_test rtree6.2.2 {
SELECT * FROM t1,t2 WHERE k=ii AND x1<10
} {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 2:C0}
- 0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)}
+ QUERY PLAN
+ |--SCAN TABLE t1 VIRTUAL TABLE INDEX 2:C0
+ `--SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)
}
do_eqp_test rtree6.2.3 {
SELECT * FROM t1,t2 WHERE k=ii
} {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 2:}
- 0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)}
+ QUERY PLAN
+ |--SCAN TABLE t1 VIRTUAL TABLE INDEX 2:
+ `--SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)
}
do_eqp_test rtree6.2.4.1 {
SELECT * FROM t1,t2 WHERE v=+ii and x1<10 and x2>10
} {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 2:C0E1}
- 0 1 1 {SEARCH TABLE t2 USING AUTOMATIC COVERING INDEX (v=?)}
+ QUERY PLAN
+ |--SCAN TABLE t1 VIRTUAL TABLE INDEX 2:C0E1
+ `--SEARCH TABLE t2 USING AUTOMATIC COVERING INDEX (v=?)
}
do_eqp_test rtree6.2.4.2 {
SELECT * FROM t1,t2 WHERE v=10 and x1<10 and x2>10
} {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 2:C0E1}
- 0 1 1 {SEARCH TABLE t2 USING AUTOMATIC PARTIAL COVERING INDEX (v=?)}
+ QUERY PLAN
+ |--SCAN TABLE t1 VIRTUAL TABLE INDEX 2:C0E1
+ `--SEARCH TABLE t2 USING AUTOMATIC PARTIAL COVERING INDEX (v=?)
}
do_eqp_test rtree6.2.5 {
SELECT * FROM t1,t2 WHERE k=ii AND x1=min_x AND t.x<=max_x AND t.y>=min_y AND t.x<=max_y
} {
- 0 0 1 {SCAN TABLE t}
- 0 1 0 {SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0}
+ QUERY PLAN
+ |--SCAN TABLE t
+ `--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0
}
do_eqp_test 1.2 {
SELECT * FROM t, r_tree
WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND t.x<=max_y
} {
- 0 0 0 {SCAN TABLE t}
- 0 1 1 {SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0}
+ QUERY PLAN
+ |--SCAN TABLE t
+ `--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0
}
do_eqp_test 1.3 {
SELECT * FROM t, r_tree
WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND ?<=max_y
} {
- 0 0 0 {SCAN TABLE t}
- 0 1 1 {SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0}
+ QUERY PLAN
+ |--SCAN TABLE t
+ `--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0
}
do_eqp_test 1.5 {
SELECT * FROM t, r_tree
} {
- 0 0 1 {SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:}
- 0 1 0 {SCAN TABLE t}
+ QUERY PLAN
+ |--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:
+ `--SCAN TABLE t
}
do_execsql_test 2.0 {
INSERT INTO t VALUES(0, 0);
INSERT INTO t VALUES(0, 1);
@@ -80,35 +84,39 @@
do_eqp_test 2.1 {
SELECT * FROM r_tree, t
WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND t.x<=max_y
} {
- 0 0 1 {SCAN TABLE t}
- 0 1 0 {SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0}
+ QUERY PLAN
+ |--SCAN TABLE t
+ `--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0
}
do_eqp_test 2.2 {
SELECT * FROM t, r_tree
WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND t.x<=max_y
} {
- 0 0 0 {SCAN TABLE t}
- 0 1 1 {SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0}
+ QUERY PLAN
+ |--SCAN TABLE t
+ `--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0
}
do_eqp_test 2.3 {
SELECT * FROM t, r_tree
WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND ?<=max_y
} {
- 0 0 0 {SCAN TABLE t}
- 0 1 1 {SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0}
+ QUERY PLAN
+ |--SCAN TABLE t
+ `--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0
}
do_eqp_test 2.5 {
SELECT * FROM t, r_tree
} {
- 0 0 1 {SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:}
- 0 1 0 {SCAN TABLE t}
+ QUERY PLAN
+ |--SCAN TABLE r_tree VIRTUAL TABLE INDEX 2:
+ `--SCAN TABLE t
}
#-------------------------------------------------------------------------
# Test that the special CROSS JOIN handling works with rtree tables.
#
@@ -117,24 +125,29 @@
CREATE TABLE t2(y);
CREATE VIRTUAL TABLE t3 USING rtree(z, x1,x2, y1,y2);
}
do_eqp_test 3.2.1 { SELECT * FROM t1 CROSS JOIN t2 } {
- 0 0 0 {SCAN TABLE t1}
- 0 1 1 {SCAN TABLE t2}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--SCAN TABLE t2
}
do_eqp_test 3.2.2 { SELECT * FROM t2 CROSS JOIN t1 } {
- 0 0 0 {SCAN TABLE t2} 0 1 1 {SCAN TABLE t1}
+ QUERY PLAN
+ |--SCAN TABLE t2
+ `--SCAN TABLE t1
}
do_eqp_test 3.3.1 { SELECT * FROM t1 CROSS JOIN t3 } {
- 0 0 0 {SCAN TABLE t1}
- 0 1 1 {SCAN TABLE t3 VIRTUAL TABLE INDEX 2:}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--SCAN TABLE t3 VIRTUAL TABLE INDEX 2:
}
do_eqp_test 3.3.2 { SELECT * FROM t3 CROSS JOIN t1 } {
- 0 0 0 {SCAN TABLE t3 VIRTUAL TABLE INDEX 2:}
- 0 1 1 {SCAN TABLE t1}
+ QUERY PLAN
+ |--SCAN TABLE t3 VIRTUAL TABLE INDEX 2:
+ `--SCAN TABLE t1
}
#--------------------------------------------------------------------
# Test that LEFT JOINs are not reordered if the right-hand-side is
# a virtual table.
@@ -187,12 +200,13 @@
# real table "t1".
#
do_eqp_test 5.2 {
SELECT * FROM t1, rt WHERE x==id;
} {
- 0 0 0 {SCAN TABLE t1}
- 0 1 1 {SCAN TABLE rt VIRTUAL TABLE INDEX 1:}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--SCAN TABLE rt VIRTUAL TABLE INDEX 1:
}
# Now create enough ANALYZE data to tell SQLite that virtual table "rt"
# contains very few rows. This causes it to move "rt" to the outer loop.
#
@@ -203,12 +217,13 @@
db close
sqlite3 db test.db
do_eqp_test 5.4 {
SELECT * FROM t1, rt WHERE x==id;
} {
- 0 0 1 {SCAN TABLE rt VIRTUAL TABLE INDEX 2:}
- 0 1 0 {SEARCH TABLE t1 USING INDEX sqlite_autoindex_t1_1 (x=?)}
+ QUERY PLAN
+ |--SCAN TABLE rt VIRTUAL TABLE INDEX 2:
+ `--SEARCH TABLE t1 USING INDEX sqlite_autoindex_t1_1 (x=?)
}
# Delete the ANALYZE data. "t1" should be the outer loop again.
#
do_execsql_test 5.5 { DROP TABLE sqlite_stat1; }
@@ -215,12 +230,13 @@
db close
sqlite3 db test.db
do_eqp_test 5.6 {
SELECT * FROM t1, rt WHERE x==id;
} {
- 0 0 0 {SCAN TABLE t1}
- 0 1 1 {SCAN TABLE rt VIRTUAL TABLE INDEX 1:}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--SCAN TABLE rt VIRTUAL TABLE INDEX 1:
}
# This time create and attach a database that contains ANALYZE data for
# tables of the same names as those used internally by virtual table
# "rt". Check that the rtree module is not fooled into using this data.
@@ -239,12 +255,13 @@
execsql { ATTACH 'test.db2' AS aux; }
} {}
do_eqp_test 5.8 {
SELECT * FROM t1, rt WHERE x==id;
} {
- 0 0 0 {SCAN TABLE t1}
- 0 1 1 {SCAN TABLE rt VIRTUAL TABLE INDEX 1:}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--SCAN TABLE rt VIRTUAL TABLE INDEX 1:
}
#--------------------------------------------------------------------
# Test that having a second connection drop the sqlite_stat1 table
# before it is required by rtreeConnect() does not cause problems.
@@ -297,35 +314,38 @@
INSERT INTO rt VALUES(3, 7, 11, 8, 12); -- Not a hit!
INSERT INTO rt VALUES(4, 5, 5, 10, 10); -- A hit!
}
-proc do_eqp_execsql_test {tn sql res} {
- set query "EXPLAIN QUERY PLAN $sql ; $sql "
- uplevel [list do_execsql_test $tn $query $res]
+proc do_eqp_execsql_test {tn sql res1 res2} {
+ do_eqp_test $tn.1 $sql $res1
+ do_execsql_test $tn.2 $sql $res2
}
do_eqp_execsql_test 7.1 {
SELECT id FROM xdir, rt, ydir
ON (y1 BETWEEN ymin AND ymax)
WHERE (x1 BETWEEN xmin AND xmax);
} {
- 0 0 0 {SCAN TABLE xdir}
- 0 1 2 {SCAN TABLE ydir}
- 0 2 1 {SCAN TABLE rt VIRTUAL TABLE INDEX 2:B2D3B0D1}
+ QUERY PLAN
+ |--SCAN TABLE xdir
+ |--SCAN TABLE ydir
+ `--SCAN TABLE rt VIRTUAL TABLE INDEX 2:B2D3B0D1
+} {
2 4
}
do_eqp_execsql_test 7.2 {
SELECT * FROM xdir, rt LEFT JOIN ydir
ON (y1 BETWEEN ymin AND ymax)
WHERE (x1 BETWEEN xmin AND xmax);
} {
- 0 0 0 {SCAN TABLE xdir}
- 0 1 1 {SCAN TABLE rt VIRTUAL TABLE INDEX 2:B0D1}
- 0 2 2 {SCAN TABLE ydir}
-
+ QUERY PLAN
+ |--SCAN TABLE xdir
+ |--SCAN TABLE rt VIRTUAL TABLE INDEX 2:B0D1
+ `--SCAN TABLE ydir
+} {
5 1 2 7 12 14 {}
5 2 2 7 8 12 10
5 4 5 5 10 10 10
}
@@ -332,23 +352,27 @@
do_eqp_execsql_test 7.3 {
SELECT id FROM xdir, rt CROSS JOIN ydir
ON (y1 BETWEEN ymin AND ymax)
WHERE (x1 BETWEEN xmin AND xmax);
} {
- 0 0 0 {SCAN TABLE xdir}
- 0 1 1 {SCAN TABLE rt VIRTUAL TABLE INDEX 2:B0D1}
- 0 2 2 {SCAN TABLE ydir}
+ QUERY PLAN
+ |--SCAN TABLE xdir
+ |--SCAN TABLE rt VIRTUAL TABLE INDEX 2:B0D1
+ `--SCAN TABLE ydir
+} {
2 4
}
do_eqp_execsql_test 7.4 {
SELECT id FROM rt, xdir CROSS JOIN ydir
ON (y1 BETWEEN ymin AND ymax)
WHERE (x1 BETWEEN xmin AND xmax);
} {
- 0 0 1 {SCAN TABLE xdir}
- 0 1 0 {SCAN TABLE rt VIRTUAL TABLE INDEX 2:B0D1}
- 0 2 2 {SCAN TABLE ydir}
+ QUERY PLAN
+ |--SCAN TABLE xdir
+ |--SCAN TABLE rt VIRTUAL TABLE INDEX 2:B0D1
+ `--SCAN TABLE ydir
+} {
2 4
}
finish_test
Index: main.mk
==================================================================
--- main.mk
+++ main.mk
@@ -70,11 +70,11 @@
notify.o opcodes.o os.o os_unix.o os_win.o \
pager.o pcache.o pcache1.o pragma.o prepare.o printf.o \
random.o resolve.o rowset.o rtree.o \
select.o sqlite3rbu.o status.o stmt.o \
table.o threads.o tokenize.o treeview.o trigger.o \
- update.o userauth.o util.o vacuum.o \
+ update.o upsert.o userauth.o util.o vacuum.o \
vdbeapi.o vdbeaux.o vdbeblob.o vdbemem.o vdbesort.o \
vdbetrace.o wal.o walker.o where.o wherecode.o whereexpr.o \
utf.o vtab.o
LIBOBJ += sqlite3session.o
@@ -160,10 +160,11 @@
$(TOP)/src/tokenize.c \
$(TOP)/src/treeview.c \
$(TOP)/src/trigger.c \
$(TOP)/src/utf.c \
$(TOP)/src/update.c \
+ $(TOP)/src/upsert.c \
$(TOP)/src/util.c \
$(TOP)/src/vacuum.c \
$(TOP)/src/vdbe.c \
$(TOP)/src/vdbe.h \
$(TOP)/src/vdbeapi.c \
Index: src/attach.c
==================================================================
--- src/attach.c
+++ src/attach.c
@@ -156,11 +156,11 @@
flags |= SQLITE_OPEN_MAIN_DB;
rc = sqlite3BtreeOpen(pVfs, zPath, db, &pNew->pBt, 0, flags);
sqlite3_free( zPath );
db->nDb++;
}
- db->skipBtreeMutex = 0;
+ db->noSharedCache = 0;
if( rc==SQLITE_CONSTRAINT ){
rc = SQLITE_ERROR;
zErrDyn = sqlite3MPrintf(db, "database is already attached");
}else if( rc==SQLITE_OK ){
Pager *pPager;
@@ -228,10 +228,11 @@
** way we found it.
*/
if( rc==SQLITE_OK ){
sqlite3BtreeEnterAll(db);
db->init.iDb = 0;
+ db->mDbFlags &= ~(DBFLAG_SchemaKnownOk);
rc = sqlite3Init(db, &zErrDyn);
sqlite3BtreeLeaveAll(db);
assert( zErrDyn==0 || rc!=SQLITE_OK );
}
#ifdef SQLITE_USER_AUTHENTICATION
@@ -500,10 +501,13 @@
}
#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER)
if( sqlite3FixSelect(pFix, pItem->pSelect) ) return 1;
if( sqlite3FixExpr(pFix, pItem->pOn) ) return 1;
#endif
+ if( pItem->fg.isTabFunc && sqlite3FixExprList(pFix, pItem->u1.pFuncArg) ){
+ return 1;
+ }
}
return 0;
}
#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER)
int sqlite3FixSelect(
@@ -599,10 +603,22 @@
return 1;
}
if( sqlite3FixExprList(pFix, pStep->pExprList) ){
return 1;
}
+#ifndef SQLITE_OMIT_UPSERT
+ if( pStep->pUpsert ){
+ Upsert *pUp = pStep->pUpsert;
+ if( sqlite3FixExprList(pFix, pUp->pUpsertTarget)
+ || sqlite3FixExpr(pFix, pUp->pUpsertTargetWhere)
+ || sqlite3FixExprList(pFix, pUp->pUpsertSet)
+ || sqlite3FixExpr(pFix, pUp->pUpsertWhere)
+ ){
+ return 1;
+ }
+ }
+#endif
pStep = pStep->pNext;
}
return 0;
}
#endif
Index: src/btmutex.c
==================================================================
--- src/btmutex.c
+++ src/btmutex.c
@@ -193,14 +193,14 @@
if( p && p->sharable ){
sqlite3BtreeEnter(p);
skipOk = 0;
}
}
- db->skipBtreeMutex = skipOk;
+ db->noSharedCache = skipOk;
}
void sqlite3BtreeEnterAll(sqlite3 *db){
- if( db->skipBtreeMutex==0 ) btreeEnterAll(db);
+ if( db->noSharedCache==0 ) btreeEnterAll(db);
}
static void SQLITE_NOINLINE btreeLeaveAll(sqlite3 *db){
int i;
Btree *p;
assert( sqlite3_mutex_held(db->mutex) );
@@ -208,11 +208,11 @@
p = db->aDb[i].pBt;
if( p ) sqlite3BtreeLeave(p);
}
}
void sqlite3BtreeLeaveAll(sqlite3 *db){
- if( db->skipBtreeMutex==0 ) btreeLeaveAll(db);
+ if( db->noSharedCache==0 ) btreeLeaveAll(db);
}
#ifndef NDEBUG
/*
** Return true if the current thread holds the database connection
Index: src/btree.c
==================================================================
--- src/btree.c
+++ src/btree.c
@@ -2975,10 +2975,14 @@
}
}
#else
# define setDefaultSyncFlag(pBt,safety_level)
#endif
+
+/* Forward declaration */
+static int newDatabase(BtShared*);
+
/*
** Get a reference to pPage1 of the database file. This will
** also acquire a readlock on that file.
**
@@ -3006,10 +3010,13 @@
*/
nPage = nPageHeader = get4byte(28+(u8*)pPage1->aData);
sqlite3PagerPagecount(pBt->pPager, &nPageFile);
if( nPage==0 || memcmp(24+(u8*)pPage1->aData, 92+(u8*)pPage1->aData,4)!=0 ){
nPage = nPageFile;
+ }
+ if( (pBt->db->flags & SQLITE_ResetDatabase)!=0 ){
+ nPage = 0;
}
if( nPage>0 ){
u32 pageSize;
u32 usableSize;
u8 *page1 = pPage1->aData;
Index: src/build.c
==================================================================
--- src/build.c
+++ src/build.c
@@ -341,28 +341,31 @@
u32 flags, /* LOCATE_VIEW or LOCATE_NOERR */
const char *zName, /* Name of the table we are looking for */
const char *zDbase /* Name of the database. Might be NULL */
){
Table *p;
+ sqlite3 *db = pParse->db;
/* Read the database schema. If an error occurs, leave an error message
** and code in pParse and return NULL. */
- if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
+ if( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0
+ && SQLITE_OK!=sqlite3ReadSchema(pParse)
+ ){
return 0;
}
- p = sqlite3FindTable(pParse->db, zName, zDbase);
+ p = sqlite3FindTable(db, zName, zDbase);
if( p==0 ){
const char *zMsg = flags & LOCATE_VIEW ? "no such view" : "no such table";
#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( sqlite3FindDbName(pParse->db, zDbase)<1 ){
+ if( sqlite3FindDbName(db, zDbase)<1 ){
/* If zName is the not the name of a table in the schema created using
** CREATE, then check to see if it is the name of an virtual table that
** can be an eponymous virtual table. */
- Module *pMod = (Module*)sqlite3HashFind(&pParse->db->aModule, zName);
+ Module *pMod = (Module*)sqlite3HashFind(&db->aModule, zName);
if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){
- pMod = sqlite3PragmaVtabRegister(pParse->db, zName);
+ pMod = sqlite3PragmaVtabRegister(db, zName);
}
if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){
return pMod->pEpoTab;
}
}
@@ -523,10 +526,11 @@
if( iDb>=0 ){
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
DbSetProperty(db, iDb, DB_ResetWanted);
DbSetProperty(db, 1, DB_ResetWanted);
+ db->mDbFlags &= ~DBFLAG_SchemaKnownOk;
}
if( db->nSchemaLock==0 ){
for(i=0; inDb; i++){
if( DbHasProperty(db, i, DB_ResetWanted) ){
@@ -548,11 +552,11 @@
Db *pDb = &db->aDb[i];
if( pDb->pSchema ){
sqlite3SchemaClear(pDb->pSchema);
}
}
- db->mDbFlags &= ~DBFLAG_SchemaChange;
+ db->mDbFlags &= ~(DBFLAG_SchemaChange|DBFLAG_SchemaKnownOk);
sqlite3VtabUnlockList(db);
sqlite3BtreeLeaveAll(db);
sqlite3CollapseDatabaseArray(db);
}
@@ -1093,19 +1097,24 @@
pCol->zName = z;
sqlite3ColumnPropertiesFromName(p, pCol);
if( pType->n==0 ){
/* If there is no type specified, columns have the default affinity
- ** 'BLOB'. */
+ ** 'BLOB' with a default size of 4 bytes. */
pCol->affinity = SQLITE_AFF_BLOB;
pCol->szEst = 1;
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ if( 4>=sqlite3GlobalConfig.szSorterRef ){
+ pCol->colFlags |= COLFLAG_SORTERREF;
+ }
+#endif
}else{
zType = z + sqlite3Strlen30(z) + 1;
memcpy(zType, pType->z, pType->n);
zType[pType->n] = 0;
sqlite3Dequote(zType);
- pCol->affinity = sqlite3AffinityType(zType, &pCol->szEst);
+ pCol->affinity = sqlite3AffinityType(zType, pCol);
pCol->colFlags |= COLFLAG_HASTYPE;
}
p->nCol++;
pParse->constraintName.n = 0;
}
@@ -1161,11 +1170,11 @@
** 'DOUB' | SQLITE_AFF_REAL
**
** If none of the substrings in the above table are found,
** SQLITE_AFF_NUMERIC is returned.
*/
-char sqlite3AffinityType(const char *zIn, u8 *pszEst){
+char sqlite3AffinityType(const char *zIn, Column *pCol){
u32 h = 0;
char aff = SQLITE_AFF_NUMERIC;
const char *zChar = 0;
assert( zIn!=0 );
@@ -1198,31 +1207,36 @@
aff = SQLITE_AFF_INTEGER;
break;
}
}
- /* If pszEst is not NULL, store an estimate of the field size. The
+ /* If pCol is not NULL, store an estimate of the field size. The
** estimate is scaled so that the size of an integer is 1. */
- if( pszEst ){
- *pszEst = 1; /* default size is approx 4 bytes */
+ if( pCol ){
+ int v = 0; /* default size is approx 4 bytes */
if( aff r=(k/4+1) */
sqlite3GetInt32(zChar, &v);
- v = v/4 + 1;
- if( v>255 ) v = 255;
- *pszEst = v; /* BLOB(k), VARCHAR(k), CHAR(k) -> r=(k/4+1) */
break;
}
zChar++;
}
}else{
- *pszEst = 5; /* BLOB, TEXT, CLOB -> r=5 (approx 20 bytes)*/
+ v = 16; /* BLOB, TEXT, CLOB -> r=5 (approx 20 bytes)*/
}
}
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ if( v>=sqlite3GlobalConfig.szSorterRef ){
+ pCol->colFlags |= COLFLAG_SORTERREF;
+ }
+#endif
+ v = v/4 + 1;
+ if( v>255 ) v = 255;
+ pCol->szEst = v;
}
return aff;
}
/*
@@ -3008,11 +3022,15 @@
if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0
&& db->init.busy==0
#if SQLITE_USER_AUTHENTICATION
&& sqlite3UserAuthTable(pTab->zName)==0
#endif
- && sqlite3StrNICmp(&pTab->zName[7],"altertab_",9)!=0 ){
+#ifdef SQLITE_ALLOW_SQLITE_MASTER_INDEX
+ && sqlite3StrICmp(&pTab->zName[7],"master")!=0
+#endif
+ && sqlite3StrNICmp(&pTab->zName[7],"altertab_",9)!=0
+ ){
sqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName);
goto exit_create_index;
}
#ifndef SQLITE_OMIT_VIEW
if( pTab->pSelect ){
Index: src/ctime.c
==================================================================
--- src/ctime.c
+++ src/ctime.c
@@ -283,10 +283,13 @@
#if SQLITE_ENABLE_SESSION
"ENABLE_SESSION",
#endif
#if SQLITE_ENABLE_SNAPSHOT
"ENABLE_SNAPSHOT",
+#endif
+#if SQLITE_ENABLE_SORTER_REFERENCES
+ "ENABLE_SORTER_REFERENCES",
#endif
#if SQLITE_ENABLE_SQLLOG
"ENABLE_SQLLOG",
#endif
#if defined(SQLITE_ENABLE_STAT4)
Index: src/delete.c
==================================================================
--- src/delete.c
+++ src/delete.c
@@ -236,11 +236,11 @@
int nIdx; /* Number of indices */
sqlite3 *db; /* Main database structure */
AuthContext sContext; /* Authorization context */
NameContext sNC; /* Name context to resolve expressions in */
int iDb; /* Database number */
- int memCnt = -1; /* Memory cell used for change counting */
+ int memCnt = 0; /* Memory cell used for change counting */
int rcauth; /* Value returned by authorization callback */
int eOnePass; /* ONEPASS_OFF or _SINGLE or _MULTI */
int aiCurOnePass[2]; /* The write cursors opened by WHERE_ONEPASS */
u8 *aToOpen = 0; /* Open cursor iTabCur+j if aToOpen[j] is true */
Index *pPk; /* The PRIMARY KEY index on the table */
@@ -341,11 +341,11 @@
v = sqlite3GetVdbe(pParse);
if( v==0 ){
goto delete_from_cleanup;
}
if( pParse->nested==0 ) sqlite3VdbeCountChanges(v);
- sqlite3BeginWriteOperation(pParse, 1, iDb);
+ sqlite3BeginWriteOperation(pParse, bComplex, iDb);
/* If we are trying to delete from a view, realize that view into
** an ephemeral table.
*/
#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
@@ -369,11 +369,14 @@
}
/* Initialize the counter of the number of rows deleted, if
** we are counting rows.
*/
- if( db->flags & SQLITE_CountRows ){
+ if( (db->flags & SQLITE_CountRows)!=0
+ && !pParse->nested
+ && !pParse->pTriggerTab
+ ){
memCnt = ++pParse->nMem;
sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt);
}
#ifndef SQLITE_OMIT_TRUNCATE_OPTIMIZATION
@@ -397,11 +400,11 @@
#endif
){
assert( !isView );
sqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName);
if( HasRowid(pTab) ){
- sqlite3VdbeAddOp4(v, OP_Clear, pTab->tnum, iDb, memCnt,
+ sqlite3VdbeAddOp4(v, OP_Clear, pTab->tnum, iDb, memCnt ? memCnt : -1,
pTab->zName, P4_STATIC);
}
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
assert( pIdx->pSchema==pTab->pSchema );
sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb);
@@ -442,13 +445,14 @@
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf, iTabCur+1);
if( pWInfo==0 ) goto delete_from_cleanup;
eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
assert( IsVirtual(pTab)==0 || eOnePass!=ONEPASS_MULTI );
assert( IsVirtual(pTab) || bComplex || eOnePass!=ONEPASS_OFF );
+ if( eOnePass!=ONEPASS_SINGLE ) sqlite3MultiWrite(pParse);
/* Keep track of the number of rows to be deleted */
- if( db->flags & SQLITE_CountRows ){
+ if( memCnt ){
sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1);
}
/* Extract the rowid or primary key for the current row */
if( pPk ){
@@ -587,11 +591,11 @@
/* Return the number of rows that were deleted. If this routine is
** generating code because of a call to sqlite3NestedParse(), do not
** invoke the callback function.
*/
- if( (db->flags&SQLITE_CountRows) && !pParse->nested && !pParse->pTriggerTab ){
+ if( memCnt ){
sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1);
sqlite3VdbeSetNumCols(v, 1);
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted", SQLITE_STATIC);
}
Index: src/expr.c
==================================================================
--- src/expr.c
+++ src/expr.c
@@ -1361,10 +1361,11 @@
pItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
pItem->zSpan = sqlite3DbStrDup(db, pOldItem->zSpan);
pItem->sortOrder = pOldItem->sortOrder;
pItem->done = 0;
pItem->bSpanIsTab = pOldItem->bSpanIsTab;
+ pItem->bSorterRef = pOldItem->bSorterRef;
pItem->u = pOldItem->u;
}
return pNew;
}
@@ -1823,10 +1824,12 @@
if( pWalker->eCode==3 && pExpr->iTable==pWalker->u.iCur ){
return WRC_Continue;
}
/* Fall through */
case TK_IF_NULL_ROW:
+ case TK_REGISTER:
+ testcase( pExpr->op==TK_REGISTER );
testcase( pExpr->op==TK_IF_NULL_ROW );
pWalker->eCode = 0;
return WRC_Abort;
case TK_VARIABLE:
if( pWalker->eCode==5 ){
@@ -1840,12 +1843,12 @@
pWalker->eCode = 0;
return WRC_Abort;
}
/* Fall through */
default:
- testcase( pExpr->op==TK_SELECT ); /* sqlite3SelectWalkFail will disallow */
- testcase( pExpr->op==TK_EXISTS ); /* sqlite3SelectWalkFail will disallow */
+ testcase( pExpr->op==TK_SELECT ); /* sqlite3SelectWalkFail() disallows */
+ testcase( pExpr->op==TK_EXISTS ); /* sqlite3SelectWalkFail() disallows */
return WRC_Continue;
}
}
static int exprIsConst(Expr *p, int initFlag, int iCur){
Walker w;
@@ -2405,15 +2408,12 @@
assert( i==nExpr || colUsed!=(MASKBIT(nExpr)-1) );
if( colUsed==(MASKBIT(nExpr)-1) ){
/* If we reach this point, that means the index pIdx is usable */
int iAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
-#ifndef SQLITE_OMIT_EXPLAIN
- sqlite3VdbeAddOp4(v, OP_Explain, 0, 0, 0,
- sqlite3MPrintf(db, "USING INDEX %s FOR IN-OPERATOR",pIdx->zName),
- P4_DYNAMIC);
-#endif
+ ExplainQueryPlan((pParse, 0,
+ "USING INDEX %s FOR IN-OPERATOR",pIdx->zName));
sqlite3VdbeAddOp3(v, OP_OpenRead, iTab, pIdx->tnum, iDb);
sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
VdbeComment((v, "%s", pIdx->zName));
assert( IN_INDEX_INDEX_DESC == IN_INDEX_INDEX_ASC+1 );
eType = IN_INDEX_INDEX_ASC + pIdx->aSortOrder[0];
@@ -2604,21 +2604,10 @@
*/
if( !ExprHasProperty(pExpr, EP_VarSelect) ){
jmpIfDynamic = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
}
-#ifndef SQLITE_OMIT_EXPLAIN
- if( pParse->explain==2 ){
- char *zMsg = sqlite3MPrintf(pParse->db, "EXECUTE %s%s SUBQUERY %d",
- jmpIfDynamic>=0?"":"CORRELATED ",
- pExpr->op==TK_IN?"LIST":"SCALAR",
- pParse->iNextSelectId
- );
- sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC);
- }
-#endif
-
switch( pExpr->op ){
case TK_IN: {
int addr; /* Address of OP_OpenEphemeral instruction */
Expr *pLeft = pExpr->pLeft; /* the LHS of the IN operator */
KeyInfo *pKeyInfo = 0; /* Key information */
@@ -2652,10 +2641,13 @@
** table allocated and opened above.
*/
Select *pSelect = pExpr->x.pSelect;
ExprList *pEList = pSelect->pEList;
+ ExplainQueryPlan((pParse, 1, "%sLIST SUBQUERY",
+ jmpIfDynamic>=0?"":"CORRELATED "
+ ));
assert( !isRowid );
/* If the LHS and RHS of the IN operator do not match, that
** error will have been caught long before we reach this point. */
if( ALWAYS(pEList->nExpr==nVal) ){
SelectDest dest;
@@ -2693,11 +2685,10 @@
char affinity; /* Affinity of the LHS of the IN */
int i;
ExprList *pList = pExpr->x.pList;
struct ExprList_item *pItem;
int r1, r2, r3;
-
affinity = sqlite3ExprAffinity(pLeft);
if( !affinity ){
affinity = SQLITE_AFF_BLOB;
}
if( pKeyInfo ){
@@ -2774,10 +2765,12 @@
testcase( pExpr->op==TK_SELECT );
assert( pExpr->op==TK_EXISTS || pExpr->op==TK_SELECT );
assert( ExprHasProperty(pExpr, EP_xIsSelect) );
pSel = pExpr->x.pSelect;
+ ExplainQueryPlan((pParse, 1, "%sSCALAR SUBQUERY",
+ jmpIfDynamic>=0?"":"CORRELATED "));
nReg = pExpr->op==TK_SELECT ? pSel->pEList->nExpr : 1;
sqlite3SelectDestInit(&dest, 0, pParse->nMem+1);
pParse->nMem += nReg;
if( pExpr->op==TK_SELECT ){
dest.eDest = SRT_Mem;
@@ -3536,10 +3529,11 @@
if( v==0 ){
assert( pParse->db->mallocFailed );
return 0;
}
+expr_code_doover:
if( pExpr==0 ){
op = TK_NULL;
}else{
op = pExpr->op;
}
@@ -3996,11 +3990,12 @@
return target;
}
case TK_SPAN:
case TK_COLLATE:
case TK_UPLUS: {
- return sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target);
+ pExpr = pExpr->pLeft;
+ goto expr_code_doover;
}
case TK_TRIGGER: {
/* If the opcode is TK_TRIGGER, then the expression is a reference
** to a column in the new.* or old.* pseudo-tables available to
@@ -4034,14 +4029,13 @@
assert( pExpr->iColumn>=-1 && pExpr->iColumnnCol );
assert( pTab->iPKey<0 || pExpr->iColumn!=pTab->iPKey );
assert( p1>=0 && p1<(pTab->nCol*2+2) );
sqlite3VdbeAddOp2(v, OP_Param, p1, target);
- VdbeComment((v, "%s.%s -> $%d",
+ VdbeComment((v, "r[%d]=%s.%s", target,
(pExpr->iTable ? "new" : "old"),
- (pExpr->iColumn<0 ? "rowid" : pExpr->pTab->aCol[pExpr->iColumn].zName),
- target
+ (pExpr->iColumn<0 ? "rowid" : pExpr->pTab->aCol[pExpr->iColumn].zName)
));
#ifndef SQLITE_OMIT_FLOATING_POINT
/* If the column has REAL affinity, it may currently be stored as an
** integer. Use OP_RealAffinity to make sure it is really real.
@@ -4369,10 +4363,16 @@
assert( pParse->pVdbe!=0 ); /* Never gets this far otherwise */
n = pList->nExpr;
if( !ConstFactorOk(pParse) ) flags &= ~SQLITE_ECEL_FACTOR;
for(pItem=pList->a, i=0; ipExpr;
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ if( pItem->bSorterRef ){
+ i--;
+ n--;
+ }else
+#endif
if( (flags & SQLITE_ECEL_REF)!=0 && (j = pItem->u.x.iOrderByCol)>0 ){
if( flags & SQLITE_ECEL_OMITREF ){
i--;
n--;
}else{
@@ -4897,21 +4897,24 @@
return 2;
}
if( pA->op!=TK_COLUMN && pA->op!=TK_AGG_COLUMN && pA->u.zToken ){
if( pA->op==TK_FUNCTION ){
if( sqlite3StrICmp(pA->u.zToken,pB->u.zToken)!=0 ) return 2;
+ }else if( pA->op==TK_COLLATE ){
+ if( sqlite3_stricmp(pA->u.zToken,pB->u.zToken)!=0 ) return 2;
}else if( strcmp(pA->u.zToken,pB->u.zToken)!=0 ){
- return pA->op==TK_COLLATE ? 1 : 2;
+ return 2;
}
}
if( (pA->flags & EP_Distinct)!=(pB->flags & EP_Distinct) ) return 2;
if( ALWAYS((combinedFlags & EP_TokenOnly)==0) ){
if( combinedFlags & EP_xIsSelect ) return 2;
if( sqlite3ExprCompare(pParse, pA->pLeft, pB->pLeft, iTab) ) return 2;
if( sqlite3ExprCompare(pParse, pA->pRight, pB->pRight, iTab) ) return 2;
if( sqlite3ExprListCompare(pA->x.pList, pB->x.pList, iTab) ) return 2;
- if( ALWAYS((combinedFlags & EP_Reduced)==0) && pA->op!=TK_STRING ){
+ assert( (combinedFlags & EP_Reduced)==0 );
+ if( pA->op!=TK_STRING && pA->op!=TK_TRUEFALSE ){
if( pA->iColumn!=pB->iColumn ) return 2;
if( pA->iTable!=pB->iTable
&& (pA->iTable!=iTab || NEVER(pB->iTable>=0)) ) return 2;
}
}
@@ -5253,12 +5256,13 @@
static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
int i;
NameContext *pNC = pWalker->u.pNC;
Parse *pParse = pNC->pParse;
SrcList *pSrcList = pNC->pSrcList;
- AggInfo *pAggInfo = pNC->pAggInfo;
+ AggInfo *pAggInfo = pNC->uNC.pAggInfo;
+ assert( pNC->ncFlags & NC_UAggInfo );
switch( pExpr->op ){
case TK_AGG_COLUMN:
case TK_COLUMN: {
testcase( pExpr->op==TK_AGG_COLUMN );
testcase( pExpr->op==TK_COLUMN );
Index: src/global.c
==================================================================
--- src/global.c
+++ src/global.c
@@ -238,11 +238,12 @@
#endif
#ifndef SQLITE_UNTESTABLE
0, /* xTestCallback */
#endif
0, /* bLocaltimeFault */
- 0x7ffffffe /* iOnceResetThreshold */
+ 0x7ffffffe, /* iOnceResetThreshold */
+ SQLITE_DEFAULT_SORTERREF_SIZE /* szSorterRef */
};
/*
** Hash table for global functions - functions common to all
** database connections. After initialization, this table is
Index: src/insert.c
==================================================================
--- src/insert.c
+++ src/insert.c
@@ -486,11 +486,12 @@
void sqlite3Insert(
Parse *pParse, /* Parser context */
SrcList *pTabList, /* Name of table into which we are inserting */
Select *pSelect, /* A SELECT statement to use as the data source */
IdList *pColumn, /* Column names corresponding to IDLIST. */
- int onError /* How to handle constraint errors */
+ int onError, /* How to handle constraint errors */
+ Upsert *pUpsert /* ON CONFLICT clauses for upsert, or NULL */
){
sqlite3 *db; /* The main database structure */
Table *pTab; /* The table to insert into. aka TABLE */
int i, j; /* Loop counters */
Vdbe *v; /* Generate code into this virtual machine */
@@ -781,11 +782,14 @@
goto insert_cleanup;
}
/* Initialize the count of rows to be inserted
*/
- if( db->flags & SQLITE_CountRows ){
+ if( (db->flags & SQLITE_CountRows)!=0
+ && !pParse->nested
+ && !pParse->pTriggerTab
+ ){
regRowCount = ++pParse->nMem;
sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
}
/* If this is not a view, open the table and and all indices */
@@ -801,10 +805,23 @@
assert( pIdx );
aRegIdx[i] = ++pParse->nMem;
pParse->nMem += pIdx->nColumn;
}
}
+#ifndef SQLITE_OMIT_UPSERT
+ if( pUpsert ){
+ pTabList->a[0].iCursor = iDataCur;
+ pUpsert->pUpsertSrc = pTabList;
+ pUpsert->regData = regData;
+ pUpsert->iDataCur = iDataCur;
+ pUpsert->iIdxCur = iIdxCur;
+ if( pUpsert->pUpsertTarget ){
+ sqlite3UpsertAnalyzeTarget(pParse, pTabList, pUpsert);
+ }
+ }
+#endif
+
/* This is the top of the main insertion loop */
if( useTempTable ){
/* This block codes the top of loop only. The complete loop is the
** following pseudocode (template 4):
@@ -1003,11 +1020,11 @@
#endif
{
int isReplace; /* Set to true if constraints may cause a replace */
int bUseSeek; /* True to use OPFLAG_SEEKRESULT */
sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur,
- regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0
+ regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0, pUpsert
);
sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0);
/* Set the OPFLAG_USESEEKRESULT flag if either (a) there are no REPLACE
** constraints or (b) there are no triggers and this table is not a
@@ -1026,11 +1043,11 @@
}
}
/* Update the count of rows that are inserted
*/
- if( (db->flags & SQLITE_CountRows)!=0 ){
+ if( regRowCount ){
sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1);
}
if( pTrigger ){
/* Code AFTER triggers */
@@ -1063,19 +1080,20 @@
/*
** Return the number of rows inserted. If this routine is
** generating code because of a call to sqlite3NestedParse(), do not
** invoke the callback function.
*/
- if( (db->flags&SQLITE_CountRows) && !pParse->nested && !pParse->pTriggerTab ){
+ if( regRowCount ){
sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
sqlite3VdbeSetNumCols(v, 1);
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows inserted", SQLITE_STATIC);
}
insert_cleanup:
sqlite3SrcListDelete(db, pTabList);
sqlite3ExprListDelete(db, pList);
+ sqlite3UpsertDelete(db, pUpsert);
sqlite3SelectDelete(db, pSelect);
sqlite3IdListDelete(db, pColumn);
sqlite3DbFree(db, aRegIdx);
}
@@ -1142,10 +1160,48 @@
testcase( w.eCode==CKCNSTRNT_COLUMN );
testcase( w.eCode==CKCNSTRNT_ROWID );
testcase( w.eCode==(CKCNSTRNT_ROWID|CKCNSTRNT_COLUMN) );
return !w.eCode;
}
+
+/*
+** An instance of the ConstraintAddr object remembers the byte-code addresses
+** for sections of the constraint checks that deal with uniqueness constraints
+** on the rowid and on the upsert constraint.
+**
+** This information is passed into checkReorderConstraintChecks() to insert
+** some OP_Goto operations so that the rowid and upsert constraints occur
+** in the correct order relative to other constraints.
+*/
+typedef struct ConstraintAddr ConstraintAddr;
+struct ConstraintAddr {
+ int ipkTop; /* Subroutine for rowid constraint check */
+ int upsertTop; /* Label for upsert constraint check subroutine */
+ int upsertTop2; /* Copy of upsertTop not cleared by the call */
+ int upsertBtm; /* upsert constraint returns to this label */
+ int ipkBtm; /* Return opcode rowid constraint check */
+};
+
+/*
+** Generate any OP_Goto operations needed to cause constraints to be
+** run that haven't already been run.
+*/
+static void reorderConstraintChecks(Vdbe *v, ConstraintAddr *p){
+ if( p->upsertTop ){
+ testcase( sqlite3VdbeLabelHasBeenResolved(v, p->upsertTop) );
+ sqlite3VdbeGoto(v, p->upsertTop);
+ VdbeComment((v, "call upsert subroutine"));
+ sqlite3VdbeResolveLabel(v, p->upsertBtm);
+ p->upsertTop = 0;
+ }
+ if( p->ipkTop ){
+ sqlite3VdbeGoto(v, p->ipkTop);
+ VdbeComment((v, "call rowid unique-check subroutine"));
+ sqlite3VdbeJumpHere(v, p->ipkBtm);
+ p->ipkTop = 0;
+ }
+}
/*
** Generate code to do constraint checks prior to an INSERT or an UPDATE
** on table pTab.
**
@@ -1238,11 +1294,12 @@
int regOldData, /* Previous content. 0 for INSERTs */
u8 pkChng, /* Non-zero if the rowid or PRIMARY KEY changed */
u8 overrideError, /* Override onError to this if not OE_Default */
int ignoreDest, /* Jump to this label on an OE_Ignore resolution */
int *pbMayReplace, /* OUT: Set to true if constraint may cause a replace */
- int *aiChng /* column i is unchanged if aiChng[i]<0 */
+ int *aiChng, /* column i is unchanged if aiChng[i]<0 */
+ Upsert *pUpsert /* ON CONFLICT clauses, if any. NULL otherwise */
){
Vdbe *v; /* VDBE under constrution */
Index *pIdx; /* Pointer to one of the indices */
Index *pPk = 0; /* The PRIMARY KEY index */
sqlite3 *db; /* Database connection */
@@ -1251,21 +1308,23 @@
int nCol; /* Number of columns */
int onError; /* Conflict resolution strategy */
int addr1; /* Address of jump instruction */
int seenReplace = 0; /* True if REPLACE is used to resolve INT PK conflict */
int nPkField; /* Number of fields in PRIMARY KEY. 1 for ROWID tables */
- int ipkTop = 0; /* Top of the rowid change constraint check */
- int ipkBottom = 0; /* Bottom of the rowid change constraint check */
+ ConstraintAddr sAddr;/* Address information for constraint reordering */
+ Index *pUpIdx = 0; /* Index to which to apply the upsert */
u8 isUpdate; /* True if this is an UPDATE operation */
u8 bAffinityDone = 0; /* True if the OP_Affinity operation has been run */
+ int upsertBypass = 0; /* Address of Goto to bypass upsert subroutine */
isUpdate = regOldData!=0;
db = pParse->db;
v = sqlite3GetVdbe(pParse);
assert( v!=0 );
assert( pTab->pSelect==0 ); /* This table is not a VIEW */
nCol = pTab->nCol;
+ memset(&sAddr, 0, sizeof(sAddr));
/* pPk is the PRIMARY KEY index for WITHOUT ROWID tables and NULL for
** normal rowid tables. nPkField is the number of key fields in the
** pPk index or 1 for a rowid table. In other words, nPkField is the
** number of fields in the true primary key of the table. */
@@ -1360,10 +1419,50 @@
sqlite3VdbeResolveLabel(v, allOk);
}
pParse->iSelfTab = 0;
}
#endif /* !defined(SQLITE_OMIT_CHECK) */
+
+ /* UNIQUE and PRIMARY KEY constraints should be handled in the following
+ ** order:
+ **
+ ** (1) OE_Abort, OE_Fail, OE_Rollback, OE_Ignore
+ ** (2) OE_Update
+ ** (3) OE_Replace
+ **
+ ** OE_Fail and OE_Ignore must happen before any changes are made.
+ ** OE_Update guarantees that only a single row will change, so it
+ ** must happen before OE_Replace. Technically, OE_Abort and OE_Rollback
+ ** could happen in any order, but they are grouped up front for
+ ** convenience.
+ **
+ ** Constraint checking code is generated in this order:
+ ** (A) The rowid constraint
+ ** (B) Unique index constraints that do not have OE_Replace as their
+ ** default conflict resolution strategy
+ ** (C) Unique index that do use OE_Replace by default.
+ **
+ ** The ordering of (2) and (3) is accomplished by making sure the linked
+ ** list of indexes attached to a table puts all OE_Replace indexes last
+ ** in the list. See sqlite3CreateIndex() for where that happens.
+ */
+
+ if( pUpsert ){
+ if( pUpsert->pUpsertTarget==0 ){
+ /* An ON CONFLICT DO NOTHING clause, without a constraint-target.
+ ** Make all unique constraint resolution be OE_Ignore */
+ assert( pUpsert->pUpsertSet==0 );
+ overrideError = OE_Ignore;
+ pUpsert = 0;
+ }else if( (pUpIdx = pUpsert->pUpsertIdx)!=0 ){
+ /* If the constraint-target is on some column other than
+ ** then ROWID, then we might need to move the UPSERT around
+ ** so that it occurs in the correct order. */
+ sAddr.upsertTop = sAddr.upsertTop2 = sqlite3VdbeMakeLabel(v);
+ sAddr.upsertBtm = sqlite3VdbeMakeLabel(v);
+ }
+ }
/* If rowid is changing, make sure the new rowid does not previously
** exist in the table.
*/
if( pkChng && pPk==0 ){
@@ -1374,10 +1473,36 @@
if( overrideError!=OE_Default ){
onError = overrideError;
}else if( onError==OE_Default ){
onError = OE_Abort;
}
+
+ /* figure out whether or not upsert applies in this case */
+ if( pUpsert && pUpsert->pUpsertIdx==0 ){
+ if( pUpsert->pUpsertSet==0 ){
+ onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */
+ }else{
+ onError = OE_Update; /* DO UPDATE */
+ }
+ }
+
+ /* If the response to a rowid conflict is REPLACE but the response
+ ** to some other UNIQUE constraint is FAIL or IGNORE, then we need
+ ** to defer the running of the rowid conflict checking until after
+ ** the UNIQUE constraints have run.
+ */
+ assert( OE_Update>OE_Replace );
+ assert( OE_Ignore=OE_Replace
+ && (pUpsert || onError!=overrideError)
+ && pTab->pIndex
+ ){
+ sAddr.ipkTop = sqlite3VdbeAddOp0(v, OP_Goto)+1;
+ }
if( isUpdate ){
/* pkChng!=0 does not mean that the rowid has changed, only that
** it might have changed. Skip the conflict logic below if the rowid
** is unchanged. */
@@ -1384,38 +1509,27 @@
sqlite3VdbeAddOp3(v, OP_Eq, regNewData, addrRowidOk, regOldData);
sqlite3VdbeChangeP5(v, SQLITE_NOTNULL);
VdbeCoverage(v);
}
- /* If the response to a rowid conflict is REPLACE but the response
- ** to some other UNIQUE constraint is FAIL or IGNORE, then we need
- ** to defer the running of the rowid conflict checking until after
- ** the UNIQUE constraints have run.
- */
- if( onError==OE_Replace && overrideError!=OE_Replace ){
- for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
- if( pIdx->onError==OE_Ignore || pIdx->onError==OE_Fail ){
- ipkTop = sqlite3VdbeAddOp0(v, OP_Goto);
- break;
- }
- }
- }
-
/* Check to see if the new rowid already exists in the table. Skip
** the following conflict logic if it does not. */
+ VdbeNoopComment((v, "uniqueness check for ROWID"));
sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, addrRowidOk, regNewData);
VdbeCoverage(v);
- /* Generate code that deals with a rowid collision */
switch( onError ){
default: {
onError = OE_Abort;
/* Fall thru into the next case */
}
case OE_Rollback:
case OE_Abort:
case OE_Fail: {
+ testcase( onError==OE_Rollback );
+ testcase( onError==OE_Abort );
+ testcase( onError==OE_Fail );
sqlite3RowidConstraint(pParse, onError, pTab);
break;
}
case OE_Replace: {
/* If there are DELETE triggers on this table and the
@@ -1448,37 +1562,42 @@
sqlite3MultiWrite(pParse);
sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur,
regNewData, 1, 0, OE_Replace, 1, -1);
}else{
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
- if( HasRowid(pTab) ){
- /* This OP_Delete opcode fires the pre-update-hook only. It does
- ** not modify the b-tree. It is more efficient to let the coming
- ** OP_Insert replace the existing entry than it is to delete the
- ** existing entry and then insert a new one. */
- sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, OPFLAG_ISNOOP);
- sqlite3VdbeAppendP4(v, pTab, P4_TABLE);
- }
+ assert( HasRowid(pTab) );
+ /* This OP_Delete opcode fires the pre-update-hook only. It does
+ ** not modify the b-tree. It is more efficient to let the coming
+ ** OP_Insert replace the existing entry than it is to delete the
+ ** existing entry and then insert a new one. */
+ sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, OPFLAG_ISNOOP);
+ sqlite3VdbeAppendP4(v, pTab, P4_TABLE);
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
if( pTab->pIndex ){
sqlite3MultiWrite(pParse);
sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,-1);
}
}
seenReplace = 1;
break;
}
+#ifndef SQLITE_OMIT_UPSERT
+ case OE_Update: {
+ sqlite3UpsertDoUpdate(pParse, pUpsert, pTab, 0, iDataCur);
+ /* Fall through */
+ }
+#endif
case OE_Ignore: {
- /*assert( seenReplace==0 );*/
+ testcase( onError==OE_Ignore );
sqlite3VdbeGoto(v, ignoreDest);
break;
}
}
sqlite3VdbeResolveLabel(v, addrRowidOk);
- if( ipkTop ){
- ipkBottom = sqlite3VdbeAddOp0(v, OP_Goto);
- sqlite3VdbeJumpHere(v, ipkTop);
+ if( sAddr.ipkTop ){
+ sAddr.ipkBtm = sqlite3VdbeAddOp0(v, OP_Goto);
+ sqlite3VdbeJumpHere(v, sAddr.ipkTop-1);
}
}
/* Test all UNIQUE constraints by creating entries for each UNIQUE
** index and making sure that duplicate entries do not already exist.
@@ -1492,16 +1611,25 @@
int regR; /* Range of registers holding conflicting PK */
int iThisCur; /* Cursor for this UNIQUE index */
int addrUniqueOk; /* Jump here if the UNIQUE constraint is satisfied */
if( aRegIdx[ix]==0 ) continue; /* Skip indices that do not change */
+ if( pUpIdx==pIdx ){
+ addrUniqueOk = sAddr.upsertBtm;
+ upsertBypass = sqlite3VdbeGoto(v, 0);
+ VdbeComment((v, "Skip upsert subroutine"));
+ sqlite3VdbeResolveLabel(v, sAddr.upsertTop2);
+ }else{
+ addrUniqueOk = sqlite3VdbeMakeLabel(v);
+ }
+ VdbeNoopComment((v, "uniqueness check for %s", pIdx->zName));
if( bAffinityDone==0 ){
sqlite3TableAffinity(v, pTab, regNewData+1);
bAffinityDone = 1;
}
iThisCur = iIdxCur+ix;
- addrUniqueOk = sqlite3VdbeMakeLabel(v);
+
/* Skip partial indices for which the WHERE clause is not true */
if( pIdx->pPartIdxWhere ){
sqlite3VdbeAddOp2(v, OP_Null, 0, aRegIdx[ix]);
pParse->iSelfTab = -(regNewData+1);
@@ -1556,10 +1684,28 @@
if( overrideError!=OE_Default ){
onError = overrideError;
}else if( onError==OE_Default ){
onError = OE_Abort;
}
+
+ /* Figure out if the upsert clause applies to this index */
+ if( pUpIdx==pIdx ){
+ if( pUpsert->pUpsertSet==0 ){
+ onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */
+ }else{
+ onError = OE_Update; /* DO UPDATE */
+ }
+ }
+
+ /* Invoke subroutines to handle IPK replace and upsert prior to running
+ ** the first REPLACE constraint check. */
+ if( onError==OE_Replace ){
+ testcase( sAddr.ipkTop );
+ testcase( sAddr.upsertTop
+ && sqlite3VdbeLabelHasBeenResolved(v,sAddr.upsertTop) );
+ reorderConstraintChecks(v, &sAddr);
+ }
/* Collision detection may be omitted if all of the following are true:
** (1) The conflict resolution algorithm is REPLACE
** (2) The table is a WITHOUT ROWID table
** (3) There are no secondary indexes on the table
@@ -1639,19 +1785,29 @@
}
}
/* Generate code that executes if the new index entry is not unique */
assert( onError==OE_Rollback || onError==OE_Abort || onError==OE_Fail
- || onError==OE_Ignore || onError==OE_Replace );
+ || onError==OE_Ignore || onError==OE_Replace || onError==OE_Update );
switch( onError ){
case OE_Rollback:
case OE_Abort:
case OE_Fail: {
+ testcase( onError==OE_Rollback );
+ testcase( onError==OE_Abort );
+ testcase( onError==OE_Fail );
sqlite3UniqueConstraint(pParse, onError, pIdx);
break;
}
+#ifndef SQLITE_OMIT_UPSERT
+ case OE_Update: {
+ sqlite3UpsertDoUpdate(pParse, pUpsert, pTab, pIdx, iIdxCur+ix);
+ /* Fall through */
+ }
+#endif
case OE_Ignore: {
+ testcase( onError==OE_Ignore );
sqlite3VdbeGoto(v, ignoreDest);
break;
}
default: {
Trigger *pTrigger = 0;
@@ -1665,18 +1821,23 @@
(pIdx==pPk ? ONEPASS_SINGLE : ONEPASS_OFF), iThisCur);
seenReplace = 1;
break;
}
}
- sqlite3VdbeResolveLabel(v, addrUniqueOk);
+ if( pUpIdx==pIdx ){
+ sqlite3VdbeJumpHere(v, upsertBypass);
+ }else{
+ sqlite3VdbeResolveLabel(v, addrUniqueOk);
+ }
sqlite3ExprCachePop(pParse);
if( regR!=regIdx ) sqlite3ReleaseTempRange(pParse, regR, nPkField);
+
}
- if( ipkTop ){
- sqlite3VdbeGoto(v, ipkTop+1);
- sqlite3VdbeJumpHere(v, ipkBottom);
- }
+ testcase( sAddr.ipkTop!=0 );
+ testcase( sAddr.upsertTop
+ && sqlite3VdbeLabelHasBeenResolved(v,sAddr.upsertTop) );
+ reorderConstraintChecks(v, &sAddr);
*pbMayReplace = seenReplace;
VdbeModuleComment((v, "END: GenCnstCks(%d)", seenReplace));
}
Index: src/main.c
==================================================================
--- src/main.c
+++ src/main.c
@@ -643,10 +643,21 @@
case SQLITE_CONFIG_STMTJRNL_SPILL: {
sqlite3GlobalConfig.nStmtSpill = va_arg(ap, int);
break;
}
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ case SQLITE_CONFIG_SORTERREF_SIZE: {
+ int iVal = va_arg(ap, int);
+ if( iVal<0 ){
+ iVal = SQLITE_DEFAULT_SORTERREF_SIZE;
+ }
+ sqlite3GlobalConfig.szSorterRef = (u32)iVal;
+ break;
+ }
+#endif /* SQLITE_ENABLE_SORTER_REFERENCES */
+
default: {
rc = SQLITE_ERROR;
break;
}
}
@@ -824,10 +835,11 @@
{ SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, SQLITE_Fts3Tokenizer },
{ SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, SQLITE_LoadExtension },
{ SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, SQLITE_NoCkptOnClose },
{ SQLITE_DBCONFIG_ENABLE_QPSG, SQLITE_EnableQPSG },
{ SQLITE_DBCONFIG_TRIGGER_EQP, SQLITE_TriggerEQP },
+ { SQLITE_DBCONFIG_RESET_DATABASE, SQLITE_ResetDatabase },
};
unsigned int i;
rc = SQLITE_ERROR; /* IMP: R-42790-23372 */
for(i=0; idbOptFlags = (u16)(va_arg(ap, int) & 0xffff);
break;
}
-#ifdef SQLITE_N_KEYWORD
- /* sqlite3_test_control(SQLITE_TESTCTRL_ISKEYWORD, const char *zWord)
- **
- ** If zWord is a keyword recognized by the parser, then return the
- ** number of keywords. Or if zWord is not a keyword, return 0.
- **
- ** This test feature is only available in the amalgamation since
- ** the SQLITE_N_KEYWORD macro is not defined in this file if SQLite
- ** is built using separate source files.
- */
- case SQLITE_TESTCTRL_ISKEYWORD: {
- const char *zWord = va_arg(ap, const char*);
- int n = sqlite3Strlen30(zWord);
- rc = (sqlite3KeywordCode((u8*)zWord, n)!=TK_ID) ? SQLITE_N_KEYWORD : 0;
- break;
- }
-#endif
-
/* sqlite3_test_control(SQLITE_TESTCTRL_LOCALTIME_FAULT, int onoff);
**
** If parameter onoff is non-zero, configure the wrappers so that all
** subsequent calls to localtime() and variants fail. If onoff is zero,
** undo this setting.
Index: src/os_win.c
==================================================================
--- src/os_win.c
+++ src/os_win.c
@@ -313,26 +313,10 @@
*/
#ifndef SQLITE_WIN32_DBG_BUF_SIZE
# define SQLITE_WIN32_DBG_BUF_SIZE ((int)(4096-sizeof(DWORD)))
#endif
-/*
- * The value used with sqlite3_win32_set_directory() to specify that
- * the data directory should be changed.
- */
-#ifndef SQLITE_WIN32_DATA_DIRECTORY_TYPE
-# define SQLITE_WIN32_DATA_DIRECTORY_TYPE (1)
-#endif
-
-/*
- * The value used with sqlite3_win32_set_directory() to specify that
- * the temporary directory should be changed.
- */
-#ifndef SQLITE_WIN32_TEMP_DIRECTORY_TYPE
-# define SQLITE_WIN32_TEMP_DIRECTORY_TYPE (2)
-#endif
-
/*
* If compiled with SQLITE_WIN32_MALLOC on Windows, we will use the
* various Win32 API heap functions instead of our own.
*/
#ifdef SQLITE_WIN32_MALLOC
@@ -1925,17 +1909,17 @@
#endif
return winUtf8ToMbcs(zText, useAnsi);
}
/*
-** This function sets the data directory or the temporary directory based on
-** the provided arguments. The type argument must be 1 in order to set the
-** data directory or 2 in order to set the temporary directory. The zValue
-** argument is the name of the directory to use. The return value will be
-** SQLITE_OK if successful.
+** This function is the same as sqlite3_win32_set_directory (below); however,
+** it accepts a UTF-8 string.
*/
-int sqlite3_win32_set_directory(DWORD type, LPCWSTR zValue){
+int sqlite3_win32_set_directory8(
+ unsigned long type, /* Identifier for directory being set or reset */
+ const char *zValue /* New value for directory being set or reset */
+){
char **ppDirectory = 0;
#ifndef SQLITE_OMIT_AUTOINIT
int rc = sqlite3_initialize();
if( rc ) return rc;
#endif
@@ -1947,23 +1931,56 @@
assert( !ppDirectory || type==SQLITE_WIN32_DATA_DIRECTORY_TYPE
|| type==SQLITE_WIN32_TEMP_DIRECTORY_TYPE
);
assert( !ppDirectory || sqlite3MemdebugHasType(*ppDirectory, MEMTYPE_HEAP) );
if( ppDirectory ){
- char *zValueUtf8 = 0;
+ char *zCopy = 0;
if( zValue && zValue[0] ){
- zValueUtf8 = winUnicodeToUtf8(zValue);
- if ( zValueUtf8==0 ){
+ zCopy = sqlite3_mprintf("%s", zValue);
+ if ( zCopy==0 ){
return SQLITE_NOMEM_BKPT;
}
}
sqlite3_free(*ppDirectory);
- *ppDirectory = zValueUtf8;
+ *ppDirectory = zCopy;
return SQLITE_OK;
}
return SQLITE_ERROR;
}
+
+/*
+** This function is the same as sqlite3_win32_set_directory (below); however,
+** it accepts a UTF-16 string.
+*/
+int sqlite3_win32_set_directory16(
+ unsigned long type, /* Identifier for directory being set or reset */
+ const void *zValue /* New value for directory being set or reset */
+){
+ int rc;
+ char *zUtf8 = 0;
+ if( zValue ){
+ zUtf8 = sqlite3_win32_unicode_to_utf8(zValue);
+ if( zUtf8==0 ) return SQLITE_NOMEM_BKPT;
+ }
+ rc = sqlite3_win32_set_directory8(type, zUtf8);
+ if( zUtf8 ) sqlite3_free(zUtf8);
+ return rc;
+}
+
+/*
+** This function sets the data directory or the temporary directory based on
+** the provided arguments. The type argument must be 1 in order to set the
+** data directory or 2 in order to set the temporary directory. The zValue
+** argument is the name of the directory to use. The return value will be
+** SQLITE_OK if successful.
+*/
+int sqlite3_win32_set_directory(
+ unsigned long type, /* Identifier for directory being set or reset */
+ void *zValue /* New value for directory being set or reset */
+){
+ return sqlite3_win32_set_directory16(type, zValue);
+}
/*
** The return value of winGetLastErrorMsg
** is zero if the error message fits in the buffer, or non-zero
** otherwise (if the message was truncated).
Index: src/parse.y
==================================================================
--- src/parse.y
+++ src/parse.y
@@ -22,12 +22,13 @@
// default type for non-terminals.
//
%token_type {Token}
%default_type {Token}
-// The generated parser function takes a 4th argument as follows:
-%extra_argument {Parse *pParse}
+// An extra argument to the constructor for the parser, which is available
+// to all actions.
+%extra_context {Parse *pParse}
// This code runs whenever there is a syntax error
//
%syntax_error {
UNUSED_PARAMETER(yymajor); /* Silence some compiler warnings */
@@ -112,13 +113,13 @@
// Input is a single SQL command
input ::= cmdlist.
cmdlist ::= cmdlist ecmd.
cmdlist ::= ecmd.
ecmd ::= SEMI.
-ecmd ::= explain cmdx SEMI.
-explain ::= .
+ecmd ::= cmdx SEMI.
%ifndef SQLITE_OMIT_EXPLAIN
+ecmd ::= explain cmdx.
explain ::= EXPLAIN. { pParse->explain = 1; }
explain ::= EXPLAIN QUERY PLAN. { pParse->explain = 2; }
%endif SQLITE_OMIT_EXPLAIN
cmdx ::= cmd. { sqlite3FinishCoding(pParse); }
@@ -203,11 +204,12 @@
// fallback to ID if they will not parse as their original value.
// This obviates the need for the "id" nonterminal.
//
%fallback ID
ABORT ACTION AFTER ANALYZE ASC ATTACH BEFORE BEGIN BY CASCADE CAST COLUMNKW
- CONFLICT DATABASE DEFERRED DESC DETACH EACH END EXCLUSIVE EXPLAIN FAIL FOR
+ CONFLICT DATABASE DEFERRED DESC DETACH DO
+ EACH END EXCLUSIVE EXPLAIN FAIL FOR
IGNORE IMMEDIATE INITIALLY INSTEAD LIKE_KW MATCH NO PLAN
QUERY KEY OF OFFSET PRAGMA RAISE RECURSIVE RELEASE REPLACE RESTRICT ROW
ROLLBACK SAVEPOINT TEMP TRIGGER VACUUM VIEW VIRTUAL WITH WITHOUT
%ifdef SQLITE_OMIT_COMPOUND_SELECT
EXCEPT INTERSECT UNION
@@ -237,10 +239,11 @@
%left PLUS MINUS.
%left STAR SLASH REM.
%left CONCAT.
%left COLLATE.
%right BITNOT.
+%nonassoc ON.
// An IDENTIFIER can be a generic identifier, or one of several
// keywords. Any non-standard keyword can also be an identifier.
//
%token_class id ID|INDEXED.
@@ -685,10 +688,25 @@
%destructor fullname {sqlite3SrcListDelete(pParse->db, $$);}
fullname(A) ::= nm(X).
{A = sqlite3SrcListAppend(pParse->db,0,&X,0); /*A-overwrites-X*/}
fullname(A) ::= nm(X) DOT nm(Y).
{A = sqlite3SrcListAppend(pParse->db,0,&X,&Y); /*A-overwrites-X*/}
+
+%type xfullname {SrcList*}
+%destructor xfullname {sqlite3SrcListDelete(pParse->db, $$);}
+xfullname(A) ::= nm(X).
+ {A = sqlite3SrcListAppend(pParse->db,0,&X,0); /*A-overwrites-X*/}
+xfullname(A) ::= nm(X) DOT nm(Y).
+ {A = sqlite3SrcListAppend(pParse->db,0,&X,&Y); /*A-overwrites-X*/}
+xfullname(A) ::= nm(X) DOT nm(Y) AS nm(Z). {
+ A = sqlite3SrcListAppend(pParse->db,0,&X,&Y); /*A-overwrites-X*/
+ if( A ) A->a[0].zAlias = sqlite3NameFromToken(pParse->db, &Z);
+}
+xfullname(A) ::= nm(X) AS nm(Z). {
+ A = sqlite3SrcListAppend(pParse->db,0,&X,0); /*A-overwrites-X*/
+ if( A ) A->a[0].zAlias = sqlite3NameFromToken(pParse->db, &Z);
+}
%type joinop {int}
joinop(X) ::= COMMA|JOIN. { X = JT_INNER; }
joinop(X) ::= JOIN_KW(A) JOIN.
{X = sqlite3JoinType(pParse,&A,0,0); /*X-overwrites-A*/}
@@ -695,14 +713,31 @@
joinop(X) ::= JOIN_KW(A) nm(B) JOIN.
{X = sqlite3JoinType(pParse,&A,&B,0); /*X-overwrites-A*/}
joinop(X) ::= JOIN_KW(A) nm(B) nm(C) JOIN.
{X = sqlite3JoinType(pParse,&A,&B,&C);/*X-overwrites-A*/}
+// There is a parsing abiguity in an upsert statement that uses a
+// SELECT on the RHS of a the INSERT:
+//
+// INSERT INTO tab SELECT * FROM aaa JOIN bbb ON CONFLICT ...
+// here ----^^
+//
+// When the ON token is encountered, the parser does not know if it is
+// the beginning of an ON CONFLICT clause, or the beginning of an ON
+// clause associated with the JOIN. The conflict is resolved in favor
+// of the JOIN. If an ON CONFLICT clause is intended, insert a dummy
+// WHERE clause in between, like this:
+//
+// INSERT INTO tab SELECT * FROM aaa JOIN bbb WHERE true ON CONFLICT ...
+//
+// The [AND] and [OR] precedence marks in the rules for on_opt cause the
+// ON in this context to always be interpreted as belonging to the JOIN.
+//
%type on_opt {Expr*}
%destructor on_opt {sqlite3ExprDelete(pParse->db, $$);}
-on_opt(N) ::= ON expr(E). {N = E;}
-on_opt(N) ::= . {N = 0;}
+on_opt(N) ::= ON expr(E). {N = E;}
+on_opt(N) ::= . [OR] {N = 0;}
// Note that this block abuses the Token type just a little. If there is
// no "INDEXED BY" clause, the returned token is empty (z==0 && n==0). If
// there is an INDEXED BY clause, then the token is populated as per normal,
// with z pointing to the token data and n containing the number of bytes
@@ -779,18 +814,18 @@
{A = sqlite3PExpr(pParse,TK_LIMIT,Y,X);}
/////////////////////////// The DELETE statement /////////////////////////////
//
%ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
-cmd ::= with DELETE FROM fullname(X) indexed_opt(I) where_opt(W)
+cmd ::= with DELETE FROM xfullname(X) indexed_opt(I) where_opt(W)
orderby_opt(O) limit_opt(L). {
sqlite3SrcListIndexedBy(pParse, X, &I);
sqlite3DeleteFrom(pParse,X,W,O,L);
}
%endif
%ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
-cmd ::= with DELETE FROM fullname(X) indexed_opt(I) where_opt(W). {
+cmd ::= with DELETE FROM xfullname(X) indexed_opt(I) where_opt(W). {
sqlite3SrcListIndexedBy(pParse, X, &I);
sqlite3DeleteFrom(pParse,X,W,0,0);
}
%endif
@@ -801,23 +836,23 @@
where_opt(A) ::= WHERE expr(X). {A = X;}
////////////////////////// The UPDATE command ////////////////////////////////
//
%ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
-cmd ::= with UPDATE orconf(R) fullname(X) indexed_opt(I) SET setlist(Y)
+cmd ::= with UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y)
where_opt(W) orderby_opt(O) limit_opt(L). {
sqlite3SrcListIndexedBy(pParse, X, &I);
sqlite3ExprListCheckLength(pParse,Y,"set list");
- sqlite3Update(pParse,X,Y,W,R,O,L);
+ sqlite3Update(pParse,X,Y,W,R,O,L,0);
}
%endif
%ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
-cmd ::= with UPDATE orconf(R) fullname(X) indexed_opt(I) SET setlist(Y)
+cmd ::= with UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y)
where_opt(W). {
sqlite3SrcListIndexedBy(pParse, X, &I);
sqlite3ExprListCheckLength(pParse,Y,"set list");
- sqlite3Update(pParse,X,Y,W,R,0,0);
+ sqlite3Update(pParse,X,Y,W,R,0,0,0);
}
%endif
%type setlist {ExprList*}
%destructor setlist {sqlite3ExprListDelete(pParse->db, $$);}
@@ -837,18 +872,35 @@
A = sqlite3ExprListAppendVector(pParse, 0, X, Y);
}
////////////////////////// The INSERT command /////////////////////////////////
//
-cmd ::= with insert_cmd(R) INTO fullname(X) idlist_opt(F) select(S). {
- sqlite3Insert(pParse, X, S, F, R);
+cmd ::= with insert_cmd(R) INTO xfullname(X) idlist_opt(F) select(S)
+ upsert(U). {
+ sqlite3Insert(pParse, X, S, F, R, U);
}
-cmd ::= with insert_cmd(R) INTO fullname(X) idlist_opt(F) DEFAULT VALUES.
+cmd ::= with insert_cmd(R) INTO xfullname(X) idlist_opt(F) DEFAULT VALUES.
{
- sqlite3Insert(pParse, X, 0, F, R);
+ sqlite3Insert(pParse, X, 0, F, R, 0);
}
+%type upsert {Upsert*}
+
+// Because upsert only occurs at the tip end of the INSERT rule for cmd,
+// there is never a case where the value of the upsert pointer will not
+// be destroyed by the cmd action. So comment-out the destructor to
+// avoid unreachable code.
+//%destructor upsert {sqlite3UpsertDelete(pParse->db,$$);}
+upsert(A) ::= . { A = 0; }
+upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW)
+ DO UPDATE SET setlist(Z) where_opt(W).
+ { A = sqlite3UpsertNew(pParse->db,T,TW,Z,W);}
+upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW) DO NOTHING.
+ { A = sqlite3UpsertNew(pParse->db,T,TW,0,0); }
+upsert(A) ::= ON CONFLICT DO NOTHING.
+ { A = sqlite3UpsertNew(pParse->db,0,0,0,0); }
+
%type insert_cmd {int}
insert_cmd(A) ::= INSERT orconf(R). {A = R;}
insert_cmd(A) ::= REPLACE. {A = OE_Replace;}
%type idlist_opt {IdList*}
@@ -1390,13 +1442,13 @@
UPDATE(B) orconf(R) trnm(X) tridxby SET setlist(Y) where_opt(Z) scanpt(E).
{A = sqlite3TriggerUpdateStep(pParse->db, &X, Y, Z, R, B.z, E);}
// INSERT
trigger_cmd(A) ::= scanpt(B) insert_cmd(R) INTO
- trnm(X) idlist_opt(F) select(S) scanpt(Z).
- {A = sqlite3TriggerInsertStep(pParse->db,&X,F,S,R,B,Z);/*A-overwrites-R*/}
-
+ trnm(X) idlist_opt(F) select(S) upsert(U) scanpt(Z). {
+ A = sqlite3TriggerInsertStep(pParse->db,&X,F,S,R,U,B,Z);/*A-overwrites-R*/
+}
// DELETE
trigger_cmd(A) ::= DELETE(B) FROM trnm(X) tridxby where_opt(Y) scanpt(E).
{A = sqlite3TriggerDeleteStep(pParse->db, &X, Y, B.z, E);}
// SELECT
Index: src/prepare.c
==================================================================
--- src/prepare.c
+++ src/prepare.c
@@ -146,10 +146,11 @@
int meta[5];
InitData initData;
const char *zMasterName;
int openedTransaction = 0;
+ assert( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 );
assert( iDb>=0 && iDbnDb );
assert( db->aDb[iDb].pSchema );
assert( sqlite3_mutex_held(db->mutex) );
assert( iDb==1 || sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) );
@@ -215,10 +216,13 @@
** Note: The #defined SQLITE_UTF* symbols in sqliteInt.h correspond to
** the possible values of meta[4].
*/
for(i=0; ipBt, i+1, (u32 *)&meta[i]);
+ }
+ if( (db->flags & SQLITE_ResetDatabase)!=0 ){
+ memset(meta, 0, sizeof(meta));
}
pDb->pSchema->schema_cookie = meta[BTREE_SCHEMA_VERSION-1];
/* If opening a non-empty database, check the text encoding. For the
** main database, set sqlite3.enc to the encoding of the main database.
@@ -375,10 +379,11 @@
rc = sqlite3InitOne(db, 0, pzErrMsg);
if( rc ) return rc;
}
/* All other schemas after the main schema. The "temp" schema must be last */
for(i=db->nDb-1; i>0; i--){
+ assert( i==1 || sqlite3BtreeHoldsMutex(db->aDb[i].pBt) );
if( !DbHasProperty(db, i, DB_SchemaLoaded) ){
rc = sqlite3InitOne(db, i, pzErrMsg);
if( rc ) return rc;
}
}
@@ -396,14 +401,16 @@
int rc = SQLITE_OK;
sqlite3 *db = pParse->db;
assert( sqlite3_mutex_held(db->mutex) );
if( !db->init.busy ){
rc = sqlite3Init(db, &pParse->zErrMsg);
- }
- if( rc!=SQLITE_OK ){
- pParse->rc = rc;
- pParse->nErr++;
+ if( rc!=SQLITE_OK ){
+ pParse->rc = rc;
+ pParse->nErr++;
+ }else if( db->noSharedCache ){
+ db->mDbFlags |= DBFLAG_SchemaKnownOk;
+ }
}
return rc;
}
@@ -610,11 +617,11 @@
#ifndef SQLITE_OMIT_EXPLAIN
if( rc==SQLITE_OK && sParse.pVdbe && sParse.explain ){
static const char * const azColName[] = {
"addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment",
- "selectid", "order", "from", "detail"
+ "id", "parent", "notused", "detail"
};
int iFirst, mx;
if( sParse.explain==2 ){
sqlite3VdbeSetNumCols(sParse.pVdbe, 4);
iFirst = 8;
Index: src/resolve.c
==================================================================
--- src/resolve.c
+++ src/resolve.c
@@ -189,11 +189,11 @@
sqlite3 *db = pParse->db; /* The database connection */
struct SrcList_item *pItem; /* Use for looping over pSrcList items */
struct SrcList_item *pMatch = 0; /* The matching pSrcList item */
NameContext *pTopNC = pNC; /* First namecontext in the list */
Schema *pSchema = 0; /* Schema of the expression */
- int isTrigger = 0; /* True if resolved to a trigger column */
+ int eNewExprOp = TK_COLUMN; /* New value for pExpr->op on success */
Table *pTab = 0; /* Table hold the row */
Column *pCol; /* A column of pTab */
assert( pNC ); /* the name context cannot be NULL. */
assert( zCol ); /* The Z in X.Y.Z cannot be NULL */
@@ -294,26 +294,39 @@
}
pSchema = pExpr->pTab->pSchema;
}
} /* if( pSrcList ) */
-#ifndef SQLITE_OMIT_TRIGGER
+#if !defined(SQLITE_OMIT_TRIGGER) || !defined(SQLITE_OMIT_UPSERT)
/* If we have not already resolved the name, then maybe
- ** it is a new.* or old.* trigger argument reference
+ ** it is a new.* or old.* trigger argument reference. Or
+ ** maybe it is an excluded.* from an upsert.
*/
- if( zDb==0 && zTab!=0 && cntTab==0 && pParse->pTriggerTab!=0 ){
- int op = pParse->eTriggerOp;
- assert( op==TK_DELETE || op==TK_UPDATE || op==TK_INSERT );
- if( op!=TK_DELETE && sqlite3StrICmp("new",zTab) == 0 ){
- pExpr->iTable = 1;
- pTab = pParse->pTriggerTab;
- }else if( op!=TK_INSERT && sqlite3StrICmp("old",zTab)==0 ){
- pExpr->iTable = 0;
- pTab = pParse->pTriggerTab;
- }else{
- pTab = 0;
- }
+ if( zDb==0 && zTab!=0 && cntTab==0 ){
+ pTab = 0;
+#ifndef SQLITE_OMIT_TRIGGER
+ if( pParse->pTriggerTab!=0 ){
+ int op = pParse->eTriggerOp;
+ assert( op==TK_DELETE || op==TK_UPDATE || op==TK_INSERT );
+ if( op!=TK_DELETE && sqlite3StrICmp("new",zTab) == 0 ){
+ pExpr->iTable = 1;
+ pTab = pParse->pTriggerTab;
+ }else if( op!=TK_INSERT && sqlite3StrICmp("old",zTab)==0 ){
+ pExpr->iTable = 0;
+ pTab = pParse->pTriggerTab;
+ }
+ }
+#endif /* SQLITE_OMIT_TRIGGER */
+#ifndef SQLITE_OMIT_UPSERT
+ if( (pNC->ncFlags & NC_UUpsert)!=0 ){
+ Upsert *pUpsert = pNC->uNC.pUpsert;
+ if( pUpsert && sqlite3StrICmp("excluded",zTab)==0 ){
+ pTab = pUpsert->pUpsertSrc->a[0].pTab;
+ pExpr->iTable = 2;
+ }
+ }
+#endif /* SQLITE_OMIT_UPSERT */
if( pTab ){
int iCol;
pSchema = pTab->pSchema;
cntTab++;
@@ -329,28 +342,39 @@
/* IMP: R-51414-32910 */
iCol = -1;
}
if( iColnCol ){
cnt++;
- if( iCol<0 ){
- pExpr->affinity = SQLITE_AFF_INTEGER;
- }else if( pExpr->iTable==0 ){
- testcase( iCol==31 );
- testcase( iCol==32 );
- pParse->oldmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<newmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<iColumn = (i16)iCol;
- pExpr->pTab = pTab;
- isTrigger = 1;
- }
- }
- }
-#endif /* !defined(SQLITE_OMIT_TRIGGER) */
+#ifndef SQLITE_OMIT_UPSERT
+ if( pExpr->iTable==2 ){
+ testcase( iCol==(-1) );
+ pExpr->iTable = pNC->uNC.pUpsert->regData + iCol;
+ eNewExprOp = TK_REGISTER;
+ }else
+#endif /* SQLITE_OMIT_UPSERT */
+ {
+#ifndef SQLITE_OMIT_TRIGGER
+ if( iCol<0 ){
+ pExpr->affinity = SQLITE_AFF_INTEGER;
+ }else if( pExpr->iTable==0 ){
+ testcase( iCol==31 );
+ testcase( iCol==32 );
+ pParse->oldmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<newmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<pTab = pTab;
+ pExpr->iColumn = (i16)iCol;
+ eNewExprOp = TK_TRIGGER;
+#endif /* SQLITE_OMIT_TRIGGER */
+ }
+ }
+ }
+ }
+#endif /* !defined(SQLITE_OMIT_TRIGGER) || !defined(SQLITE_OMIT_UPSERT) */
/*
** Perhaps the name is a reference to the ROWID
*/
if( cnt==0
@@ -381,14 +405,16 @@
** or HAVING clauses, or as part of a larger expression in the ORDER BY
** clause is not standard SQL. This is a (goofy) SQLite extension, that
** is supported for backwards compatibility only. Hence, we issue a warning
** on sqlite3_log() whenever the capability is used.
*/
- if( (pEList = pNC->pEList)!=0
- && zTab==0
+ if( (pNC->ncFlags & NC_UEList)!=0
&& cnt==0
+ && zTab==0
){
+ pEList = pNC->uNC.pEList;
+ assert( pEList!=0 );
for(j=0; jnExpr; j++){
char *zAs = pEList->a[j].zName;
if( zAs!=0 && sqlite3StrICmp(zAs, zCol)==0 ){
Expr *pOrig;
assert( pExpr->pLeft==0 && pExpr->pRight==0 );
@@ -481,11 +507,11 @@
*/
sqlite3ExprDelete(db, pExpr->pLeft);
pExpr->pLeft = 0;
sqlite3ExprDelete(db, pExpr->pRight);
pExpr->pRight = 0;
- pExpr->op = (isTrigger ? TK_TRIGGER : TK_COLUMN);
+ pExpr->op = eNewExprOp;
ExprSetProperty(pExpr, EP_Leaf);
lookupname_end:
if( cnt==1 ){
assert( pNC!=0 );
if( !ExprHasProperty(pExpr, EP_Alias) ){
@@ -913,12 +939,12 @@
/* Resolve all names in the ORDER BY term expression
*/
memset(&nc, 0, sizeof(nc));
nc.pParse = pParse;
nc.pSrcList = pSelect->pSrc;
- nc.pEList = pEList;
- nc.ncFlags = NC_AllowAgg;
+ nc.uNC.pEList = pEList;
+ nc.ncFlags = NC_AllowAgg|NC_UEList;
nc.nErr = 0;
db = pParse->db;
savedSuppErr = db->suppressErr;
db->suppressErr = 1;
rc = sqlite3ResolveExprNames(&nc, pE);
@@ -1297,11 +1323,13 @@
** aliases in the result set.
**
** Minor point: If this is the case, then the expression will be
** re-evaluated for each reference to it.
*/
- sNC.pEList = p->pEList;
+ assert( (sNC.ncFlags & (NC_UAggInfo|NC_UUpsert))==0 );
+ sNC.uNC.pEList = p->pEList;
+ sNC.ncFlags |= NC_UEList;
if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort;
if( sqlite3ResolveExprNames(&sNC, p->pWhere) ) return WRC_Abort;
/* Resolve names in table-valued-function arguments */
for(i=0; ipSrc->nSrc; i++){
@@ -1530,11 +1558,11 @@
void sqlite3ResolveSelfReference(
Parse *pParse, /* Parsing context */
Table *pTab, /* The table being referenced */
int type, /* NC_IsCheck or NC_PartIdx or NC_IdxExpr */
Expr *pExpr, /* Expression to resolve. May be NULL. */
- ExprList *pList /* Expression list to resolve. May be NUL. */
+ ExprList *pList /* Expression list to resolve. May be NULL. */
){
SrcList sSrc; /* Fake SrcList for pParse->pNewTable */
NameContext sNC; /* Name context for pParse->pNewTable */
assert( type==NC_IsCheck || type==NC_PartIdx || type==NC_IdxExpr );
Index: src/select.c
==================================================================
--- src/select.c
+++ src/select.c
@@ -19,11 +19,11 @@
*/
#if SELECTTRACE_ENABLED
/***/ int sqlite3SelectTrace = 0;
# define SELECTTRACE(K,P,S,X) \
if(sqlite3SelectTrace&(K)) \
- sqlite3DebugPrintf("%s/%p: ",(S)->zSelName,(S)),\
+ sqlite3DebugPrintf("%s/%d/%p: ",(S)->zSelName,(P)->addrExplain,(S)),\
sqlite3DebugPrintf X
#else
# define SELECTTRACE(K,P,S,X)
#endif
@@ -42,10 +42,24 @@
};
/*
** An instance of the following object is used to record information about
** the ORDER BY (or GROUP BY) clause of query is being coded.
+**
+** The aDefer[] array is used by the sorter-references optimization. For
+** example, assuming there is no index that can be used for the ORDER BY,
+** for the query:
+**
+** SELECT a, bigblob FROM t1 ORDER BY a LIMIT 10;
+**
+** it may be more efficient to add just the "a" values to the sorter, and
+** retrieve the associated "bigblob" values directly from table t1 as the
+** 10 smallest "a" values are extracted from the sorter.
+**
+** When the sorter-reference optimization is used, there is one entry in the
+** aDefer[] array for each database table that may be read as values are
+** extracted from the sorter.
*/
typedef struct SortCtx SortCtx;
struct SortCtx {
ExprList *pOrderBy; /* The ORDER BY (or GROUP BY clause) */
int nOBSat; /* Number of ORDER BY terms satisfied by indices */
@@ -54,10 +68,19 @@
int labelBkOut; /* Start label for the block-output subroutine */
int addrSortIndex; /* Address of the OP_SorterOpen or OP_OpenEphemeral */
int labelDone; /* Jump here when done, ex: LIMIT reached */
u8 sortFlags; /* Zero or more SORTFLAG_* bits */
u8 bOrderedInnerLoop; /* ORDER BY correctly sorts the inner loop */
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ u8 nDefer; /* Number of valid entries in aDefer[] */
+ struct DeferredCsr {
+ Table *pTab; /* Table definition */
+ int iCsr; /* Cursor number for table */
+ int nKey; /* Number of PK columns for table pTab (>=1) */
+ } aDefer[4];
+#endif
+ struct RowLoadInfo *pDeferredRowLoad; /* Deferred row loading info or NULL */
};
#define SORTFLAG_UseSorter 0x01 /* Use SorterOpen instead of OpenEphemeral */
/*
** Delete all the content of a Select structure. Deallocate the structure
@@ -511,10 +534,66 @@
Parse *pParse, /* Parsing context */
ExprList *pList, /* Form the KeyInfo object from this ExprList */
int iStart, /* Begin with this column of pList */
int nExtra /* Add this many extra columns to the end */
);
+
+/*
+** An instance of this object holds information (beyond pParse and pSelect)
+** needed to load the next result row that is to be added to the sorter.
+*/
+typedef struct RowLoadInfo RowLoadInfo;
+struct RowLoadInfo {
+ int regResult; /* Store results in array of registers here */
+ u8 ecelFlags; /* Flag argument to ExprCodeExprList() */
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ ExprList *pExtra; /* Extra columns needed by sorter refs */
+ int regExtraResult; /* Where to load the extra columns */
+#endif
+};
+
+/*
+** This routine does the work of loading query data into an array of
+** registers so that it can be added to the sorter.
+*/
+static void innerLoopLoadRow(
+ Parse *pParse, /* Statement under construction */
+ Select *pSelect, /* The query being coded */
+ RowLoadInfo *pInfo /* Info needed to complete the row load */
+){
+ sqlite3ExprCodeExprList(pParse, pSelect->pEList, pInfo->regResult,
+ 0, pInfo->ecelFlags);
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ if( pInfo->pExtra ){
+ sqlite3ExprCodeExprList(pParse, pInfo->pExtra, pInfo->regExtraResult, 0, 0);
+ sqlite3ExprListDelete(pParse->db, pInfo->pExtra);
+ }
+#endif
+}
+
+/*
+** Code the OP_MakeRecord instruction that generates the entry to be
+** added into the sorter.
+**
+** Return the register in which the result is stored.
+*/
+static int makeSorterRecord(
+ Parse *pParse,
+ SortCtx *pSort,
+ Select *pSelect,
+ int regBase,
+ int nBase
+){
+ int nOBSat = pSort->nOBSat;
+ Vdbe *v = pParse->pVdbe;
+ int regOut = ++pParse->nMem;
+ if( pSort->pDeferredRowLoad ){
+ innerLoopLoadRow(pParse, pSelect, pSort->pDeferredRowLoad);
+ }
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase+nOBSat, nBase-nOBSat, regOut);
+ return regOut;
+}
/*
** Generate code that will push the record in registers regData
** through regData+nData-1 onto the sorter.
*/
@@ -522,28 +601,43 @@
Parse *pParse, /* Parser context */
SortCtx *pSort, /* Information about the ORDER BY clause */
Select *pSelect, /* The whole SELECT statement */
int regData, /* First register holding data to be sorted */
int regOrigData, /* First register holding data before packing */
- int nData, /* Number of elements in the data array */
+ int nData, /* Number of elements in the regData data array */
int nPrefixReg /* No. of reg prior to regData available for use */
){
Vdbe *v = pParse->pVdbe; /* Stmt under construction */
int bSeq = ((pSort->sortFlags & SORTFLAG_UseSorter)==0);
int nExpr = pSort->pOrderBy->nExpr; /* No. of ORDER BY terms */
int nBase = nExpr + bSeq + nData; /* Fields in sorter record */
int regBase; /* Regs for sorter record */
- int regRecord = ++pParse->nMem; /* Assembled sorter record */
+ int regRecord = 0; /* Assembled sorter record */
int nOBSat = pSort->nOBSat; /* ORDER BY terms to skip */
int op; /* Opcode to add sorter record to sorter */
int iLimit; /* LIMIT counter */
+ int iSkip = 0; /* End of the sorter insert loop */
assert( bSeq==0 || bSeq==1 );
+
+ /* Three cases:
+ ** (1) The data to be sorted has already been packed into a Record
+ ** by a prior OP_MakeRecord. In this case nData==1 and regData
+ ** will be completely unrelated to regOrigData.
+ ** (2) All output columns are included in the sort record. In that
+ ** case regData==regOrigData.
+ ** (3) Some output columns are omitted from the sort record due to
+ ** the SQLITE_ENABLE_SORTER_REFERENCE optimization, or due to the
+ ** SQLITE_ECEL_OMITREF optimization. In that case, regOrigData==0
+ ** to prevent this routine from trying to copy values that might
+ ** not exist.
+ */
assert( nData==1 || regData==regOrigData || regOrigData==0 );
+
if( nPrefixReg ){
assert( nPrefixReg==nExpr+bSeq );
- regBase = regData - nExpr - bSeq;
+ regBase = regData - nPrefixReg;
}else{
regBase = pParse->nMem + 1;
pParse->nMem += nBase;
}
assert( pSelect->iOffset==0 || pSelect->iLimit!=0 );
@@ -555,19 +649,19 @@
sqlite3VdbeAddOp2(v, OP_Sequence, pSort->iECursor, regBase+nExpr);
}
if( nPrefixReg==0 && nData>0 ){
sqlite3ExprCodeMove(pParse, regData, regBase+nExpr+bSeq, nData);
}
- sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase+nOBSat, nBase-nOBSat, regRecord);
if( nOBSat>0 ){
int regPrevKey; /* The first nOBSat columns of the previous row */
int addrFirst; /* Address of the OP_IfNot opcode */
int addrJmp; /* Address of the OP_Jump opcode */
VdbeOp *pOp; /* Opcode that opens the sorter */
int nKey; /* Number of sorting key columns, including OP_Sequence */
KeyInfo *pKI; /* Original KeyInfo on the sorter table */
+ regRecord = makeSorterRecord(pParse, pSort, pSelect, regBase, nBase);
regPrevKey = pParse->nMem+1;
pParse->nMem += pSort->nOBSat;
nKey = nExpr - pSort->nOBSat + bSeq;
if( bSeq ){
addrFirst = sqlite3VdbeAddOp1(v, OP_IfNot, regBase+nExpr);
@@ -597,44 +691,49 @@
}
sqlite3VdbeJumpHere(v, addrFirst);
sqlite3ExprCodeMove(pParse, regBase, regPrevKey, pSort->nOBSat);
sqlite3VdbeJumpHere(v, addrJmp);
}
+ if( iLimit ){
+ /* At this point the values for the new sorter entry are stored
+ ** in an array of registers. They need to be composed into a record
+ ** and inserted into the sorter if either (a) there are currently
+ ** less than LIMIT+OFFSET items or (b) the new record is smaller than
+ ** the largest record currently in the sorter. If (b) is true and there
+ ** are already LIMIT+OFFSET items in the sorter, delete the largest
+ ** entry before inserting the new one. This way there are never more
+ ** than LIMIT+OFFSET items in the sorter.
+ **
+ ** If the new record does not need to be inserted into the sorter,
+ ** jump to the next iteration of the loop. Or, if the
+ ** pSort->bOrderedInnerLoop flag is set to indicate that the inner
+ ** loop delivers items in sorted order, jump to the next iteration
+ ** of the outer loop.
+ */
+ int iCsr = pSort->iECursor;
+ sqlite3VdbeAddOp2(v, OP_IfNotZero, iLimit, sqlite3VdbeCurrentAddr(v)+4);
+ VdbeCoverage(v);
+ sqlite3VdbeAddOp2(v, OP_Last, iCsr, 0);
+ iSkip = sqlite3VdbeAddOp4Int(v, OP_IdxLE,
+ iCsr, 0, regBase+nOBSat, nExpr-nOBSat);
+ VdbeCoverage(v);
+ sqlite3VdbeAddOp1(v, OP_Delete, iCsr);
+ }
+ if( regRecord==0 ){
+ regRecord = makeSorterRecord(pParse, pSort, pSelect, regBase, nBase);
+ }
if( pSort->sortFlags & SORTFLAG_UseSorter ){
op = OP_SorterInsert;
}else{
op = OP_IdxInsert;
}
sqlite3VdbeAddOp4Int(v, op, pSort->iECursor, regRecord,
regBase+nOBSat, nBase-nOBSat);
- if( iLimit ){
- int addr;
- int r1 = 0;
- /* Fill the sorter until it contains LIMIT+OFFSET entries. (The iLimit
- ** register is initialized with value of LIMIT+OFFSET.) After the sorter
- ** fills up, delete the least entry in the sorter after each insert.
- ** Thus we never hold more than the LIMIT+OFFSET rows in memory at once */
- addr = sqlite3VdbeAddOp1(v, OP_IfNotZero, iLimit); VdbeCoverage(v);
- sqlite3VdbeAddOp1(v, OP_Last, pSort->iECursor);
- if( pSort->bOrderedInnerLoop ){
- r1 = ++pParse->nMem;
- sqlite3VdbeAddOp3(v, OP_Column, pSort->iECursor, nExpr, r1);
- VdbeComment((v, "seq"));
- }
- sqlite3VdbeAddOp1(v, OP_Delete, pSort->iECursor);
- if( pSort->bOrderedInnerLoop ){
- /* If the inner loop is driven by an index such that values from
- ** the same iteration of the inner loop are in sorted order, then
- ** immediately jump to the next iteration of an inner loop if the
- ** entry from the current iteration does not fit into the top
- ** LIMIT+OFFSET entries of the sorter. */
- int iBrk = sqlite3VdbeCurrentAddr(v) + 2;
- sqlite3VdbeAddOp3(v, OP_Eq, regBase+nExpr, iBrk, r1);
- sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
- VdbeCoverage(v);
- }
- sqlite3VdbeJumpHere(v, addr);
+ if( iSkip ){
+ assert( pSort->bOrderedInnerLoop==0 || pSort->bOrderedInnerLoop==1 );
+ sqlite3VdbeChangeP2(v, iSkip,
+ sqlite3VdbeCurrentAddr(v) + pSort->bOrderedInnerLoop);
}
}
/*
** Add code to implement the OFFSET
@@ -676,10 +775,91 @@
sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r1, iMem, N);
sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
sqlite3ReleaseTempReg(pParse, r1);
}
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+/*
+** This function is called as part of inner-loop generation for a SELECT
+** statement with an ORDER BY that is not optimized by an index. It
+** determines the expressions, if any, that the sorter-reference
+** optimization should be used for. The sorter-reference optimization
+** is used for SELECT queries like:
+**
+** SELECT a, bigblob FROM t1 ORDER BY a LIMIT 10
+**
+** If the optimization is used for expression "bigblob", then instead of
+** storing values read from that column in the sorter records, the PK of
+** the row from table t1 is stored instead. Then, as records are extracted from
+** the sorter to return to the user, the required value of bigblob is
+** retrieved directly from table t1. If the values are very large, this
+** can be more efficient than storing them directly in the sorter records.
+**
+** The ExprList_item.bSorterRef flag is set for each expression in pEList
+** for which the sorter-reference optimization should be enabled.
+** Additionally, the pSort->aDefer[] array is populated with entries
+** for all cursors required to evaluate all selected expressions. Finally.
+** output variable (*ppExtra) is set to an expression list containing
+** expressions for all extra PK values that should be stored in the
+** sorter records.
+*/
+static void selectExprDefer(
+ Parse *pParse, /* Leave any error here */
+ SortCtx *pSort, /* Sorter context */
+ ExprList *pEList, /* Expressions destined for sorter */
+ ExprList **ppExtra /* Expressions to append to sorter record */
+){
+ int i;
+ int nDefer = 0;
+ ExprList *pExtra = 0;
+ for(i=0; inExpr; i++){
+ struct ExprList_item *pItem = &pEList->a[i];
+ if( pItem->u.x.iOrderByCol==0 ){
+ Expr *pExpr = pItem->pExpr;
+ Table *pTab = pExpr->pTab;
+ if( pExpr->op==TK_COLUMN && pTab && !IsVirtual(pTab)
+ && (pTab->aCol[pExpr->iColumn].colFlags & COLFLAG_SORTERREF)
+ ){
+ int j;
+ for(j=0; jaDefer[j].iCsr==pExpr->iTable ) break;
+ }
+ if( j==nDefer ){
+ if( nDefer==ArraySize(pSort->aDefer) ){
+ continue;
+ }else{
+ int nKey = 1;
+ int k;
+ Index *pPk = 0;
+ if( !HasRowid(pTab) ){
+ pPk = sqlite3PrimaryKeyIndex(pTab);
+ nKey = pPk->nKeyCol;
+ }
+ for(k=0; kiTable = pExpr->iTable;
+ pNew->pTab = pExpr->pTab;
+ pNew->iColumn = pPk ? pPk->aiColumn[k] : -1;
+ pExtra = sqlite3ExprListAppend(pParse, pExtra, pNew);
+ }
+ }
+ pSort->aDefer[nDefer].pTab = pExpr->pTab;
+ pSort->aDefer[nDefer].iCsr = pExpr->iTable;
+ pSort->aDefer[nDefer].nKey = nKey;
+ nDefer++;
+ }
+ }
+ pItem->bSorterRef = 1;
+ }
+ }
+ }
+ pSort->nDefer = (u8)nDefer;
+ *ppExtra = pExtra;
+}
+#endif
+
/*
** This routine generates the code for the inside of the inner loop
** of a SELECT.
**
** If srcTab is negative, then the p->pEList expressions
@@ -702,10 +882,11 @@
int hasDistinct; /* True if the DISTINCT keyword is present */
int eDest = pDest->eDest; /* How to dispose of results */
int iParm = pDest->iSDParm; /* First argument to disposal method */
int nResultCol; /* Number of result columns */
int nPrefixReg = 0; /* Number of extra registers before regResult */
+ RowLoadInfo sRowLoadInfo; /* Info for deferred row loading */
/* Usually, regResult is the first cell in an array of memory cells
** containing the current result row. In this case regOrig is set to the
** same value. However, if the results are being sent to the sorter, the
** values for any expressions that are also part of the sort-key are omitted
@@ -748,14 +929,18 @@
for(i=0; ipEList->a[i].zName));
}
}else if( eDest!=SRT_Exists ){
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ ExprList *pExtra = 0;
+#endif
/* If the destination is an EXISTS(...) expression, the actual
** values returned by the SELECT are not required.
*/
- u8 ecelFlags;
+ u8 ecelFlags; /* "ecel" is an abbreviation of "ExprCodeExprList" */
+ ExprList *pEList;
if( eDest==SRT_Mem || eDest==SRT_Output || eDest==SRT_Coroutine ){
ecelFlags = SQLITE_ECEL_DUP;
}else{
ecelFlags = 0;
}
@@ -765,22 +950,71 @@
** iOrderByCol value to one more than the index of the ORDER BY
** expression within the sort-key that pushOntoSorter() will generate.
** This allows the p->pEList field to be omitted from the sorted record,
** saving space and CPU cycles. */
ecelFlags |= (SQLITE_ECEL_OMITREF|SQLITE_ECEL_REF);
+
for(i=pSort->nOBSat; ipOrderBy->nExpr; i++){
int j;
if( (j = pSort->pOrderBy->a[i].u.x.iOrderByCol)>0 ){
p->pEList->a[j-1].u.x.iOrderByCol = i+1-pSort->nOBSat;
}
}
- regOrig = 0;
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ selectExprDefer(pParse, pSort, p->pEList, &pExtra);
+ if( pExtra && pParse->db->mallocFailed==0 ){
+ /* If there are any extra PK columns to add to the sorter records,
+ ** allocate extra memory cells and adjust the OpenEphemeral
+ ** instruction to account for the larger records. This is only
+ ** required if there are one or more WITHOUT ROWID tables with
+ ** composite primary keys in the SortCtx.aDefer[] array. */
+ VdbeOp *pOp = sqlite3VdbeGetOp(v, pSort->addrSortIndex);
+ pOp->p2 += (pExtra->nExpr - pSort->nDefer);
+ pOp->p4.pKeyInfo->nAllField += (pExtra->nExpr - pSort->nDefer);
+ pParse->nMem += pExtra->nExpr;
+ }
+#endif
+
+ /* Adjust nResultCol to account for columns that are omitted
+ ** from the sorter by the optimizations in this branch */
+ pEList = p->pEList;
+ for(i=0; inExpr; i++){
+ if( pEList->a[i].u.x.iOrderByCol>0
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ || pEList->a[i].bSorterRef
+#endif
+ ){
+ nResultCol--;
+ regOrig = 0;
+ }
+ }
+
+ testcase( regOrig );
+ testcase( eDest==SRT_Set );
+ testcase( eDest==SRT_Mem );
+ testcase( eDest==SRT_Coroutine );
+ testcase( eDest==SRT_Output );
assert( eDest==SRT_Set || eDest==SRT_Mem
|| eDest==SRT_Coroutine || eDest==SRT_Output );
}
- nResultCol = sqlite3ExprCodeExprList(pParse,p->pEList,regResult,
- 0,ecelFlags);
+ sRowLoadInfo.regResult = regResult;
+ sRowLoadInfo.ecelFlags = ecelFlags;
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ sRowLoadInfo.pExtra = pExtra;
+ sRowLoadInfo.regExtraResult = regResult + nResultCol;
+ if( pExtra ) nResultCol += pExtra->nExpr;
+#endif
+ if( p->iLimit
+ && (ecelFlags & SQLITE_ECEL_OMITREF)!=0
+ && nPrefixReg>0
+ ){
+ assert( pSort!=0 );
+ assert( hasDistinct==0 );
+ pSort->pDeferredRowLoad = &sRowLoadInfo;
+ }else{
+ innerLoopLoadRow(pParse, p, &sRowLoadInfo);
+ }
}
/* If the DISTINCT keyword was present on the SELECT statement
** and this row has been seen before, then do not make this row
** part of the result.
@@ -892,11 +1126,12 @@
sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm+1, r1,regResult,nResultCol);
assert( pSort==0 );
}
#endif
if( pSort ){
- pushOntoSorter(pParse, pSort, p, r1+nPrefixReg,regResult,1,nPrefixReg);
+ assert( regResult==regOrig );
+ pushOntoSorter(pParse, pSort, p, r1+nPrefixReg, regOrig, 1, nPrefixReg);
}else{
int r2 = sqlite3GetTempReg(pParse);
sqlite3VdbeAddOp2(v, OP_NewRowid, iParm, r2);
sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, r2);
sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
@@ -1159,15 +1394,11 @@
**
** where xxx is one of "DISTINCT", "ORDER BY" or "GROUP BY". Exactly which
** is determined by the zUsage argument.
*/
static void explainTempTable(Parse *pParse, const char *zUsage){
- if( pParse->explain==2 ){
- Vdbe *v = pParse->pVdbe;
- char *zMsg = sqlite3MPrintf(pParse->db, "USE TEMP B-TREE FOR %s", zUsage);
- sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC);
- }
+ ExplainQueryPlan((pParse, 0, "USE TEMP B-TREE FOR %s", zUsage));
}
/*
** Assign expression b to lvalue a. A second, no-op, version of this macro
** is provided when SQLITE_OMIT_EXPLAIN is defined. This allows the code
@@ -1181,46 +1412,10 @@
/* No-op versions of the explainXXX() functions and macros. */
# define explainTempTable(y,z)
# define explainSetInteger(y,z)
#endif
-#if !defined(SQLITE_OMIT_EXPLAIN) && !defined(SQLITE_OMIT_COMPOUND_SELECT)
-/*
-** Unless an "EXPLAIN QUERY PLAN" command is being processed, this function
-** is a no-op. Otherwise, it adds a single row of output to the EQP result,
-** where the caption is of one of the two forms:
-**
-** "COMPOSITE SUBQUERIES iSub1 and iSub2 (op)"
-** "COMPOSITE SUBQUERIES iSub1 and iSub2 USING TEMP B-TREE (op)"
-**
-** where iSub1 and iSub2 are the integers passed as the corresponding
-** function parameters, and op is the text representation of the parameter
-** of the same name. The parameter "op" must be one of TK_UNION, TK_EXCEPT,
-** TK_INTERSECT or TK_ALL. The first form is used if argument bUseTmp is
-** false, or the second form if it is true.
-*/
-static void explainComposite(
- Parse *pParse, /* Parse context */
- int op, /* One of TK_UNION, TK_EXCEPT etc. */
- int iSub1, /* Subquery id 1 */
- int iSub2, /* Subquery id 2 */
- int bUseTmp /* True if a temp table was used */
-){
- assert( op==TK_UNION || op==TK_EXCEPT || op==TK_INTERSECT || op==TK_ALL );
- if( pParse->explain==2 ){
- Vdbe *v = pParse->pVdbe;
- char *zMsg = sqlite3MPrintf(
- pParse->db, "COMPOUND SUBQUERIES %d AND %d %s(%s)", iSub1, iSub2,
- bUseTmp?"USING TEMP B-TREE ":"", selectOpName(op)
- );
- sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC);
- }
-}
-#else
-/* No-op versions of the explainXXX() functions and macros. */
-# define explainComposite(v,w,x,y,z)
-#endif
/*
** If the inner loop was generated using a non-null pOrderBy argument,
** then the results were placed in a sorter. After the loop is terminated
** we need to run the sorter and output the results. The following
@@ -1234,50 +1429,60 @@
SelectDest *pDest /* Write the sorted results here */
){
Vdbe *v = pParse->pVdbe; /* The prepared statement */
int addrBreak = pSort->labelDone; /* Jump here to exit loop */
int addrContinue = sqlite3VdbeMakeLabel(v); /* Jump here for next cycle */
- int addr;
+ int addr; /* Top of output loop. Jump for Next. */
int addrOnce = 0;
int iTab;
ExprList *pOrderBy = pSort->pOrderBy;
int eDest = pDest->eDest;
int iParm = pDest->iSDParm;
int regRow;
int regRowid;
int iCol;
- int nKey;
+ int nKey; /* Number of key columns in sorter record */
int iSortTab; /* Sorter cursor to read from */
- int nSortData; /* Trailing values to read from sorter */
int i;
int bSeq; /* True if sorter record includes seq. no. */
+ int nRefKey = 0;
struct ExprList_item *aOutEx = p->pEList->a;
assert( addrBreak<0 );
if( pSort->labelBkOut ){
sqlite3VdbeAddOp2(v, OP_Gosub, pSort->regReturn, pSort->labelBkOut);
sqlite3VdbeGoto(v, addrBreak);
sqlite3VdbeResolveLabel(v, pSort->labelBkOut);
}
+
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ /* Open any cursors needed for sorter-reference expressions */
+ for(i=0; inDefer; i++){
+ Table *pTab = pSort->aDefer[i].pTab;
+ int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ sqlite3OpenTable(pParse, pSort->aDefer[i].iCsr, iDb, pTab, OP_OpenRead);
+ nRefKey = MAX(nRefKey, pSort->aDefer[i].nKey);
+ }
+#endif
+
iTab = pSort->iECursor;
if( eDest==SRT_Output || eDest==SRT_Coroutine || eDest==SRT_Mem ){
regRowid = 0;
regRow = pDest->iSdst;
- nSortData = nColumn;
}else{
regRowid = sqlite3GetTempReg(pParse);
regRow = sqlite3GetTempRange(pParse, nColumn);
- nSortData = nColumn;
}
nKey = pOrderBy->nExpr - pSort->nOBSat;
if( pSort->sortFlags & SORTFLAG_UseSorter ){
int regSortOut = ++pParse->nMem;
iSortTab = pParse->nTab++;
if( pSort->labelBkOut ){
addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
}
- sqlite3VdbeAddOp3(v, OP_OpenPseudo, iSortTab, regSortOut, nKey+1+nSortData);
+ sqlite3VdbeAddOp3(v, OP_OpenPseudo, iSortTab, regSortOut,
+ nKey+1+nColumn+nRefKey);
if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce);
addr = 1 + sqlite3VdbeAddOp2(v, OP_SorterSort, iTab, addrBreak);
VdbeCoverage(v);
codeOffset(v, p->iOffset, addrContinue);
sqlite3VdbeAddOp3(v, OP_SorterData, iTab, regSortOut, iSortTab);
@@ -1286,22 +1491,63 @@
addr = 1 + sqlite3VdbeAddOp2(v, OP_Sort, iTab, addrBreak); VdbeCoverage(v);
codeOffset(v, p->iOffset, addrContinue);
iSortTab = iTab;
bSeq = 1;
}
- for(i=0, iCol=nKey+bSeq-1; i=0; i--){
- int iRead;
- if( aOutEx[i].u.x.iOrderByCol ){
- iRead = aOutEx[i].u.x.iOrderByCol-1;
- }else{
- iRead = iCol--;
- }
- sqlite3VdbeAddOp3(v, OP_Column, iSortTab, iRead, regRow+i);
- VdbeComment((v, "%s", aOutEx[i].zName ? aOutEx[i].zName : aOutEx[i].zSpan));
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ if( pSort->nDefer ){
+ int iKey = iCol+1;
+ int regKey = sqlite3GetTempRange(pParse, nRefKey);
+
+ for(i=0; inDefer; i++){
+ int iCsr = pSort->aDefer[i].iCsr;
+ Table *pTab = pSort->aDefer[i].pTab;
+ int nKey = pSort->aDefer[i].nKey;
+
+ sqlite3VdbeAddOp1(v, OP_NullRow, iCsr);
+ if( HasRowid(pTab) ){
+ sqlite3VdbeAddOp3(v, OP_Column, iSortTab, iKey++, regKey);
+ sqlite3VdbeAddOp3(v, OP_SeekRowid, iCsr,
+ sqlite3VdbeCurrentAddr(v)+1, regKey);
+ }else{
+ int k;
+ int iJmp;
+ assert( sqlite3PrimaryKeyIndex(pTab)->nKeyCol==nKey );
+ for(k=0; k=0; i--){
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ if( aOutEx[i].bSorterRef ){
+ sqlite3ExprCode(pParse, aOutEx[i].pExpr, regRow+i);
+ }else
+#endif
+ {
+ int iRead;
+ if( aOutEx[i].u.x.iOrderByCol ){
+ iRead = aOutEx[i].u.x.iOrderByCol-1;
+ }else{
+ iRead = iCol--;
+ }
+ sqlite3VdbeAddOp3(v, OP_Column, iSortTab, iRead, regRow+i);
+ VdbeComment((v, "%s", aOutEx[i].zName?aOutEx[i].zName : aOutEx[i].zSpan));
+ }
}
switch( eDest ){
case SRT_Table:
case SRT_EphemTab: {
sqlite3VdbeAddOp2(v, OP_NewRowid, iParm, regRowid);
@@ -1615,11 +1861,11 @@
if( pParse->explain ){
return;
}
#endif
- if( pParse->colNamesSet || db->mallocFailed ) return;
+ if( pParse->colNamesSet ) return;
/* Column names are determined by the left-most term of a compound select */
while( pSelect->pPrior ) pSelect = pSelect->pPrior;
SELECTTRACE(1,pParse,pSelect,("generating column names\n"));
pTabList = pSelect->pSrc;
pEList = pSelect->pEList;
@@ -2142,10 +2388,11 @@
/* Detach the ORDER BY clause from the compound SELECT */
p->pOrderBy = 0;
/* Store the results of the setup-query in Queue. */
pSetup->pNext = 0;
+ ExplainQueryPlan((pParse, 1, "SETUP"));
rc = sqlite3Select(pParse, pSetup, &destQueue);
pSetup->pNext = p;
if( rc ) goto end_of_recursive_query;
/* Find the next row in the Queue and output that row */
@@ -2176,10 +2423,11 @@
*/
if( p->selFlags & SF_Aggregate ){
sqlite3ErrorMsg(pParse, "recursive aggregate queries not supported");
}else{
p->pPrior = 0;
+ ExplainQueryPlan((pParse, 1, "RECURSIVE STEP"));
sqlite3Select(pParse, p, &destQueue);
assert( p->pPrior==0 );
p->pPrior = pSetup;
}
@@ -2221,30 +2469,28 @@
static int multiSelectValues(
Parse *pParse, /* Parsing context */
Select *p, /* The right-most of SELECTs to be coded */
SelectDest *pDest /* What to do with query results */
){
- Select *pPrior;
- Select *pRightmost = p;
int nRow = 1;
int rc = 0;
+ int bShowAll = p->pLimit==0;
assert( p->selFlags & SF_MultiValue );
do{
assert( p->selFlags & SF_Values );
assert( p->op==TK_ALL || (p->op==TK_SELECT && p->pPrior==0) );
assert( p->pNext==0 || p->pEList->nExpr==p->pNext->pEList->nExpr );
if( p->pPrior==0 ) break;
assert( p->pPrior->pNext==p );
p = p->pPrior;
- nRow++;
+ nRow += bShowAll;
}while(1);
+ ExplainQueryPlan((pParse, 0, "SCAN %d CONSTANT ROW%s", nRow,
+ nRow==1 ? "" : "S"));
while( p ){
- pPrior = p->pPrior;
- p->pPrior = 0;
- rc = sqlite3Select(pParse, p, pDest);
- p->pPrior = pPrior;
- if( rc || pRightmost->pLimit ) break;
+ selectInnerLoop(pParse, p, -1, 0, 0, pDest, 1, 1);
+ if( !bShowAll ) break;
p->nSelectRow = nRow;
p = p->pNext;
}
return rc;
}
@@ -2289,14 +2535,10 @@
Select *pPrior; /* Another SELECT immediately to our left */
Vdbe *v; /* Generate code to this VDBE */
SelectDest dest; /* Alternative data destination */
Select *pDelete = 0; /* Chain of simple selects to delete */
sqlite3 *db; /* Database connection */
-#ifndef SQLITE_OMIT_EXPLAIN
- int iSub1 = 0; /* EQP id of left-hand query */
- int iSub2 = 0; /* EQP id of right-hand query */
-#endif
/* Make sure there is no ORDER BY or LIMIT clause on prior SELECTs. Only
** the last (right-most) SELECT in the series may have an ORDER BY or LIMIT.
*/
assert( p && p->pPrior ); /* Calling function guarantees this much */
@@ -2343,221 +2585,235 @@
/* Compound SELECTs that have an ORDER BY clause are handled separately.
*/
if( p->pOrderBy ){
return multiSelectOrderBy(pParse, p, pDest);
- }else
-
- /* Generate code for the left and right SELECT statements.
- */
- switch( p->op ){
- case TK_ALL: {
- int addr = 0;
- int nLimit;
- assert( !pPrior->pLimit );
- pPrior->iLimit = p->iLimit;
- pPrior->iOffset = p->iOffset;
- pPrior->pLimit = p->pLimit;
- explainSetInteger(iSub1, pParse->iNextSelectId);
- rc = sqlite3Select(pParse, pPrior, &dest);
- p->pLimit = 0;
- if( rc ){
- goto multi_select_end;
- }
- p->pPrior = 0;
- p->iLimit = pPrior->iLimit;
- p->iOffset = pPrior->iOffset;
- if( p->iLimit ){
- addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v);
- VdbeComment((v, "Jump ahead if LIMIT reached"));
- if( p->iOffset ){
- sqlite3VdbeAddOp3(v, OP_OffsetLimit,
- p->iLimit, p->iOffset+1, p->iOffset);
- }
- }
- explainSetInteger(iSub2, pParse->iNextSelectId);
- rc = sqlite3Select(pParse, p, &dest);
- testcase( rc!=SQLITE_OK );
- pDelete = p->pPrior;
- p->pPrior = pPrior;
- p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow);
- if( pPrior->pLimit
- && sqlite3ExprIsInteger(pPrior->pLimit->pLeft, &nLimit)
- && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit)
- ){
- p->nSelectRow = sqlite3LogEst((u64)nLimit);
- }
- if( addr ){
- sqlite3VdbeJumpHere(v, addr);
- }
- break;
- }
- case TK_EXCEPT:
- case TK_UNION: {
- int unionTab; /* Cursor number of the temporary table holding result */
- u8 op = 0; /* One of the SRT_ operations to apply to self */
- int priorOp; /* The SRT_ operation to apply to prior selects */
- Expr *pLimit; /* Saved values of p->nLimit */
- int addr;
- SelectDest uniondest;
-
- testcase( p->op==TK_EXCEPT );
- testcase( p->op==TK_UNION );
- priorOp = SRT_Union;
- if( dest.eDest==priorOp ){
- /* We can reuse a temporary table generated by a SELECT to our
- ** right.
- */
- assert( p->pLimit==0 ); /* Not allowed on leftward elements */
- unionTab = dest.iSDParm;
- }else{
- /* We will need to create our own temporary table to hold the
- ** intermediate results.
- */
- unionTab = pParse->nTab++;
- assert( p->pOrderBy==0 );
- addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, unionTab, 0);
- assert( p->addrOpenEphm[0] == -1 );
- p->addrOpenEphm[0] = addr;
- findRightmost(p)->selFlags |= SF_UsesEphemeral;
- assert( p->pEList );
- }
-
- /* Code the SELECT statements to our left
- */
- assert( !pPrior->pOrderBy );
- sqlite3SelectDestInit(&uniondest, priorOp, unionTab);
- explainSetInteger(iSub1, pParse->iNextSelectId);
- rc = sqlite3Select(pParse, pPrior, &uniondest);
- if( rc ){
- goto multi_select_end;
- }
-
- /* Code the current SELECT statement
- */
- if( p->op==TK_EXCEPT ){
- op = SRT_Except;
- }else{
- assert( p->op==TK_UNION );
- op = SRT_Union;
- }
- p->pPrior = 0;
- pLimit = p->pLimit;
- p->pLimit = 0;
- uniondest.eDest = op;
- explainSetInteger(iSub2, pParse->iNextSelectId);
- rc = sqlite3Select(pParse, p, &uniondest);
- testcase( rc!=SQLITE_OK );
- /* Query flattening in sqlite3Select() might refill p->pOrderBy.
- ** Be sure to delete p->pOrderBy, therefore, to avoid a memory leak. */
- sqlite3ExprListDelete(db, p->pOrderBy);
- pDelete = p->pPrior;
- p->pPrior = pPrior;
- p->pOrderBy = 0;
- if( p->op==TK_UNION ){
- p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow);
- }
- sqlite3ExprDelete(db, p->pLimit);
- p->pLimit = pLimit;
- p->iLimit = 0;
- p->iOffset = 0;
-
- /* Convert the data in the temporary table into whatever form
- ** it is that we currently need.
- */
- assert( unionTab==dest.iSDParm || dest.eDest!=priorOp );
- if( dest.eDest!=priorOp ){
- int iCont, iBreak, iStart;
- assert( p->pEList );
- iBreak = sqlite3VdbeMakeLabel(v);
- iCont = sqlite3VdbeMakeLabel(v);
- computeLimitRegisters(pParse, p, iBreak);
- sqlite3VdbeAddOp2(v, OP_Rewind, unionTab, iBreak); VdbeCoverage(v);
- iStart = sqlite3VdbeCurrentAddr(v);
- selectInnerLoop(pParse, p, unionTab,
- 0, 0, &dest, iCont, iBreak);
- sqlite3VdbeResolveLabel(v, iCont);
- sqlite3VdbeAddOp2(v, OP_Next, unionTab, iStart); VdbeCoverage(v);
- sqlite3VdbeResolveLabel(v, iBreak);
- sqlite3VdbeAddOp2(v, OP_Close, unionTab, 0);
- }
- break;
- }
- default: assert( p->op==TK_INTERSECT ); {
- int tab1, tab2;
- int iCont, iBreak, iStart;
- Expr *pLimit;
- int addr;
- SelectDest intersectdest;
- int r1;
-
- /* INTERSECT is different from the others since it requires
- ** two temporary tables. Hence it has its own case. Begin
- ** by allocating the tables we will need.
- */
- tab1 = pParse->nTab++;
- tab2 = pParse->nTab++;
- assert( p->pOrderBy==0 );
-
- addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab1, 0);
- assert( p->addrOpenEphm[0] == -1 );
- p->addrOpenEphm[0] = addr;
- findRightmost(p)->selFlags |= SF_UsesEphemeral;
- assert( p->pEList );
-
- /* Code the SELECTs to our left into temporary table "tab1".
- */
- sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1);
- explainSetInteger(iSub1, pParse->iNextSelectId);
- rc = sqlite3Select(pParse, pPrior, &intersectdest);
- if( rc ){
- goto multi_select_end;
- }
-
- /* Code the current SELECT into temporary table "tab2"
- */
- addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab2, 0);
- assert( p->addrOpenEphm[1] == -1 );
- p->addrOpenEphm[1] = addr;
- p->pPrior = 0;
- pLimit = p->pLimit;
- p->pLimit = 0;
- intersectdest.iSDParm = tab2;
- explainSetInteger(iSub2, pParse->iNextSelectId);
- rc = sqlite3Select(pParse, p, &intersectdest);
- testcase( rc!=SQLITE_OK );
- pDelete = p->pPrior;
- p->pPrior = pPrior;
- if( p->nSelectRow>pPrior->nSelectRow ) p->nSelectRow = pPrior->nSelectRow;
- sqlite3ExprDelete(db, p->pLimit);
- p->pLimit = pLimit;
-
- /* Generate code to take the intersection of the two temporary
- ** tables.
- */
- assert( p->pEList );
- iBreak = sqlite3VdbeMakeLabel(v);
- iCont = sqlite3VdbeMakeLabel(v);
- computeLimitRegisters(pParse, p, iBreak);
- sqlite3VdbeAddOp2(v, OP_Rewind, tab1, iBreak); VdbeCoverage(v);
- r1 = sqlite3GetTempReg(pParse);
- iStart = sqlite3VdbeAddOp2(v, OP_RowData, tab1, r1);
- sqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0); VdbeCoverage(v);
- sqlite3ReleaseTempReg(pParse, r1);
- selectInnerLoop(pParse, p, tab1,
- 0, 0, &dest, iCont, iBreak);
- sqlite3VdbeResolveLabel(v, iCont);
- sqlite3VdbeAddOp2(v, OP_Next, tab1, iStart); VdbeCoverage(v);
- sqlite3VdbeResolveLabel(v, iBreak);
- sqlite3VdbeAddOp2(v, OP_Close, tab2, 0);
- sqlite3VdbeAddOp2(v, OP_Close, tab1, 0);
- break;
- }
- }
-
- explainComposite(pParse, p->op, iSub1, iSub2, p->op!=TK_ALL);
-
+ }else{
+
+#ifndef SQLITE_OMIT_EXPLAIN
+ if( pPrior->pPrior==0 ){
+ ExplainQueryPlan((pParse, 1, "COMPOUND QUERY"));
+ ExplainQueryPlan((pParse, 1, "LEFT-MOST SUBQUERY"));
+ }
+#endif
+
+ /* Generate code for the left and right SELECT statements.
+ */
+ switch( p->op ){
+ case TK_ALL: {
+ int addr = 0;
+ int nLimit;
+ assert( !pPrior->pLimit );
+ pPrior->iLimit = p->iLimit;
+ pPrior->iOffset = p->iOffset;
+ pPrior->pLimit = p->pLimit;
+ rc = sqlite3Select(pParse, pPrior, &dest);
+ p->pLimit = 0;
+ if( rc ){
+ goto multi_select_end;
+ }
+ p->pPrior = 0;
+ p->iLimit = pPrior->iLimit;
+ p->iOffset = pPrior->iOffset;
+ if( p->iLimit ){
+ addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v);
+ VdbeComment((v, "Jump ahead if LIMIT reached"));
+ if( p->iOffset ){
+ sqlite3VdbeAddOp3(v, OP_OffsetLimit,
+ p->iLimit, p->iOffset+1, p->iOffset);
+ }
+ }
+ ExplainQueryPlan((pParse, 1, "UNION ALL"));
+ rc = sqlite3Select(pParse, p, &dest);
+ testcase( rc!=SQLITE_OK );
+ pDelete = p->pPrior;
+ p->pPrior = pPrior;
+ p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow);
+ if( pPrior->pLimit
+ && sqlite3ExprIsInteger(pPrior->pLimit->pLeft, &nLimit)
+ && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit)
+ ){
+ p->nSelectRow = sqlite3LogEst((u64)nLimit);
+ }
+ if( addr ){
+ sqlite3VdbeJumpHere(v, addr);
+ }
+ break;
+ }
+ case TK_EXCEPT:
+ case TK_UNION: {
+ int unionTab; /* Cursor number of the temp table holding result */
+ u8 op = 0; /* One of the SRT_ operations to apply to self */
+ int priorOp; /* The SRT_ operation to apply to prior selects */
+ Expr *pLimit; /* Saved values of p->nLimit */
+ int addr;
+ SelectDest uniondest;
+
+ testcase( p->op==TK_EXCEPT );
+ testcase( p->op==TK_UNION );
+ priorOp = SRT_Union;
+ if( dest.eDest==priorOp ){
+ /* We can reuse a temporary table generated by a SELECT to our
+ ** right.
+ */
+ assert( p->pLimit==0 ); /* Not allowed on leftward elements */
+ unionTab = dest.iSDParm;
+ }else{
+ /* We will need to create our own temporary table to hold the
+ ** intermediate results.
+ */
+ unionTab = pParse->nTab++;
+ assert( p->pOrderBy==0 );
+ addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, unionTab, 0);
+ assert( p->addrOpenEphm[0] == -1 );
+ p->addrOpenEphm[0] = addr;
+ findRightmost(p)->selFlags |= SF_UsesEphemeral;
+ assert( p->pEList );
+ }
+
+ /* Code the SELECT statements to our left
+ */
+ assert( !pPrior->pOrderBy );
+ sqlite3SelectDestInit(&uniondest, priorOp, unionTab);
+ rc = sqlite3Select(pParse, pPrior, &uniondest);
+ if( rc ){
+ goto multi_select_end;
+ }
+
+ /* Code the current SELECT statement
+ */
+ if( p->op==TK_EXCEPT ){
+ op = SRT_Except;
+ }else{
+ assert( p->op==TK_UNION );
+ op = SRT_Union;
+ }
+ p->pPrior = 0;
+ pLimit = p->pLimit;
+ p->pLimit = 0;
+ uniondest.eDest = op;
+ ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE",
+ selectOpName(p->op)));
+ rc = sqlite3Select(pParse, p, &uniondest);
+ testcase( rc!=SQLITE_OK );
+ /* Query flattening in sqlite3Select() might refill p->pOrderBy.
+ ** Be sure to delete p->pOrderBy, therefore, to avoid a memory leak. */
+ sqlite3ExprListDelete(db, p->pOrderBy);
+ pDelete = p->pPrior;
+ p->pPrior = pPrior;
+ p->pOrderBy = 0;
+ if( p->op==TK_UNION ){
+ p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow);
+ }
+ sqlite3ExprDelete(db, p->pLimit);
+ p->pLimit = pLimit;
+ p->iLimit = 0;
+ p->iOffset = 0;
+
+ /* Convert the data in the temporary table into whatever form
+ ** it is that we currently need.
+ */
+ assert( unionTab==dest.iSDParm || dest.eDest!=priorOp );
+ if( dest.eDest!=priorOp ){
+ int iCont, iBreak, iStart;
+ assert( p->pEList );
+ iBreak = sqlite3VdbeMakeLabel(v);
+ iCont = sqlite3VdbeMakeLabel(v);
+ computeLimitRegisters(pParse, p, iBreak);
+ sqlite3VdbeAddOp2(v, OP_Rewind, unionTab, iBreak); VdbeCoverage(v);
+ iStart = sqlite3VdbeCurrentAddr(v);
+ selectInnerLoop(pParse, p, unionTab,
+ 0, 0, &dest, iCont, iBreak);
+ sqlite3VdbeResolveLabel(v, iCont);
+ sqlite3VdbeAddOp2(v, OP_Next, unionTab, iStart); VdbeCoverage(v);
+ sqlite3VdbeResolveLabel(v, iBreak);
+ sqlite3VdbeAddOp2(v, OP_Close, unionTab, 0);
+ }
+ break;
+ }
+ default: assert( p->op==TK_INTERSECT ); {
+ int tab1, tab2;
+ int iCont, iBreak, iStart;
+ Expr *pLimit;
+ int addr;
+ SelectDest intersectdest;
+ int r1;
+
+ /* INTERSECT is different from the others since it requires
+ ** two temporary tables. Hence it has its own case. Begin
+ ** by allocating the tables we will need.
+ */
+ tab1 = pParse->nTab++;
+ tab2 = pParse->nTab++;
+ assert( p->pOrderBy==0 );
+
+ addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab1, 0);
+ assert( p->addrOpenEphm[0] == -1 );
+ p->addrOpenEphm[0] = addr;
+ findRightmost(p)->selFlags |= SF_UsesEphemeral;
+ assert( p->pEList );
+
+ /* Code the SELECTs to our left into temporary table "tab1".
+ */
+ sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1);
+ rc = sqlite3Select(pParse, pPrior, &intersectdest);
+ if( rc ){
+ goto multi_select_end;
+ }
+
+ /* Code the current SELECT into temporary table "tab2"
+ */
+ addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab2, 0);
+ assert( p->addrOpenEphm[1] == -1 );
+ p->addrOpenEphm[1] = addr;
+ p->pPrior = 0;
+ pLimit = p->pLimit;
+ p->pLimit = 0;
+ intersectdest.iSDParm = tab2;
+ ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE",
+ selectOpName(p->op)));
+ rc = sqlite3Select(pParse, p, &intersectdest);
+ testcase( rc!=SQLITE_OK );
+ pDelete = p->pPrior;
+ p->pPrior = pPrior;
+ if( p->nSelectRow>pPrior->nSelectRow ){
+ p->nSelectRow = pPrior->nSelectRow;
+ }
+ sqlite3ExprDelete(db, p->pLimit);
+ p->pLimit = pLimit;
+
+ /* Generate code to take the intersection of the two temporary
+ ** tables.
+ */
+ assert( p->pEList );
+ iBreak = sqlite3VdbeMakeLabel(v);
+ iCont = sqlite3VdbeMakeLabel(v);
+ computeLimitRegisters(pParse, p, iBreak);
+ sqlite3VdbeAddOp2(v, OP_Rewind, tab1, iBreak); VdbeCoverage(v);
+ r1 = sqlite3GetTempReg(pParse);
+ iStart = sqlite3VdbeAddOp2(v, OP_RowData, tab1, r1);
+ sqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0);
+ VdbeCoverage(v);
+ sqlite3ReleaseTempReg(pParse, r1);
+ selectInnerLoop(pParse, p, tab1,
+ 0, 0, &dest, iCont, iBreak);
+ sqlite3VdbeResolveLabel(v, iCont);
+ sqlite3VdbeAddOp2(v, OP_Next, tab1, iStart); VdbeCoverage(v);
+ sqlite3VdbeResolveLabel(v, iBreak);
+ sqlite3VdbeAddOp2(v, OP_Close, tab2, 0);
+ sqlite3VdbeAddOp2(v, OP_Close, tab1, 0);
+ break;
+ }
+ }
+
+ #ifndef SQLITE_OMIT_EXPLAIN
+ if( p->pNext==0 ){
+ ExplainQueryPlanPop(pParse);
+ }
+ #endif
+ }
+
/* Compute collating sequences used by
** temporary tables needed to implement the compound select.
** Attach the KeyInfo structure to all temporary tables.
**
** This section is run by the right-most SELECT statement only.
@@ -2891,14 +3147,10 @@
KeyInfo *pKeyMerge; /* Comparison information for merging rows */
sqlite3 *db; /* Database connection */
ExprList *pOrderBy; /* The ORDER BY clause */
int nOrderBy; /* Number of terms in the ORDER BY clause */
int *aPermute; /* Mapping from ORDER BY terms to result set columns */
-#ifndef SQLITE_OMIT_EXPLAIN
- int iSub1; /* EQP id of left-hand query */
- int iSub2; /* EQP id of right-hand query */
-#endif
assert( p->pOrderBy!=0 );
assert( pKeyDup==0 ); /* "Managed" code needs this. Ticket #3382. */
db = pParse->db;
v = pParse->pVdbe;
@@ -3014,18 +3266,20 @@
regOutA = ++pParse->nMem;
regOutB = ++pParse->nMem;
sqlite3SelectDestInit(&destA, SRT_Coroutine, regAddrA);
sqlite3SelectDestInit(&destB, SRT_Coroutine, regAddrB);
+ ExplainQueryPlan((pParse, 1, "MERGE (%s)", selectOpName(p->op)));
+
/* Generate a coroutine to evaluate the SELECT statement to the
** left of the compound operator - the "A" select.
*/
addrSelectA = sqlite3VdbeCurrentAddr(v) + 1;
addr1 = sqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrA, 0, addrSelectA);
VdbeComment((v, "left SELECT"));
pPrior->iLimit = regLimitA;
- explainSetInteger(iSub1, pParse->iNextSelectId);
+ ExplainQueryPlan((pParse, 1, "LEFT"));
sqlite3Select(pParse, pPrior, &destA);
sqlite3VdbeEndCoroutine(v, regAddrA);
sqlite3VdbeJumpHere(v, addr1);
/* Generate a coroutine to evaluate the SELECT statement on
@@ -3036,11 +3290,11 @@
VdbeComment((v, "right SELECT"));
savedLimit = p->iLimit;
savedOffset = p->iOffset;
p->iLimit = regLimitB;
p->iOffset = 0;
- explainSetInteger(iSub2, pParse->iNextSelectId);
+ ExplainQueryPlan((pParse, 1, "RIGHT"));
sqlite3Select(pParse, p, &destB);
p->iLimit = savedLimit;
p->iOffset = savedOffset;
sqlite3VdbeEndCoroutine(v, regAddrB);
@@ -3148,11 +3402,11 @@
p->pPrior = pPrior;
pPrior->pNext = p;
/*** TBD: Insert subroutine calls to close cursors on incomplete
**** subqueries ****/
- explainComposite(pParse, p->op, iSub1, iSub2, 0);
+ ExplainQueryPlanPop(pParse);
return pParse->nErr!=0;
}
#endif
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
@@ -3635,13 +3889,12 @@
}else{
pNew->pPrior = pPrior;
if( pPrior ) pPrior->pNext = pNew;
pNew->pNext = p;
p->pPrior = pNew;
- SELECTTRACE(2,pParse,p,
- ("compound-subquery flattener creates %s.%p as peer\n",
- pNew->zSelName, pNew));
+ SELECTTRACE(2,pParse,p,("compound-subquery flattener"
+ " creates %s.%p as peer\n",pNew->zSelName, pNew));
}
if( db->mallocFailed ) return 1;
}
/* Begin flattening the iFrom-th entry of the FROM clause
@@ -4937,18 +5190,15 @@
Table *pTab, /* Table being queried */
Index *pIdx /* Index used to optimize scan, or NULL */
){
if( pParse->explain==2 ){
int bCover = (pIdx!=0 && (HasRowid(pTab) || !IsPrimaryKeyIndex(pIdx)));
- char *zEqp = sqlite3MPrintf(pParse->db, "SCAN TABLE %s%s%s",
+ sqlite3VdbeExplain(pParse, 0, "SCAN TABLE %s%s%s",
pTab->zName,
bCover ? " USING COVERING INDEX " : "",
bCover ? pIdx->zName : ""
);
- sqlite3VdbeAddOp4(
- pParse->pVdbe, OP_Explain, pParse->iSelectId, 0, 0, zEqp, P4_DYNAMIC
- );
}
}
#else
# define explainSimpleCount(a,b,c)
#endif
@@ -5157,23 +5407,19 @@
int iEnd; /* Address of the end of the query */
sqlite3 *db; /* The database connection */
ExprList *pMinMaxOrderBy = 0; /* Added ORDER BY for min/max queries */
u8 minMaxFlag; /* Flag for min/max queries */
-#ifndef SQLITE_OMIT_EXPLAIN
- int iRestoreSelectId = pParse->iSelectId;
- pParse->iSelectId = pParse->iNextSelectId++;
-#endif
-
db = pParse->db;
+ v = sqlite3GetVdbe(pParse);
if( p==0 || db->mallocFailed || pParse->nErr ){
return 1;
}
if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1;
memset(&sAggInfo, 0, sizeof(sAggInfo));
#if SELECTTRACE_ENABLED
- SELECTTRACE(1,pParse,p, ("begin processing:\n"));
+ SELECTTRACE(1,pParse,p, ("begin processing:\n", pParse->addrExplain));
if( sqlite3SelectTrace & 0x100 ){
sqlite3TreeViewSelect(0, p, 0);
}
#endif
@@ -5200,20 +5446,16 @@
goto select_end;
}
assert( p->pEList!=0 );
isAgg = (p->selFlags & SF_Aggregate)!=0;
#if SELECTTRACE_ENABLED
- if( sqlite3SelectTrace & 0x100 ){
- SELECTTRACE(0x100,pParse,p, ("after name resolution:\n"));
+ if( sqlite3SelectTrace & 0x104 ){
+ SELECTTRACE(0x104,pParse,p, ("after name resolution:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
#endif
- /* Get a pointer the VDBE under construction, allocating a new VDBE if one
- ** does not already exist */
- v = sqlite3GetVdbe(pParse);
- if( v==0 ) goto select_end;
if( pDest->eDest==SRT_Output ){
generateColumnNames(pParse, p);
}
/* Try to various optimizations (flattening subqueries, and strength
@@ -5302,14 +5544,17 @@
/* Handle compound SELECT statements using the separate multiSelect()
** procedure.
*/
if( p->pPrior ){
rc = multiSelect(pParse, p, pDest);
- explainSetInteger(pParse->iSelectId, iRestoreSelectId);
#if SELECTTRACE_ENABLED
- SELECTTRACE(1,pParse,p,("end compound-select processing\n"));
+ SELECTTRACE(0x1,pParse,p,("end compound-select processing\n"));
+ if( (sqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){
+ sqlite3TreeViewSelect(0, p, 0);
+ }
#endif
+ if( p->pNext==0 ) ExplainQueryPlanPop(pParse);
return rc;
}
#endif
/* For each term in the FROM clause, do two things:
@@ -5417,11 +5662,11 @@
pItem->regReturn = ++pParse->nMem;
sqlite3VdbeAddOp3(v, OP_InitCoroutine, pItem->regReturn, 0, addrTop);
VdbeComment((v, "%s", pItem->pTab->zName));
pItem->addrFillSub = addrTop;
sqlite3SelectDestInit(&dest, SRT_Coroutine, pItem->regReturn);
- explainSetInteger(pItem->iSelectId, (u8)pParse->iNextSelectId);
+ ExplainQueryPlan((pParse, 1, "CO-ROUTINE 0x%p", pSub));
sqlite3Select(pParse, pSub, &dest);
pItem->pTab->nRowLogEst = pSub->nSelectRow;
pItem->fg.viaCoroutine = 1;
pItem->regResult = dest.iSdst;
sqlite3VdbeEndCoroutine(v, pItem->regReturn);
@@ -5452,16 +5697,15 @@
VdbeNoopComment((v, "materialize \"%s\"", pItem->pTab->zName));
}
pPrior = isSelfJoinView(pTabList, pItem);
if( pPrior ){
sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor);
- explainSetInteger(pItem->iSelectId, pPrior->iSelectId);
assert( pPrior->pSelect!=0 );
pSub->nSelectRow = pPrior->pSelect->nSelectRow;
}else{
sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor);
- explainSetInteger(pItem->iSelectId, (u8)pParse->iNextSelectId);
+ ExplainQueryPlan((pParse, 1, "MATERIALIZE 0x%p", pSub));
sqlite3Select(pParse, pSub, &dest);
}
pItem->pTab->nRowLogEst = pSub->nSelectRow;
if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr);
retAddr = sqlite3VdbeAddOp1(v, OP_Return, pItem->regReturn);
@@ -5684,11 +5928,12 @@
** SELECT statement.
*/
memset(&sNC, 0, sizeof(sNC));
sNC.pParse = pParse;
sNC.pSrcList = pTabList;
- sNC.pAggInfo = &sAggInfo;
+ sNC.uNC.pAggInfo = &sAggInfo;
+ VVA_ONLY( sNC.ncFlags = NC_UAggInfo; )
sAggInfo.mnReg = pParse->nMem+1;
sAggInfo.nSortingColumn = pGroupBy ? pGroupBy->nExpr : 0;
sAggInfo.pGroupBy = pGroupBy;
sqlite3ExprAnalyzeAggList(&sNC, pEList);
sqlite3ExprAnalyzeAggList(&sNC, sSort.pOrderBy);
@@ -6073,10 +6318,11 @@
** and send them to the callback one by one.
*/
if( sSort.pOrderBy ){
explainTempTable(pParse,
sSort.nOBSat>0 ? "RIGHT PART OF ORDER BY":"ORDER BY");
+ assert( p->pEList==pEList );
generateSortTail(pParse, p, &sSort, pEList->nExpr, pDest);
}
/* Jump here to skip this query
*/
@@ -6088,14 +6334,17 @@
/* Control jumps to here if an error is encountered above, or upon
** successful coding of the SELECT.
*/
select_end:
- explainSetInteger(pParse->iSelectId, iRestoreSelectId);
sqlite3ExprListDelete(db, pMinMaxOrderBy);
sqlite3DbFree(db, sAggInfo.aCol);
sqlite3DbFree(db, sAggInfo.aFunc);
#if SELECTTRACE_ENABLED
- SELECTTRACE(1,pParse,p,("end processing\n"));
+ SELECTTRACE(0x1,pParse,p,("end processing\n"));
+ if( (sqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){
+ sqlite3TreeViewSelect(0, p, 0);
+ }
#endif
+ ExplainQueryPlanPop(pParse);
return rc;
}
Index: src/shell.c.in
==================================================================
--- src/shell.c.in
+++ src/shell.c.in
@@ -433,10 +433,16 @@
** includes string formatting (e.g. "%s").
*/
#if !defined(raw_printf)
# define raw_printf fprintf
#endif
+
+/* Indicate out-of-memory and exit. */
+static void shell_out_of_memory(void){
+ raw_printf(stderr,"Error: out of memory\n");
+ exit(1);
+}
/*
** Write I/O traces to the following stream.
*/
#ifdef SQLITE_ENABLE_IOTRACE
@@ -766,49 +772,16 @@
** that quoting is required.
**
** Return '"' if quoting is required. Return 0 if no quoting is required.
*/
static char quoteChar(const char *zName){
- /* All SQLite keywords, in alphabetical order */
- static const char *azKeywords[] = {
- "ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ANALYZE", "AND", "AS",
- "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "BY",
- "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT",
- "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT_DATE",
- "CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT", "DEFERRABLE",
- "DEFERRED", "DELETE", "DESC", "DETACH", "DISTINCT", "DROP", "EACH",
- "ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUSIVE", "EXISTS", "EXPLAIN",
- "FAIL", "FOR", "FOREIGN", "FROM", "FULL", "GLOB", "GROUP", "HAVING", "IF",
- "IGNORE", "IMMEDIATE", "IN", "INDEX", "INDEXED", "INITIALLY", "INNER",
- "INSERT", "INSTEAD", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY",
- "LEFT", "LIKE", "LIMIT", "MATCH", "NATURAL", "NO", "NOT", "NOTNULL",
- "NULL", "OF", "OFFSET", "ON", "OR", "ORDER", "OUTER", "PLAN", "PRAGMA",
- "PRIMARY", "QUERY", "RAISE", "RECURSIVE", "REFERENCES", "REGEXP",
- "REINDEX", "RELEASE", "RENAME", "REPLACE", "RESTRICT", "RIGHT",
- "ROLLBACK", "ROW", "SAVEPOINT", "SELECT", "SET", "TABLE", "TEMP",
- "TEMPORARY", "THEN", "TO", "TRANSACTION", "TRIGGER", "UNION", "UNIQUE",
- "UPDATE", "USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", "WHERE",
- "WITH", "WITHOUT",
- };
- int i, lwr, upr, mid, c;
+ int i;
if( !isalpha((unsigned char)zName[0]) && zName[0]!='_' ) return '"';
for(i=0; zName[i]; i++){
if( !isalnum((unsigned char)zName[i]) && zName[i]!='_' ) return '"';
}
- lwr = 0;
- upr = sizeof(azKeywords)/sizeof(azKeywords[0]) - 1;
- while( lwr<=upr ){
- mid = (lwr+upr)/2;
- c = sqlite3_stricmp(azKeywords[mid], zName);
- if( c==0 ) return '"';
- if( c<0 ){
- lwr = mid+1;
- }else{
- upr = mid-1;
- }
- }
- return 0;
+ return sqlite3_keyword_check(zName, i) ? '"' : 0;
}
/*
** Construct a fake object name and column list to describe the structure
** of the view, virtual table, or table valued function zSchema.zName.
@@ -1003,23 +976,43 @@
struct ExpertInfo {
sqlite3expert *pExpert;
int bVerbose;
};
+/* A single line in the EQP output */
+typedef struct EQPGraphRow EQPGraphRow;
+struct EQPGraphRow {
+ int iEqpId; /* ID for this row */
+ int iParentId; /* ID of the parent row */
+ EQPGraphRow *pNext; /* Next row in sequence */
+ char zText[1]; /* Text to display for this row */
+};
+
+/* All EQP output is collected into an instance of the following */
+typedef struct EQPGraph EQPGraph;
+struct EQPGraph {
+ EQPGraphRow *pRow; /* Linked list of all rows of the EQP output */
+ EQPGraphRow *pLast; /* Last element of the pRow list */
+ char zPrefix[100]; /* Graph prefix */
+};
+
/*
** State information about the database connection is contained in an
** instance of the following structure.
*/
typedef struct ShellState ShellState;
struct ShellState {
sqlite3 *db; /* The database */
u8 autoExplain; /* Automatically turn on .explain mode */
u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to seach SQL stmt */
+ u8 autoEQPtest; /* autoEQP is in test mode */
u8 statsOn; /* True to display memory stats before each finalize */
u8 scanstatsOn; /* True to display scan stats before each finalize */
u8 openMode; /* SHELL_OPEN_NORMAL, _APPENDVFS, or _ZIPFILE */
u8 doXdgOpen; /* Invoke start/open/xdg-open in output_reset() */
+ u8 nEqpLevel; /* Depth of the EQP output graph */
+ unsigned mEqpLines; /* Mask of veritical lines in the EQP output graph */
int outCount; /* Revert to stdout when reaching zero */
int cnt; /* Number of records displayed so far */
FILE *out; /* Write results here */
FILE *traceOut; /* Output for sqlite3_trace() */
int nErr; /* Number of errors seen */
@@ -1049,10 +1042,11 @@
sqlite3_stmt *pStmt; /* Current statement if any. */
FILE *pLog; /* Write log output here */
int *aiIndent; /* Array of indents used in MODE_Explain */
int nIndent; /* Size of array aiIndent[] */
int iIndent; /* Index of current op in aiIndent[] */
+ EQPGraph sGraph; /* Information for the graphical EXPLAIN QUERY PLAN */
#if defined(SQLITE_ENABLE_SESSION)
int nSession; /* Number of active sessions */
OpenSession aSession[4]; /* Array of sessions. [0] is in focus. */
#endif
ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */
@@ -1059,14 +1053,14 @@
};
/* Allowed values for ShellState.autoEQP
*/
-#define AUTOEQP_off 0
-#define AUTOEQP_on 1
-#define AUTOEQP_trigger 2
-#define AUTOEQP_full 3
+#define AUTOEQP_off 0 /* Automatic EXPLAIN QUERY PLAN is off */
+#define AUTOEQP_on 1 /* Automatic EQP is on */
+#define AUTOEQP_trigger 2 /* On and also show plans for triggers */
+#define AUTOEQP_full 3 /* Show full EXPLAIN */
/* Allowed values for ShellState.openMode
*/
#define SHELL_OPEN_UNSPEC 0 /* No open-mode specified */
#define SHELL_OPEN_NORMAL 1 /* Normal database file */
@@ -1105,10 +1099,11 @@
#define MODE_Tcl 7 /* Generate ANSI-C or TCL quoted elements */
#define MODE_Csv 8 /* Quote strings, numbers are plain */
#define MODE_Explain 9 /* Like MODE_Column, but do not truncate data */
#define MODE_Ascii 10 /* Use ASCII unit and record separators (0x1F/0x1E) */
#define MODE_Pretty 11 /* Pretty-print schemas */
+#define MODE_EQP 12 /* Converts EXPLAIN QUERY PLAN output into a graph */
static const char *modeDescr[] = {
"line",
"column",
"list",
@@ -1119,10 +1114,11 @@
"tcl",
"csv",
"explain",
"ascii",
"prettyprint",
+ "eqp"
};
/*
** These are the column/row/line separators used by the various
** import/export modes.
@@ -1667,11 +1663,97 @@
if( z[i]=='-' && z[i+1]=='-' ) return 1;
return 0;
}
return 1;
}
-
+
+/*
+** Add a new entry to the EXPLAIN QUERY PLAN data
+*/
+static void eqp_append(ShellState *p, int iEqpId, int p2, const char *zText){
+ EQPGraphRow *pNew;
+ int nText = strlen30(zText);
+ if( p->autoEQPtest ){
+ utf8_printf(p->out, "%d,%d,%s\n", iEqpId, p2, zText);
+ }
+ pNew = sqlite3_malloc64( sizeof(*pNew) + nText );
+ if( pNew==0 ) shell_out_of_memory();
+ pNew->iEqpId = iEqpId;
+ pNew->iParentId = p2;
+ memcpy(pNew->zText, zText, nText+1);
+ pNew->pNext = 0;
+ if( p->sGraph.pLast ){
+ p->sGraph.pLast->pNext = pNew;
+ }else{
+ p->sGraph.pRow = pNew;
+ }
+ p->sGraph.pLast = pNew;
+}
+
+/*
+** Free and reset the EXPLAIN QUERY PLAN data that has been collected
+** in p->sGraph.
+*/
+static void eqp_reset(ShellState *p){
+ EQPGraphRow *pRow, *pNext;
+ for(pRow = p->sGraph.pRow; pRow; pRow = pNext){
+ pNext = pRow->pNext;
+ sqlite3_free(pRow);
+ }
+ memset(&p->sGraph, 0, sizeof(p->sGraph));
+}
+
+/* Return the next EXPLAIN QUERY PLAN line with iEqpId that occurs after
+** pOld, or return the first such line if pOld is NULL
+*/
+static EQPGraphRow *eqp_next_row(ShellState *p, int iEqpId, EQPGraphRow *pOld){
+ EQPGraphRow *pRow = pOld ? pOld->pNext : p->sGraph.pRow;
+ while( pRow && pRow->iParentId!=iEqpId ) pRow = pRow->pNext;
+ return pRow;
+}
+
+/* Render a single level of the graph that has iEqpId as its parent. Called
+** recursively to render sublevels.
+*/
+static void eqp_render_level(ShellState *p, int iEqpId){
+ EQPGraphRow *pRow, *pNext;
+ int n = strlen30(p->sGraph.zPrefix);
+ char *z;
+ for(pRow = eqp_next_row(p, iEqpId, 0); pRow; pRow = pNext){
+ pNext = eqp_next_row(p, iEqpId, pRow);
+ z = pRow->zText;
+ utf8_printf(p->out, "%s%s%s\n", p->sGraph.zPrefix, pNext ? "|--" : "`--", z);
+ if( nsGraph.zPrefix)-7 ){
+ memcpy(&p->sGraph.zPrefix[n], pNext ? "| " : " ", 4);
+ eqp_render_level(p, pRow->iEqpId);
+ p->sGraph.zPrefix[n] = 0;
+ }
+ }
+}
+
+/*
+** Display and reset the EXPLAIN QUERY PLAN data
+*/
+static void eqp_render(ShellState *p){
+ EQPGraphRow *pRow = p->sGraph.pRow;
+ if( pRow ){
+ if( pRow->zText[0]=='-' ){
+ if( pRow->pNext==0 ){
+ eqp_reset(p);
+ return;
+ }
+ utf8_printf(p->out, "%s\n", pRow->zText+3);
+ p->sGraph.pRow = pRow->pNext;
+ sqlite3_free(pRow);
+ }else{
+ utf8_printf(p->out, "QUERY PLAN\n");
+ }
+ p->sGraph.zPrefix[0] = 0;
+ eqp_render_level(p, 0);
+ eqp_reset(p);
+ }
+}
/*
** This is the callback routine that the shell
** invokes for each row of a query result.
*/
@@ -2018,10 +2100,14 @@
utf8_printf(p->out,"%s",azArg[i] ? azArg[i] : p->nullValue);
}
utf8_printf(p->out, "%s", p->rowSeparator);
break;
}
+ case MODE_EQP: {
+ eqp_append(p, atoi(azArg[0]), atoi(azArg[1]), azArg[3]);
+ break;
+ }
}
return 0;
}
/*
@@ -2116,14 +2202,11 @@
if( zName==0 ) return;
cQuote = quoteChar(zName);
n = strlen30(zName);
if( cQuote ) n += n+2;
z = p->zDestTable = malloc( n+1 );
- if( z==0 ){
- raw_printf(stderr,"Error: out of memory\n");
- exit(1);
- }
+ if( z==0 ) shell_out_of_memory();
n = 0;
if( cQuote ) z[n++] = cQuote;
for(i=0; zName[i]; i++){
z[n++] = zName[i];
if( zName[i]==cQuote ) z[n++] = cQuote;
@@ -2859,15 +2942,17 @@
}
zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zStmtSql);
rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
if( rc==SQLITE_OK ){
while( sqlite3_step(pExplain)==SQLITE_ROW ){
- raw_printf(pArg->out,"--EQP-- %d,",sqlite3_column_int(pExplain, 0));
- raw_printf(pArg->out,"%d,", sqlite3_column_int(pExplain, 1));
- raw_printf(pArg->out,"%d,", sqlite3_column_int(pExplain, 2));
- utf8_printf(pArg->out,"%s\n", sqlite3_column_text(pExplain, 3));
+ const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3);
+ int iEqpId = sqlite3_column_int(pExplain, 0);
+ int iParentId = sqlite3_column_int(pExplain, 1);
+ if( zEQPLine[0]=='-' ) eqp_render(pArg);
+ eqp_append(pArg, iEqpId, iParentId, zEQPLine);
}
+ eqp_render(pArg);
}
sqlite3_finalize(pExplain);
sqlite3_free(zEQP);
if( pArg->autoEQP>=AUTOEQP_full ){
/* Also do an EXPLAIN for ".eqp full" mode */
@@ -2891,15 +2976,20 @@
restore_debug_trace_modes();
}
if( pArg ){
pArg->cMode = pArg->mode;
- if( pArg->autoExplain
- && sqlite3_column_count(pStmt)==8
- && sqlite3_strlike("EXPLAIN%", zStmtSql,0)==0
- ){
- pArg->cMode = MODE_Explain;
+ if( pArg->autoExplain ){
+ if( sqlite3_column_count(pStmt)==8
+ && sqlite3_strlike("EXPLAIN%", zStmtSql,0)==0
+ ){
+ pArg->cMode = MODE_Explain;
+ }
+ if( sqlite3_column_count(pStmt)==4
+ && sqlite3_strlike("EXPLAIN QUERY PLAN%", zStmtSql,0)==0 ){
+ pArg->cMode = MODE_EQP;
+ }
}
/* If the shell is currently in ".explain" mode, gather the extra
** data required to add indents to the output.*/
if( pArg->cMode==MODE_Explain ){
@@ -2907,10 +2997,11 @@
}
}
exec_prepared_stmt(pArg, pStmt);
explain_data_delete(pArg);
+ eqp_render(pArg);
/* print usage stats if stats on */
if( pArg && pArg->statsOn ){
display_stats(db, pArg, 0);
}
@@ -2984,14 +3075,11 @@
if( rc ) return 0;
while( sqlite3_step(pStmt)==SQLITE_ROW ){
if( nCol>=nAlloc-2 ){
nAlloc = nAlloc*2 + nCol + 10;
azCol = sqlite3_realloc(azCol, nAlloc*sizeof(azCol[0]));
- if( azCol==0 ){
- raw_printf(stderr, "Error: out of memory\n");
- exit(1);
- }
+ if( azCol==0 ) shell_out_of_memory();
}
azCol[++nCol] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1));
if( sqlite3_column_int(pStmt, 5) ){
nPK++;
if( nPK==1
@@ -3240,10 +3328,11 @@
".cd DIRECTORY Change the working directory to DIRECTORY\n"
".changes on|off Show number of rows changed by SQL\n"
".check GLOB Fail if output since .testcase does not match\n"
".clone NEWDB Clone data into NEWDB from the existing database\n"
".databases List names and files of attached databases\n"
+ ".dbconfig ?op? ?val? List or change sqlite3_db_config() options\n"
".dbinfo ?DB? Show status information about the database\n"
".dump ?TABLE? ... Dump the database in an SQL text format\n"
" If TABLE specified, only dump tables matching\n"
" LIKE pattern TABLE.\n"
".echo on|off Turn command echo on or off\n"
@@ -3482,11 +3571,10 @@
** Make sure the database is open. If it is not, then open it. If
** the database fails to open, print an error message and exit.
*/
static void open_db(ShellState *p, int keepAlive){
if( p->db==0 ){
- sqlite3_initialize();
if( p->openMode==SHELL_OPEN_UNSPEC && access(p->zDbFilename,0)==0 ){
p->openMode = (u8)deduceDatabaseType(p->zDbFilename, 0);
}
switch( p->openMode ){
case SHELL_OPEN_APPENDVFS: {
@@ -3785,14 +3873,11 @@
/* Append a single byte to z[] */
static void import_append_char(ImportCtx *p, int c){
if( p->n+1>=p->nAlloc ){
p->nAlloc += p->nAlloc + 100;
p->z = sqlite3_realloc64(p->z, p->nAlloc);
- if( p->z==0 ){
- raw_printf(stderr, "out of memory\n");
- exit(1);
- }
+ if( p->z==0 ) shell_out_of_memory();
}
p->z[p->n++] = (char)c;
}
/* Read a single field of CSV text. Compatible with rfc4180 and extended
@@ -3949,14 +4034,11 @@
zQuery);
goto end_data_xfer;
}
n = sqlite3_column_count(pQuery);
zInsert = sqlite3_malloc64(200 + nTable + n*3);
- if( zInsert==0 ){
- raw_printf(stderr, "out of memory\n");
- goto end_data_xfer;
- }
+ if( zInsert==0 ) shell_out_of_memory();
sqlite3_snprintf(200+nTable,zInsert,
"INSERT OR IGNORE INTO \"%s\" VALUES(?", zTable);
i = strlen30(zInsert);
for(j=1; j=3 && strncmp(azArg[0], "dbconfig", n)==0 ){
+ static const struct DbConfigChoices {const char *zName; int op;} aDbConfig[] = {
+ { "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY },
+ { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER },
+ { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
+ { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
+ { "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE },
+ { "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG },
+ { "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP },
+ { "reset_database", SQLITE_DBCONFIG_RESET_DATABASE },
+ };
+ int ii, v;
+ open_db(p, 0);
+ for(ii=0; ii1 && strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
+ if( nArg>=3 ){
+ sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0);
+ }
+ sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v);
+ utf8_printf(p->out, "%18s %s\n", aDbConfig[ii].zName, v ? "on" : "off");
+ if( nArg>1 ) break;
+ }
+ if( nArg>1 && ii==ArraySize(aDbConfig) ){
+ utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]);
+ utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n");
+ }
+ }else
+
+ if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){
rc = shell_dbinfo_command(p, nArg, azArg);
}else
if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
const char *zLike = 0;
@@ -5800,14 +5902,18 @@
}
}else
if( c=='e' && strncmp(azArg[0], "eqp", n)==0 ){
if( nArg==2 ){
+ p->autoEQPtest = 0;
if( strcmp(azArg[1],"full")==0 ){
p->autoEQP = AUTOEQP_full;
}else if( strcmp(azArg[1],"trigger")==0 ){
p->autoEQP = AUTOEQP_trigger;
+ }else if( strcmp(azArg[1],"test")==0 ){
+ p->autoEQP = AUTOEQP_on;
+ p->autoEQPtest = 1;
}else{
p->autoEQP = (u8)booleanValue(azArg[1]);
}
}else{
raw_printf(stderr, "Usage: .eqp off|on|trigger|full\n");
@@ -5994,13 +6100,12 @@
}
sCtx.cColSep = p->colSeparator[0];
sCtx.cRowSep = p->rowSeparator[0];
zSql = sqlite3_mprintf("SELECT * FROM %s", zTable);
if( zSql==0 ){
- raw_printf(stderr, "Error: out of memory\n");
xCloser(sCtx.in);
- return 1;
+ shell_out_of_memory();
}
nByte = strlen30(zSql);
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */
if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
@@ -6041,13 +6146,12 @@
sqlite3_finalize(pStmt);
pStmt = 0;
if( nCol==0 ) return 0; /* no columns, no error */
zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
if( zSql==0 ){
- raw_printf(stderr, "Error: out of memory\n");
xCloser(sCtx.in);
- return 1;
+ shell_out_of_memory();
}
sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\" VALUES(?", zTable);
j = strlen30(zSql);
for(i=1; idb, "main", 0, 1);
+ goto meta_command_exit;
+ }
zSql = sqlite3_mprintf("SELECT rootpage FROM sqlite_master"
" WHERE name='%q' AND type='index'", azArg[1]);
sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
if( sqlite3_step(pStmt)==SQLITE_ROW ){
@@ -7309,22 +7418,16 @@
while( sqlite3_step(pStmt)==SQLITE_ROW ){
if( nRow>=nAlloc ){
char **azNew;
int n2 = nAlloc*2 + 10;
azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2);
- if( azNew==0 ){
- rc = shellNomemError();
- break;
- }
+ if( azNew==0 ) shell_out_of_memory();
nAlloc = n2;
azResult = azNew;
}
azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
- if( 0==azResult[nRow] ){
- rc = shellNomemError();
- break;
- }
+ if( 0==azResult[nRow] ) shell_out_of_memory();
nRow++;
}
if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
rc = shellDatabaseError(p->db);
}
@@ -7381,13 +7484,10 @@
/*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS, "" },*/
/*{ "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, "" },*/
{ "byteorder", SQLITE_TESTCTRL_BYTEORDER, "" },
/*{ "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, "" }, */
{ "imposter", SQLITE_TESTCTRL_IMPOSTER, "SCHEMA ON/OFF ROOTPAGE"},
-#ifdef SQLITE_N_KEYWORD
- { "iskeyword", SQLITE_TESTCTRL_ISKEYWORD, "IDENTIFIER" },
-#endif
{ "localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,"BOOLEAN" },
{ "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT, "BOOLEAN" },
{ "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS, "DISABLE-MASK" },
#ifdef YYCOVERAGE
{ "parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE, "" },
@@ -7495,21 +7595,10 @@
rc2 = sqlite3_test_control(testctrl, opt);
isOk = 3;
}
break;
- /* sqlite3_test_control(int, char *) */
-#ifdef SQLITE_N_KEYWORD
- case SQLITE_TESTCTRL_ISKEYWORD:
- if( nArg==3 ){
- const char *opt = azArg[2];
- rc2 = sqlite3_test_control(testctrl, opt);
- isOk = 1;
- }
- break;
-#endif
-
case SQLITE_TESTCTRL_IMPOSTER:
if( nArg==5 ){
rc2 = sqlite3_test_control(testctrl, p->db,
azArg[2],
integerValue(azArg[3]),
@@ -7891,14 +7980,11 @@
}
nLine = strlen30(zLine);
if( nSql+nLine+2>=nAlloc ){
nAlloc = nSql+nLine+100;
zSql = realloc(zSql, nAlloc);
- if( zSql==0 ){
- raw_printf(stderr, "Error: out of memory\n");
- exit(1);
- }
+ if( zSql==0 ) shell_out_of_memory();
}
nSqlPrior = nSql;
if( nSql==0 ){
int i;
for(i=0; zLine[i] && IsSpace(zLine[i]); i++){}
@@ -8023,11 +8109,10 @@
if( home_dir==0 ){
raw_printf(stderr, "-- warning: cannot find home directory;"
" cannot read ~/.sqliterc\n");
return;
}
- sqlite3_initialize();
zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir);
sqliterc = zBuf;
}
in = fopen(sqliterc,"rb");
if( in ){
@@ -8074,10 +8159,13 @@
" -nullvalue TEXT set text string for NULL values. Default ''\n"
" -pagecache SIZE N use N slots of SZ bytes each for page cache memory\n"
" -quote set output mode to 'quote'\n"
" -readonly open the database read-only\n"
" -separator SEP set output column separator. Default: '|'\n"
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ " -sorterref SIZE sorter references threshold size\n"
+#endif
" -stats print memory stats before each finalize\n"
" -version show SQLite version\n"
" -vfs NAME use NAME as the default VFS\n"
#ifdef SQLITE_ENABLE_VFSTRACE
" -vfstrace enable tracing of all VFS calls\n"
@@ -8096,10 +8184,21 @@
}else{
raw_printf(stderr, "Use the -help option for additional information\n");
}
exit(1);
}
+
+/*
+** Internal check: Verify that the SQLite is uninitialized. Print a
+** error message if it is initialized.
+*/
+static void verify_uninitialized(void){
+ if( sqlite3_config(-1)==SQLITE_MISUSE ){
+ utf8_printf(stdout, "WARNING: attempt to configuration SQLite after"
+ " initialization.\n");
+ }
+}
/*
** Initialize the state information in data
*/
static void main_init(ShellState *data) {
@@ -8108,10 +8207,11 @@
data->autoExplain = 1;
memcpy(data->colSeparator,SEP_Column, 2);
memcpy(data->rowSeparator,SEP_Row, 2);
data->showHeader = 0;
data->shellFlgs = SHFLG_Lookaside;
+ verify_uninitialized();
sqlite3_config(SQLITE_CONFIG_URI, 1);
sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data);
sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> ");
sqlite3_snprintf(sizeof(continuePrompt), continuePrompt," ...> ");
@@ -8171,10 +8271,11 @@
int rc = 0;
int warnInmemoryDb = 0;
int readStdin = 1;
int nCmd = 0;
char **azCmd = 0;
+ const char *zVfs = 0; /* Value of -vfs command-line option */
setBinaryMode(stdin, 0);
setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
stdin_is_interactive = isatty(0);
stdout_is_console = isatty(1);
@@ -8195,27 +8296,18 @@
** memory that does not come from the SQLite memory allocator.
*/
#if !SQLITE_SHELL_IS_UTF8
sqlite3_initialize();
argv = malloc(sizeof(argv[0])*argc);
- if( argv==0 ){
- raw_printf(stderr, "out of memory\n");
- exit(1);
- }
+ if( argv==0 ) shell_out_of_memory();
for(i=0; iSQLITE_CONFIG_SORTERREF_SIZE
+** The SQLITE_CONFIG_SORTERREF_SIZE option accepts a single parameter
+** of type (int) - the new value of the sorter-reference size threshold.
+** Usually, when SQLite uses an external sort to order records according
+** to an ORDER BY clause, all fields required by the caller are present in the
+** sorted records. However, if SQLite determines based on the declared type
+** of a table column that its values are likely to be very large - larger
+** than the configured sorter-reference size threshold - then a reference
+** is stored in each sorted record and the required column values loaded
+** from the database as records are returned in sorted order. The default
+** value for this option is to never use this optimization. Specifying a
+** negative value for this option restores the default behaviour.
+** This option is only available if SQLite is compiled with the
+** [SQLITE_ENABLE_SORTER_REFERENCES] compile-time option.
**
*/
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */
#define SQLITE_CONFIG_SERIALIZED 3 /* nil */
@@ -1958,10 +1974,11 @@
#define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */
#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */
#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */
#define SQLITE_CONFIG_STMTJRNL_SPILL 26 /* int nByte */
#define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */
+#define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */
/*
** CAPI3REF: Database Connection Configuration Options
**
** These constants are the available integer configuration options that
@@ -2094,10 +2111,25 @@
** or negative to leave the setting unchanged.
** 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.
**
+**
+** SQLITE_DBCONFIG_RESET_DATABASE
+** Set the SQLITE_DBCONFIG_RESET_DATABASE flag and then run
+** [VACUUM] in order to reset a database back to an empty database
+** with no schema and no content. The following process works even for
+** a badly corrupted database file:
+**
+** - sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0);
+**
- [sqlite3_exec](db, "[VACUUM]", 0, 0, 0);
+**
- sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0);
+**
+** Because resetting a database is destructive and irreversible, the
+** process requires the use of this obscure API and multiple steps to help
+** ensure that it does not happen by accident.
+**
**
*/
#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */
#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */
#define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */
@@ -2105,11 +2137,12 @@
#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_ENABLE_QPSG 1007 /* int int* */
#define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */
-#define SQLITE_DBCONFIG_MAX 1008 /* Largest DBCONFIG */
+#define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */
+#define SQLITE_DBCONFIG_MAX 1009 /* Largest DBCONFIG */
/*
** CAPI3REF: Enable Or Disable Extended Result Codes
** METHOD: sqlite3
**
@@ -5491,10 +5524,45 @@
** made NULL or made to point to memory obtained from [sqlite3_malloc]
** or else the use of the [data_store_directory pragma] should be avoided.
*/
SQLITE_EXTERN char *sqlite3_data_directory;
+/*
+** CAPI3REF: Win32 Specific Interface
+**
+** These interfaces are available only on Windows. The
+** [sqlite3_win32_set_directory] interface is used to set the value associated
+** with the [sqlite3_temp_directory] or [sqlite3_data_directory] variable, to
+** zValue, depending on the value of the type parameter. The zValue parameter
+** should be NULL to cause the previous value to be freed via [sqlite3_free];
+** a non-NULL value will be copied into memory obtained from [sqlite3_malloc]
+** prior to being used. The [sqlite3_win32_set_directory] interface returns
+** [SQLITE_OK] to indicate success, [SQLITE_ERROR] if the type is unsupported,
+** or [SQLITE_NOMEM] if memory could not be allocated. The value of the
+** [sqlite3_data_directory] variable is intended to act as a replacement for
+** the current directory on the sub-platforms of Win32 where that concept is
+** not present, e.g. WinRT and UWP. The [sqlite3_win32_set_directory8] and
+** [sqlite3_win32_set_directory16] interfaces behave exactly the same as the
+** sqlite3_win32_set_directory interface except the string parameter must be
+** UTF-8 or UTF-16, respectively.
+*/
+int sqlite3_win32_set_directory(
+ unsigned long type, /* Identifier for directory being set or reset */
+ void *zValue /* New value for directory being set or reset */
+);
+int sqlite3_win32_set_directory8(unsigned long type, const char *zValue);
+int sqlite3_win32_set_directory16(unsigned long type, const void *zValue);
+
+/*
+** CAPI3REF: Win32 Directory Types
+**
+** These macros are only available on Windows. They define the allowed values
+** for the type argument to the [sqlite3_win32_set_directory] interface.
+*/
+#define SQLITE_WIN32_DATA_DIRECTORY_TYPE 1
+#define SQLITE_WIN32_TEMP_DIRECTORY_TYPE 2
+
/*
** CAPI3REF: Test For Auto-Commit Mode
** KEYWORDS: {autocommit mode}
** METHOD: sqlite3
**
@@ -6998,11 +7066,11 @@
#define SQLITE_TESTCTRL_PENDING_BYTE 11
#define SQLITE_TESTCTRL_ASSERT 12
#define SQLITE_TESTCTRL_ALWAYS 13
#define SQLITE_TESTCTRL_RESERVE 14
#define SQLITE_TESTCTRL_OPTIMIZATIONS 15
-#define SQLITE_TESTCTRL_ISKEYWORD 16
+#define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */
#define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */
#define SQLITE_TESTCTRL_LOCALTIME_FAULT 18
#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */
#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19
#define SQLITE_TESTCTRL_NEVER_CORRUPT 20
@@ -7012,10 +7080,61 @@
#define SQLITE_TESTCTRL_SORTER_MMAP 24
#define SQLITE_TESTCTRL_IMPOSTER 25
#define SQLITE_TESTCTRL_PARSER_COVERAGE 26
#define SQLITE_TESTCTRL_LAST 26 /* Largest TESTCTRL */
+/*
+** CAPI3REF: SQL Keyword Checking
+**
+** These routines provide access to the set of SQL language keywords
+** recognized by SQLite. Applications can uses these routines to determine
+** whether or not a specific identifier needs to be escaped (for example,
+** by enclosing in double-quotes) so as not to confuse the parser.
+**
+** The sqlite3_keyword_count() interface returns the number of distinct
+** keywords understood by SQLite.
+**
+** The sqlite3_keyword_name(N,Z,L) interface finds the N-th keyword and
+** makes *Z point to that keyword expressed as UTF8 and writes the number
+** of bytes in the keyword into *L. The string that *Z points to is not
+** zero-terminated. The sqlite3_keyword_name(N,Z,L) routine returns
+** SQLITE_OK if N is within bounds and SQLITE_ERROR if not. If either Z
+** or L are NULL or invalid pointers then calls to
+** sqlite3_keyword_name(N,Z,L) result in undefined behavior.
+**
+** The sqlite3_keyword_check(Z,L) interface checks to see whether or not
+** the L-byte UTF8 identifier that Z points to is a keyword, returning non-zero
+** if it is and zero if not.
+**
+** The parser used by SQLite is forgiving. It is often possible to use
+** a keyword as an identifier as long as such use does not result in a
+** parsing ambiguity. For example, the statement
+** "CREATE TABLE BEGIN(REPLACE,PRAGMA,END);" is accepted by SQLite, and
+** creates a new table named "BEGIN" with three columns named
+** "REPLACE", "PRAGMA", and "END". Nevertheless, best practice is to avoid
+** using keywords as identifiers. Common techniques used to avoid keyword
+** name collisions include:
+**
+** - Put all indentifier names inside double-quotes. This is the official
+** SQL way to escape identifier names.
+**
- Put identifier names inside [...]. This is not standard SQL,
+** but it is what SQL Server does and so lots of programmers use this
+** technique.
+**
- Begin every identifier with the letter "Z" as no SQL keywords start
+** with "Z".
+**
- Include a digit somewhere in every identifier name.
+**
+**
+** Note that the number of keywords understood by SQLite can depend on
+** compile-time options. For example, "VACUUM" is not a keyword if
+** SQLite is compiled with the [-DSQLITE_OMIT_VACUUM] option. Also,
+** new keywords may be added to future releases of SQLite.
+*/
+int sqlite3_keyword_count(void);
+int sqlite3_keyword_name(int,const char**,int*);
+int sqlite3_keyword_check(const char*,int);
+
/*
** CAPI3REF: SQLite Runtime Status
**
** ^These interfaces are used to retrieve runtime status information
** about the performance of SQLite, and optionally to reset various
Index: src/sqliteInt.h
==================================================================
--- src/sqliteInt.h
+++ src/sqliteInt.h
@@ -635,10 +635,17 @@
*/
#ifndef SQLITE_DEFAULT_PCACHE_INITSZ
# define SQLITE_DEFAULT_PCACHE_INITSZ 20
#endif
+/*
+** Default value for the SQLITE_CONFIG_SORTERREF_SIZE option.
+*/
+#ifndef SQLITE_DEFAULT_SORTERREF_SIZE
+# define SQLITE_DEFAULT_SORTERREF_SIZE 0x7fffffff
+#endif
+
/*
** The compile-time options SQLITE_MMAP_READWRITE and
** SQLITE_ENABLE_BATCH_ATOMIC_WRITE are not compatible with one another.
** You must choose one or the other (or neither) but not both.
*/
@@ -1093,10 +1100,11 @@
typedef struct TreeView TreeView;
typedef struct Trigger Trigger;
typedef struct TriggerPrg TriggerPrg;
typedef struct TriggerStep TriggerStep;
typedef struct UnpackedRecord UnpackedRecord;
+typedef struct Upsert Upsert;
typedef struct VTable VTable;
typedef struct VtabCtx VtabCtx;
typedef struct Walker Walker;
typedef struct WhereInfo WhereInfo;
typedef struct With With;
@@ -1352,11 +1360,11 @@
signed char nextAutovac; /* Autovac setting after VACUUM if >=0 */
u8 suppressErr; /* Do not issue error messages if true */
u8 vtabOnConflict; /* Value to return for s3_vtab_on_conflict() */
u8 isTransactionSavepoint; /* True if the outermost savepoint is a TS */
u8 mTrace; /* zero or more SQLITE_TRACE flags */
- u8 skipBtreeMutex; /* True if no shared-cache backends */
+ u8 noSharedCache; /* True if no shared-cache backends */
u8 nSqlExec; /* Number of pending OP_SqlExec opcodes */
int nextPagesize; /* Pagesize after VACUUM if >0 */
u32 magic; /* Magic number for detect library misuse */
int nChange; /* Value returned by sqlite3_changes() */
int nTotalChange; /* Value returned by sqlite3_total_changes() */
@@ -1496,10 +1504,11 @@
#define SQLITE_QueryOnly 0x00100000 /* Disable database changes */
#define SQLITE_CellSizeCk 0x00200000 /* Check btree cell sizes on load */
#define SQLITE_Fts3Tokenizer 0x00400000 /* Enable fts3_tokenizer(2) */
#define SQLITE_EnableQPSG 0x00800000 /* Query Planner Stability Guarantee*/
#define SQLITE_TriggerEQP 0x01000000 /* Show trigger EXPLAIN QUERY PLAN */
+#define SQLITE_ResetDatabase 0x02000000 /* Reset the database */
/* Flags used only if debugging */
#ifdef SQLITE_DEBUG
#define SQLITE_SqlTrace 0x08000000 /* Debug print SQL as it executes */
#define SQLITE_VdbeListing 0x10000000 /* Debug listings of VDBE programs */
@@ -1512,10 +1521,11 @@
** Allowed values for sqlite3.mDbFlags
*/
#define DBFLAG_SchemaChange 0x0001 /* Uncommitted Hash table changes */
#define DBFLAG_PreferBuiltin 0x0002 /* Preference to built-in funcs */
#define DBFLAG_Vacuum 0x0004 /* Currently in a VACUUM */
+#define DBFLAG_SchemaKnownOk 0x0008 /* Schema is known to be valid */
/*
** Bits of the sqlite3.dbOptFlags field that are used by the
** sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface to
** selectively disable various optimizations.
@@ -1757,10 +1767,11 @@
*/
#define COLFLAG_PRIMKEY 0x0001 /* Column is part of the primary key */
#define COLFLAG_HIDDEN 0x0002 /* A hidden column in a virtual table */
#define COLFLAG_HASTYPE 0x0004 /* Type name follows column name */
#define COLFLAG_UNIQUE 0x0008 /* Column def contains "UNIQUE" or "PK" */
+#define COLFLAG_SORTERREF 0x0010 /* Use sorter-refs with this column */
/*
** A "Collating Sequence" is defined by an instance of the following
** structure. Conceptually, a collating sequence consists of a name and
** a comparison routine that defines the order of that sequence.
@@ -2044,17 +2055,16 @@
#define OE_Rollback 1 /* Fail the operation and rollback the transaction */
#define OE_Abort 2 /* Back out changes but do no rollback transaction */
#define OE_Fail 3 /* Stop the operation but leave all prior changes */
#define OE_Ignore 4 /* Ignore the error. Do not do the INSERT or UPDATE */
#define OE_Replace 5 /* Delete existing record, then do INSERT or UPDATE */
-
-#define OE_Restrict 6 /* OE_Abort for IMMEDIATE, OE_Rollback for DEFERRED */
-#define OE_SetNull 7 /* Set the foreign key value to NULL */
-#define OE_SetDflt 8 /* Set the foreign key value to its default */
-#define OE_Cascade 9 /* Cascade the changes */
-
-#define OE_Default 10 /* Do whatever the default action is */
+#define OE_Update 6 /* Process as a DO UPDATE in an upsert */
+#define OE_Restrict 7 /* OE_Abort for IMMEDIATE, OE_Rollback for DEFERRED */
+#define OE_SetNull 8 /* Set the foreign key value to NULL */
+#define OE_SetDflt 9 /* Set the foreign key value to its default */
+#define OE_Cascade 10 /* Cascade the changes */
+#define OE_Default 11 /* Do whatever the default action is */
/*
** An instance of the following structure is passed as the first
** argument to sqlite3VdbeKeyCompare and is used to control the
@@ -2497,10 +2507,11 @@
char *zSpan; /* Original text of the expression */
u8 sortOrder; /* 1 for DESC or 0 for ASC */
unsigned done :1; /* A flag to indicate when processing is finished */
unsigned bSpanIsTab :1; /* zSpan holds DB.TABLE.COLUMN */
unsigned reusable :1; /* Constant expression is reusable */
+ unsigned bSorterRef :1; /* Defer evaluation until after sorting */
union {
struct {
u16 iOrderByCol; /* For ORDER BY, column number in result set */
u16 iAlias; /* Index into Parse.aAlias[] for zName */
} x;
@@ -2596,13 +2607,10 @@
unsigned isTabFunc :1; /* True if table-valued-function syntax */
unsigned isCorrelated :1; /* True if sub-query is correlated */
unsigned viaCoroutine :1; /* Implemented as a co-routine */
unsigned isRecursive :1; /* True for recursive reference in WITH */
} fg;
-#ifndef SQLITE_OMIT_EXPLAIN
- u8 iSelectId; /* If pSelect!=0, the id of the sub-select in EQP */
-#endif
int iCursor; /* The VDBE cursor number used to access this table */
Expr *pOn; /* The ON clause of a join */
IdList *pUsing; /* The USING clause of a join */
Bitmask colUsed; /* Bit N (1<zTail = zSql;
assert( pzErrMsg!=0 );
/* sqlite3ParserTrace(stdout, "parser: "); */
#ifdef sqlite3Parser_ENGINEALWAYSONSTACK
pEngine = &sEngine;
- sqlite3ParserInit(pEngine);
+ sqlite3ParserInit(pEngine, pParse);
#else
- pEngine = sqlite3ParserAlloc(sqlite3Malloc);
+ pEngine = sqlite3ParserAlloc(sqlite3Malloc, pParse);
if( pEngine==0 ){
sqlite3OomFault(db);
return SQLITE_NOMEM_BKPT;
}
#endif
@@ -540,11 +540,11 @@
}
zSql += n;
}else{
pParse->sLastToken.z = zSql;
pParse->sLastToken.n = n;
- sqlite3Parser(pEngine, tokenType, pParse->sLastToken, pParse);
+ sqlite3Parser(pEngine, tokenType, pParse->sLastToken);
lastTokenParsed = tokenType;
zSql += n;
if( pParse->rc!=SQLITE_OK || db->mallocFailed ) break;
}
}
Index: src/treeview.c
==================================================================
--- src/treeview.c
+++ src/treeview.c
@@ -60,15 +60,17 @@
for(i=0; iiLevel && ibLine)-1; i++){
sqlite3StrAccumAppend(&acc, p->bLine[i] ? "| " : " ", 4);
}
sqlite3StrAccumAppend(&acc, p->bLine[i] ? "|-- " : "'-- ", 4);
}
- va_start(ap, zFormat);
- sqlite3VXPrintf(&acc, zFormat, ap);
- va_end(ap);
- assert( acc.nChar>0 );
- if( zBuf[acc.nChar-1]!='\n' ) sqlite3StrAccumAppend(&acc, "\n", 1);
+ if( zFormat!=0 ){
+ va_start(ap, zFormat);
+ sqlite3VXPrintf(&acc, zFormat, ap);
+ va_end(ap);
+ assert( acc.nChar>0 );
+ sqlite3StrAccumAppend(&acc, "\n", 1);
+ }
sqlite3StrAccumFinish(&acc);
fprintf(stdout,"%s", zBuf);
fflush(stdout);
}
@@ -534,20 +536,25 @@
int i;
sqlite3TreeViewLine(pView, "%s", zLabel);
for(i=0; inExpr; i++){
int j = pList->a[i].u.x.iOrderByCol;
char *zName = pList->a[i].zName;
+ int moreToFollow = inExpr - 1;
if( j || zName ){
- sqlite3TreeViewPush(pView, 0);
- }
- if( zName ){
- sqlite3TreeViewLine(pView, "AS %s", zName);
- }
- if( j ){
- sqlite3TreeViewLine(pView, "iOrderByCol=%d", j);
- }
- sqlite3TreeViewExpr(pView, pList->a[i].pExpr, inExpr-1);
+ sqlite3TreeViewPush(pView, moreToFollow);
+ moreToFollow = 0;
+ sqlite3TreeViewLine(pView, 0);
+ if( zName ){
+ fprintf(stdout, "AS %s ", zName);
+ }
+ if( j ){
+ fprintf(stdout, "iOrderByCol=%d", j);
+ }
+ fprintf(stdout, "\n");
+ fflush(stdout);
+ }
+ sqlite3TreeViewExpr(pView, pList->a[i].pExpr, moreToFollow);
if( j || zName ){
sqlite3TreeViewPop(pView);
}
}
}
Index: src/trigger.c
==================================================================
--- src/trigger.c
+++ src/trigger.c
@@ -23,10 +23,11 @@
sqlite3ExprDelete(db, pTmp->pWhere);
sqlite3ExprListDelete(db, pTmp->pExprList);
sqlite3SelectDelete(db, pTmp->pSelect);
sqlite3IdListDelete(db, pTmp->pIdList);
+ sqlite3UpsertDelete(db, pTmp->pUpsert);
sqlite3DbFree(db, pTmp->zSpan);
sqlite3DbFree(db, pTmp);
}
}
@@ -414,10 +415,11 @@
sqlite3 *db, /* The database connection */
Token *pTableName, /* Name of the table into which we insert */
IdList *pColumn, /* List of columns in pTableName to insert into */
Select *pSelect, /* A SELECT statement that supplies values */
u8 orconf, /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */
+ Upsert *pUpsert, /* ON CONFLICT clauses for upsert */
const char *zStart, /* Start of SQL text */
const char *zEnd /* End of SQL text */
){
TriggerStep *pTriggerStep;
@@ -425,13 +427,17 @@
pTriggerStep = triggerStepAllocate(db, TK_INSERT, pTableName, zStart, zEnd);
if( pTriggerStep ){
pTriggerStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE);
pTriggerStep->pIdList = pColumn;
+ pTriggerStep->pUpsert = pUpsert;
pTriggerStep->orconf = orconf;
}else{
+ testcase( pColumn );
sqlite3IdListDelete(db, pColumn);
+ testcase( pUpsert );
+ sqlite3UpsertDelete(db, pUpsert);
}
sqlite3SelectDelete(db, pSelect);
return pTriggerStep;
}
@@ -744,20 +750,21 @@
case TK_UPDATE: {
sqlite3Update(pParse,
targetSrcList(pParse, pStep),
sqlite3ExprListDup(db, pStep->pExprList, 0),
sqlite3ExprDup(db, pStep->pWhere, 0),
- pParse->eOrconf, 0, 0
+ pParse->eOrconf, 0, 0, 0
);
break;
}
case TK_INSERT: {
sqlite3Insert(pParse,
targetSrcList(pParse, pStep),
sqlite3SelectDup(db, pStep->pSelect, 0),
sqlite3IdListDup(db, pStep->pIdList),
- pParse->eOrconf
+ pParse->eOrconf,
+ sqlite3UpsertDup(db, pStep->pUpsert)
);
break;
}
case TK_DELETE: {
sqlite3DeleteFrom(pParse,
Index: src/update.c
==================================================================
--- src/update.c
+++ src/update.c
@@ -91,11 +91,12 @@
SrcList *pTabList, /* The table in which we should change things */
ExprList *pChanges, /* Things to be changed */
Expr *pWhere, /* The WHERE clause. May be null */
int onError, /* How to handle constraint errors */
ExprList *pOrderBy, /* ORDER BY clause. May be null */
- Expr *pLimit /* LIMIT clause. May be null */
+ Expr *pLimit, /* LIMIT clause. May be null */
+ Upsert *pUpsert /* ON CONFLICT clause, or null */
){
int i, j; /* Loop counters */
Table *pTab; /* The table to be updated */
int addrTop = 0; /* VDBE instruction address of the start of the loop */
WhereInfo *pWInfo; /* Information about the WHERE clause */
@@ -198,20 +199,27 @@
/* Allocate a cursors for the main database table and for all indices.
** The index cursors might not be used, but if they are used they
** need to occur right after the database cursor. So go ahead and
** allocate enough space, just in case.
*/
- pTabList->a[0].iCursor = iBaseCur = iDataCur = pParse->nTab++;
+ iBaseCur = iDataCur = pParse->nTab++;
iIdxCur = iDataCur+1;
pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab);
+ testcase( pPk!=0 && pPk!=pTab->pIndex );
for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){
- if( IsPrimaryKeyIndex(pIdx) && pPk!=0 ){
+ if( pPk==pIdx ){
iDataCur = pParse->nTab;
- pTabList->a[0].iCursor = iDataCur;
}
pParse->nTab++;
}
+ if( pUpsert ){
+ /* On an UPSERT, reuse the same cursors already opened by INSERT */
+ iDataCur = pUpsert->iDataCur;
+ iIdxCur = pUpsert->iIdxCur;
+ pParse->nTab = iBaseCur;
+ }
+ pTabList->a[0].iCursor = iDataCur;
/* Allocate space for aXRef[], aRegIdx[], and aToOpen[].
** Initialize aXRef[] and aToOpen[] to their default values.
*/
aXRef = sqlite3DbMallocRawNN(db, sizeof(int) * (pTab->nCol+nIdx) + nIdx+2 );
@@ -224,10 +232,12 @@
/* Initialize the name-context */
memset(&sNC, 0, sizeof(sNC));
sNC.pParse = pParse;
sNC.pSrcList = pTabList;
+ sNC.uNC.pUpsert = pUpsert;
+ sNC.ncFlags = NC_UUpsert;
/* Resolve the column names in all the expressions of the
** of the UPDATE statement. Also find the column index
** for each column to be updated in the pChanges array. For each
** column to be updated, make sure we have authorization to change
@@ -327,11 +337,11 @@
/* Begin generating code. */
v = sqlite3GetVdbe(pParse);
if( v==0 ) goto update_cleanup;
if( pParse->nested==0 ) sqlite3VdbeCountChanges(v);
- sqlite3BeginWriteOperation(pParse, 1, iDb);
+ sqlite3BeginWriteOperation(pParse, pTrigger || hasFK, iDb);
/* Allocate required registers. */
if( !IsVirtual(pTab) ){
regRowSet = ++pParse->nMem;
regOldRowid = regNewRowid = ++pParse->nMem;
@@ -378,12 +388,20 @@
pWhere, onError);
goto update_cleanup;
}
#endif
- /* Initialize the count of updated rows */
- if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab ){
+ /* Jump to labelBreak to abandon further processing of this UPDATE */
+ labelContinue = labelBreak = sqlite3VdbeMakeLabel(v);
+
+ /* Not an UPSERT. Normal processing. Begin by
+ ** initialize the count of updated rows */
+ if( (db->flags&SQLITE_CountRows)!=0
+ && !pParse->pTriggerTab
+ && !pParse->nested
+ && pUpsert==0
+ ){
regRowCount = ++pParse->nMem;
sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
}
if( HasRowid(pTab) ){
@@ -392,50 +410,65 @@
assert( pPk!=0 );
nPk = pPk->nKeyCol;
iPk = pParse->nMem+1;
pParse->nMem += nPk;
regKey = ++pParse->nMem;
- iEph = pParse->nTab++;
-
- sqlite3VdbeAddOp3(v, OP_Null, 0, iPk, iPk+nPk-1);
- addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nPk);
- sqlite3VdbeSetP4KeyInfo(pParse, pPk);
- }
-
- /* Begin the database scan.
- **
- ** Do not consider a single-pass strategy for a multi-row update if
- ** there are any triggers or foreign keys to process, or rows may
- ** be deleted as a result of REPLACE conflict handling. Any of these
- ** things might disturb a cursor being used to scan through the table
- ** or index, causing a single-pass approach to malfunction. */
- flags = WHERE_ONEPASS_DESIRED|WHERE_SEEK_UNIQ_TABLE;
- if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){
- flags |= WHERE_ONEPASS_MULTIROW;
- }
- pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags, iIdxCur);
- if( pWInfo==0 ) goto update_cleanup;
-
- /* A one-pass strategy that might update more than one row may not
- ** be used if any column of the index used for the scan is being
- ** updated. Otherwise, if there is an index on "b", statements like
- ** the following could create an infinite loop:
- **
- ** UPDATE t1 SET b=b+1 WHERE b>?
- **
- ** Fall back to ONEPASS_OFF if where.c has selected a ONEPASS_MULTI
- ** strategy that uses an index for which one or more columns are being
- ** updated. */
- eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
- if( eOnePass==ONEPASS_MULTI ){
- int iCur = aiCurOnePass[1];
- if( iCur>=0 && iCur!=iDataCur && aToOpen[iCur-iBaseCur] ){
- eOnePass = ONEPASS_OFF;
- }
- assert( iCur!=iDataCur || !HasRowid(pTab) );
- }
-
+ if( pUpsert==0 ){
+ iEph = pParse->nTab++;
+ sqlite3VdbeAddOp3(v, OP_Null, 0, iPk, iPk+nPk-1);
+ addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nPk);
+ sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+ }
+ }
+
+ if( pUpsert ){
+ /* If this is an UPSERT, then all cursors have already been opened by
+ ** the outer INSERT and the data cursor should be pointing at the row
+ ** that is to be updated. So bypass the code that searches for the
+ ** row(s) to be updated.
+ */
+ pWInfo = 0;
+ eOnePass = ONEPASS_SINGLE;
+ sqlite3ExprIfFalse(pParse, pWhere, labelBreak, SQLITE_JUMPIFNULL);
+ }else{
+ /* Begin the database scan.
+ **
+ ** Do not consider a single-pass strategy for a multi-row update if
+ ** there are any triggers or foreign keys to process, or rows may
+ ** be deleted as a result of REPLACE conflict handling. Any of these
+ ** things might disturb a cursor being used to scan through the table
+ ** or index, causing a single-pass approach to malfunction. */
+ flags = WHERE_ONEPASS_DESIRED|WHERE_SEEK_UNIQ_TABLE;
+ if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){
+ flags |= WHERE_ONEPASS_MULTIROW;
+ }
+ pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags, iIdxCur);
+ if( pWInfo==0 ) goto update_cleanup;
+
+ /* A one-pass strategy that might update more than one row may not
+ ** be used if any column of the index used for the scan is being
+ ** updated. Otherwise, if there is an index on "b", statements like
+ ** the following could create an infinite loop:
+ **
+ ** UPDATE t1 SET b=b+1 WHERE b>?
+ **
+ ** Fall back to ONEPASS_OFF if where.c has selected a ONEPASS_MULTI
+ ** strategy that uses an index for which one or more columns are being
+ ** updated. */
+ eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
+ if( eOnePass!=ONEPASS_SINGLE ){
+ sqlite3MultiWrite(pParse);
+ if( eOnePass==ONEPASS_MULTI ){
+ int iCur = aiCurOnePass[1];
+ if( iCur>=0 && iCur!=iDataCur && aToOpen[iCur-iBaseCur] ){
+ eOnePass = ONEPASS_OFF;
+ }
+ assert( iCur!=iDataCur || !HasRowid(pTab) );
+ }
+ }
+ }
+
if( HasRowid(pTab) ){
/* Read the rowid of the current row of the WHERE scan. In ONEPASS_OFF
** mode, write the rowid into the FIFO. In either of the one-pass modes,
** leave it in register regOldRowid. */
sqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regOldRowid);
@@ -451,73 +484,72 @@
for(i=0; iaiColumn[i]>=0 );
sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur,pPk->aiColumn[i],iPk+i);
}
if( eOnePass ){
- sqlite3VdbeChangeToNoop(v, addrOpen);
+ if( addrOpen ) sqlite3VdbeChangeToNoop(v, addrOpen);
nKey = nPk;
regKey = iPk;
}else{
sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey,
sqlite3IndexAffinityStr(db, pPk), nPk);
sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEph, regKey, iPk, nPk);
}
}
- if( eOnePass!=ONEPASS_MULTI ){
- sqlite3WhereEnd(pWInfo);
- }
-
- labelBreak = sqlite3VdbeMakeLabel(v);
- if( !isView ){
- int addrOnce = 0;
-
- /* Open every index that needs updating. */
- if( eOnePass!=ONEPASS_OFF ){
- if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iBaseCur] = 0;
- if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iBaseCur] = 0;
- }
-
- if( eOnePass==ONEPASS_MULTI && (nIdx-(aiCurOnePass[1]>=0))>0 ){
- addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
- }
- sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, iBaseCur, aToOpen,
- 0, 0);
- if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce);
- }
-
- /* Top of the update loop */
- if( eOnePass!=ONEPASS_OFF ){
- if( !isView && aiCurOnePass[0]!=iDataCur && aiCurOnePass[1]!=iDataCur ){
- assert( pPk );
- sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelBreak, regKey, nKey);
- VdbeCoverageNeverTaken(v);
- }
- if( eOnePass==ONEPASS_SINGLE ){
- labelContinue = labelBreak;
- }else{
- labelContinue = sqlite3VdbeMakeLabel(v);
- }
- sqlite3VdbeAddOp2(v, OP_IsNull, pPk ? regKey : regOldRowid, labelBreak);
- VdbeCoverageIf(v, pPk==0);
- VdbeCoverageIf(v, pPk!=0);
- }else if( pPk ){
- labelContinue = sqlite3VdbeMakeLabel(v);
- sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); VdbeCoverage(v);
- addrTop = sqlite3VdbeAddOp2(v, OP_RowData, iEph, regKey);
- sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey, 0);
- VdbeCoverage(v);
- }else{
- labelContinue = sqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet, labelBreak,
- regOldRowid);
- VdbeCoverage(v);
- sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid);
- VdbeCoverage(v);
- }
-
- /* If the record number will change, set register regNewRowid to
- ** contain the new value. If the record number is not being modified,
+ if( pUpsert==0 ){
+ if( eOnePass!=ONEPASS_MULTI ){
+ sqlite3WhereEnd(pWInfo);
+ }
+
+ if( !isView ){
+ int addrOnce = 0;
+
+ /* Open every index that needs updating. */
+ if( eOnePass!=ONEPASS_OFF ){
+ if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iBaseCur] = 0;
+ if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iBaseCur] = 0;
+ }
+
+ if( eOnePass==ONEPASS_MULTI && (nIdx-(aiCurOnePass[1]>=0))>0 ){
+ addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
+ }
+ sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, iBaseCur,
+ aToOpen, 0, 0);
+ if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce);
+ }
+
+ /* Top of the update loop */
+ if( eOnePass!=ONEPASS_OFF ){
+ if( !isView && aiCurOnePass[0]!=iDataCur && aiCurOnePass[1]!=iDataCur ){
+ assert( pPk );
+ sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelBreak, regKey,nKey);
+ VdbeCoverageNeverTaken(v);
+ }
+ if( eOnePass!=ONEPASS_SINGLE ){
+ labelContinue = sqlite3VdbeMakeLabel(v);
+ }
+ sqlite3VdbeAddOp2(v, OP_IsNull, pPk ? regKey : regOldRowid, labelBreak);
+ VdbeCoverageIf(v, pPk==0);
+ VdbeCoverageIf(v, pPk!=0);
+ }else if( pPk ){
+ labelContinue = sqlite3VdbeMakeLabel(v);
+ sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); VdbeCoverage(v);
+ addrTop = sqlite3VdbeAddOp2(v, OP_RowData, iEph, regKey);
+ sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey, 0);
+ VdbeCoverage(v);
+ }else{
+ labelContinue = sqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet,labelBreak,
+ regOldRowid);
+ VdbeCoverage(v);
+ sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid);
+ VdbeCoverage(v);
+ }
+ }
+
+ /* If the rowid value will change, set register regNewRowid to
+ ** contain the new value. If the rowid is not being modified,
** then regNewRowid is the same register as regOldRowid, which is
** already populated. */
assert( chngKey || pTrigger || hasFK || regOldRowid==regNewRowid );
if( chngRowid ){
sqlite3ExprCode(pParse, pRowidExpr, regNewRowid);
@@ -577,10 +609,16 @@
** a new.* reference in a trigger program.
*/
testcase( i==31 );
testcase( i==32 );
sqlite3ExprCodeGetColumnToReg(pParse, pTab, i, iDataCur, regNew+i);
+ if( tmask & TRIGGER_BEFORE ){
+ /* This value will be recomputed in After-BEFORE-trigger-reload-loop
+ ** below, so make sure that it is not cached and reused.
+ ** Ticket d85fffd6ffe856092ed8daefa811b1e399706b28. */
+ sqlite3ExprCacheRemove(pParse, regNew+i, 1);
+ }
}else{
sqlite3VdbeAddOp2(v, OP_Null, 0, regNew+i);
}
}
}
@@ -605,14 +643,18 @@
}else{
sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid);
VdbeCoverage(v);
}
- /* If it did not delete it, the row-trigger may still have modified
+ /* After-BEFORE-trigger-reload-loop:
+ ** If it did not delete it, the BEFORE trigger may still have modified
** some of the columns of the row being updated. Load the values for
- ** all columns not modified by the update statement into their
- ** registers in case this has happened.
+ ** all columns not modified by the update statement into their registers
+ ** in case this has happened. Only unmodified columns are reloaded.
+ ** The values computed for modified columns use the values before the
+ ** BEFORE trigger runs. See test case trigger1-18.0 (added 2018-04-26)
+ ** for an example.
*/
for(i=0; inCol; i++){
if( aXRef[i]<0 && i!=pTab->iPKey ){
sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, regNew+i);
}
@@ -624,11 +666,11 @@
/* Do constraint checks. */
assert( regOldRowid>0 );
sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur,
regNewRowid, regOldRowid, chngKey, onError, labelContinue, &bReplace,
- aXRef);
+ aXRef, 0);
/* Do FK constraint checks. */
if( hasFK ){
sqlite3FkCheck(pParse, pTab, regOldRowid, 0, aXRef, chngKey);
}
@@ -694,11 +736,11 @@
}
}
/* Increment the row counter
*/
- if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab){
+ if( regRowCount ){
sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1);
}
sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
TRIGGER_AFTER, pTab, regOldRowid, onError, labelContinue);
@@ -721,20 +763,19 @@
/* Update the sqlite_sequence table by storing the content of the
** maximum rowid counter values recorded while inserting into
** autoincrement tables.
*/
- if( pParse->nested==0 && pParse->pTriggerTab==0 ){
+ if( pParse->nested==0 && pParse->pTriggerTab==0 && pUpsert==0 ){
sqlite3AutoincrementEnd(pParse);
}
/*
- ** Return the number of rows that were changed. If this routine is
- ** generating code because of a call to sqlite3NestedParse(), do not
- ** invoke the callback function.
+ ** Return the number of rows that were changed, if we are tracking
+ ** that information.
*/
- if( (db->flags&SQLITE_CountRows) && !pParse->pTriggerTab && !pParse->nested ){
+ if( regRowCount ){
sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
sqlite3VdbeSetNumCols(v, 1);
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows updated", SQLITE_STATIC);
}
@@ -851,19 +892,16 @@
bOnePass = sqlite3WhereOkOnePass(pWInfo, aDummy);
if( bOnePass ){
/* If using the onepass strategy, no-op out the OP_OpenEphemeral coded
- ** above. Also, if this is a top-level parse (not a trigger), clear the
- ** multi-write flag so that the VM does not open a statement journal */
+ ** above. */
sqlite3VdbeChangeToNoop(v, addr);
- if( sqlite3IsToplevel(pParse) ){
- pParse->isMultiWrite = 0;
- }
}else{
/* Create a record from the argument register contents and insert it into
** the ephemeral table. */
+ sqlite3MultiWrite(pParse);
sqlite3VdbeAddOp3(v, OP_MakeRecord, regArg, nArg, regRec);
#ifdef SQLITE_DEBUG
/* Signal an assert() within OP_MakeRecord that it is allowed to
** accept no-change records with serial_type 10 */
sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC);
ADDED src/upsert.c
Index: src/upsert.c
==================================================================
--- /dev/null
+++ src/upsert.c
@@ -0,0 +1,249 @@
+/*
+** 2018-04-12
+**
+** 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 contains code to implement various aspects of UPSERT
+** processing and handling of the Upsert object.
+*/
+#include "sqliteInt.h"
+
+#ifndef SQLITE_OMIT_UPSERT
+/*
+** Free a list of Upsert objects
+*/
+void sqlite3UpsertDelete(sqlite3 *db, Upsert *p){
+ if( p ){
+ sqlite3ExprListDelete(db, p->pUpsertTarget);
+ sqlite3ExprDelete(db, p->pUpsertTargetWhere);
+ sqlite3ExprListDelete(db, p->pUpsertSet);
+ sqlite3ExprDelete(db, p->pUpsertWhere);
+ sqlite3DbFree(db, p);
+ }
+}
+
+/*
+** Duplicate an Upsert object.
+*/
+Upsert *sqlite3UpsertDup(sqlite3 *db, Upsert *p){
+ if( p==0 ) return 0;
+ return sqlite3UpsertNew(db,
+ sqlite3ExprListDup(db, p->pUpsertTarget, 0),
+ sqlite3ExprDup(db, p->pUpsertTargetWhere, 0),
+ sqlite3ExprListDup(db, p->pUpsertSet, 0),
+ sqlite3ExprDup(db, p->pUpsertWhere, 0)
+ );
+}
+
+/*
+** Create a new Upsert object.
+*/
+Upsert *sqlite3UpsertNew(
+ sqlite3 *db, /* Determines which memory allocator to use */
+ ExprList *pTarget, /* Target argument to ON CONFLICT, or NULL */
+ Expr *pTargetWhere, /* Optional WHERE clause on the target */
+ ExprList *pSet, /* UPDATE columns, or NULL for a DO NOTHING */
+ Expr *pWhere /* WHERE clause for the ON CONFLICT UPDATE */
+){
+ Upsert *pNew;
+ pNew = sqlite3DbMallocRaw(db, sizeof(Upsert));
+ if( pNew==0 ){
+ sqlite3ExprListDelete(db, pTarget);
+ sqlite3ExprDelete(db, pTargetWhere);
+ sqlite3ExprListDelete(db, pSet);
+ sqlite3ExprDelete(db, pWhere);
+ return 0;
+ }else{
+ pNew->pUpsertTarget = pTarget;
+ pNew->pUpsertTargetWhere = pTargetWhere;
+ pNew->pUpsertSet = pSet;
+ pNew->pUpsertWhere = pWhere;
+ pNew->pUpsertIdx = 0;
+ }
+ return pNew;
+}
+
+/*
+** Analyze the ON CONFLICT clause described by pUpsert. Resolve all
+** symbols in the conflict-target.
+**
+** Return SQLITE_OK if everything works, or an error code is something
+** is wrong.
+*/
+int sqlite3UpsertAnalyzeTarget(
+ Parse *pParse, /* The parsing context */
+ SrcList *pTabList, /* Table into which we are inserting */
+ Upsert *pUpsert /* The ON CONFLICT clauses */
+){
+ Table *pTab; /* That table into which we are inserting */
+ int rc; /* Result code */
+ int iCursor; /* Cursor used by pTab */
+ Index *pIdx; /* One of the indexes of pTab */
+ ExprList *pTarget; /* The conflict-target clause */
+ Expr *pTerm; /* One term of the conflict-target clause */
+ NameContext sNC; /* Context for resolving symbolic names */
+ Expr sCol[2]; /* Index column converted into an Expr */
+
+ assert( pTabList->nSrc==1 );
+ assert( pTabList->a[0].pTab!=0 );
+ assert( pUpsert!=0 );
+ assert( pUpsert->pUpsertTarget!=0 );
+
+ /* Resolve all symbolic names in the conflict-target clause, which
+ ** includes both the list of columns and the optional partial-index
+ ** WHERE clause.
+ */
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = pParse;
+ sNC.pSrcList = pTabList;
+ rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget);
+ if( rc ) return rc;
+ rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere);
+ if( rc ) return rc;
+
+ /* Check to see if the conflict target matches the rowid. */
+ pTab = pTabList->a[0].pTab;
+ pTarget = pUpsert->pUpsertTarget;
+ iCursor = pTabList->a[0].iCursor;
+ if( HasRowid(pTab)
+ && pTarget->nExpr==1
+ && (pTerm = pTarget->a[0].pExpr)->op==TK_COLUMN
+ && pTerm->iColumn==XN_ROWID
+ ){
+ /* The conflict-target is the rowid of the primary table */
+ assert( pUpsert->pUpsertIdx==0 );
+ return SQLITE_OK;
+ }
+
+ /* Initialize sCol[0..1] to be an expression parse tree for a
+ ** single column of an index. The sCol[0] node will be the TK_COLLATE
+ ** operator and sCol[1] will be the TK_COLUMN operator. Code below
+ ** will populate the specific collation and column number values
+ ** prior to comparing against the conflict-target expression.
+ */
+ memset(sCol, 0, sizeof(sCol));
+ sCol[0].op = TK_COLLATE;
+ sCol[0].pLeft = &sCol[1];
+ sCol[1].op = TK_COLUMN;
+ sCol[1].iTable = pTabList->a[0].iCursor;
+
+ /* Check for matches against other indexes */
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ int ii, jj, nn;
+ if( !IsUniqueIndex(pIdx) ) continue;
+ if( pTarget->nExpr!=pIdx->nKeyCol ) continue;
+ if( pIdx->pPartIdxWhere ){
+ if( pUpsert->pUpsertTargetWhere==0 ) continue;
+ if( sqlite3ExprCompare(pParse, pUpsert->pUpsertTargetWhere,
+ pIdx->pPartIdxWhere, iCursor)!=0 ){
+ continue;
+ }
+ }
+ nn = pIdx->nKeyCol;
+ for(ii=0; iiazColl[ii];
+ if( pIdx->aiColumn[ii]==XN_EXPR ){
+ assert( pIdx->aColExpr!=0 );
+ assert( pIdx->aColExpr->nExpr>ii );
+ pExpr = pIdx->aColExpr->a[ii].pExpr;
+ if( pExpr->op!=TK_COLLATE ){
+ sCol[0].pLeft = pExpr;
+ pExpr = &sCol[0];
+ }
+ }else{
+ sCol[0].pLeft = &sCol[1];
+ sCol[1].iColumn = pIdx->aiColumn[ii];
+ pExpr = &sCol[0];
+ }
+ for(jj=0; jja[jj].pExpr, pExpr,iCursor)<2 ){
+ break; /* Column ii of the index matches column jj of target */
+ }
+ }
+ if( jj>=nn ){
+ /* The target contains no match for column jj of the index */
+ break;
+ }
+ }
+ if( iipUpsertIdx = pIdx;
+ return SQLITE_OK;
+ }
+ sqlite3ErrorMsg(pParse, "ON CONFLICT clause does not match any "
+ "PRIMARY KEY or UNIQUE constraint");
+ return SQLITE_ERROR;
+}
+
+/*
+** Generate bytecode that does an UPDATE as part of an upsert.
+**
+** If pIdx is NULL, then the UNIQUE constraint that failed was the IPK.
+** In this case parameter iCur is a cursor open on the table b-tree that
+** currently points to the conflicting table row. Otherwise, if pIdx
+** is not NULL, then pIdx is the constraint that failed and iCur is a
+** cursor points to the conflicting row.
+*/
+void sqlite3UpsertDoUpdate(
+ Parse *pParse, /* The parsing and code-generating context */
+ Upsert *pUpsert, /* The ON CONFLICT clause for the upsert */
+ Table *pTab, /* The table being updated */
+ Index *pIdx, /* The UNIQUE constraint that failed */
+ int iCur /* Cursor for pIdx (or pTab if pIdx==NULL) */
+){
+ Vdbe *v = pParse->pVdbe;
+ sqlite3 *db = pParse->db;
+ SrcList *pSrc; /* FROM clause for the UPDATE */
+ int iDataCur = pUpsert->iDataCur;
+
+ assert( v!=0 );
+ VdbeNoopComment((v, "Begin DO UPDATE of UPSERT"));
+ if( pIdx && iCur!=iDataCur ){
+ if( HasRowid(pTab) ){
+ int regRowid = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp2(v, OP_IdxRowid, iCur, regRowid);
+ sqlite3VdbeAddOp3(v, OP_SeekRowid, iDataCur, 0, regRowid);
+ VdbeCoverage(v);
+ sqlite3ReleaseTempReg(pParse, regRowid);
+ }else{
+ Index *pPk = sqlite3PrimaryKeyIndex(pTab);
+ int nPk = pPk->nKeyCol;
+ int iPk = pParse->nMem+1;
+ int i;
+ pParse->nMem += nPk;
+ for(i=0; iaiColumn[i]>=0 );
+ k = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[i]);
+ sqlite3VdbeAddOp3(v, OP_Column, iCur, k, iPk+i);
+ VdbeComment((v, "%s.%s", pIdx->zName,
+ pTab->aCol[pPk->aiColumn[i]].zName));
+ }
+ i = sqlite3VdbeAddOp4Int(v, OP_Found, iDataCur, 0, iPk, nPk);
+ VdbeCoverage(v);
+ sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_CORRUPT, OE_Abort, 0,
+ "corrupt database", P4_STATIC);
+ sqlite3VdbeJumpHere(v, i);
+ }
+ }
+ /* pUpsert does not own pUpsertSrc - the outer INSERT statement does. So
+ ** we have to make a copy before passing it down into sqlite3Update() */
+ pSrc = sqlite3SrcListDup(db, pUpsert->pUpsertSrc, 0);
+ sqlite3Update(pParse, pSrc, pUpsert->pUpsertSet,
+ pUpsert->pUpsertWhere, OE_Abort, 0, 0, pUpsert);
+ pUpsert->pUpsertSet = 0; /* Will have been deleted by sqlite3Update() */
+ pUpsert->pUpsertWhere = 0; /* Will have been deleted by sqlite3Update() */
+ VdbeNoopComment((v, "End DO UPDATE of UPSERT"));
+}
+
+#endif /* SQLITE_OMIT_UPSERT */
Index: src/vacuum.c
==================================================================
--- src/vacuum.c
+++ src/vacuum.c
@@ -37,12 +37,18 @@
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
if( rc!=SQLITE_OK ) return rc;
while( SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){
const char *zSubSql = (const char*)sqlite3_column_text(pStmt,0);
assert( sqlite3_strnicmp(zSql,"SELECT",6)==0 );
- assert( sqlite3_strnicmp(zSubSql,"SELECT",6)!=0 || CORRUPT_DB );
- if( zSubSql && zSubSql[0]!='S' ){
+ /* The secondary SQL must be one of CREATE TABLE, CREATE INDEX,
+ ** or INSERT. Historically there have been attacks that first
+ ** corrupt the sqlite_master.sql field with other kinds of statements
+ ** then run VACUUM to get those statements to execute at inappropriate
+ ** times. */
+ if( zSubSql
+ && (strncmp(zSubSql,"CRE",3)==0 || strncmp(zSubSql,"INS",3)==0)
+ ){
rc = execSql(db, pzErrMsg, zSubSql);
if( rc!=SQLITE_OK ) break;
}
}
assert( rc!=SQLITE_ROW );
@@ -251,11 +257,11 @@
zDbMain
);
if( rc!=SQLITE_OK ) goto end_of_vacuum;
rc = execSqlF(db, pzErrMsg,
"SELECT sql FROM \"%w\".sqlite_master"
- " WHERE type='index' AND length(sql)>10",
+ " WHERE type='index'",
zDbMain
);
if( rc!=SQLITE_OK ) goto end_of_vacuum;
db->init.iDb = 0;
Index: src/vdbe.h
==================================================================
--- src/vdbe.h
+++ src/vdbe.h
@@ -195,11 +195,23 @@
void sqlite3VdbeVerifyNoResultRow(Vdbe *p);
#else
# define sqlite3VdbeVerifyNoMallocRequired(A,B)
# define sqlite3VdbeVerifyNoResultRow(A)
#endif
-VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp, int iLineno);
+VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp,int iLineno);
+#ifndef SQLITE_OMIT_EXPLAIN
+ void sqlite3VdbeExplain(Parse*,u8,const char*,...);
+ void sqlite3VdbeExplainPop(Parse*);
+ int sqlite3VdbeExplainParent(Parse*);
+# define ExplainQueryPlan(P) sqlite3VdbeExplain P
+# define ExplainQueryPlanPop(P) sqlite3VdbeExplainPop(P)
+# define ExplainQueryPlanParent(P) sqlite3VdbeExplainParent(P)
+#else
+# define ExplainQueryPlan(P)
+# define ExplainQueryPlanPop(P)
+# define ExplainQueryPlanParent(P) 0
+#endif
void sqlite3VdbeAddParseSchemaOp(Vdbe*,int,char*);
void sqlite3VdbeChangeOpcode(Vdbe*, u32 addr, u8);
void sqlite3VdbeChangeP1(Vdbe*, u32 addr, int P1);
void sqlite3VdbeChangeP2(Vdbe*, u32 addr, int P2);
void sqlite3VdbeChangeP3(Vdbe*, u32 addr, int P3);
@@ -218,10 +230,13 @@
void sqlite3VdbeDelete(Vdbe*);
void sqlite3VdbeClearObject(sqlite3*,Vdbe*);
void sqlite3VdbeMakeReady(Vdbe*,Parse*);
int sqlite3VdbeFinalize(Vdbe*);
void sqlite3VdbeResolveLabel(Vdbe*, int);
+#ifdef SQLITE_COVERAGE_TEST
+ int sqlite3VdbeLabelHasBeenResolved(Vdbe*,int);
+#endif
int sqlite3VdbeCurrentAddr(Vdbe*);
#ifdef SQLITE_DEBUG
int sqlite3VdbeAssertMayAbort(Vdbe *, int);
#endif
void sqlite3VdbeResetStepResult(Vdbe*);
Index: src/vdbeaux.c
==================================================================
--- src/vdbeaux.c
+++ src/vdbeaux.c
@@ -301,10 +301,53 @@
char *p4copy = sqlite3DbMallocRawNN(sqlite3VdbeDb(p), 8);
if( p4copy ) memcpy(p4copy, zP4, 8);
return sqlite3VdbeAddOp4(p, op, p1, p2, p3, p4copy, p4type);
}
+#ifndef SQLITE_OMIT_EXPLAIN
+/*
+** Return the address of the current EXPLAIN QUERY PLAN baseline.
+** 0 means "none".
+*/
+int sqlite3VdbeExplainParent(Parse *pParse){
+ VdbeOp *pOp;
+ if( pParse->addrExplain==0 ) return 0;
+ pOp = sqlite3VdbeGetOp(pParse->pVdbe, pParse->addrExplain);
+ return pOp->p2;
+}
+
+/*
+** Add a new OP_Explain opcode.
+**
+** If the bPush flag is true, then make this opcode the parent for
+** subsequent Explains until sqlite3VdbeExplainPop() is called.
+*/
+void sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){
+ if( pParse->explain==2 ){
+ char *zMsg;
+ Vdbe *v = pParse->pVdbe;
+ va_list ap;
+ int iThis;
+ va_start(ap, zFmt);
+ zMsg = sqlite3VMPrintf(pParse->db, zFmt, ap);
+ va_end(ap);
+ v = pParse->pVdbe;
+ iThis = v->nOp;
+ sqlite3VdbeAddOp4(v, OP_Explain, iThis, pParse->addrExplain, 0,
+ zMsg, P4_DYNAMIC);
+ if( bPush) pParse->addrExplain = iThis;
+ }
+}
+
+/*
+** Pop the EXPLAIN QUERY PLAN stack one level.
+*/
+void sqlite3VdbeExplainPop(Parse *pParse){
+ pParse->addrExplain = sqlite3VdbeExplainParent(pParse);
+}
+#endif /* SQLITE_OMIT_EXPLAIN */
+
/*
** Add an OP_ParseSchema opcode. This routine is broken out from
** sqlite3VdbeAddOp4() since it needs to also needs to mark all btrees
** as having been used.
**
@@ -390,14 +433,33 @@
int j = ADDR(x);
assert( v->magic==VDBE_MAGIC_INIT );
assert( jnLabel );
assert( j>=0 );
if( p->aLabel ){
+#ifdef SQLITE_DEBUG
+ if( p->db->flags & SQLITE_VdbeAddopTrace ){
+ printf("RESOLVE LABEL %d to %d\n", x, v->nOp);
+ }
+#endif
+ assert( p->aLabel[j]==(-1) ); /* Labels may only be resolved once */
p->aLabel[j] = v->nOp;
}
}
+#ifdef SQLITE_COVERAGE_TEST
+/*
+** Return TRUE if and only if the label x has already been resolved.
+** Return FALSE (zero) if label x is still unresolved.
+**
+** This routine is only used inside of testcase() macros, and so it
+** only exists when measuring test coverage.
+*/
+int sqlite3VdbeLabelHasBeenResolved(Vdbe *v, int x){
+ return v->pParse->aLabel && v->pParse->aLabel[ADDR(x)]>=0;
+}
+#endif /* SQLITE_COVERAGE_TEST */
+
/*
** Mark the VDBE as one that can only be run one time.
*/
void sqlite3VdbeRunOnlyOnce(Vdbe *p){
p->runOnlyOnce = 1;
@@ -1624,10 +1686,13 @@
**
** When p->explain==1, each instruction is listed. When
** p->explain==2, only OP_Explain instructions are listed and these
** are shown in a different format. p->explain==2 is used to implement
** EXPLAIN QUERY PLAN.
+** 2018-04-24: In p->explain==2 mode, the OP_Init opcodes of triggers
+** are also shown, so that the boundaries between the main program and
+** each trigger are clear.
**
** When p->explain==1, first the main program is listed, then each of
** the trigger subprograms are listed one by one.
*/
int sqlite3VdbeList(
@@ -1686,11 +1751,11 @@
for(i=0; inOp;
}
}
- do{
+ while(1){ /* Loop exits via break */
i = p->pc++;
if( i>=nRow ){
p->rc = SQLITE_OK;
rc = SQLITE_DONE;
break;
@@ -1732,11 +1797,14 @@
pSub->flags |= MEM_Blob;
pSub->n = nSub*sizeof(SubProgram*);
nRow += pOp->p4.pProgram->nOp;
}
}
- }while( p->explain==2 && pOp->opcode!=OP_Explain );
+ if( p->explain<2 ) break;
+ if( pOp->opcode==OP_Explain ) break;
+ if( pOp->opcode==OP_Init && p->pc>1 ) break;
+ }
if( rc==SQLITE_OK ){
if( db->u1.isInterrupted ){
p->rc = SQLITE_INTERRUPT;
rc = SQLITE_ERROR;
Index: src/vdbemem.c
==================================================================
--- src/vdbemem.c
+++ src/vdbemem.c
@@ -1476,16 +1476,20 @@
assert( zVal[nVal]=='\'' );
sqlite3VdbeMemSetStr(pVal, sqlite3HexToBlob(db, zVal, nVal), nVal/2,
0, SQLITE_DYNAMIC);
}
#endif
-
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
else if( op==TK_FUNCTION && pCtx!=0 ){
rc = valueFromFunction(db, pExpr, enc, affinity, &pVal, pCtx);
}
#endif
+ else if( op==TK_TRUEFALSE ){
+ pVal = valueNew(db, pCtx);
+ pVal->flags = MEM_Int;
+ pVal->u.i = pExpr->u.zToken[4]==0;
+ }
*ppVal = pVal;
return rc;
no_mem:
Index: src/where.c
==================================================================
--- src/where.c
+++ src/where.c
@@ -2418,19 +2418,16 @@
/* Do not allow the upper bound of a LIKE optimization range constraint
** to mix with a lower range bound from some other source */
if( pTerm->wtFlags & TERM_LIKEOPT && pTerm->eOperator==WO_LT ) continue;
- /* Do not allow IS constraints from the WHERE clause to be used by the
+ /* Do not allow constraints from the WHERE clause to be used by the
** right table of a LEFT JOIN. Only constraints in the ON clause are
** allowed */
if( (pSrc->fg.jointype & JT_LEFT)!=0
&& !ExprHasProperty(pTerm->pExpr, EP_FromJoin)
- && (eOp & (WO_IS|WO_ISNULL))!=0
){
- testcase( eOp & WO_IS );
- testcase( eOp & WO_ISNULL );
continue;
}
if( IsUniqueIndex(pProbe) && saved_nEq==pProbe->nKeyCol-1 ){
pBuilder->bldFlags |= SQLITE_BLDF_UNIQUE;
@@ -4593,10 +4590,11 @@
if( nTabList==0 ){
if( pOrderBy ) pWInfo->nOBSat = pOrderBy->nExpr;
if( wctrlFlags & WHERE_WANT_DISTINCT ){
pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE;
}
+ ExplainQueryPlan((pParse, 0, "SCAN CONSTANT ROW"));
}else{
/* Assign a bit from the bitmask to every term in the FROM clause.
**
** The N-th term of the FROM clause is assigned a bitmask of 1<a[pLevel->iFrom];
Vdbe *v = pParse->pVdbe; /* VM being constructed */
sqlite3 *db = pParse->db; /* Database handle */
- int iId = pParse->iSelectId; /* Select id (left-most output column) */
int isSearch; /* True for a SEARCH. False for SCAN. */
WhereLoop *pLoop; /* The controlling WhereLoop object */
u32 flags; /* Flags that describe this loop */
char *zMsg; /* Text to add to EQP output */
StrAccum str; /* EQP output string */
@@ -151,11 +150,11 @@
|| (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX));
sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH);
sqlite3StrAccumAppendAll(&str, isSearch ? "SEARCH" : "SCAN");
if( pItem->pSelect ){
- sqlite3XPrintf(&str, " SUBQUERY %d", pItem->iSelectId);
+ sqlite3XPrintf(&str, " SUBQUERY 0x%p", pItem->pSelect);
}else{
sqlite3XPrintf(&str, " TABLE %s", pItem->zName);
}
if( pItem->zAlias ){
@@ -212,11 +211,12 @@
}else{
sqlite3StrAccumAppend(&str, " (~1 row)", 9);
}
#endif
zMsg = sqlite3StrAccumFinish(&str);
- ret = sqlite3VdbeAddOp4(v, OP_Explain, iId, iLevel, iFrom, zMsg,P4_DYNAMIC);
+ ret = sqlite3VdbeAddOp4(v, OP_Explain, sqlite3VdbeCurrentAddr(v),
+ pParse->addrExplain, 0, zMsg,P4_DYNAMIC);
}
return ret;
}
#endif /* SQLITE_OMIT_EXPLAIN */
@@ -1213,10 +1213,13 @@
/* If this is the right table of a LEFT OUTER JOIN, allocate and
** initialize a memory cell that records if this table matches any
** row of the left table of the join.
*/
+ assert( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)
+ || pLevel->iFrom>0 || (pTabItem[0].fg.jointype & JT_LEFT)==0
+ );
if( pLevel->iFrom>0 && (pTabItem[0].fg.jointype & JT_LEFT)!=0 ){
pLevel->iLeftJoin = ++pParse->nMem;
sqlite3VdbeAddOp2(v, OP_Integer, 0, pLevel->iLeftJoin);
VdbeComment((v, "init LEFT JOIN no-match flag"));
}
@@ -1746,13 +1749,20 @@
}
/* If pIdx is an index on one or more expressions, then look through
** all the expressions in pWInfo and try to transform matching expressions
** into reference to index columns.
+ **
+ ** Do not do this for the RHS of a LEFT JOIN. This is because the
+ ** expression may be evaluated after OP_NullRow has been executed on
+ ** the cursor. In this case it is important to do the full evaluation,
+ ** as the result of the expression may not be NULL, even if all table
+ ** column values are. https://www.sqlite.org/src/info/7fa8049685b50b5a
*/
- whereIndexExprTrans(pIdx, iCur, iIdxCur, pWInfo);
-
+ if( pLevel->iLeftJoin==0 ){
+ whereIndexExprTrans(pIdx, iCur, iIdxCur, pWInfo);
+ }
/* Record the instruction used to terminate the loop. */
if( pLoop->wsFlags & WHERE_ONEROW ){
pLevel->op = OP_Noop;
}else if( bRev ){
@@ -1904,11 +1914,10 @@
if( pWC->nTerm>1 ){
int iTerm;
for(iTerm=0; iTermnTerm; iTerm++){
Expr *pExpr = pWC->a[iTerm].pExpr;
if( &pWC->a[iTerm] == pTerm ) continue;
- if( ExprHasProperty(pExpr, EP_FromJoin) ) continue;
testcase( pWC->a[iTerm].wtFlags & TERM_VIRTUAL );
testcase( pWC->a[iTerm].wtFlags & TERM_CODED );
if( (pWC->a[iTerm].wtFlags & (TERM_VIRTUAL|TERM_CODED))!=0 ) continue;
if( (pWC->a[iTerm].eOperator & WO_ALL)==0 ) continue;
testcase( pWC->a[iTerm].wtFlags & TERM_ORINFO );
@@ -1923,17 +1932,21 @@
/* Run a separate WHERE clause for each term of the OR clause. After
** eliminating duplicates from other WHERE clauses, the action for each
** sub-WHERE clause is to to invoke the main loop body as a subroutine.
*/
wctrlFlags = WHERE_OR_SUBCLAUSE | (pWInfo->wctrlFlags & WHERE_SEEK_TABLE);
+ ExplainQueryPlan((pParse, 1, "MULTI-INDEX OR"));
for(ii=0; iinTerm; ii++){
WhereTerm *pOrTerm = &pOrWc->a[ii];
if( pOrTerm->leftCursor==iCur || (pOrTerm->eOperator & WO_AND)!=0 ){
WhereInfo *pSubWInfo; /* Info for single OR-term scan */
Expr *pOrExpr = pOrTerm->pExpr; /* Current OR clause term */
int jmp1 = 0; /* Address of jump operation */
- if( pAndExpr && !ExprHasProperty(pOrExpr, EP_FromJoin) ){
+ assert( (pTabItem[0].fg.jointype & JT_LEFT)==0
+ || ExprHasProperty(pOrExpr, EP_FromJoin)
+ );
+ if( pAndExpr ){
pAndExpr->pLeft = pOrExpr;
pOrExpr = pAndExpr;
}
/* Loop through table entries that match term pOrTerm. */
WHERETRACE(0xffff, ("Subplan for OR-clause:\n"));
@@ -2040,10 +2053,11 @@
/* Finish the loop through table entries that match term pOrTerm. */
sqlite3WhereEnd(pSubWInfo);
}
}
}
+ ExplainQueryPlanPop(pParse);
pLevel->u.pCovidx = pCov;
if( pCov ) pLevel->iIdxCur = iCovCur;
if( pAndExpr ){
pAndExpr->pLeft = 0;
sqlite3ExprDelete(db, pAndExpr);
@@ -2112,11 +2126,11 @@
pWInfo->untestedTerms = 1;
continue;
}
pE = pTerm->pExpr;
assert( pE!=0 );
- if( pLevel->iLeftJoin && !ExprHasProperty(pE, EP_FromJoin) ){
+ if( (pTabItem->fg.jointype&JT_LEFT) && !ExprHasProperty(pE,EP_FromJoin) ){
continue;
}
if( iLoop==1 && !sqlite3ExprCoveredByIndex(pE, pLevel->iTabCur, pIdx) ){
iNext = 2;
Index: src/whereexpr.c
==================================================================
--- src/whereexpr.c
+++ src/whereexpr.c
@@ -816,11 +816,10 @@
pTerm = &pWC->a[idxTerm];
markTermAsChild(pWC, idxNew, idxTerm);
}else{
sqlite3ExprListDelete(db, pList);
}
- pTerm->eOperator = WO_NOOP; /* case 1 trumps case 3 */
}
}
}
#endif /* !SQLITE_OMIT_OR_OPTIMIZATION && !SQLITE_OMIT_SUBQUERY */
Index: test/analyze3.test
==================================================================
--- test/analyze3.test
+++ test/analyze3.test
@@ -116,44 +116,44 @@
# it is better to use the index. But the second visits every row in
# the table (1000 in total) so it is better to do a full-table scan.
#
do_eqp_test analyze3-1.1.2 {
SELECT sum(y) FROM t1 WHERE x>200 AND x<300
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (x>? AND x)}}
+} {SEARCH TABLE t1 USING INDEX i1 (x>? AND x)}
do_eqp_test analyze3-1.1.3 {
SELECT sum(y) FROM t1 WHERE x>0 AND x<1100
-} {0 0 0 {SCAN TABLE t1}}
+} {SCAN TABLE t1}
# 2017-06-26: Verify that the SQLITE_DBCONFIG_ENABLE_QPSG setting disables
# the use of bound parameters by STAT4
#
db cache flush
unset -nocomplain l
unset -nocomplain u
do_eqp_test analyze3-1.1.3.100 {
SELECT sum(y) FROM t1 WHERE x>$l AND x<$u
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (x>? AND x)}}
+} {SEARCH TABLE t1 USING INDEX i1 (x>? AND x)}
set l 200
set u 300
do_eqp_test analyze3-1.1.3.101 {
SELECT sum(y) FROM t1 WHERE x>$l AND x<$u
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (x>? AND x)}}
+} {SEARCH TABLE t1 USING INDEX i1 (x>? AND x)}
set l 0
set u 1100
do_eqp_test analyze3-1.1.3.102 {
SELECT sum(y) FROM t1 WHERE x>$l AND x<$u
-} {0 0 0 {SCAN TABLE t1}}
+} {SCAN TABLE t1}
db cache flush
sqlite3_db_config db ENABLE_QPSG 1
do_eqp_test analyze3-1.1.3.103 {
SELECT sum(y) FROM t1 WHERE x>$l AND x<$u
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (x>? AND x)}}
+} {SEARCH TABLE t1 USING INDEX i1 (x>? AND x)}
db cache flush
sqlite3_db_config db ENABLE_QPSG 0
do_eqp_test analyze3-1.1.3.104 {
SELECT sum(y) FROM t1 WHERE x>$l AND x<$u
-} {0 0 0 {SCAN TABLE t1}}
+} {SCAN TABLE t1}
do_test analyze3-1.1.4 {
sf_execsql { SELECT sum(y) FROM t1 WHERE x>200 AND x<300 }
} {199 0 14850}
do_test analyze3-1.1.5 {
@@ -199,14 +199,14 @@
SELECT count(*) FROM t2 WHERE x>1 AND x<2;
SELECT count(*) FROM t2 WHERE x>0 AND x<99;
} {200 990}
do_eqp_test analyze3-1.2.2 {
SELECT sum(y) FROM t2 WHERE x>1 AND x<2
-} {0 0 0 {SEARCH TABLE t2 USING INDEX i2 (x>? AND x)}}
+} {SEARCH TABLE t2 USING INDEX i2 (x>? AND x)}
do_eqp_test analyze3-1.2.3 {
SELECT sum(y) FROM t2 WHERE x>0 AND x<99
-} {0 0 0 {SCAN TABLE t2}}
+} {SCAN TABLE t2}
do_test analyze3-1.2.4 {
sf_execsql { SELECT sum(y) FROM t2 WHERE x>12 AND x<20 }
} {161 0 4760}
do_test analyze3-1.2.5 {
@@ -251,14 +251,14 @@
SELECT count(*) FROM t3 WHERE x>200 AND x<300;
SELECT count(*) FROM t3 WHERE x>0 AND x<1100
} {99 1000}
do_eqp_test analyze3-1.3.2 {
SELECT sum(y) FROM t3 WHERE x>200 AND x<300
-} {0 0 0 {SEARCH TABLE t3 USING INDEX i3 (x>? AND x)}}
+} {SEARCH TABLE t3 USING INDEX i3 (x>? AND x)}
do_eqp_test analyze3-1.3.3 {
SELECT sum(y) FROM t3 WHERE x>0 AND x<1100
-} {0 0 0 {SCAN TABLE t3}}
+} {SCAN TABLE t3}
do_test analyze3-1.3.4 {
sf_execsql { SELECT sum(y) FROM t3 WHERE x>200 AND x<300 }
} {199 0 14850}
do_test analyze3-1.3.5 {
@@ -306,14 +306,14 @@
}
execsql COMMIT
} {}
do_eqp_test analyze3-2.2 {
SELECT count(a) FROM t1 WHERE b LIKE 'a%'
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (b>? AND b)}}
+} {SEARCH TABLE t1 USING INDEX i1 (b>? AND b)}
do_eqp_test analyze3-2.3 {
SELECT count(a) FROM t1 WHERE b LIKE '%a'
-} {0 0 0 {SCAN TABLE t1}}
+} {SCAN TABLE t1}
# Return the first argument if like_match_blobs is true (the default)
# or the second argument if not
#
proc ilmb {a b} {
@@ -696,15 +696,15 @@
execsql ANALYZE
} {}
do_eqp_test analyze3-6-3 {
SELECT * FROM t1 WHERE a = 5 AND c = 13;
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i2 (c=?)}}
+} {SEARCH TABLE t1 USING INDEX i2 (c=?)}
do_eqp_test analyze3-6-2 {
SELECT * FROM t1 WHERE a = 5 AND b > 'w' AND c = 13;
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i2 (c=?)}}
+} {SEARCH TABLE t1 USING INDEX i2 (c=?)}
#-----------------------------------------------------------------------------
# 2015-04-20.
# Memory leak in sqlite3Stat4ProbeFree(). (Discovered while fuzzing.)
#
Index: test/analyze4.test
==================================================================
--- test/analyze4.test
+++ test/analyze4.test
@@ -36,11 +36,11 @@
ANALYZE;
}
# Should choose the t1a index since it is more specific than t1b.
db eval {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=5 AND b IS NULL}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/}
# Verify that the t1b index shows that it does not narrow down the
# search any at all.
#
do_test analyze4-1.1 {
Index: test/analyze6.test
==================================================================
--- test/analyze6.test
+++ test/analyze6.test
@@ -59,18 +59,22 @@
# in EV. (Prior to the 2011-03-04 enhancement to where.c, this query would
# have used EV for the outer loop instead of CAT - which was about 3x slower.)
#
do_test analyze6-1.1 {
eqp {SELECT count(*) FROM ev, cat WHERE x=y}
-} {0 0 1 {SCAN TABLE cat USING COVERING INDEX catx} 0 1 0 {SEARCH TABLE ev USING COVERING INDEX evy (y=?)}}
+} {/*SCAN TABLE cat USING COVERING INDEX catx*SEARCH TABLE ev USING COVERING INDEX evy (y=?)*/}
# The same plan is chosen regardless of the order of the tables in the
# FROM clause.
#
-do_test analyze6-1.2 {
- eqp {SELECT count(*) FROM cat, ev WHERE x=y}
-} {0 0 0 {SCAN TABLE cat USING COVERING INDEX catx} 0 1 1 {SEARCH TABLE ev USING COVERING INDEX evy (y=?)}}
+do_eqp_test analyze6-1.2 {
+ SELECT count(*) FROM cat, ev WHERE x=y
+} {
+ QUERY PLAN
+ |--SCAN TABLE cat USING COVERING INDEX catx
+ `--SEARCH TABLE ev USING COVERING INDEX evy (y=?)
+}
# Ticket [83ea97620bd3101645138b7b0e71c12c5498fe3d] 2011-03-30
# If ANALYZE is run on an empty table, make sure indices are used
# on the table.
@@ -80,43 +84,43 @@
CREATE TABLE t201(x INTEGER PRIMARY KEY, y UNIQUE, z);
CREATE INDEX t201z ON t201(z);
ANALYZE;
}
eqp {SELECT * FROM t201 WHERE z=5}
-} {0 0 0 {SEARCH TABLE t201 USING INDEX t201z (z=?)}}
+} {/*SEARCH TABLE t201 USING INDEX t201z (z=?)*/}
do_test analyze6-2.2 {
eqp {SELECT * FROM t201 WHERE y=5}
-} {0 0 0 {SEARCH TABLE t201 USING INDEX sqlite_autoindex_t201_1 (y=?)}}
+} {/*SEARCH TABLE t201 USING INDEX sqlite_autoindex_t201_1 (y=?)*/}
do_test analyze6-2.3 {
eqp {SELECT * FROM t201 WHERE x=5}
-} {0 0 0 {SEARCH TABLE t201 USING INTEGER PRIMARY KEY (rowid=?)}}
+} {/*SEARCH TABLE t201 USING INTEGER PRIMARY KEY (rowid=?)*/}
do_test analyze6-2.4 {
execsql {
INSERT INTO t201 VALUES(1,2,3),(2,3,4),(3,4,5);
ANALYZE t201;
}
eqp {SELECT * FROM t201 WHERE z=5}
-} {0 0 0 {SEARCH TABLE t201 USING INDEX t201z (z=?)}}
+} {/*SEARCH TABLE t201 USING INDEX t201z (z=?)*/}
do_test analyze6-2.5 {
eqp {SELECT * FROM t201 WHERE y=5}
-} {0 0 0 {SEARCH TABLE t201 USING INDEX sqlite_autoindex_t201_1 (y=?)}}
+} {/*SEARCH TABLE t201 USING INDEX sqlite_autoindex_t201_1 (y=?)*/}
do_test analyze6-2.6 {
eqp {SELECT * FROM t201 WHERE x=5}
-} {0 0 0 {SEARCH TABLE t201 USING INTEGER PRIMARY KEY (rowid=?)}}
+} {/*SEARCH TABLE t201 USING INTEGER PRIMARY KEY (rowid=?)*/}
do_test analyze6-2.7 {
execsql {
INSERT INTO t201 VALUES(4,5,7);
INSERT INTO t201 SELECT x+100, y+100, z+100 FROM t201;
INSERT INTO t201 SELECT x+200, y+200, z+200 FROM t201;
INSERT INTO t201 SELECT x+400, y+400, z+400 FROM t201;
ANALYZE t201;
}
eqp {SELECT * FROM t201 WHERE z=5}
-} {0 0 0 {SEARCH TABLE t201 USING INDEX t201z (z=?)}}
+} {/*SEARCH TABLE t201 USING INDEX t201z (z=?)*/}
do_test analyze6-2.8 {
eqp {SELECT * FROM t201 WHERE y=5}
-} {0 0 0 {SEARCH TABLE t201 USING INDEX sqlite_autoindex_t201_1 (y=?)}}
+} {/*SEARCH TABLE t201 USING INDEX sqlite_autoindex_t201_1 (y=?)*/}
do_test analyze6-2.9 {
eqp {SELECT * FROM t201 WHERE x=5}
-} {0 0 0 {SEARCH TABLE t201 USING INTEGER PRIMARY KEY (rowid=?)}}
+} {/*SEARCH TABLE t201 USING INTEGER PRIMARY KEY (rowid=?)*/}
finish_test
Index: test/analyze7.test
==================================================================
--- test/analyze7.test
+++ test/analyze7.test
@@ -35,80 +35,80 @@
CREATE VIRTUAL TABLE nums USING wholenumber;
INSERT INTO t1 SELECT value, value, value/100, value FROM nums
WHERE value BETWEEN 1 AND 256;
EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123;
}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/}
do_test analyze7-1.1 {
execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE b=123;}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1b (b=?)*/}
do_test analyze7-1.2 {
execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=2;}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1cd (c=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1cd (c=?)*/}
# Run an analyze on one of the three indices. Verify that this
# effects the row-count estimate on the one query that uses that
# one index.
#
do_test analyze7-2.0 {
execsql {ANALYZE t1a;}
db cache flush
execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123;}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/}
do_test analyze7-2.1 {
execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE b=123;}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1b (b=?)*/}
do_test analyze7-2.2 {
execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=2;}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1cd (c=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1cd (c=?)*/}
# Verify that since the query planner now things that t1a is more
# selective than t1b, it prefers to use t1a.
#
do_test analyze7-2.3 {
execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123 AND b=123}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/}
# Run an analysis on another of the three indices. Verify that this
# new analysis works and does not disrupt the previous analysis.
#
do_test analyze7-3.0 {
execsql {ANALYZE t1cd;}
db cache flush;
execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123;}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/}
do_test analyze7-3.1 {
execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE b=123;}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1b (b=?)*/}
do_test analyze7-3.2.1 {
execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=?;}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1cd (c=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1cd (c=?)*/}
ifcapable stat4||stat3 {
# If ENABLE_STAT4 is defined, SQLite comes up with a different estimated
# row count for (c=2) than it does for (c=?).
do_test analyze7-3.2.2 {
execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=2;}
- } {0 0 0 {SEARCH TABLE t1 USING INDEX t1cd (c=?)}}
+ } {/*SEARCH TABLE t1 USING INDEX t1cd (c=?)*/}
} else {
# If ENABLE_STAT4 is not defined, the expected row count for (c=2) is the
# same as that for (c=?).
do_test analyze7-3.2.3 {
execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=2;}
- } {0 0 0 {SEARCH TABLE t1 USING INDEX t1cd (c=?)}}
+ } {/*SEARCH TABLE t1 USING INDEX t1cd (c=?)*/}
}
do_test analyze7-3.3 {
execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123 AND b=123}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/}
ifcapable {!stat4 && !stat3} {
do_test analyze7-3.4 {
execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=123 AND b=123}
- } {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b=?)}}
+ } {/*SEARCH TABLE t1 USING INDEX t1b (b=?)*/}
do_test analyze7-3.5 {
execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123 AND c=123}
- } {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?)}}
+ } {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/}
}
do_test analyze7-3.6 {
execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=123 AND d=123 AND b=123}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1cd (c=? AND d=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1cd (c=? AND d=?)*/}
finish_test
Index: test/analyze8.test
==================================================================
--- test/analyze8.test
+++ test/analyze8.test
@@ -59,29 +59,29 @@
# Buf ro a==99 and a==101, there are far fewer rows so choose
# the t1a index.
#
do_test 1.1 {
eqp {SELECT * FROM t1 WHERE a=100 AND b=55}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1b (b=?)*/}
do_test 1.2 {
eqp {SELECT * FROM t1 WHERE a=99 AND b=55}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/}
do_test 1.3 {
eqp {SELECT * FROM t1 WHERE a=101 AND b=55}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/}
do_test 1.4 {
eqp {SELECT * FROM t1 WHERE a=100 AND b=56}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1b (b=?)*/}
do_test 1.5 {
eqp {SELECT * FROM t1 WHERE a=99 AND b=56}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/}
do_test 1.6 {
eqp {SELECT * FROM t1 WHERE a=101 AND b=56}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/}
do_test 2.1 {
eqp {SELECT * FROM t1 WHERE a=100 AND b BETWEEN 50 AND 54}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b>? AND b)}}
+} {/*SEARCH TABLE t1 USING INDEX t1b (b>? AND b)*/}
# There are many more values of c between 0 and 100000 than there are
# between 800000 and 900000. So t1c is more selective for the latter
# range.
#
@@ -97,19 +97,19 @@
SELECT count(*) FROM t1 WHERE c BETWEEN 0 AND 100000;
SELECT count(*) FROM t1 WHERE c BETWEEN 800000 AND 900000;
} {50 376 32}
do_test 3.1 {
eqp {SELECT * FROM t1 WHERE b BETWEEN 30 AND 34 AND c BETWEEN 0 AND 100000}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b>? AND b)}}
+} {/*SEARCH TABLE t1 USING INDEX t1b (b>? AND b)*/}
do_test 3.2 {
eqp {SELECT * FROM t1
WHERE b BETWEEN 30 AND 34 AND c BETWEEN 800000 AND 900000}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1c (c>? AND c)}}
+} {/*SEARCH TABLE t1 USING INDEX t1c (c>? AND c)*/}
do_test 3.3 {
eqp {SELECT * FROM t1 WHERE a=100 AND c BETWEEN 0 AND 100000}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?)}}
+} {/*SEARCH TABLE t1 USING INDEX t1a (a=?)*/}
do_test 3.4 {
eqp {SELECT * FROM t1
WHERE a=100 AND c BETWEEN 800000 AND 900000}
-} {0 0 0 {SEARCH TABLE t1 USING INDEX t1c (c>? AND c)}}
+} {/*SEARCH TABLE t1 USING INDEX t1c (c>? AND c)*/}
finish_test
Index: test/analyze9.test
==================================================================
--- test/analyze9.test
+++ test/analyze9.test
@@ -985,11 +985,12 @@
#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 22.0 {
CREATE TABLE t3(a, b, c, d, PRIMARY KEY(a, b)) WITHOUT ROWID;
-}
+ SELECT * FROM t3;
+} {}
do_execsql_test 22.1 {
WITH r(x) AS (
SELECT 1
UNION ALL
SELECT x+1 FROM r WHERE x<=100
@@ -1053,19 +1054,15 @@
SELECT * FROM t4 WHERE
(e=1 AND b='xyz' AND c='zyx' AND a<'AEA') AND f<300
-- Formerly used index i41. But i41 is not a covering index whereas
-- the PRIMARY KEY is a covering index, and so as of 2017-10-15, the
-- PRIMARY KEY is preferred.
-} {
- 0 0 0 {SEARCH TABLE t4 USING PRIMARY KEY (c=? AND b=? AND a)}
-}
+} {SEARCH TABLE t4 USING PRIMARY KEY (c=? AND b=? AND a)}
do_eqp_test 23.2 {
SELECT * FROM t4 WHERE
(e=1 AND b='xyz' AND c='zyx' AND a<'JJJ') AND f<300
-} {
- 0 0 0 {SEARCH TABLE t4 USING INDEX i42 (f)}
-}
+} {SEARCH TABLE t4 USING INDEX i42 (f)}
do_execsql_test 24.0 {
CREATE TABLE t5(c, d, b, e, a, PRIMARY KEY(a, b, c)) WITHOUT ROWID;
WITH data(a, b, c, d, e) AS (
SELECT 'z', 'y', 0, 0, 0
@@ -1106,37 +1103,30 @@
ANALYZE;
}
# Term (b) is estimated at 25%. Better than (a<30) but not as
# good as (a<20).
- do_eqp_test 25.2.1 { SELECT * FROM t6 WHERE a<30 AND b } {
- 0 0 0 {SEARCH TABLE t6 USING INDEX bb (b)}
- }
- do_eqp_test 25.2.2 { SELECT * FROM t6 WHERE a<20 AND b } {
- 0 0 0 {SEARCH TABLE t6 USING INDEX aa (a)}
- }
+ do_eqp_test 25.2.1 { SELECT * FROM t6 WHERE a<30 AND b } \
+ {SEARCH TABLE t6 USING INDEX bb (b)}
+ do_eqp_test 25.2.2 { SELECT * FROM t6 WHERE a<20 AND b } \
+ {SEARCH TABLE t6 USING INDEX aa (a)}
# Term (b BETWEEN ? AND ?) is estimated at 1/64.
do_eqp_test 25.3.1 {
SELECT * FROM t6 WHERE a BETWEEN 5 AND 10 AND b BETWEEN ? AND ?
- } {
- 0 0 0 {SEARCH TABLE t6 USING INDEX bb (b>? AND b)}
- }
+ } {SEARCH TABLE t6 USING INDEX bb (b>? AND b)}
# Term (b BETWEEN ? AND 60) is estimated to return roughly 15 rows -
# 60 from (b<=60) multiplied by 0.25 for the b>=? term. Better than
# (a<20) but not as good as (a<10).
do_eqp_test 25.4.1 {
SELECT * FROM t6 WHERE a < 10 AND (b BETWEEN ? AND 60)
- } {
- 0 0 0 {SEARCH TABLE t6 USING INDEX aa (a)}
- }
+ } {SEARCH TABLE t6 USING INDEX aa (a)}
+
do_eqp_test 25.4.2 {
SELECT * FROM t6 WHERE a < 20 AND (b BETWEEN ? AND 60)
- } {
- 0 0 0 {SEARCH TABLE t6 USING INDEX bb (b>? AND b)}
- }
+ } {SEARCH TABLE t6 USING INDEX bb (b>? AND b)}
}
#-------------------------------------------------------------------------
# Check that a problem in they way stat4 data is used has been
# resolved (see below).
@@ -1188,13 +1178,11 @@
# At one point though, due to a problem in whereKeyStats(), the planner was
# estimating that (x=10000 AND y<50) would match only 2 rows.
#
do_eqp_test 26.1.4 {
SELECT * FROM t1 WHERE x = 10000 AND y < 50 AND z = 444;
-} {
- 0 0 0 {SEARCH TABLE t1 USING INDEX t1z (z=?)}
-}
+} {SEARCH TABLE t1 USING INDEX t1z (z=?)}
# This test - 26.2.* - tests that another manifestation of the same problem
# is no longer present in the library. Assuming:
#
@@ -1239,11 +1227,9 @@
COMMIT;
}
do_eqp_test 26.2.2 {
SELECT * FROM t1 WHERE x='B' AND y>25 AND z=?;
-} {
- 0 0 0 {SEARCH TABLE t1 USING INDEX i1 (x=? AND y>?)}
-}
+} {SEARCH TABLE t1 USING INDEX i1 (x=? AND y>?)}
finish_test
Index: test/analyzeA.test
==================================================================
--- test/analyzeA.test
+++ test/analyzeA.test
@@ -134,14 +134,14 @@
do_execsql_test 1.$tn.2.3 { SELECT count(*) FROM t1 WHERE b=125 } 49
do_execsql_test 1.$tn.2.4 { SELECT count(*) FROM t1 WHERE c=16 } 1
do_eqp_test 1.$tn.2.5 {
SELECT * FROM t1 WHERE b = 31 AND c = 0;
- } {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b=?)}}
+ } {SEARCH TABLE t1 USING INDEX t1b (b=?)}
do_eqp_test 1.$tn.2.6 {
SELECT * FROM t1 WHERE b = 125 AND c = 16;
- } {0 0 0 {SEARCH TABLE t1 USING INDEX t1c (c=?)}}
+ } {SEARCH TABLE t1 USING INDEX t1c (c=?)}
do_execsql_test 1.$tn.3.1 {
SELECT count(*) FROM t1 WHERE b BETWEEN 0 AND 50
} {6}
do_execsql_test 1.$tn.3.2 {
@@ -154,33 +154,33 @@
SELECT count(*) FROM t1 WHERE c BETWEEN 75 AND 125
} {6}
do_eqp_test 1.$tn.3.5 {
SELECT * FROM t1 WHERE b BETWEEN 0 AND 50 AND c BETWEEN 0 AND 50
- } {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b>? AND b)}}
+ } {SEARCH TABLE t1 USING INDEX t1b (b>? AND b)}
do_eqp_test 1.$tn.3.6 {
SELECT * FROM t1 WHERE b BETWEEN 75 AND 125 AND c BETWEEN 75 AND 125
- } {0 0 0 {SEARCH TABLE t1 USING INDEX t1c (c>? AND c)}}
+ } {SEARCH TABLE t1 USING INDEX t1c (c>? AND c)}
do_eqp_test 1.$tn.3.7 {
SELECT * FROM t1 WHERE b BETWEEN +0 AND +50 AND c BETWEEN +0 AND +50
- } {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b>? AND b)}}
+ } {SEARCH TABLE t1 USING INDEX t1b (b>? AND b)}
do_eqp_test 1.$tn.3.8 {
SELECT * FROM t1
WHERE b BETWEEN cast('0' AS int) AND cast('50.0' AS real)
AND c BETWEEN cast('0' AS numeric) AND cast('50.0' AS real)
- } {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b>? AND b)}}
+ } {SEARCH TABLE t1 USING INDEX t1b (b>? AND b)}
do_eqp_test 1.$tn.3.9 {
SELECT * FROM t1 WHERE b BETWEEN +75 AND +125 AND c BETWEEN +75 AND +125
- } {0 0 0 {SEARCH TABLE t1 USING INDEX t1c (c>? AND c)}}
+ } {SEARCH TABLE t1 USING INDEX t1c (c>? AND c)}
do_eqp_test 1.$tn.3.10 {
SELECT * FROM t1
WHERE b BETWEEN cast('75' AS int) AND cast('125.0' AS real)
AND c BETWEEN cast('75' AS numeric) AND cast('125.0' AS real)
- } {0 0 0 {SEARCH TABLE t1 USING INDEX t1c (c>? AND c)}}
+ } {SEARCH TABLE t1 USING INDEX t1c (c>? AND c)}
}
finish_test
Index: test/analyzeD.test
==================================================================
--- test/analyzeD.test
+++ test/analyzeD.test
@@ -61,13 +61,11 @@
# With full ANALYZE data, SQLite sees that c=150 (5 rows) is better than
# a=3001 (7 rows).
#
do_eqp_test 1.2 {
SELECT * FROM t1 WHERE a=3001 AND c=150;
-} {
- 0 0 0 {SEARCH TABLE t1 USING INDEX t1_c (c=?)}
-}
+} {SEARCH TABLE t1 USING INDEX t1_c (c=?)}
do_test 1.3 {
execsql { DELETE FROM sqlite_stat1 }
db close
sqlite3 db test.db
@@ -78,13 +76,11 @@
# chooses it over the c=150 index (5 rows). Even with stat1 data, things
# worked this way before commit [e6f7f97dbc].
#
do_eqp_test 1.4 {
SELECT * FROM t1 WHERE a=3001 AND c=150;
-} {
- 0 0 0 {SEARCH TABLE t1 USING INDEX t1_ab (a=?)}
-}
+} {SEARCH TABLE t1 USING INDEX t1_ab (a=?)}
do_test 1.5 {
execsql {
UPDATE t1 SET a=13 WHERE a = 3001;
ANALYZE;
@@ -91,13 +87,11 @@
}
} {}
do_eqp_test 1.6 {
SELECT * FROM t1 WHERE a=13 AND c=150;
-} {
- 0 0 0 {SEARCH TABLE t1 USING INDEX t1_c (c=?)}
-}
+} {SEARCH TABLE t1 USING INDEX t1_c (c=?)}
do_test 1.7 {
execsql { DELETE FROM sqlite_stat1 }
db close
sqlite3 db test.db
@@ -106,10 +100,8 @@
# Same test as 1.4, except this time the 7 rows that match the a=? condition
# do not feature larger values than all rows in the stat4 table. So SQLite
# gets this right, even without stat1 data.
do_eqp_test 1.8 {
SELECT * FROM t1 WHERE a=13 AND c=150;
-} {
- 0 0 0 {SEARCH TABLE t1 USING INDEX t1_c (c=?)}
-}
+} {SEARCH TABLE t1 USING INDEX t1_c (c=?)}
finish_test
Index: test/analyzeF.test
==================================================================
--- test/analyzeF.test
+++ test/analyzeF.test
@@ -60,11 +60,11 @@
10 "x = str('4') AND y = str('19')" {t1y (y=?)}
11 "x = nullif('19', 0) AND y = nullif('4', 0)" {t1y (y=?)}
12 "x = nullif('4', 0) AND y = nullif('19', 0)" {t1y (y=?)}
} {
- set res "0 0 0 {SEARCH TABLE t1 USING INDEX $idx}"
+ set res "SEARCH TABLE t1 USING INDEX $idx"
do_eqp_test 1.$tn "SELECT * FROM t1 WHERE $where" $res
}
# Test that functions that do not exist - "func()" - do not cause an error.
#
@@ -90,11 +90,11 @@
2 "x = det19() AND y = det4()" {t1y (y=?)}
3 "x = nondet4() AND y = nondet19()" {t1y (y=?)}
4 "x = nondet19() AND y = nondet4()" {t1y (y=?)}
} {
- set res "0 0 0 {SEARCH TABLE t1 USING INDEX $idx}"
+ set res "SEARCH TABLE t1 USING INDEX $idx"
do_eqp_test 3.$tn "SELECT * FROM t1 WHERE $where" $res
}
execsql { DELETE FROM t1 }
Index: test/autoinc.test
==================================================================
--- test/autoinc.test
+++ test/autoinc.test
@@ -673,8 +673,15 @@
CREATE TABLE t10b(x INTEGER PRIMARY KEY AUTOINCREMENT, y UNIQUE);
INSERT INTO t10b SELECT * FROM t10a;
SELECT * FROM sqlite_sequence;
} {t10a 888 t10b 888}
-
+# 2018-04-21 autoincrement does not cause problems for upsert
+#
+do_execsql_test autoinc-11.1 {
+ CREATE TABLE t11(a INTEGER PRIMARY KEY AUTOINCREMENT,b UNIQUE);
+ INSERT INTO t11(a,b) VALUES(2,3),(5,6),(4,3),(1,2)
+ ON CONFLICT(b) DO UPDATE SET a=a+1000;
+ SELECT seq FROM sqlite_sequence WHERE name='t11';
+} {5}
finish_test
Index: test/autoindex1.test
==================================================================
--- test/autoindex1.test
+++ test/autoindex1.test
@@ -175,38 +175,39 @@
CREATE TABLE t501(a INTEGER PRIMARY KEY, b);
CREATE TABLE t502(x INTEGER PRIMARY KEY, y);
INSERT INTO sqlite_stat1(tbl,idx,stat) VALUES('t501',null,'1000000');
INSERT INTO sqlite_stat1(tbl,idx,stat) VALUES('t502',null,'1000');
ANALYZE sqlite_master;
- EXPLAIN QUERY PLAN
+}
+do_eqp_test autoindex1-500.1 {
SELECT b FROM t501
WHERE t501.a IN (SELECT x FROM t502 WHERE y=?);
} {
- 0 0 0 {SEARCH TABLE t501 USING INTEGER PRIMARY KEY (rowid=?)}
- 0 0 0 {EXECUTE LIST SUBQUERY 1}
- 1 0 0 {SCAN TABLE t502}
+ QUERY PLAN
+ |--SEARCH TABLE t501 USING INTEGER PRIMARY KEY (rowid=?)
+ `--LIST SUBQUERY
+ `--SCAN TABLE t502
}
-do_execsql_test autoindex1-501 {
- EXPLAIN QUERY PLAN
+do_eqp_test autoindex1-501 {
SELECT b FROM t501
WHERE t501.a IN (SELECT x FROM t502 WHERE y=t501.b);
} {
- 0 0 0 {SCAN TABLE t501}
- 0 0 0 {EXECUTE CORRELATED LIST SUBQUERY 1}
- 1 0 0 {SEARCH TABLE t502 USING AUTOMATIC COVERING INDEX (y=?)}
+ QUERY PLAN
+ |--SCAN TABLE t501
+ `--CORRELATED LIST SUBQUERY
+ `--SEARCH TABLE t502 USING AUTOMATIC COVERING INDEX (y=?)
}
-do_execsql_test autoindex1-502 {
- EXPLAIN QUERY PLAN
+do_eqp_test autoindex1-502 {
SELECT b FROM t501
WHERE t501.a=123
AND t501.a IN (SELECT x FROM t502 WHERE y=t501.b);
} {
- 0 0 0 {SEARCH TABLE t501 USING INTEGER PRIMARY KEY (rowid=?)}
- 0 0 0 {EXECUTE CORRELATED LIST SUBQUERY 1}
- 1 0 0 {SCAN TABLE t502}
+ QUERY PLAN
+ |--SEARCH TABLE t501 USING INTEGER PRIMARY KEY (rowid=?)
+ `--CORRELATED LIST SUBQUERY
+ `--SCAN TABLE t502
}
-
# The following code checks a performance regression reported on the
# mailing list on 2010-10-19. The problem is that the nRowEst field
# of ephermeral tables was not being initialized correctly and so no
# automatic index was being created for the emphemeral table when it was
@@ -255,11 +256,12 @@
ON flock_owner (owner_person_id);
CREATE INDEX sheep_org_flock_index
ON sheep (originating_flock);
CREATE INDEX sheep_reg_flock_index
ON sheep (registering_flock);
- EXPLAIN QUERY PLAN
+}
+do_eqp_test autoindex1-600a {
SELECT x.sheep_no, x.registering_flock, x.date_of_registration
FROM sheep x LEFT JOIN
(SELECT s.sheep_no, prev.flock_no, prev.owner_person_id,
s.date_of_registration, prev.owner_change_date
FROM sheep s JOIN flock_owner prev ON s.registering_flock =
@@ -272,25 +274,30 @@
AND later.owner_change_date <= s.date_of_registration||' 00:00:00')
) y ON x.sheep_no = y.sheep_no
WHERE y.sheep_no IS NULL
ORDER BY x.registering_flock;
} {
- 1 0 0 {SCAN TABLE sheep AS s}
- 1 1 1 {SEARCH TABLE flock_owner AS prev USING INDEX sqlite_autoindex_flock_owner_1 (flock_no=? AND owner_change_date)}
- 1 0 0 {EXECUTE CORRELATED SCALAR SUBQUERY 2}
- 2 0 0 {SEARCH TABLE flock_owner AS later USING COVERING INDEX sqlite_autoindex_flock_owner_1 (flock_no=? AND owner_change_date>? AND owner_change_date)}
- 0 0 0 {SCAN TABLE sheep AS x USING INDEX sheep_reg_flock_index}
- 0 1 1 {SEARCH SUBQUERY 1 AS y USING AUTOMATIC COVERING INDEX (sheep_no=?)}
+ QUERY PLAN
+ |--MATERIALIZE xxxxxx
+ | |--SCAN TABLE sheep AS s
+ | |--SEARCH TABLE flock_owner AS prev USING INDEX sqlite_autoindex_flock_owner_1 (flock_no=? AND owner_change_date)
+ | `--CORRELATED SCALAR SUBQUERY
+ | `--SEARCH TABLE flock_owner AS later USING COVERING INDEX sqlite_autoindex_flock_owner_1 (flock_no=? AND owner_change_date>? AND owner_change_date)
+ |--SCAN TABLE sheep AS x USING INDEX sheep_reg_flock_index
+ `--SEARCH SUBQUERY xxxxxx AS y USING AUTOMATIC COVERING INDEX (sheep_no=?)
}
do_execsql_test autoindex1-700 {
CREATE TABLE t5(a, b, c);
- EXPLAIN QUERY PLAN SELECT a FROM t5 WHERE b=10 ORDER BY c;
+}
+do_eqp_test autoindex1-700a {
+ SELECT a FROM t5 WHERE b=10 ORDER BY c;
} {
- 0 0 0 {SCAN TABLE t5}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SCAN TABLE t5
+ `--USE TEMP B-TREE FOR ORDER BY
}
# The following checks a performance issue reported on the sqlite-dev
# mailing list on 2013-01-10
#
Index: test/autoindex3.test
==================================================================
--- test/autoindex3.test
+++ test/autoindex3.test
@@ -82,11 +82,12 @@
# automatic index scan.
#
do_eqp_test 220 {
select count(*) from u, v where u.b = v.b and v.e > 34;
} {
- 0 0 1 {SEARCH TABLE v USING INDEX ve (e>?)}
- 0 1 0 {SEARCH TABLE u USING AUTOMATIC COVERING INDEX (b=?)}
+ QUERY PLAN
+ |--SEARCH TABLE v USING INDEX ve (e>?)
+ `--SEARCH TABLE u USING AUTOMATIC COVERING INDEX (b=?)
}
finish_test
Index: test/autoindex5.test
==================================================================
--- test/autoindex5.test
+++ test/autoindex5.test
@@ -82,12 +82,11 @@
} {}
# The following query should use an automatic index for the view
# in FROM clause of the subquery of the second result column.
#
-do_execsql_test autoindex5-1.1 {
- EXPLAIN QUERY PLAN
+do_eqp_test autoindex5-1.1 {
SELECT
st.bug_name,
(SELECT ALL debian_cve.bug FROM debian_cve
WHERE debian_cve.bug_name = st.bug_name
ORDER BY debian_cve.bug),
@@ -101,11 +100,11 @@
AND st.bug_name = bugs.name
AND ( st.bug_name LIKE 'CVE-%' OR st.bug_name LIKE 'TEMP-%' )
AND ( sp.release = 'sid' OR sp.release = 'stretch' OR sp.release = 'jessie'
OR sp.release = 'wheezy' OR sp.release = 'squeeze' )
ORDER BY sp.name, st.bug_name, sp.release, sp.subrelease;
-} {/SEARCH SUBQUERY 2 USING AUTOMATIC COVERING INDEX .bug_name=/}
+} {SEARCH SUBQUERY * USING AUTOMATIC COVERING INDEX (bug_name=?)}
#-------------------------------------------------------------------------
# Test that ticket [8a2adec1] has been fixed.
#
do_execsql_test 2.1 {
Index: test/bestindex1.test
==================================================================
--- test/bestindex1.test
+++ test/bestindex1.test
@@ -49,20 +49,15 @@
CREATE VIRTUAL TABLE x1 USING tcl(vtab_command);
} {}
do_eqp_test 1.1 {
SELECT * FROM x1 WHERE a = 'abc'
-} {
- 0 0 0 {SCAN TABLE x1 VIRTUAL TABLE INDEX 555:eq!}
-}
+} {SCAN TABLE x1 VIRTUAL TABLE INDEX 555:eq!}
do_eqp_test 1.2 {
SELECT * FROM x1 WHERE a IN ('abc', 'def');
-} {
- 0 0 0 {SCAN TABLE x1 VIRTUAL TABLE INDEX 555:eq!}
- 0 0 0 {EXECUTE LIST SUBQUERY 1}
-}
+} {SCAN TABLE x1 VIRTUAL TABLE INDEX 555:eq!}
#-------------------------------------------------------------------------
#
reset_db
register_tcl_module db
@@ -143,28 +138,28 @@
do_execsql_test 2.2.$mode.5 {
SELECT rowid FROM t1 WHERE a IN ('one', 'four') ORDER BY +rowid
} {1 4}
set plan(use) {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x WHERE a='%1%'}
- 0 0 0 {EXECUTE LIST SUBQUERY 1}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x WHERE a='%1%'
+ `--USE TEMP B-TREE FOR ORDER BY
}
set plan(omit) {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x WHERE a='%1%'}
- 0 0 0 {EXECUTE LIST SUBQUERY 1}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x WHERE a='%1%'
+ `--USE TEMP B-TREE FOR ORDER BY
}
set plan(use2) {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x}
- 0 0 0 {EXECUTE LIST SUBQUERY 1}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:SELECT * FROM t1x
+ `--USE TEMP B-TREE FOR ORDER BY
}
do_eqp_test 2.2.$mode.6 {
SELECT rowid FROM t1 WHERE a IN ('one', 'four') ORDER BY +rowid
- } $plan($mode)
+ } [string map {"\n " "\n"} $plan($mode)]
}
# 2016-04-09.
# Demonstrate a register overwrite problem when using two virtual
# tables where the outer loop uses the IN operator.
Index: test/bestindex2.test
==================================================================
--- test/bestindex2.test
+++ test/bestindex2.test
@@ -87,55 +87,56 @@
CREATE VIRTUAL TABLE t3 USING tcl("vtab_cmd t3 {e f}");
}
do_eqp_test 1.1 {
SELECT * FROM t1 WHERE a='abc'
-} {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:indexed(a=?)}
-}
+} {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:indexed(a=?)}
+
do_eqp_test 1.2 {
SELECT * FROM t1 WHERE a='abc' AND b='def'
-} {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:indexed(a=? AND b=?)}
-}
+} {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:indexed(a=? AND b=?)}
+
do_eqp_test 1.3 {
SELECT * FROM t1 WHERE a='abc' AND a='def'
-} {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:indexed(a=?)}
-}
+} {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:indexed(a=?)}
+
do_eqp_test 1.4 {
SELECT * FROM t1,t2 WHERE c=a
} {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:}
- 0 1 1 {SCAN TABLE t2 VIRTUAL TABLE INDEX 0:indexed(c=?)}
+ QUERY PLAN
+ |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:
+ `--SCAN TABLE t2 VIRTUAL TABLE INDEX 0:indexed(c=?)
}
do_eqp_test 1.5 {
SELECT * FROM t1, t2 CROSS JOIN t3 WHERE t2.c = +t1.b AND t3.e=t2.d
} {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:}
- 0 1 1 {SCAN TABLE t2 VIRTUAL TABLE INDEX 0:indexed(c=?)}
- 0 2 2 {SCAN TABLE t3 VIRTUAL TABLE INDEX 0:indexed(e=?)}
+ QUERY PLAN
+ |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:
+ |--SCAN TABLE t2 VIRTUAL TABLE INDEX 0:indexed(c=?)
+ `--SCAN TABLE t3 VIRTUAL TABLE INDEX 0:indexed(e=?)
}
do_eqp_test 1.6 {
SELECT * FROM t1, t2, t3 WHERE t2.c = +t1.b AND t3.e = t2.d
} {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:}
- 0 1 1 {SCAN TABLE t2 VIRTUAL TABLE INDEX 0:indexed(c=?)}
- 0 2 2 {SCAN TABLE t3 VIRTUAL TABLE INDEX 0:indexed(e=?)}
+ QUERY PLAN
+ |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:
+ |--SCAN TABLE t2 VIRTUAL TABLE INDEX 0:indexed(c=?)
+ `--SCAN TABLE t3 VIRTUAL TABLE INDEX 0:indexed(e=?)
}
do_execsql_test 1.7.1 {
CREATE TABLE x1(a, b);
}
do_eqp_test 1.7.2 {
SELECT * FROM x1 CROSS JOIN t1, t2, t3
WHERE t1.a = t2.c AND t1.b = t3.e
} {
- 0 0 0 {SCAN TABLE x1}
- 0 1 1 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:}
- 0 2 2 {SCAN TABLE t2 VIRTUAL TABLE INDEX 0:indexed(c=?)}
- 0 3 3 {SCAN TABLE t3 VIRTUAL TABLE INDEX 0:indexed(e=?)}
+ QUERY PLAN
+ |--SCAN TABLE x1
+ |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:
+ |--SCAN TABLE t2 VIRTUAL TABLE INDEX 0:indexed(c=?)
+ `--SCAN TABLE t3 VIRTUAL TABLE INDEX 0:indexed(e=?)
}
finish_test
Index: test/bestindex3.test
==================================================================
--- test/bestindex3.test
+++ test/bestindex3.test
@@ -77,32 +77,32 @@
CREATE VIRTUAL TABLE t1 USING tcl("vtab_cmd 0");
}
do_eqp_test 1.1 {
SELECT * FROM t1 WHERE a LIKE 'abc';
-} {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a LIKE ?}
-}
+} {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a LIKE ?}
do_eqp_test 1.2 {
SELECT * FROM t1 WHERE a = 'abc';
-} {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a EQ ?}
-}
+} {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a EQ ?}
do_eqp_test 1.3 {
SELECT * FROM t1 WHERE a = 'abc' OR b = 'def';
} {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a EQ ?}
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:b EQ ?}
+ QUERY PLAN
+ `--MULTI-INDEX OR
+ |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a EQ ?
+ `--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:b EQ ?
}
do_eqp_test 1.4 {
SELECT * FROM t1 WHERE a LIKE 'abc%' OR b = 'def';
} {
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a LIKE ?}
- 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:b EQ ?}
+ QUERY PLAN
+ `--MULTI-INDEX OR
+ |--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a LIKE ?
+ `--SCAN TABLE t1 VIRTUAL TABLE INDEX 0:b EQ ?
}
do_execsql_test 1.5 {
CREATE TABLE ttt(a, b, c);
@@ -145,14 +145,16 @@
CREATE INDEX t2y ON t2(y);
}
do_eqp_test 2.2 {
SELECT * FROM t2 WHERE x LIKE 'abc%' OR y = 'def'
- } {
- 0 0 0 {SEARCH TABLE t2 USING INDEX t2x (x>? AND x)}
- 0 0 0 {SEARCH TABLE t2 USING INDEX t2y (y=?)}
- }
+ } [string map {"\n " \n} {
+ QUERY PLAN
+ `--MULTI-INDEX OR
+ |--SEARCH TABLE t2 USING INDEX t2x (x>? AND x)
+ `--SEARCH TABLE t2 USING INDEX t2y (y=?)
+ }]
}
#-------------------------------------------------------------------------
# Test that any PRIMARY KEY within a sqlite3_decl_vtab() CREATE TABLE
# statement is currently ignored.
Index: test/bigmmap.test
==================================================================
--- test/bigmmap.test
+++ test/bigmmap.test
@@ -90,15 +90,15 @@
do_eqp_test 2.$i.$t.3 "
SELECT * FROM t$t AS o WHERE
NOT EXISTS( SELECT * FROM t$t AS i WHERE a=o.a AND +b=o.b AND +c=o.c )
ORDER BY b, c;
- " "
- 0 0 0 {SCAN TABLE t$t AS o USING COVERING INDEX sqlite_autoindex_t${t}_1}
- 0 0 0 {EXECUTE CORRELATED SCALAR SUBQUERY 1}
- 1 0 0 {SEARCH TABLE t$t AS i USING INTEGER PRIMARY KEY (rowid=?)}
- "
+ " [string map {"\n " "\n"} "
+ QUERY PLAN
+ |--SCAN TABLE t$t AS o USING COVERING INDEX sqlite_autoindex_t${t}_1
+ `--CORRELATED SCALAR SUBQUERY
+ `--SEARCH TABLE t$t AS i USING INTEGER PRIMARY KEY (rowid=?)
+ "]
}
}
finish_test
-
Index: test/closure01.test
==================================================================
--- test/closure01.test
+++ test/closure01.test
@@ -270,7 +270,26 @@
AND tablename='t1'
AND idcolumn='x'
AND parentcolumn='y'
ORDER BY id;
} {8 9 10 11 12 13 14 15}
+
+#-------------------------------------------------------------------------
+# At one point the following join query was causing a malfunction in
+# xBestIndex.
+#
+do_execsql_test 6.0 {
+ CREATE TABLE t4 (
+ id INTEGER PRIMARY KEY,
+ name TEXT NOT NULL,
+ parent_id INTEGER
+ );
+ CREATE VIRTUAL TABLE vt4 USING transitive_closure (
+ idcolumn=id, parentcolumn=parent_id, tablename=t4
+ );
+}
+
+do_execsql_test 6.1 {
+ SELECT * FROM t4, vt4 WHERE t4.id = vt4.root AND vt4.id=4 AND vt4.depth=2;
+}
finish_test
Index: test/cost.test
==================================================================
--- test/cost.test
+++ test/cost.test
@@ -22,12 +22,13 @@
CREATE UNIQUE INDEX i4 ON t4(c, d);
}
do_eqp_test 1.2 {
SELECT e FROM t3, t4 WHERE b=c ORDER BY b, d;
} {
- 0 0 0 {SCAN TABLE t3 USING COVERING INDEX i3}
- 0 1 1 {SEARCH TABLE t4 USING INDEX i4 (c=?)}
+ QUERY PLAN
+ |--SCAN TABLE t3 USING COVERING INDEX i3
+ `--SEARCH TABLE t4 USING INDEX i4 (c=?)
}
do_execsql_test 2.1 {
CREATE TABLE t1(a, b);
@@ -36,13 +37,11 @@
# It is better to use an index for ORDER BY than sort externally, even
# if the index is a non-covering index.
do_eqp_test 2.2 {
SELECT * FROM t1 ORDER BY a;
-} {
- 0 0 0 {SCAN TABLE t1 USING INDEX i1}
-}
+} {SCAN TABLE t1 USING INDEX i1}
do_execsql_test 3.1 {
CREATE TABLE t5(a INTEGER PRIMARY KEY,b,c,d,e,f,g);
CREATE INDEX t5b ON t5(b);
CREATE INDEX t5c ON t5(c);
@@ -55,14 +54,16 @@
do_eqp_test 3.2 {
SELECT a FROM t5
WHERE b IS NULL OR c IS NULL OR d IS NULL
ORDER BY a;
} {
- 0 0 0 {SEARCH TABLE t5 USING INDEX t5b (b=?)}
- 0 0 0 {SEARCH TABLE t5 USING INDEX t5c (c=?)}
- 0 0 0 {SEARCH TABLE t5 USING INDEX t5d (d=?)}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--MULTI-INDEX OR
+ | |--SEARCH TABLE t5 USING INDEX t5b (b=?)
+ | |--SEARCH TABLE t5 USING INDEX t5c (c=?)
+ | `--SEARCH TABLE t5 USING INDEX t5d (d=?)
+ `--USE TEMP B-TREE FOR ORDER BY
}
#-------------------------------------------------------------------------
# If there is no likelihood() or stat3 data, SQLite assumes that a closed
# range scan (e.g. one constrained by "col BETWEEN ? AND ?" constraint)
@@ -77,18 +78,15 @@
CREATE INDEX i1 ON t1(a);
CREATE INDEX i2 ON t1(b);
}
do_eqp_test 4.2 {
SELECT * FROM t1 WHERE likelihood(a=?, 0.014) AND b BETWEEN ? AND ?;
-} {
- 0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}
-}
+} {SEARCH TABLE t1 USING INDEX i1 (a=?)}
+
do_eqp_test 4.3 {
SELECT * FROM t1 WHERE likelihood(a=?, 0.016) AND b BETWEEN ? AND ?;
-} {
- 0 0 0 {SEARCH TABLE t1 USING INDEX i2 (b>? AND b)}
-}
+} {SEARCH TABLE t1 USING INDEX i2 (b>? AND b)}
#-------------------------------------------------------------------------
#
reset_db
@@ -98,19 +96,21 @@
}
do_eqp_test 5.2 {
SELECT * FROM t2 ORDER BY x, y;
} {
- 0 0 0 {SCAN TABLE t2 USING INDEX t2i1}
- 0 0 0 {USE TEMP B-TREE FOR RIGHT PART OF ORDER BY}
+ QUERY PLAN
+ |--SCAN TABLE t2 USING INDEX t2i1
+ `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY
}
do_eqp_test 5.3 {
SELECT * FROM t2 WHERE x BETWEEN ? AND ? ORDER BY rowid;
} {
- 0 0 0 {SEARCH TABLE t2 USING INDEX t2i1 (x>? AND x)}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SEARCH TABLE t2 USING INDEX t2i1 (x>? AND x)
+ `--USE TEMP B-TREE FOR ORDER BY
}
# where7.test, where8.test:
#
do_execsql_test 6.1 {
@@ -120,13 +120,15 @@
}
do_eqp_test 6.2 {
SELECT a FROM t3 WHERE (b BETWEEN 2 AND 4) OR c=100 ORDER BY a
} {
- 0 0 0 {SEARCH TABLE t3 USING INDEX t3i1 (b>? AND b)}
- 0 0 0 {SEARCH TABLE t3 USING INDEX t3i2 (c=?)}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--MULTI-INDEX OR
+ | |--SEARCH TABLE t3 USING INDEX t3i1 (b>? AND b)
+ | `--SEARCH TABLE t3 USING INDEX t3i2 (c=?)
+ `--USE TEMP B-TREE FOR ORDER BY
}
#-------------------------------------------------------------------------
#
reset_db
@@ -143,29 +145,27 @@
do_eqp_test 7.2 {
SELECT a FROM t1
WHERE (b>=950 AND b<=1010) OR (b IS NULL AND c NOT NULL)
ORDER BY a
} {
- 0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b>? AND b)}
- 0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b=?)}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--MULTI-INDEX OR
+ | |--SEARCH TABLE t1 USING INDEX t1b (b>? AND b)
+ | `--SEARCH TABLE t1 USING INDEX t1b (b=?)
+ `--USE TEMP B-TREE FOR ORDER BY
}
do_eqp_test 7.3 {
SELECT rowid FROM t1
WHERE (+b IS NULL AND c NOT NULL AND d NOT NULL)
OR (b NOT NULL AND c IS NULL AND d NOT NULL)
OR (b NOT NULL AND c NOT NULL AND d IS NULL)
-} {
- 0 0 0 {SCAN TABLE t1}
-}
+} {SCAN TABLE t1}
do_eqp_test 7.4 {
SELECT rowid FROM t1 WHERE (+b IS NULL AND c NOT NULL) OR c IS NULL
-} {
- 0 0 0 {SCAN TABLE t1}
-}
+} {SCAN TABLE t1}
#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 8.1 {
@@ -192,14 +192,15 @@
FROM album, composer, track
WHERE cname LIKE '%bach%'
AND unlikely(composer.cid=track.cid)
AND unlikely(album.aid=track.aid);
} {
- 0 0 2 {SCAN TABLE track}
- 0 1 0 {SEARCH TABLE album USING INTEGER PRIMARY KEY (rowid=?)}
- 0 2 1 {SEARCH TABLE composer USING INTEGER PRIMARY KEY (rowid=?)}
- 0 0 0 {USE TEMP B-TREE FOR DISTINCT}
+ QUERY PLAN
+ |--SCAN TABLE track
+ |--SEARCH TABLE album USING INTEGER PRIMARY KEY (rowid=?)
+ |--SEARCH TABLE composer USING INTEGER PRIMARY KEY (rowid=?)
+ `--USE TEMP B-TREE FOR DISTINCT
}
#-------------------------------------------------------------------------
#
do_execsql_test 9.1 {
@@ -261,29 +262,21 @@
execsql ANALYZE
} {}
do_eqp_test 10.3 {
SELECT rowid FROM t6 WHERE a=0 AND c=0
- } {
- 0 0 0 {SEARCH TABLE t6 USING INDEX t6i2 (c=?)}
- }
+ } {SEARCH TABLE t6 USING INDEX t6i2 (c=?)}
do_eqp_test 10.4 {
SELECT rowid FROM t6 WHERE a=0 AND b='xyz' AND c=0
- } {
- 0 0 0 {SEARCH TABLE t6 USING INDEX t6i2 (c=?)}
- }
+ } {SEARCH TABLE t6 USING INDEX t6i2 (c=?)}
do_eqp_test 10.5 {
SELECT rowid FROM t6 WHERE likelihood(a=0, 0.1) AND c=0
- } {
- 0 0 0 {SEARCH TABLE t6 USING INDEX t6i1 (a=?)}
- }
+ } {SEARCH TABLE t6 USING INDEX t6i1 (a=?)}
do_eqp_test 10.6 {
SELECT rowid FROM t6 WHERE likelihood(a=0, 0.1) AND b='xyz' AND c=0
- } {
- 0 0 0 {SEARCH TABLE t6 USING INDEX t6i1 (a=? AND b=?)}
- }
+ } {SEARCH TABLE t6 USING INDEX t6i1 (a=? AND b=?)}
}
finish_test
Index: test/coveridxscan.test
==================================================================
--- test/coveridxscan.test
+++ test/coveridxscan.test
@@ -107,18 +107,13 @@
CREATE INDEX i2 ON t2($cols);
"
do_eqp_test 5.1.1 {
SELECT * FROM t1 ORDER BY c1, c2;
-} {
- 0 0 0 {SCAN TABLE t1 USING COVERING INDEX i1}
-}
+} {SCAN TABLE t1 USING COVERING INDEX i1}
do_eqp_test 5.1.2 {
SELECT * FROM t2 ORDER BY c1, c2;
-} {
- 0 0 0 {SCAN TABLE t2 USING COVERING INDEX i2}
-}
-
+} {SCAN TABLE t2 USING COVERING INDEX i2}
finish_test
Index: test/csv01.test
==================================================================
--- test/csv01.test
+++ test/csv01.test
@@ -138,7 +138,14 @@
'CREATE TABLE t3(a,b,c,d) WITHOUT ROWID',
testflags=1
);
} {1 {vtable constructor failed: t5}}
+# 2018-04-24
+# Memory leak reported on the sqlite-users mailing list by Ralf Junker.
+#
+do_catchsql_test 4.3 {
+ CREATE VIRTUAL TABLE IF NOT EXISTS temp.t1
+ USING csv(filename='FileDoesNotExist.csv');
+} {1 {cannot open 'FileDoesNotExist.csv' for reading}}
finish_test
Index: test/delete.test
==================================================================
--- test/delete.test
+++ test/delete.test
@@ -400,8 +400,20 @@
}
do_execsql_test delete-10.2 {
SELECT * FROM t1 WHERE a='1' AND b='2';
}
+
+do_execsql_test delete-11.0 {
+ CREATE TABLE t11(a INTEGER PRIMARY KEY, b INT);
+ WITH RECURSIVE cnt(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM cnt WHERE x<20)
+ INSERT INTO t11(a,b) SELECT x, (x*17)%100 FROM cnt;
+ SELECT * FROM t11;
+} {1 17 2 34 3 51 4 68 5 85 6 2 7 19 8 36 9 53 10 70 11 87 12 4 13 21 14 38 15 55 16 72 17 89 18 6 19 23 20 40}
+do_execsql_test delete-11.1 {
+ DELETE FROM t11 AS xyz
+ WHERE EXISTS(SELECT 1 FROM t11 WHERE t11.a>xyz.a AND t11.b<=xyz.b);
+ SELECT * FROM t11;
+} {6 2 12 4 18 6 19 23 20 40}
finish_test
Index: test/e_createtable.test
==================================================================
--- test/e_createtable.test
+++ test/e_createtable.test
@@ -654,15 +654,15 @@
3 "CREATE TABLE x1 AS SELECT * FROM t1, t2" {a b c d e f}
4 "CREATE TABLE x1 AS SELECT count(*) FROM t1" {count(*)}
5 "CREATE TABLE x1 AS SELECT count(a) AS a, max(b) FROM t1" {a max(b)}
}
-# EVIDENCE-OF: R-37111-22855 The declared type of each column is
+# EVIDENCE-OF: R-55407-45319 The declared type of each column is
# determined by the expression affinity of the corresponding expression
# in the result set of the SELECT statement, as follows: Expression
# Affinity Column Declared Type TEXT "TEXT" NUMERIC "NUM" INTEGER "INT"
-# REAL "REAL" NONE "" (empty string)
+# REAL "REAL" BLOB (a.k.a "NONE") "" (empty string)
#
do_createtable_tests 2.2 -tclquery {
table_column_decltypes x1
} -repair {
catchsql { DROP TABLE x1 }
@@ -1383,17 +1383,17 @@
CREATE TABLE t1(a, b PRIMARY KEY);
CREATE TABLE t2(a, b, c, UNIQUE(b, c));
}
do_createtable_tests 4.10 {
1 "EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE b = 5"
- {0 0 0 {SEARCH TABLE t1 USING INDEX sqlite_autoindex_t1_1 (b=?)}}
+ {/*SEARCH TABLE t1 USING INDEX sqlite_autoindex_t1_1 (b=?)*/}
2 "EXPLAIN QUERY PLAN SELECT * FROM t2 ORDER BY b, c"
- {0 0 0 {SCAN TABLE t2 USING INDEX sqlite_autoindex_t2_1}}
+ {/*SCAN TABLE t2 USING INDEX sqlite_autoindex_t2_1*/}
3 "EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE b=10 AND c>10"
- {0 0 0 {SEARCH TABLE t2 USING INDEX sqlite_autoindex_t2_1 (b=? AND c>?)}}
+ {/*SEARCH TABLE t2 USING INDEX sqlite_autoindex_t2_1 (b=? AND c>?)*/}
}
# EVIDENCE-OF: R-45493-35653 A CHECK constraint may be attached to a
# column definition or specified as a table constraint. In practice it
# makes no difference.
Index: test/eqp.test
==================================================================
--- test/eqp.test
+++ test/eqp.test
@@ -41,82 +41,114 @@
}
do_eqp_test 1.2 {
SELECT * FROM t2, t1 WHERE t1.a=1 OR t1.b=2;
} {
- 0 0 1 {SEARCH TABLE t1 USING INDEX i1 (a=?)}
- 0 0 1 {SEARCH TABLE t1 USING INDEX i2 (b=?)}
- 0 1 0 {SCAN TABLE t2}
+ QUERY PLAN
+ |--MULTI-INDEX OR
+ | |--SEARCH TABLE t1 USING INDEX i1 (a=?)
+ | `--SEARCH TABLE t1 USING INDEX i2 (b=?)
+ `--SCAN TABLE t2
}
do_eqp_test 1.3 {
SELECT * FROM t2 CROSS JOIN t1 WHERE t1.a=1 OR t1.b=2;
} {
- 0 0 0 {SCAN TABLE t2}
- 0 1 1 {SEARCH TABLE t1 USING INDEX i1 (a=?)}
- 0 1 1 {SEARCH TABLE t1 USING INDEX i2 (b=?)}
+ QUERY PLAN
+ |--SCAN TABLE t2
+ `--MULTI-INDEX OR
+ |--SEARCH TABLE t1 USING INDEX i1 (a=?)
+ `--SEARCH TABLE t1 USING INDEX i2 (b=?)
}
do_eqp_test 1.3 {
SELECT a FROM t1 ORDER BY a
} {
- 0 0 0 {SCAN TABLE t1 USING COVERING INDEX i1}
+ QUERY PLAN
+ `--SCAN TABLE t1 USING COVERING INDEX i1
}
do_eqp_test 1.4 {
SELECT a FROM t1 ORDER BY +a
} {
- 0 0 0 {SCAN TABLE t1 USING COVERING INDEX i1}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SCAN TABLE t1 USING COVERING INDEX i1
+ `--USE TEMP B-TREE FOR ORDER BY
}
do_eqp_test 1.5 {
SELECT a FROM t1 WHERE a=4
} {
- 0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i1 (a=?)}
+ QUERY PLAN
+ `--SEARCH TABLE t1 USING COVERING INDEX i1 (a=?)
}
do_eqp_test 1.6 {
SELECT DISTINCT count(*) FROM t3 GROUP BY a;
} {
- 0 0 0 {SCAN TABLE t3}
- 0 0 0 {USE TEMP B-TREE FOR GROUP BY}
- 0 0 0 {USE TEMP B-TREE FOR DISTINCT}
+ QUERY PLAN
+ |--SCAN TABLE t3
+ |--USE TEMP B-TREE FOR GROUP BY
+ `--USE TEMP B-TREE FOR DISTINCT
}
do_eqp_test 1.7 {
SELECT * FROM t3 JOIN (SELECT 1)
} {
- 0 0 1 {SCAN SUBQUERY 1}
- 0 1 0 {SCAN TABLE t3}
+ QUERY PLAN
+ |--MATERIALIZE xxxxxx
+ | `--SCAN CONSTANT ROW
+ |--SCAN SUBQUERY xxxxxx
+ `--SCAN TABLE t3
}
do_eqp_test 1.8 {
SELECT * FROM t3 JOIN (SELECT 1 UNION SELECT 2)
} {
- 1 0 0 {COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)}
- 0 0 1 {SCAN SUBQUERY 1}
- 0 1 0 {SCAN TABLE t3}
+ QUERY PLAN
+ |--MATERIALIZE xxxxxx
+ | `--COMPOUND QUERY
+ | |--LEFT-MOST SUBQUERY
+ | | `--SCAN CONSTANT ROW
+ | `--UNION USING TEMP B-TREE
+ | `--SCAN CONSTANT ROW
+ |--SCAN SUBQUERY xxxxxx
+ `--SCAN TABLE t3
}
do_eqp_test 1.9 {
SELECT * FROM t3 JOIN (SELECT 1 EXCEPT SELECT a FROM t3 LIMIT 17)
} {
- 3 0 0 {SCAN TABLE t3}
- 1 0 0 {COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (EXCEPT)}
- 0 0 1 {SCAN SUBQUERY 1}
- 0 1 0 {SCAN TABLE t3}
+ QUERY PLAN
+ |--MATERIALIZE xxxxxx
+ | `--COMPOUND QUERY
+ | |--LEFT-MOST SUBQUERY
+ | | `--SCAN CONSTANT ROW
+ | `--EXCEPT USING TEMP B-TREE
+ | `--SCAN TABLE t3
+ |--SCAN SUBQUERY xxxxxx
+ `--SCAN TABLE t3
}
do_eqp_test 1.10 {
SELECT * FROM t3 JOIN (SELECT 1 INTERSECT SELECT a FROM t3 LIMIT 17)
} {
- 3 0 0 {SCAN TABLE t3}
- 1 0 0 {COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (INTERSECT)}
- 0 0 1 {SCAN SUBQUERY 1}
- 0 1 0 {SCAN TABLE t3}
+ QUERY PLAN
+ |--MATERIALIZE xxxxxx
+ | `--COMPOUND QUERY
+ | |--LEFT-MOST SUBQUERY
+ | | `--SCAN CONSTANT ROW
+ | `--INTERSECT USING TEMP B-TREE
+ | `--SCAN TABLE t3
+ |--SCAN SUBQUERY xxxxxx
+ `--SCAN TABLE t3
}
do_eqp_test 1.11 {
SELECT * FROM t3 JOIN (SELECT 1 UNION ALL SELECT a FROM t3 LIMIT 17)
} {
- 3 0 0 {SCAN TABLE t3}
- 1 0 0 {COMPOUND SUBQUERIES 2 AND 3 (UNION ALL)}
- 0 0 1 {SCAN SUBQUERY 1}
- 0 1 0 {SCAN TABLE t3}
+ QUERY PLAN
+ |--MATERIALIZE xxxxxx
+ | `--COMPOUND QUERY
+ | |--LEFT-MOST SUBQUERY
+ | | `--SCAN CONSTANT ROW
+ | `--UNION ALL
+ | `--SCAN TABLE t3
+ |--SCAN SUBQUERY xxxxxx
+ `--SCAN TABLE t3
}
#-------------------------------------------------------------------------
# Test cases eqp-2.* - tests for single select statements.
#
@@ -127,52 +159,62 @@
CREATE TABLE t2(x INT, y INT, ex TEXT);
CREATE INDEX t2i1 ON t2(x);
}
det 2.2.1 "SELECT DISTINCT min(x), max(x) FROM t1 GROUP BY x ORDER BY 1" {
- 0 0 0 {SCAN TABLE t1}
- 0 0 0 {USE TEMP B-TREE FOR GROUP BY}
- 0 0 0 {USE TEMP B-TREE FOR DISTINCT}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ |--USE TEMP B-TREE FOR GROUP BY
+ |--USE TEMP B-TREE FOR DISTINCT
+ `--USE TEMP B-TREE FOR ORDER BY
}
det 2.2.2 "SELECT DISTINCT min(x), max(x) FROM t2 GROUP BY x ORDER BY 1" {
- 0 0 0 {SCAN TABLE t2 USING COVERING INDEX t2i1}
- 0 0 0 {USE TEMP B-TREE FOR DISTINCT}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SCAN TABLE t2 USING COVERING INDEX t2i1
+ |--USE TEMP B-TREE FOR DISTINCT
+ `--USE TEMP B-TREE FOR ORDER BY
}
det 2.2.3 "SELECT DISTINCT * FROM t1" {
- 0 0 0 {SCAN TABLE t1}
- 0 0 0 {USE TEMP B-TREE FOR DISTINCT}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--USE TEMP B-TREE FOR DISTINCT
}
det 2.2.4 "SELECT DISTINCT * FROM t1, t2" {
- 0 0 0 {SCAN TABLE t1}
- 0 1 1 {SCAN TABLE t2}
- 0 0 0 {USE TEMP B-TREE FOR DISTINCT}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ |--SCAN TABLE t2
+ `--USE TEMP B-TREE FOR DISTINCT
}
det 2.2.5 "SELECT DISTINCT * FROM t1, t2 ORDER BY t1.x" {
- 0 0 0 {SCAN TABLE t1}
- 0 1 1 {SCAN TABLE t2}
- 0 0 0 {USE TEMP B-TREE FOR DISTINCT}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ |--SCAN TABLE t2
+ |--USE TEMP B-TREE FOR DISTINCT
+ `--USE TEMP B-TREE FOR ORDER BY
}
det 2.2.6 "SELECT DISTINCT t2.x FROM t1, t2 ORDER BY t2.x" {
- 0 0 1 {SCAN TABLE t2 USING COVERING INDEX t2i1}
- 0 1 0 {SCAN TABLE t1}
+ QUERY PLAN
+ |--SCAN TABLE t2 USING COVERING INDEX t2i1
+ `--SCAN TABLE t1
}
det 2.3.1 "SELECT max(x) FROM t2" {
- 0 0 0 {SEARCH TABLE t2 USING COVERING INDEX t2i1}
+ QUERY PLAN
+ `--SEARCH TABLE t2 USING COVERING INDEX t2i1
}
det 2.3.2 "SELECT min(x) FROM t2" {
- 0 0 0 {SEARCH TABLE t2 USING COVERING INDEX t2i1}
+ QUERY PLAN
+ `--SEARCH TABLE t2 USING COVERING INDEX t2i1
}
det 2.3.3 "SELECT min(x), max(x) FROM t2" {
- 0 0 0 {SCAN TABLE t2 USING COVERING INDEX t2i1}
+ QUERY PLAN
+ `--SCAN TABLE t2 USING COVERING INDEX t2i1
}
det 2.4.1 "SELECT * FROM t1 WHERE rowid=?" {
- 0 0 0 {SEARCH TABLE t1 USING INTEGER PRIMARY KEY (rowid=?)}
+ QUERY PLAN
+ `--SEARCH TABLE t1 USING INTEGER PRIMARY KEY (rowid=?)
}
#-------------------------------------------------------------------------
@@ -179,227 +221,278 @@
# Test cases eqp-3.* - tests for select statements that use sub-selects.
#
do_eqp_test 3.1.1 {
SELECT (SELECT x FROM t1 AS sub) FROM t1;
} {
- 0 0 0 {SCAN TABLE t1}
- 0 0 0 {EXECUTE SCALAR SUBQUERY 1}
- 1 0 0 {SCAN TABLE t1 AS sub}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--SCALAR SUBQUERY
+ `--SCAN TABLE t1 AS sub
}
do_eqp_test 3.1.2 {
SELECT * FROM t1 WHERE (SELECT x FROM t1 AS sub);
} {
- 0 0 0 {SCAN TABLE t1}
- 0 0 0 {EXECUTE SCALAR SUBQUERY 1}
- 1 0 0 {SCAN TABLE t1 AS sub}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--SCALAR SUBQUERY
+ `--SCAN TABLE t1 AS sub
}
do_eqp_test 3.1.3 {
SELECT * FROM t1 WHERE (SELECT x FROM t1 AS sub ORDER BY y);
} {
- 0 0 0 {SCAN TABLE t1}
- 0 0 0 {EXECUTE SCALAR SUBQUERY 1}
- 1 0 0 {SCAN TABLE t1 AS sub}
- 1 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--SCALAR SUBQUERY
+ |--SCAN TABLE t1 AS sub
+ `--USE TEMP B-TREE FOR ORDER BY
}
do_eqp_test 3.1.4 {
SELECT * FROM t1 WHERE (SELECT x FROM t2 ORDER BY x);
} {
- 0 0 0 {SCAN TABLE t1}
- 0 0 0 {EXECUTE SCALAR SUBQUERY 1}
- 1 0 0 {SCAN TABLE t2 USING COVERING INDEX t2i1}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--SCALAR SUBQUERY
+ `--SCAN TABLE t2 USING COVERING INDEX t2i1
}
det 3.2.1 {
SELECT * FROM (SELECT * FROM t1 ORDER BY x LIMIT 10) ORDER BY y LIMIT 5
} {
- 1 0 0 {SCAN TABLE t1}
- 1 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 0 0 0 {SCAN SUBQUERY 1}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--CO-ROUTINE xxxxxx
+ | |--SCAN TABLE t1
+ | `--USE TEMP B-TREE FOR ORDER BY
+ |--SCAN SUBQUERY xxxxxx
+ `--USE TEMP B-TREE FOR ORDER BY
}
det 3.2.2 {
SELECT * FROM
(SELECT * FROM t1 ORDER BY x LIMIT 10) AS x1,
(SELECT * FROM t2 ORDER BY x LIMIT 10) AS x2
ORDER BY x2.y LIMIT 5
} {
- 1 0 0 {SCAN TABLE t1}
- 1 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 2 0 0 {SCAN TABLE t2 USING INDEX t2i1}
- 0 0 0 {SCAN SUBQUERY 1 AS x1}
- 0 1 1 {SCAN SUBQUERY 2 AS x2}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--MATERIALIZE xxxxxx
+ | |--SCAN TABLE t1
+ | `--USE TEMP B-TREE FOR ORDER BY
+ |--MATERIALIZE xxxxxx
+ | `--SCAN TABLE t2 USING INDEX t2i1
+ |--SCAN SUBQUERY xxxxxx AS x1
+ |--SCAN SUBQUERY xxxxxx AS x2
+ `--USE TEMP B-TREE FOR ORDER BY
}
det 3.3.1 {
SELECT * FROM t1 WHERE y IN (SELECT y FROM t2)
} {
- 0 0 0 {SCAN TABLE t1}
- 0 0 0 {EXECUTE LIST SUBQUERY 1}
- 1 0 0 {SCAN TABLE t2}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--LIST SUBQUERY
+ `--SCAN TABLE t2
}
det 3.3.2 {
SELECT * FROM t1 WHERE y IN (SELECT y FROM t2 WHERE t1.x!=t2.x)
} {
- 0 0 0 {SCAN TABLE t1}
- 0 0 0 {EXECUTE CORRELATED LIST SUBQUERY 1}
- 1 0 0 {SCAN TABLE t2}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--CORRELATED LIST SUBQUERY
+ `--SCAN TABLE t2
}
det 3.3.3 {
SELECT * FROM t1 WHERE EXISTS (SELECT y FROM t2 WHERE t1.x!=t2.x)
} {
- 0 0 0 {SCAN TABLE t1}
- 0 0 0 {EXECUTE CORRELATED SCALAR SUBQUERY 1}
- 1 0 0 {SCAN TABLE t2}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--CORRELATED SCALAR SUBQUERY
+ `--SCAN TABLE t2
}
#-------------------------------------------------------------------------
# Test cases eqp-4.* - tests for composite select statements.
#
do_eqp_test 4.1.1 {
SELECT * FROM t1 UNION ALL SELECT * FROM t2
} {
- 1 0 0 {SCAN TABLE t1}
- 2 0 0 {SCAN TABLE t2}
- 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (UNION ALL)}
+ QUERY PLAN
+ `--COMPOUND QUERY
+ |--LEFT-MOST SUBQUERY
+ | `--SCAN TABLE t1
+ `--UNION ALL
+ `--SCAN TABLE t2
}
do_eqp_test 4.1.2 {
SELECT * FROM t1 UNION ALL SELECT * FROM t2 ORDER BY 2
} {
- 1 0 0 {SCAN TABLE t1}
- 1 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 2 0 0 {SCAN TABLE t2}
- 2 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (UNION ALL)}
+ QUERY PLAN
+ `--MERGE (UNION ALL)
+ |--LEFT
+ | |--SCAN TABLE t1
+ | `--USE TEMP B-TREE FOR ORDER BY
+ `--RIGHT
+ |--SCAN TABLE t2
+ `--USE TEMP B-TREE FOR ORDER BY
}
do_eqp_test 4.1.3 {
SELECT * FROM t1 UNION SELECT * FROM t2 ORDER BY 2
} {
- 1 0 0 {SCAN TABLE t1}
- 1 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 2 0 0 {SCAN TABLE t2}
- 2 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (UNION)}
+ QUERY PLAN
+ `--MERGE (UNION)
+ |--LEFT
+ | |--SCAN TABLE t1
+ | `--USE TEMP B-TREE FOR ORDER BY
+ `--RIGHT
+ |--SCAN TABLE t2
+ `--USE TEMP B-TREE FOR ORDER BY
}
do_eqp_test 4.1.4 {
SELECT * FROM t1 INTERSECT SELECT * FROM t2 ORDER BY 2
} {
- 1 0 0 {SCAN TABLE t1}
- 1 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 2 0 0 {SCAN TABLE t2}
- 2 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (INTERSECT)}
+ QUERY PLAN
+ `--MERGE (INTERSECT)
+ |--LEFT
+ | |--SCAN TABLE t1
+ | `--USE TEMP B-TREE FOR ORDER BY
+ `--RIGHT
+ |--SCAN TABLE t2
+ `--USE TEMP B-TREE FOR ORDER BY
}
do_eqp_test 4.1.5 {
SELECT * FROM t1 EXCEPT SELECT * FROM t2 ORDER BY 2
} {
- 1 0 0 {SCAN TABLE t1}
- 1 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 2 0 0 {SCAN TABLE t2}
- 2 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (EXCEPT)}
+ QUERY PLAN
+ `--MERGE (EXCEPT)
+ |--LEFT
+ | |--SCAN TABLE t1
+ | `--USE TEMP B-TREE FOR ORDER BY
+ `--RIGHT
+ |--SCAN TABLE t2
+ `--USE TEMP B-TREE FOR ORDER BY
}
do_eqp_test 4.2.2 {
SELECT * FROM t1 UNION ALL SELECT * FROM t2 ORDER BY 1
} {
- 1 0 0 {SCAN TABLE t1}
- 1 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 2 0 0 {SCAN TABLE t2 USING INDEX t2i1}
- 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (UNION ALL)}
+ QUERY PLAN
+ `--MERGE (UNION ALL)
+ |--LEFT
+ | |--SCAN TABLE t1
+ | `--USE TEMP B-TREE FOR ORDER BY
+ `--RIGHT
+ `--SCAN TABLE t2 USING INDEX t2i1
}
do_eqp_test 4.2.3 {
SELECT * FROM t1 UNION SELECT * FROM t2 ORDER BY 1
} {
- 1 0 0 {SCAN TABLE t1}
- 1 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 2 0 0 {SCAN TABLE t2 USING INDEX t2i1}
- 2 0 0 {USE TEMP B-TREE FOR RIGHT PART OF ORDER BY}
- 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (UNION)}
+ QUERY PLAN
+ `--MERGE (UNION)
+ |--LEFT
+ | |--SCAN TABLE t1
+ | `--USE TEMP B-TREE FOR ORDER BY
+ `--RIGHT
+ |--SCAN TABLE t2 USING INDEX t2i1
+ `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY
}
do_eqp_test 4.2.4 {
SELECT * FROM t1 INTERSECT SELECT * FROM t2 ORDER BY 1
} {
- 1 0 0 {SCAN TABLE t1}
- 1 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 2 0 0 {SCAN TABLE t2 USING INDEX t2i1}
- 2 0 0 {USE TEMP B-TREE FOR RIGHT PART OF ORDER BY}
- 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (INTERSECT)}
+ QUERY PLAN
+ `--MERGE (INTERSECT)
+ |--LEFT
+ | |--SCAN TABLE t1
+ | `--USE TEMP B-TREE FOR ORDER BY
+ `--RIGHT
+ |--SCAN TABLE t2 USING INDEX t2i1
+ `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY
}
do_eqp_test 4.2.5 {
SELECT * FROM t1 EXCEPT SELECT * FROM t2 ORDER BY 1
} {
- 1 0 0 {SCAN TABLE t1}
- 1 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 2 0 0 {SCAN TABLE t2 USING INDEX t2i1}
- 2 0 0 {USE TEMP B-TREE FOR RIGHT PART OF ORDER BY}
- 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (EXCEPT)}
+ QUERY PLAN
+ `--MERGE (EXCEPT)
+ |--LEFT
+ | |--SCAN TABLE t1
+ | `--USE TEMP B-TREE FOR ORDER BY
+ `--RIGHT
+ |--SCAN TABLE t2 USING INDEX t2i1
+ `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY
}
do_eqp_test 4.3.1 {
SELECT x FROM t1 UNION SELECT x FROM t2
} {
- 1 0 0 {SCAN TABLE t1}
- 2 0 0 {SCAN TABLE t2 USING COVERING INDEX t2i1}
- 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 USING TEMP B-TREE (UNION)}
+ QUERY PLAN
+ `--COMPOUND QUERY
+ |--LEFT-MOST SUBQUERY
+ | `--SCAN TABLE t1
+ `--UNION USING TEMP B-TREE
+ `--SCAN TABLE t2 USING COVERING INDEX t2i1
}
do_eqp_test 4.3.2 {
SELECT x FROM t1 UNION SELECT x FROM t2 UNION SELECT x FROM t1
} {
- 2 0 0 {SCAN TABLE t1}
- 3 0 0 {SCAN TABLE t2 USING COVERING INDEX t2i1}
- 1 0 0 {COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)}
- 4 0 0 {SCAN TABLE t1}
- 0 0 0 {COMPOUND SUBQUERIES 1 AND 4 USING TEMP B-TREE (UNION)}
+ QUERY PLAN
+ `--COMPOUND QUERY
+ |--LEFT-MOST SUBQUERY
+ | `--SCAN TABLE t1
+ |--UNION USING TEMP B-TREE
+ | `--SCAN TABLE t2 USING COVERING INDEX t2i1
+ `--UNION USING TEMP B-TREE
+ `--SCAN TABLE t1
}
do_eqp_test 4.3.3 {
SELECT x FROM t1 UNION SELECT x FROM t2 UNION SELECT x FROM t1 ORDER BY 1
} {
- 2 0 0 {SCAN TABLE t1}
- 2 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 3 0 0 {SCAN TABLE t2 USING COVERING INDEX t2i1}
- 1 0 0 {COMPOUND SUBQUERIES 2 AND 3 (UNION)}
- 4 0 0 {SCAN TABLE t1}
- 4 0 0 {USE TEMP B-TREE FOR ORDER BY}
- 0 0 0 {COMPOUND SUBQUERIES 1 AND 4 (UNION)}
+ QUERY PLAN
+ `--MERGE (UNION)
+ |--LEFT
+ | `--MERGE (UNION)
+ | |--LEFT
+ | | |--SCAN TABLE t1
+ | | `--USE TEMP B-TREE FOR ORDER BY
+ | `--RIGHT
+ | `--SCAN TABLE t2 USING COVERING INDEX t2i1
+ `--RIGHT
+ |--SCAN TABLE t1
+ `--USE TEMP B-TREE FOR ORDER BY
}
+if 0 {
#-------------------------------------------------------------------------
# This next block of tests verifies that the examples on the
# lang_explain.html page are correct.
#
drop_all_tables
-# EVIDENCE-OF: R-47779-47605 sqlite> EXPLAIN QUERY PLAN SELECT a, b
+# XVIDENCE-OF: R-47779-47605 sqlite> EXPLAIN QUERY PLAN SELECT a, b
# FROM t1 WHERE a=1;
# 0|0|0|SCAN TABLE t1
#
do_execsql_test 5.1.0 { CREATE TABLE t1(a INT, b INT, ex TEXT) }
det 5.1.1 "SELECT a, b FROM t1 WHERE a=1" {
0 0 0 {SCAN TABLE t1}
}
-# EVIDENCE-OF: R-55852-17599 sqlite> CREATE INDEX i1 ON t1(a);
+# XVIDENCE-OF: R-55852-17599 sqlite> CREATE INDEX i1 ON t1(a);
# sqlite> EXPLAIN QUERY PLAN SELECT a, b FROM t1 WHERE a=1;
# 0|0|0|SEARCH TABLE t1 USING INDEX i1
#
do_execsql_test 5.2.0 { CREATE INDEX i1 ON t1(a) }
det 5.2.1 "SELECT a, b FROM t1 WHERE a=1" {
0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}
}
-# EVIDENCE-OF: R-21179-11011 sqlite> CREATE INDEX i2 ON t1(a, b);
+# XVIDENCE-OF: R-21179-11011 sqlite> CREATE INDEX i2 ON t1(a, b);
# sqlite> EXPLAIN QUERY PLAN SELECT a, b FROM t1 WHERE a=1;
# 0|0|0|SEARCH TABLE t1 USING COVERING INDEX i2 (a=?)
#
do_execsql_test 5.3.0 { CREATE INDEX i2 ON t1(a, b) }
det 5.3.1 "SELECT a, b FROM t1 WHERE a=1" {
0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i2 (a=?)}
}
-# EVIDENCE-OF: R-09991-48941 sqlite> EXPLAIN QUERY PLAN
+# XVIDENCE-OF: R-09991-48941 sqlite> EXPLAIN QUERY PLAN
# SELECT t1.*, t2.* FROM t1, t2 WHERE t1.a=1 AND t1.b>2;
# 0|0|0|SEARCH TABLE t1 USING COVERING INDEX i2 (a=? AND b>?)
# 0|1|1|SCAN TABLE t2
#
do_execsql_test 5.4.0 {CREATE TABLE t2(c INT, d INT, ex TEXT)}
@@ -406,21 +499,21 @@
det 5.4.1 "SELECT t1.a, t2.c FROM t1, t2 WHERE t1.a=1 AND t1.b>2" {
0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i2 (a=? AND b>?)}
0 1 1 {SCAN TABLE t2}
}
-# EVIDENCE-OF: R-33626-61085 sqlite> EXPLAIN QUERY PLAN
+# XVIDENCE-OF: R-33626-61085 sqlite> EXPLAIN QUERY PLAN
# SELECT t1.*, t2.* FROM t2, t1 WHERE t1.a=1 AND t1.b>2;
# 0|0|1|SEARCH TABLE t1 USING COVERING INDEX i2 (a=? AND b>?)
# 0|1|0|SCAN TABLE t2
#
det 5.5 "SELECT t1.a, t2.c FROM t2, t1 WHERE t1.a=1 AND t1.b>2" {
0 0 1 {SEARCH TABLE t1 USING COVERING INDEX i2 (a=? AND b>?)}
0 1 0 {SCAN TABLE t2}
}
-# EVIDENCE-OF: R-04002-25654 sqlite> CREATE INDEX i3 ON t1(b);
+# XVIDENCE-OF: R-04002-25654 sqlite> CREATE INDEX i3 ON t1(b);
# sqlite> EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=1 OR b=2;
# 0|0|0|SEARCH TABLE t1 USING COVERING INDEX i2 (a=?)
# 0|0|0|SEARCH TABLE t1 USING INDEX i3 (b=?)
#
do_execsql_test 5.5.0 {CREATE INDEX i3 ON t1(b)}
@@ -427,30 +520,30 @@
det 5.6.1 "SELECT a, b FROM t1 WHERE a=1 OR b=2" {
0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i2 (a=?)}
0 0 0 {SEARCH TABLE t1 USING INDEX i3 (b=?)}
}
-# EVIDENCE-OF: R-24577-38891 sqlite> EXPLAIN QUERY PLAN
+# XVIDENCE-OF: R-24577-38891 sqlite> EXPLAIN QUERY PLAN
# SELECT c, d FROM t2 ORDER BY c;
# 0|0|0|SCAN TABLE t2
# 0|0|0|USE TEMP B-TREE FOR ORDER BY
#
det 5.7 "SELECT c, d FROM t2 ORDER BY c" {
0 0 0 {SCAN TABLE t2}
0 0 0 {USE TEMP B-TREE FOR ORDER BY}
}
-# EVIDENCE-OF: R-58157-12355 sqlite> CREATE INDEX i4 ON t2(c);
+# XVIDENCE-OF: R-58157-12355 sqlite> CREATE INDEX i4 ON t2(c);
# sqlite> EXPLAIN QUERY PLAN SELECT c, d FROM t2 ORDER BY c;
# 0|0|0|SCAN TABLE t2 USING INDEX i4
#
do_execsql_test 5.8.0 {CREATE INDEX i4 ON t2(c)}
det 5.8.1 "SELECT c, d FROM t2 ORDER BY c" {
0 0 0 {SCAN TABLE t2 USING INDEX i4}
}
-# EVIDENCE-OF: R-13931-10421 sqlite> EXPLAIN QUERY PLAN SELECT
+# XVIDENCE-OF: R-13931-10421 sqlite> EXPLAIN QUERY PLAN SELECT
# (SELECT b FROM t1 WHERE a=0), (SELECT a FROM t1 WHERE b=t2.c) FROM t2;
# 0|0|0|SCAN TABLE t2
# 0|0|0|EXECUTE SCALAR SUBQUERY 1
# 1|0|0|SEARCH TABLE t1 USING COVERING INDEX i2 (a=?)
# 0|0|0|EXECUTE CORRELATED SCALAR SUBQUERY 2
@@ -464,11 +557,11 @@
1 0 0 {SEARCH TABLE t1 USING COVERING INDEX i2 (a=?)}
0 0 0 {EXECUTE CORRELATED SCALAR SUBQUERY 2}
2 0 0 {SEARCH TABLE t1 USING INDEX i3 (b=?)}
}
-# EVIDENCE-OF: R-50892-45943 sqlite> EXPLAIN QUERY PLAN
+# XVIDENCE-OF: R-50892-45943 sqlite> EXPLAIN QUERY PLAN
# SELECT count(*) FROM (SELECT max(b) AS x FROM t1 GROUP BY a) GROUP BY x;
# 1|0|0|SCAN TABLE t1 USING COVERING INDEX i2
# 0|0|0|SCAN SUBQUERY 1
# 0|0|0|USE TEMP B-TREE FOR GROUP BY
#
@@ -478,21 +571,21 @@
1 0 0 {SCAN TABLE t1 USING COVERING INDEX i2}
0 0 0 {SCAN SUBQUERY 1}
0 0 0 {USE TEMP B-TREE FOR GROUP BY}
}
-# EVIDENCE-OF: R-46219-33846 sqlite> EXPLAIN QUERY PLAN
+# XVIDENCE-OF: R-46219-33846 sqlite> EXPLAIN QUERY PLAN
# SELECT * FROM (SELECT * FROM t2 WHERE c=1), t1;
# 0|0|0|SEARCH TABLE t2 USING INDEX i4 (c=?)
# 0|1|1|SCAN TABLE t1
#
det 5.11 "SELECT a, b FROM (SELECT * FROM t2 WHERE c=1), t1" {
0 0 0 {SEARCH TABLE t2 USING INDEX i4 (c=?)}
0 1 1 {SCAN TABLE t1 USING COVERING INDEX i2}
}
-# EVIDENCE-OF: R-37879-39987 sqlite> EXPLAIN QUERY PLAN
+# XVIDENCE-OF: R-37879-39987 sqlite> EXPLAIN QUERY PLAN
# SELECT a FROM t1 UNION SELECT c FROM t2;
# 1|0|0|SCAN TABLE t1
# 2|0|0|SCAN TABLE t2
# 0|0|0|COMPOUND SUBQUERIES 1 AND 2 USING TEMP B-TREE (UNION)
#
@@ -500,11 +593,11 @@
1 0 0 {SCAN TABLE t1 USING COVERING INDEX i2}
2 0 0 {SCAN TABLE t2 USING COVERING INDEX i4}
0 0 0 {COMPOUND SUBQUERIES 1 AND 2 USING TEMP B-TREE (UNION)}
}
-# EVIDENCE-OF: R-44864-63011 sqlite> EXPLAIN QUERY PLAN
+# XVIDENCE-OF: R-44864-63011 sqlite> EXPLAIN QUERY PLAN
# SELECT a FROM t1 EXCEPT SELECT d FROM t2 ORDER BY 1;
# 1|0|0|SCAN TABLE t1 USING COVERING INDEX i2
# 2|0|0|SCAN TABLE t2 2|0|0|USE TEMP B-TREE FOR ORDER BY
# 0|0|0|COMPOUND SUBQUERIES 1 AND 2 (EXCEPT)
#
@@ -512,11 +605,10 @@
1 0 0 {SCAN TABLE t1 USING COVERING INDEX i1}
2 0 0 {SCAN TABLE t2}
2 0 0 {USE TEMP B-TREE FOR ORDER BY}
0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (EXCEPT)}
}
-
if {![nonzero_reserved_bytes]} {
#-------------------------------------------------------------------------
# The following tests - eqp-6.* - test that the example C code on
# documentation page eqp.html works. The C code is duplicated in test1.c
@@ -555,10 +647,11 @@
2 0 0 SCAN TABLE t2
2 0 0 USE TEMP B-TREE FOR ORDER BY
0 0 0 COMPOUND SUBQUERIES 1 AND 2 (EXCEPT)
}]
}
+}
#-------------------------------------------------------------------------
# The following tests - eqp-7.* - test that queries that use the OP_Count
# optimization return something sensible with EQP.
#
@@ -569,15 +662,17 @@
CREATE TABLE t2(a INT, b INT, ex CHAR(100));
CREATE INDEX i1 ON t2(a);
}
det 7.1 "SELECT count(*) FROM t1" {
- 0 0 0 {SCAN TABLE t1}
+ QUERY PLAN
+ `--SCAN TABLE t1
}
det 7.2 "SELECT count(*) FROM t2" {
- 0 0 0 {SCAN TABLE t2 USING COVERING INDEX i1}
+ QUERY PLAN
+ `--SCAN TABLE t2 USING COVERING INDEX i1
}
do_execsql_test 7.3 {
INSERT INTO t1(a,b) VALUES(1, 2);
INSERT INTO t1(a,b) VALUES(3, 4);
@@ -591,15 +686,17 @@
db close
sqlite3 db test.db
det 7.4 "SELECT count(*) FROM t1" {
- 0 0 0 {SCAN TABLE t1}
+ QUERY PLAN
+ `--SCAN TABLE t1
}
det 7.5 "SELECT count(*) FROM t2" {
- 0 0 0 {SCAN TABLE t2 USING COVERING INDEX i1}
+ QUERY PLAN
+ `--SCAN TABLE t2 USING COVERING INDEX i1
}
#-------------------------------------------------------------------------
# The following tests - eqp-8.* - test that queries that use the OP_Count
# optimization return something sensible with EQP.
@@ -610,39 +707,46 @@
CREATE TABLE t1(a, b, c, PRIMARY KEY(b, c)) WITHOUT ROWID;
CREATE TABLE t2(a, b, c);
}
det 8.1.1 "SELECT * FROM t2" {
- 0 0 0 {SCAN TABLE t2}
+ QUERY PLAN
+ `--SCAN TABLE t2
}
det 8.1.2 "SELECT * FROM t2 WHERE rowid=?" {
- 0 0 0 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)}
+ QUERY PLAN
+ `--SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)
}
det 8.1.3 "SELECT count(*) FROM t2" {
- 0 0 0 {SCAN TABLE t2}
+ QUERY PLAN
+ `--SCAN TABLE t2
}
det 8.2.1 "SELECT * FROM t1" {
- 0 0 0 {SCAN TABLE t1}
+ QUERY PLAN
+ `--SCAN TABLE t1
}
det 8.2.2 "SELECT * FROM t1 WHERE b=?" {
- 0 0 0 {SEARCH TABLE t1 USING PRIMARY KEY (b=?)}
+ QUERY PLAN
+ `--SEARCH TABLE t1 USING PRIMARY KEY (b=?)
}
det 8.2.3 "SELECT * FROM t1 WHERE b=? AND c=?" {
- 0 0 0 {SEARCH TABLE t1 USING PRIMARY KEY (b=? AND c=?)}
+ QUERY PLAN
+ `--SEARCH TABLE t1 USING PRIMARY KEY (b=? AND c=?)
}
det 8.2.4 "SELECT count(*) FROM t1" {
- 0 0 0 {SCAN TABLE t1}
+ QUERY PLAN
+ `--SCAN TABLE t1
}
finish_test
Index: test/fts3aux1.test
==================================================================
--- test/fts3aux1.test
+++ test/fts3aux1.test
@@ -103,14 +103,14 @@
# Use EQP to show that the WHERE expression "term='braid'" uses a different
# index number (1) than "+term='braid'" (0).
#
do_execsql_test 2.1.1.1 {
EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term='braid'
-} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 1:} }
+} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 1:*/}
do_execsql_test 2.1.1.2 {
EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term='braid'
-} {0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0:}}
+} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 0:*/}
# Now show that using "term='braid'" means the virtual table returns
# only 1 row to SQLite, but "+term='braid'" means all 19 are returned.
#
do_test 2.1.2.1 {
@@ -152,28 +152,28 @@
#
do_execsql_test 2.1.5 { SELECT * FROM terms WHERE term=NULL } {}
do_execsql_test 2.2.1.1 {
EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term>'brain'
-} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 2:} }
+} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 2:*/}
do_execsql_test 2.2.1.2 {
EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term>'brain'
-} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0:} }
+} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 0:*/}
do_execsql_test 2.2.1.3 {
EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term<'brain'
-} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 4:} }
+} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 4:*/}
do_execsql_test 2.2.1.4 {
EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term<'brain'
-} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0:} }
+} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 0:*/}
do_execsql_test 2.2.1.5 {
EXPLAIN QUERY PLAN SELECT * FROM terms WHERE term BETWEEN 'brags' AND 'brain'
-} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 6:} }
+} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 6:*/}
do_execsql_test 2.2.1.6 {
EXPLAIN QUERY PLAN SELECT * FROM terms WHERE +term BETWEEN 'brags' AND 'brain'
-} { 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0:} }
+} {/*SCAN TABLE terms VIRTUAL TABLE INDEX 0:*/}
do_test 2.2.2.1 {
set cnt 0
execsql { SELECT * FROM terms WHERE rec('cnt', term) AND term>'brain' }
set cnt
@@ -333,12 +333,13 @@
7 1 "ORDER BY occurrences ASC"
8 1 "ORDER BY occurrences"
9 1 "ORDER BY occurrences DESC"
} {
- set res [list 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0:}]
- if {$sort} { lappend res 0 0 0 {USE TEMP B-TREE FOR ORDER BY} }
+ set res {SCAN TABLE terms VIRTUAL TABLE INDEX 0:}
+ if {$sort} { append res {*USE TEMP B-TREE FOR ORDER BY} }
+ set res "/*$res*/"
set sql "SELECT * FROM terms $orderby"
do_execsql_test 2.3.1.$tn "EXPLAIN QUERY PLAN $sql" $res
}
@@ -401,43 +402,52 @@
INSERT INTO x2 SELECT term FROM terms WHERE col = '*';
INSERT INTO x3 SELECT term FROM terms WHERE col = '*';
}
-proc do_plansql_test {tn sql r} {
- uplevel do_execsql_test $tn [list "EXPLAIN QUERY PLAN $sql ; $sql"] [list $r]
+proc do_plansql_test {tn sql r1 r2} {
+ do_eqp_test $tn.eqp $sql $r1
+ do_execsql_test $tn $sql $r2
}
do_plansql_test 4.2 {
SELECT y FROM x2, terms WHERE y = term AND col = '*'
} {
- 0 0 0 {SCAN TABLE x2}
- 0 1 1 {SCAN TABLE terms VIRTUAL TABLE INDEX 1:}
+ QUERY PLAN
+ |--SCAN TABLE x2
+ `--SCAN TABLE terms VIRTUAL TABLE INDEX 1:
+} {
a b c d e f g h i j k l
}
do_plansql_test 4.3 {
SELECT y FROM terms, x2 WHERE y = term AND col = '*'
} {
- 0 0 1 {SCAN TABLE x2}
- 0 1 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 1:}
+ QUERY PLAN
+ |--SCAN TABLE x2
+ `--SCAN TABLE terms VIRTUAL TABLE INDEX 1:
+} {
a b c d e f g h i j k l
}
do_plansql_test 4.4 {
SELECT y FROM x3, terms WHERE y = term AND col = '*'
} {
- 0 0 1 {SCAN TABLE terms VIRTUAL TABLE INDEX 0:}
- 0 1 0 {SEARCH TABLE x3 USING COVERING INDEX i1 (y=?)}
+ QUERY PLAN
+ |--SCAN TABLE terms VIRTUAL TABLE INDEX 0:
+ `--SEARCH TABLE x3 USING COVERING INDEX i1 (y=?)
+} {
a b c d e f g h i j k l
}
do_plansql_test 4.5 {
SELECT y FROM terms, x3 WHERE y = term AND occurrences>1 AND col = '*'
} {
- 0 0 0 {SCAN TABLE terms VIRTUAL TABLE INDEX 0:}
- 0 1 1 {SEARCH TABLE x3 USING COVERING INDEX i1 (y=?)}
+ QUERY PLAN
+ |--SCAN TABLE terms VIRTUAL TABLE INDEX 0:
+ `--SEARCH TABLE x3 USING COVERING INDEX i1 (y=?)
+} {
a k l
}
#-------------------------------------------------------------------------
# The following tests check that fts4aux can handle an fts table with an
Index: test/fts3expr.test
==================================================================
--- test/fts3expr.test
+++ test/fts3expr.test
@@ -407,11 +407,11 @@
do_test fts3expr-5.1 {
catchsql { SELECT fts3_exprtest('simple', 'a b') }
} {1 {Usage: fts3_exprtest(tokenizer, expr, col1, ...}}
do_test fts3expr-5.2 {
catchsql { SELECT fts3_exprtest('doesnotexist', 'a b', 'c') }
-} {1 {No such tokenizer module}}
+} {1 {unknown tokenizer: doesnotexist}}
do_test fts3expr-5.3 {
catchsql { SELECT fts3_exprtest('simple', 'a b OR', 'c') }
} {1 {Error parsing expression}}
#------------------------------------------------------------------------
Index: test/fts3expr4.test
==================================================================
--- test/fts3expr4.test
+++ test/fts3expr4.test
@@ -27,11 +27,12 @@
proc test_fts3expr {tokenizer expr} {
db one {SELECT fts3_exprtest($tokenizer, $expr, 'a', 'b', 'c')}
}
proc do_icu_expr_test {tn expr res} {
- uplevel [list do_test $tn [list test_fts3expr icu $expr] [list {*}$res]]
+ set res2 [list {*}$res]
+ uplevel [list do_test $tn [list test_fts3expr "icu en_US" $expr] $res2]
}
proc do_simple_expr_test {tn expr res} {
uplevel [list do_test $tn [list test_fts3expr simple $expr] [list {*}$res]]
}
Index: test/fts3join.test
==================================================================
--- test/fts3join.test
+++ test/fts3join.test
@@ -94,11 +94,13 @@
SELECT * FROM t4 LEFT JOIN (
SELECT docid, * FROM ft4 WHERE ft4 MATCH ?
) AS rr ON t4.rowid=rr.docid
WHERE t4.y = ?;
} {
- 1 0 0 {SCAN TABLE ft4 VIRTUAL TABLE INDEX 3:}
- 0 0 0 {SCAN TABLE t4}
- 0 1 1 {SEARCH SUBQUERY 1 AS rr USING AUTOMATIC COVERING INDEX (docid=?)}
+ QUERY PLAN
+ |--MATERIALIZE xxxxxx
+ | `--SCAN TABLE ft4 VIRTUAL TABLE INDEX 3:
+ |--SCAN TABLE t4
+ `--SEARCH SUBQUERY xxxxxx AS rr USING AUTOMATIC COVERING INDEX (docid=?)
}
finish_test
Index: test/fts3query.test
==================================================================
--- test/fts3query.test
+++ test/fts3query.test
@@ -116,30 +116,34 @@
}
} {}
do_eqp_test fts3query-4.2 {
SELECT t1.number FROM t1, ft WHERE t1.number=ft.rowid ORDER BY t1.date
} {
- 0 0 0 {SCAN TABLE t1 USING COVERING INDEX i1}
- 0 1 1 {SCAN TABLE ft VIRTUAL TABLE INDEX 1:}
+ QUERY PLAN
+ |--SCAN TABLE t1 USING COVERING INDEX i1
+ `--SCAN TABLE ft VIRTUAL TABLE INDEX 1:
}
do_eqp_test fts3query-4.3 {
SELECT t1.number FROM ft, t1 WHERE t1.number=ft.rowid ORDER BY t1.date
} {
- 0 0 1 {SCAN TABLE t1 USING COVERING INDEX i1}
- 0 1 0 {SCAN TABLE ft VIRTUAL TABLE INDEX 1:}
+ QUERY PLAN
+ |--SCAN TABLE t1 USING COVERING INDEX i1
+ `--SCAN TABLE ft VIRTUAL TABLE INDEX 1:
}
do_eqp_test fts3query-4.4 {
SELECT t1.number FROM t1, bt WHERE t1.number=bt.rowid ORDER BY t1.date
} {
- 0 0 0 {SCAN TABLE t1 USING COVERING INDEX i1}
- 0 1 1 {SEARCH TABLE bt USING INTEGER PRIMARY KEY (rowid=?)}
+ QUERY PLAN
+ |--SCAN TABLE t1 USING COVERING INDEX i1
+ `--SEARCH TABLE bt USING INTEGER PRIMARY KEY (rowid=?)
}
do_eqp_test fts3query-4.5 {
SELECT t1.number FROM bt, t1 WHERE t1.number=bt.rowid ORDER BY t1.date
} {
- 0 0 1 {SCAN TABLE t1 USING COVERING INDEX i1}
- 0 1 0 {SEARCH TABLE bt USING INTEGER PRIMARY KEY (rowid=?)}
+ QUERY PLAN
+ |--SCAN TABLE t1 USING COVERING INDEX i1
+ `--SEARCH TABLE bt USING INTEGER PRIMARY KEY (rowid=?)
}
# Test that calling matchinfo() with the wrong number of arguments, or with
# an invalid argument returns an error.
Index: test/fuzz_malloc.test
==================================================================
--- test/fuzz_malloc.test
+++ test/fuzz_malloc.test
@@ -55,13 +55,24 @@
for {set ii 0} {$ii < $::fuzzyopts(-repeats)} {incr ii} {
expr srand($jj)
incr jj
set ::sql [subst $::fuzzyopts(-template)]
# puts fuzyy-sql=\[$::sql\]; flush stdout
- foreach {rc res} [catchsql "$::sql"] {}
+ foreach {rc ::fmtres} [catchsql "$::sql"] {}
if {$rc==0} {
- do_malloc_test $testname-$ii -sqlbody $::sql -sqlprep $::prep
+ set nErr1 [set_test_counter errors]
+ do_faultsim_test $testname-$ii -faults oom* -body {
+ execsql $::sql
+ } -test {
+ if {$testrc && $testresult!="datatype mismatch"} {
+ faultsim_test_result {0 {}}
+ }
+ }
+ if {[set_test_counter errors]>$nErr1} {
+ puts "Previous fuzzy-sql=\[$::sql\]"
+ flush stdout
+ }
} else {
incr ii -1
}
}
}
Index: test/index6.test
==================================================================
--- test/index6.test
+++ test/index6.test
@@ -316,12 +316,13 @@
} {}
do_eqp_test index6-8.1 {
SELECT * FROM t8a LEFT JOIN t8b ON (x = 'value' AND y = a)
} {
- 0 0 0 {SCAN TABLE t8a}
- 0 1 1 {SEARCH TABLE t8b USING INDEX i8c (y=?)}
+ QUERY PLAN
+ |--SCAN TABLE t8a
+ `--SEARCH TABLE t8b USING INDEX i8c (y=?)
}
do_execsql_test index6-8.2 {
SELECT * FROM t8a LEFT JOIN t8b ON (x = 'value' AND y = a)
} {
Index: test/index7.test
==================================================================
--- test/index7.test
+++ test/index7.test
@@ -319,13 +319,12 @@
} {
def xyz
}
do_eqp_test index7-6.4 {
SELECT * FROM v4 WHERE d='xyz' AND c='def'
-} {
- 0 0 0 {SEARCH TABLE t4 USING INDEX i4 (c=?)}
-}
+} {SEARCH TABLE t4 USING INDEX i4 (c=?)}
+
do_catchsql_test index7-6.5 {
CREATE INDEX t5a ON t5(a) WHERE a=#1;
} {1 {near "#1": syntax error}}
Index: test/indexedby.test
==================================================================
--- test/indexedby.test
+++ test/indexedby.test
@@ -38,29 +38,30 @@
uplevel "execsql {EXPLAIN QUERY PLAN $sql}"
}
# These tests are to check that "EXPLAIN QUERY PLAN" is working as expected.
#
-do_execsql_test indexedby-1.2 {
- EXPLAIN QUERY PLAN select * from t1 WHERE a = 10;
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}}
-do_execsql_test indexedby-1.3 {
- EXPLAIN QUERY PLAN select * from t1 ;
-} {0 0 0 {SCAN TABLE t1}}
-do_execsql_test indexedby-1.4 {
- EXPLAIN QUERY PLAN select * from t1, t2 WHERE c = 10;
+do_eqp_test indexedby-1.2 {
+ select * from t1 WHERE a = 10;
+} {SEARCH TABLE t1 USING INDEX i1 (a=?)}
+do_eqp_test indexedby-1.3 {
+ select * from t1 ;
+} {SCAN TABLE t1}
+do_eqp_test indexedby-1.4 {
+ select * from t1, t2 WHERE c = 10;
} {
- 0 0 1 {SEARCH TABLE t2 USING INDEX i3 (c=?)}
- 0 1 0 {SCAN TABLE t1}
+ QUERY PLAN
+ |--SEARCH TABLE t2 USING INDEX i3 (c=?)
+ `--SCAN TABLE t1
}
# Parser tests. Test that an INDEXED BY or NOT INDEX clause can be
# attached to a table in the FROM clause, but not to a sub-select or
# SQL view. Also test that specifying an index that does not exist or
# is attached to a different table is detected as an error.
#
-# EVIDENCE-OF: R-07004-11522 -- syntax diagram qualified-table-name
+# X-EVIDENCE-OF: R-07004-11522 -- syntax diagram qualified-table-name
#
# EVIDENCE-OF: R-58230-57098 The "INDEXED BY index-name" phrase
# specifies that the named index must be used in order to look up values
# on the preceding table.
#
@@ -113,29 +114,27 @@
# index shall be used when accessing the preceding table, including
# implied indices create by UNIQUE and PRIMARY KEY constraints. However,
# the rowid can still be used to look up entries even when "NOT INDEXED"
# is specified.
#
-do_execsql_test indexedby-3.1 {
- EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a = 'one' AND b = 'two'
+do_eqp_test indexedby-3.1 {
+ SELECT * FROM t1 WHERE a = 'one' AND b = 'two'
} {/SEARCH TABLE t1 USING INDEX/}
-do_execsql_test indexedby-3.1.1 {
- EXPLAIN QUERY PLAN SELECT * FROM t1 NOT INDEXED WHERE a = 'one' AND b = 'two'
-} {0 0 0 {SCAN TABLE t1}}
-do_execsql_test indexedby-3.1.2 {
- EXPLAIN QUERY PLAN SELECT * FROM t1 NOT INDEXED WHERE rowid=1
+do_eqp_test indexedby-3.1.1 {
+ SELECT * FROM t1 NOT INDEXED WHERE a = 'one' AND b = 'two'
+} {SCAN TABLE t1}
+do_eqp_test indexedby-3.1.2 {
+ SELECT * FROM t1 NOT INDEXED WHERE rowid=1
} {/SEARCH TABLE t1 USING INTEGER PRIMARY KEY .rowid=/}
-do_execsql_test indexedby-3.2 {
- EXPLAIN QUERY PLAN
+do_eqp_test indexedby-3.2 {
SELECT * FROM t1 INDEXED BY i1 WHERE a = 'one' AND b = 'two'
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}}
-do_execsql_test indexedby-3.3 {
- EXPLAIN QUERY PLAN
+} {SEARCH TABLE t1 USING INDEX i1 (a=?)}
+do_eqp_test indexedby-3.3 {
SELECT * FROM t1 INDEXED BY i2 WHERE a = 'one' AND b = 'two'
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i2 (b=?)}}
+} {SEARCH TABLE t1 USING INDEX i2 (b=?)}
do_test indexedby-3.4 {
catchsql { SELECT * FROM t1 INDEXED BY i2 WHERE a = 'one' }
} {1 {no query solution}}
do_test indexedby-3.5 {
catchsql { SELECT * FROM t1 INDEXED BY i2 ORDER BY a }
@@ -145,38 +144,38 @@
} {0 {}}
do_test indexedby-3.7 {
catchsql { SELECT * FROM t1 INDEXED BY i1 ORDER BY a }
} {0 {}}
-do_execsql_test indexedby-3.8 {
- EXPLAIN QUERY PLAN
+do_eqp_test indexedby-3.8 {
SELECT * FROM t3 INDEXED BY sqlite_autoindex_t3_1 ORDER BY e
-} {0 0 0 {SCAN TABLE t3 USING INDEX sqlite_autoindex_t3_1}}
-do_execsql_test indexedby-3.9 {
- EXPLAIN QUERY PLAN
+} {SCAN TABLE t3 USING INDEX sqlite_autoindex_t3_1}
+do_eqp_test indexedby-3.9 {
SELECT * FROM t3 INDEXED BY sqlite_autoindex_t3_1 WHERE e = 10
-} {0 0 0 {SEARCH TABLE t3 USING INDEX sqlite_autoindex_t3_1 (e=?)}}
+} {SEARCH TABLE t3 USING INDEX sqlite_autoindex_t3_1 (e=?)}
do_test indexedby-3.10 {
catchsql { SELECT * FROM t3 INDEXED BY sqlite_autoindex_t3_1 WHERE f = 10 }
} {1 {no query solution}}
do_test indexedby-3.11 {
catchsql { SELECT * FROM t3 INDEXED BY sqlite_autoindex_t3_2 WHERE f = 10 }
} {1 {no such index: sqlite_autoindex_t3_2}}
# Tests for multiple table cases.
#
-do_execsql_test indexedby-4.1 {
- EXPLAIN QUERY PLAN SELECT * FROM t1, t2 WHERE a = c
-} {
- 0 0 0 {SCAN TABLE t1}
- 0 1 1 {SEARCH TABLE t2 USING INDEX i3 (c=?)}
-}
-do_execsql_test indexedby-4.2 {
- EXPLAIN QUERY PLAN SELECT * FROM t1 INDEXED BY i1, t2 WHERE a = c
-} {
- 0 0 1 {SCAN TABLE t2}
- 0 1 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}
+do_eqp_test indexedby-4.1 {
+ SELECT * FROM t1, t2 WHERE a = c
+} {
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--SEARCH TABLE t2 USING INDEX i3 (c=?)
+}
+do_eqp_test indexedby-4.2 {
+ SELECT * FROM t1 INDEXED BY i1, t2 WHERE a = c
+} {
+ QUERY PLAN
+ |--SCAN TABLE t2
+ `--SEARCH TABLE t1 USING INDEX i1 (a=?)
}
do_test indexedby-4.3 {
catchsql {
SELECT * FROM t1 INDEXED BY i1, t2 INDEXED BY i3 WHERE a=c
}
@@ -192,14 +191,14 @@
# a CREATE VIEW statement is dropped and recreated.
#
do_execsql_test indexedby-5.1 {
CREATE VIEW v2 AS SELECT * FROM t1 INDEXED BY i1 WHERE a > 5;
EXPLAIN QUERY PLAN SELECT * FROM v2
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a>?)}}
+} {/*SEARCH TABLE t1 USING INDEX i1 (a>?)*/}
do_execsql_test indexedby-5.2 {
EXPLAIN QUERY PLAN SELECT * FROM v2 WHERE b = 10
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a>?)}}
+} {/*SEARCH TABLE t1 USING INDEX i1 (a>?)*/}
do_test indexedby-5.3 {
execsql { DROP INDEX i1 }
catchsql { SELECT * FROM v2 }
} {1 {no such index: i1}}
do_test indexedby-5.4 {
@@ -214,61 +213,59 @@
catchsql { SELECT * FROM v2 }
} {0 {}}
# Test that "NOT INDEXED" may use the rowid index, but not others.
#
-do_execsql_test indexedby-6.1 {
- EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE b = 10 ORDER BY rowid
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i2 (b=?)}}
-do_execsql_test indexedby-6.2 {
- EXPLAIN QUERY PLAN SELECT * FROM t1 NOT INDEXED WHERE b = 10 ORDER BY rowid
-} {0 0 0 {SCAN TABLE t1}}
+do_eqp_test indexedby-6.1 {
+ SELECT * FROM t1 WHERE b = 10 ORDER BY rowid
+} {SEARCH TABLE t1 USING INDEX i2 (b=?)}
+do_eqp_test indexedby-6.2 {
+ SELECT * FROM t1 NOT INDEXED WHERE b = 10 ORDER BY rowid
+} {SCAN TABLE t1}
# EVIDENCE-OF: R-40297-14464 The INDEXED BY phrase forces the SQLite
# query planner to use a particular named index on a DELETE, SELECT, or
# UPDATE statement.
#
# Test that "INDEXED BY" can be used in a DELETE statement.
#
-do_execsql_test indexedby-7.1 {
- EXPLAIN QUERY PLAN DELETE FROM t1 WHERE a = 5
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}}
-do_execsql_test indexedby-7.2 {
- EXPLAIN QUERY PLAN DELETE FROM t1 NOT INDEXED WHERE a = 5
-} {0 0 0 {SCAN TABLE t1}}
-do_execsql_test indexedby-7.3 {
- EXPLAIN QUERY PLAN DELETE FROM t1 INDEXED BY i1 WHERE a = 5
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}}
-do_execsql_test indexedby-7.4 {
- EXPLAIN QUERY PLAN DELETE FROM t1 INDEXED BY i1 WHERE a = 5 AND b = 10
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}}
-do_execsql_test indexedby-7.5 {
- EXPLAIN QUERY PLAN DELETE FROM t1 INDEXED BY i2 WHERE a = 5 AND b = 10
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i2 (b=?)}}
+do_eqp_test indexedby-7.1 {
+ DELETE FROM t1 WHERE a = 5
+} {SEARCH TABLE t1 USING INDEX i1 (a=?)}
+do_eqp_test indexedby-7.2 {
+ DELETE FROM t1 NOT INDEXED WHERE a = 5
+} {SCAN TABLE t1}
+do_eqp_test indexedby-7.3 {
+ DELETE FROM t1 INDEXED BY i1 WHERE a = 5
+} {SEARCH TABLE t1 USING INDEX i1 (a=?)}
+do_eqp_test indexedby-7.4 {
+ DELETE FROM t1 INDEXED BY i1 WHERE a = 5 AND b = 10
+} {SEARCH TABLE t1 USING INDEX i1 (a=?)}
+do_eqp_test indexedby-7.5 {
+ DELETE FROM t1 INDEXED BY i2 WHERE a = 5 AND b = 10
+} {SEARCH TABLE t1 USING INDEX i2 (b=?)}
do_test indexedby-7.6 {
catchsql { DELETE FROM t1 INDEXED BY i2 WHERE a = 5}
} {1 {no query solution}}
# Test that "INDEXED BY" can be used in an UPDATE statement.
#
-do_execsql_test indexedby-8.1 {
- EXPLAIN QUERY PLAN UPDATE t1 SET rowid=rowid+1 WHERE a = 5
-} {0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i1 (a=?)}}
-do_execsql_test indexedby-8.2 {
- EXPLAIN QUERY PLAN UPDATE t1 NOT INDEXED SET rowid=rowid+1 WHERE a = 5
-} {0 0 0 {SCAN TABLE t1}}
-do_execsql_test indexedby-8.3 {
- EXPLAIN QUERY PLAN UPDATE t1 INDEXED BY i1 SET rowid=rowid+1 WHERE a = 5
-} {0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i1 (a=?)}}
-do_execsql_test indexedby-8.4 {
- EXPLAIN QUERY PLAN
+do_eqp_test indexedby-8.1 {
+ UPDATE t1 SET rowid=rowid+1 WHERE a = 5
+} {SEARCH TABLE t1 USING COVERING INDEX i1 (a=?)}
+do_eqp_test indexedby-8.2 {
+ UPDATE t1 NOT INDEXED SET rowid=rowid+1 WHERE a = 5
+} {SCAN TABLE t1}
+do_eqp_test indexedby-8.3 {
+ UPDATE t1 INDEXED BY i1 SET rowid=rowid+1 WHERE a = 5
+} {SEARCH TABLE t1 USING COVERING INDEX i1 (a=?)}
+do_eqp_test indexedby-8.4 {
UPDATE t1 INDEXED BY i1 SET rowid=rowid+1 WHERE a = 5 AND b = 10
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}}
-do_execsql_test indexedby-8.5 {
- EXPLAIN QUERY PLAN
+} {SEARCH TABLE t1 USING INDEX i1 (a=?)}
+do_eqp_test indexedby-8.5 {
UPDATE t1 INDEXED BY i2 SET rowid=rowid+1 WHERE a = 5 AND b = 10
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i2 (b=?)}}
+} {SEARCH TABLE t1 USING INDEX i2 (b=?)}
do_test indexedby-8.6 {
catchsql { UPDATE t1 INDEXED BY i2 SET rowid=rowid+1 WHERE a = 5}
} {1 {no query solution}}
# Test that bug #3560 is fixed.
@@ -339,11 +336,11 @@
do_execsql_test 11.4 {
SELECT a,b,rowid FROM x1 INDEXED BY x1i WHERE a=1 AND b=1 AND rowid='3.0';
} {1 1 3}
do_eqp_test 11.5 {
SELECT a,b,rowid FROM x1 INDEXED BY x1i WHERE a=1 AND b=1 AND rowid='3.0';
-} {0 0 0 {SEARCH TABLE x1 USING COVERING INDEX x1i (a=? AND b=? AND rowid=?)}}
+} {SEARCH TABLE x1 USING COVERING INDEX x1i (a=? AND b=? AND rowid=?)}
do_execsql_test 11.6 {
CREATE TABLE x2(c INTEGER PRIMARY KEY, a, b TEXT);
CREATE INDEX x2i ON x2(a, b);
INSERT INTO x2 VALUES(1, 1, 1);
@@ -360,11 +357,11 @@
do_execsql_test 11.9 {
SELECT a,b,c FROM x2 INDEXED BY x2i WHERE a=1 AND b=1 AND c='3.0';
} {1 1 3}
do_eqp_test 11.10 {
SELECT a,b,c FROM x2 INDEXED BY x2i WHERE a=1 AND b=1 AND c='3.0';
-} {0 0 0 {SEARCH TABLE x2 USING COVERING INDEX x2i (a=? AND b=? AND rowid=?)}}
+} {SEARCH TABLE x2 USING COVERING INDEX x2i (a=? AND b=? AND rowid=?)}
#-------------------------------------------------------------------------
# Check INDEXED BY works (throws an exception) with partial indexes that
# cannot be used.
do_execsql_test 12.1 {
Index: test/indexexpr2.test
==================================================================
--- test/indexexpr2.test
+++ test/indexexpr2.test
@@ -89,14 +89,15 @@
do_eqp_test 3.3.1 {
SELECT json_extract(x, '$.b') FROM t2
WHERE json_extract(x, '$.b') IS NOT NULL AND json_extract(x, '$.a') IS NULL
GROUP BY json_extract(x, '$.b') COLLATE nocase
ORDER BY json_extract(x, '$.b') COLLATE nocase;
- } {
- 0 0 0 {SCAN TABLE t2}
- 0 0 0 {USE TEMP B-TREE FOR GROUP BY}
- }
+ } [string map {"\n " \n} {
+ QUERY PLAN
+ |--SCAN TABLE t2
+ `--USE TEMP B-TREE FOR GROUP BY
+ }]
do_execsql_test 3.3.2 {
CREATE INDEX i3 ON t3(json_extract(x, '$.a'), json_extract(x, '$.b'));
} {}
@@ -103,14 +104,15 @@
do_eqp_test 3.3.3 {
SELECT json_extract(x, '$.b') FROM t3
WHERE json_extract(x, '$.b') IS NOT NULL AND json_extract(x, '$.a') IS NULL
GROUP BY json_extract(x, '$.b') COLLATE nocase
ORDER BY json_extract(x, '$.b') COLLATE nocase;
- } {
- 0 0 0 {SEARCH TABLE t3 USING INDEX i3 (=?)}
- 0 0 0 {USE TEMP B-TREE FOR GROUP BY}
- }
+ } [string map {"\n " \n} {
+ QUERY PLAN
+ |--SEARCH TABLE t3 USING INDEX i3 (=?)
+ `--USE TEMP B-TREE FOR GROUP BY
+ }]
}
do_execsql_test 3.4.0 {
CREATE TABLE t4(a, b);
INSERT INTO t4 VALUES('.ABC', 1);
Index: test/istrue.test
==================================================================
--- test/istrue.test
+++ test/istrue.test
@@ -141,6 +141,21 @@
do_execsql_test istrue-600.$tn.4 {
SELECT x IS FALSE FROM t1;
} {0}
}
+do_execsql_test istrue-700 {
+ CREATE TABLE t7(
+ a INTEGER PRIMARY KEY,
+ b BOOLEAN DEFAULT false,
+ c BOOLEAN DEFAULT true
+ );
+ INSERT INTO t7(a) VALUES(1);
+ INSERT INTO t7(a,b,c) VALUES(2,true,false);
+ ALTER TABLE t7 ADD COLUMN d BOOLEAN DEFAULT false;
+ ALTER TABLE t7 ADD COLUMN e BOOLEAN DEFAULT true;
+ INSERT INTO t7(a,b,c) VALUES(3,true,false);
+ INSERT INTO t7 VALUES(4,false,true,true,false);
+ SELECT *,'x' FROM t7 ORDER BY a;
+} {1 0 1 0 1 x 2 1 0 0 1 x 3 1 0 0 1 x 4 0 1 1 0 x}
+
finish_test
Index: test/join2.test
==================================================================
--- test/join2.test
+++ test/join2.test
@@ -110,19 +110,21 @@
}
do_eqp_test 3.1 {
SELECT v2 FROM t1 LEFT JOIN t2 USING (k2) LEFT JOIN t3_1 USING (k3);
} {
- 0 0 0 {SCAN TABLE t1}
- 0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)
}
do_eqp_test 3.2 {
SELECT v2 FROM t1 LEFT JOIN t2 USING (k2) LEFT JOIN t3_2 USING (k3);
} {
- 0 0 0 {SCAN TABLE t1}
- 0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)}
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)
}
#-------------------------------------------------------------------------
# Test that tables other than the rightmost can be omitted from a
# LEFT JOIN query.
@@ -156,19 +158,21 @@
} {2 v3 2 v3 1112 {} 1112 {}}
do_eqp_test 4.1.5 {
SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v2);
} {
- 0 0 0 {SCAN TABLE c1}
- 0 1 1 {SEARCH TABLE c2 USING INTEGER PRIMARY KEY (rowid=?)}
- 0 2 2 {SEARCH TABLE c3 USING INTEGER PRIMARY KEY (rowid=?)}
+ QUERY PLAN
+ |--SCAN TABLE c1
+ |--SEARCH TABLE c2 USING INTEGER PRIMARY KEY (rowid=?)
+ `--SEARCH TABLE c3 USING INTEGER PRIMARY KEY (rowid=?)
}
do_eqp_test 4.1.6 {
SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v1+1);
} {
- 0 0 0 {SCAN TABLE c1}
- 0 1 2 {SEARCH TABLE c3 USING INTEGER PRIMARY KEY (rowid=?)}
+ QUERY PLAN
+ |--SCAN TABLE c1
+ `--SEARCH TABLE c3 USING INTEGER PRIMARY KEY (rowid=?)
}
do_execsql_test 4.2.0 {
DROP TABLE c1;
DROP TABLE c2;
@@ -201,19 +205,21 @@
} {2 v3 2 v3 1112 {} 1112 {}}
do_eqp_test 4.2.5 {
SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v2);
} {
- 0 0 0 {SCAN TABLE c1}
- 0 1 1 {SEARCH TABLE c2 USING INDEX sqlite_autoindex_c2_1 (k=?)}
- 0 2 2 {SEARCH TABLE c3 USING INDEX sqlite_autoindex_c3_1 (k=?)}
+ QUERY PLAN
+ |--SCAN TABLE c1
+ |--SEARCH TABLE c2 USING INDEX sqlite_autoindex_c2_1 (k=?)
+ `--SEARCH TABLE c3 USING INDEX sqlite_autoindex_c3_1 (k=?)
}
do_eqp_test 4.2.6 {
SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v1+1);
} {
- 0 0 0 {SCAN TABLE c1}
- 0 1 2 {SEARCH TABLE c3 USING INDEX sqlite_autoindex_c3_1 (k=?)}
+ QUERY PLAN
+ |--SCAN TABLE c1
+ `--SEARCH TABLE c3 USING INDEX sqlite_autoindex_c3_1 (k=?)
}
# 2017-11-23 (Thanksgiving day)
# OSSFuzz found an assertion fault in the new LEFT JOIN eliminator code.
#
@@ -243,29 +249,24 @@
CREATE TABLE s3 (a INTEGER);
CREATE UNIQUE INDEX ndx on s3(a);
}
do_eqp_test 5.1 {
SELECT s1.a FROM s1 left join s2 using (a);
-} {
- 0 0 0 {SCAN TABLE s1}
-}
+} {SCAN TABLE s1}
+
do_eqp_test 5.2 {
SELECT s1.a FROM s1 left join s3 using (a);
-} {
- 0 0 0 {SCAN TABLE s1}
-}
+} {SCAN TABLE s1}
do_execsql_test 6.0 {
CREATE TABLE u1(a INTEGER PRIMARY KEY, b, c);
CREATE TABLE u2(a INTEGER PRIMARY KEY, b, c);
CREATE INDEX u1ab ON u1(b, c);
}
do_eqp_test 6.1 {
SELECT u2.* FROM u2 LEFT JOIN u1 ON( u1.a=u2.a AND u1.b=u2.b AND u1.c=u2.c );
-} {
- 0 0 0 {SCAN TABLE u2}
-}
+} {SCAN TABLE u2}
db close
sqlite3 db :memory:
do_execsql_test 7.0 {
CREATE TABLE t1(a,b); INSERT INTO t1 VALUES(1,2),(3,4),(5,6);
Index: test/join5.test
==================================================================
--- test/join5.test
+++ test/join5.test
@@ -209,7 +209,87 @@
} 1
do_execsql_test 5.5 {
SELECT * FROM ( SELECT * FROM y1 ) LEFT JOIN y2 ON x
} {0 0 1 {}}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 6.1 {
+ CREATE TABLE t1(x);
+ INSERT INTO t1 VALUES(1);
+
+ CREATE TABLE t2(y INTEGER PRIMARY KEY,a,b);
+ INSERT INTO t2 VALUES(1,2,3);
+ CREATE INDEX t2a ON t2(a);
+ CREATE INDEX t2b ON t2(b);
+}
+
+do_execsql_test 6.2 {
+ SELECT * FROM t1 LEFT JOIN t2 ON a=2 OR b=3 WHERE y IS NULL;
+} {}
+
+do_execsql_test 6.3.1 {
+ CREATE TABLE t3(x);
+ INSERT INTO t3 VALUES(1);
+ CREATE TABLE t4(y, z);
+ SELECT ifnull(z, '!!!') FROM t3 LEFT JOIN t4 ON (x=y);
+} {!!!}
+
+do_execsql_test 6.3.2 {
+ CREATE INDEX t4i ON t4(y, ifnull(z, '!!!'));
+ SELECT ifnull(z, '!!!') FROM t3 LEFT JOIN t4 ON (x=y);
+} {!!!}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 7.0 {
+ CREATE TABLE t1(x);
+ INSERT INTO t1 VALUES(1);
+}
+
+do_execsql_test 7.1 {
+ CREATE TABLE t2(x, y, z);
+ CREATE INDEX t2xy ON t2(x, y);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<50000
+ )
+ INSERT INTO t2 SELECT i/10, i, NULL FROM s;
+ ANALYZE;
+}
+
+do_eqp_test 7.2 {
+ SELECT * FROM t1 LEFT JOIN t2 ON (
+ t2.x = t1.x AND (t2.y=? OR (t2.y=? AND t2.z IS NOT NULL))
+ );
+} {
+ QUERY PLAN
+ |--SCAN TABLE t1
+ `--MULTI-INDEX OR
+ |--SEARCH TABLE t2 USING INDEX t2xy (x=? AND y=?)
+ `--SEARCH TABLE t2 USING INDEX t2xy (x=? AND y=?)
+}
+
+do_execsql_test 7.3 {
+ CREATE TABLE t3(x);
+
+ CREATE TABLE t4(x, y, z);
+ CREATE INDEX t4xy ON t4(x, y);
+ CREATE INDEX t4xz ON t4(x, z);
+
+ WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<50000)
+ INSERT INTO t4 SELECT i/10, i, i FROM s;
+
+ ANALYZE;
+}
+
+do_eqp_test 7.4 {
+ SELECT * FROM t3 LEFT JOIN t4 ON (t4.x = t3.x) WHERE (t4.y = ? OR t4.z = ?);
+} {
+ QUERY PLAN
+ |--SCAN TABLE t3
+ `--SEARCH TABLE t4 USING INDEX t4xz (x=?)
+}
finish_test
Index: test/mallocK.test
==================================================================
--- test/mallocK.test
+++ test/mallocK.test
@@ -119,14 +119,15 @@
} {1}
ifcapable stat4 {
do_eqp_test 6.1 {
SELECT DISTINCT c FROM t3 WHERE b BETWEEN '.xx..' AND '.xxxx';
- } {
- 0 0 0 {SEARCH TABLE t3 USING INDEX i3 (ANY(a) AND b>? AND b)}
- 0 0 0 {USE TEMP B-TREE FOR DISTINCT}
- }
+ } [string map {"\n " \n} {
+ QUERY PLAN
+ |--SEARCH TABLE t3 USING INDEX i3 (ANY(a) AND b>? AND b)
+ `--USE TEMP B-TREE FOR DISTINCT
+ }]
}
do_faultsim_test 6 -faults oom* -body {
db cache flush
db eval { SELECT DISTINCT c FROM t3 WHERE b BETWEEN '.xx..' AND '.xxxx' }
Index: test/orderby1.test
==================================================================
--- test/orderby1.test
+++ test/orderby1.test
@@ -452,13 +452,16 @@
}
} {1 13 1 14 1 15 1 16}
# No sorting of queries that omit the FROM clause.
#
-do_execsql_test 5.0 {
- EXPLAIN QUERY PLAN SELECT 5 ORDER BY 1
-} {}
+do_eqp_test 5.0 {
+ SELECT 5 ORDER BY 1
+} {
+ QUERY PLAN
+ `--SCAN CONSTANT ROW
+}
do_execsql_test 5.1 {
EXPLAIN QUERY PLAN SELECT 5 UNION ALL SELECT 3 ORDER BY 1
} {~/B-TREE/}
do_execsql_test 5.2 {
SELECT 5 UNION ALL SELECT 3 ORDER BY 1
@@ -510,12 +513,13 @@
}
do_eqp_test 8.1 {
SELECT * FROM t1 ORDER BY a, b;
} {
- 0 0 0 {SCAN TABLE t1 USING INDEX i1}
- 0 0 0 {USE TEMP B-TREE FOR RIGHT PART OF ORDER BY}
+ QUERY PLAN
+ |--SCAN TABLE t1 USING INDEX i1
+ `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY
}
do_execsql_test 8.2 {
WITH cnt(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM cnt WHERE i<10000
Index: test/permutations.test
==================================================================
--- test/permutations.test
+++ test/permutations.test
@@ -1070,10 +1070,30 @@
#$::dbhandle cache size 0
} -files [
test_set $allquicktests -exclude *malloc* *ioerr* *fault* \
stmtvtab1.test index9.test
]
+
+test_suite "sorterref" -prefix "" -description {
+ Run the "veryquick" test suite with SQLITE_CONFIG_SORTERREF_SIZE set
+ to 0 so that sorter-references are used whenever possible.
+} -files [
+ test_set $allquicktests -exclude *malloc* *ioerr* *fault* *bigfile* *_err* \
+ *fts5corrupt* *fts5big* *fts5aj*
+] -initialize {
+ catch {db close}
+ sqlite3_shutdown
+ sqlite3_config_sorterref 0
+ sqlite3_initialize
+ autoinstall_test_functions
+} -shutdown {
+ catch {db close}
+ sqlite3_shutdown
+ sqlite3_config_sorterref -1
+ sqlite3_initialize
+ autoinstall_test_functions
+}
# End of tests
#############################################################################
# run_tests NAME OPTIONS
ADDED test/resetdb.test
Index: test/resetdb.test
==================================================================
--- /dev/null
+++ test/resetdb.test
@@ -0,0 +1,138 @@
+# 2018-04-28
+#
+# 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.
+#
+#***********************************************************************
+# Test cases for SQLITE_DBCONFIG_RESET_DATABASE
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix resetdb
+
+ifcapable !vtab||!compound {
+ finish_test
+ return
+}
+
+# Create a sample database
+do_execsql_test 100 {
+ PRAGMA page_size=4096;
+ CREATE TABLE t1(a,b);
+ WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<20)
+ INSERT INTO t1(a,b) SELECT x, randomblob(300) FROM c;
+ CREATE INDEX t1a ON t1(a);
+ CREATE INDEX t1b ON t1(b);
+ SELECT sum(a), sum(length(b)) FROM t1;
+ PRAGMA integrity_check;
+ PRAGMA journal_mode;
+ PRAGMA page_count;
+} {210 6000 ok delete 8}
+
+# Verify that the same content is seen from a separate database connection
+sqlite3 db2 test.db
+do_test 110 {
+ execsql {
+ SELECT sum(a), sum(length(b)) FROM t1;
+ PRAGMA integrity_check;
+ PRAGMA journal_mode;
+ PRAGMA page_count;
+ } db2
+} {210 6000 ok delete 8}
+
+do_test 200 {
+ # Thoroughly corrupt the database file by overwriting the first
+ # page with randomness.
+ catchsql {
+ UPDATE sqlite_dbpage SET data=randomblob(4096) WHERE pgno=1;
+ PRAGMA quick_check;
+ }
+} {1 {unsupported file format}}
+do_test 201 {
+ catchsql {
+ PRAGMA quick_check;
+ } db2
+} {1 {unsupported file format}}
+
+do_test 210 {
+ # Reset the database file using SQLITE_DBCONFIG_RESET_DATABASE
+ sqlite3_db_config db RESET_DB 1
+ db eval VACUUM
+ sqlite3_db_config db RESET_DB 0
+
+ # Verify that the reset took, even on the separate database connection
+ catchsql {
+ PRAGMA page_count;
+ PRAGMA page_size;
+ PRAGMA quick_check;
+ PRAGMA journal_mode;
+ } db2
+} {0 {1 4096 ok delete}}
+
+# Delete the old connections and database and start over again
+# with a different page size and in WAL mode.
+#
+db close
+db2 close
+forcedelete test.db
+sqlite3 db test.db
+do_execsql_test 300 {
+ PRAGMA page_size=8192;
+ PRAGMA journal_mode=WAL;
+ CREATE TABLE t1(a,b);
+ WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<20)
+ INSERT INTO t1(a,b) SELECT x, randomblob(1300) FROM c;
+ CREATE INDEX t1a ON t1(a);
+ CREATE INDEX t1b ON t1(b);
+ SELECT sum(a), sum(length(b)) FROM t1;
+ PRAGMA integrity_check;
+ PRAGMA journal_mode;
+ PRAGMA page_size;
+ PRAGMA page_count;
+} {wal 210 26000 ok wal 8192 12}
+sqlite3 db2 test.db
+do_test 310 {
+ execsql {
+ SELECT sum(a), sum(length(b)) FROM t1;
+ PRAGMA integrity_check;
+ PRAGMA journal_mode;
+ PRAGMA page_size;
+ PRAGMA page_count;
+ } db2
+} {210 26000 ok wal 8192 12}
+
+# Corrupt the database again
+do_catchsql_test 320 {
+ UPDATE sqlite_dbpage SET data=randomblob(8192) WHERE pgno=1;
+ PRAGMA quick_check
+} {1 {file is not a database}}
+
+do_test 330 {
+ catchsql {
+ PRAGMA quick_check
+ } db2
+} {1 {file is not a database}}
+
+# Reset the database yet again. Verify that the page size and
+# journal mode are preserved.
+#
+do_test 400 {
+ sqlite3_db_config db RESET_DB 1
+ db eval VACUUM
+ sqlite3_db_config db RESET_DB 0
+ catchsql {
+ PRAGMA page_count;
+ PRAGMA page_size;
+ PRAGMA journal_mode;
+ PRAGMA quick_check;
+ } db2
+} {0 {1 8192 wal ok}}
+db2 close
+
+
+finish_test
Index: test/rollback2.test
==================================================================
--- test/rollback2.test
+++ test/rollback2.test
@@ -99,11 +99,11 @@
#--------------------------------------------------------------------
# Try with some index scans
#
do_eqp_test 3.1 {
SELECT i FROM t1 WHERE (i%2)==0 ORDER BY h DESC;
-} {0 0 0 {SCAN TABLE t1 USING INDEX i1}}
+} {SCAN TABLE t1 USING INDEX i1}
do_rollback_test 3.2 -setup {
BEGIN;
DELETE FROM t1 WHERE (i%2)==1;
} -select {
SELECT i FROM t1 WHERE (i%2)==0 ORDER BY h DESC;
@@ -129,11 +129,11 @@
set leader [string repeat "abcdefghij" 70]
do_execsql_test 4.1 { UPDATE t1 SET h = $leader || h; }
do_eqp_test 4.2 {
SELECT i FROM t1 WHERE (i%2)==0 ORDER BY h ASC;
-} {0 0 0 {SCAN TABLE t1 USING INDEX i1}}
+} {SCAN TABLE t1 USING INDEX i1}
do_rollback_test 4.3 -setup {
BEGIN;
DELETE FROM t1 WHERE (i%2)==1;
} -select {
SELECT i FROM t1 WHERE (i%2)==0 ORDER BY h ASC;
Index: test/rowvalue.test
==================================================================
--- test/rowvalue.test
+++ test/rowvalue.test
@@ -173,23 +173,23 @@
}
foreach {tn sql res eqp} {
1 "SELECT * FROM xy WHERE (i, j) IS (2, 2)" {2 2 2}
- "0 0 0 {SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid=?)}"
+ "SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid=?)"
2 "SELECT * FROM xy WHERE (k, j) < (2, 3)" {1 1 1 2 2 2}
- "0 0 0 {SCAN TABLE xy}"
+ "SCAN TABLE xy"
3 "SELECT * FROM xy WHERE (i, j) < (2, 3)" {1 1 1 2 2 2}
- "0 0 0 {SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid)}"
+ "SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid)"
4 "SELECT * FROM xy WHERE (i, j) > (2, 1)" {2 2 2 3 3 3 4 4 4}
- "0 0 0 {SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid>?)}"
+ "SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid>?)"
5 "SELECT * FROM xy WHERE (i, j) > ('2', 1)" {2 2 2 3 3 3 4 4 4}
- "0 0 0 {SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid>?)}"
+ "SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid>?)"
} {
do_eqp_test 7.$tn.1 $sql $eqp
do_execsql_test 7.$tn.2 $sql $res
}
Index: test/rowvalue4.test
==================================================================
--- test/rowvalue4.test
+++ test/rowvalue4.test
@@ -182,38 +182,37 @@
CREATE INDEX c1ab ON c1(a, b);
CREATE INDEX c1cd ON c1(c, d);
ANALYZE;
}
- do_eqp_test 3.1.1 { SELECT * FROM c1 WHERE a=1 AND c=2 } {
- 0 0 0 {SEARCH TABLE c1 USING INDEX c1cd (c=?)}
- }
- do_eqp_test 3.1.2 { SELECT * FROM c1 WHERE a=1 AND b>'d' AND c=2 } {
- 0 0 0 {SEARCH TABLE c1 USING INDEX c1cd (c=?)}
- }
- do_eqp_test 3.1.3 { SELECT * FROM c1 WHERE a=1 AND b>'l' AND c=2 } {
- 0 0 0 {SEARCH TABLE c1 USING INDEX c1ab (a=? AND b>?)}
- }
-
- do_eqp_test 3.2.1 { SELECT * FROM c1 WHERE a=1 AND c>1 } {
- 0 0 0 {SEARCH TABLE c1 USING INDEX c1cd (c>?)}
- }
- do_eqp_test 3.2.2 { SELECT * FROM c1 WHERE a=1 AND c>0 } {
- 0 0 0 {SEARCH TABLE c1 USING INDEX c1ab (a=?)}
- }
- do_eqp_test 3.2.3 { SELECT * FROM c1 WHERE a=1 AND c>=1 } {
- 0 0 0 {SEARCH TABLE c1 USING INDEX c1ab (a=?)}
- }
- do_eqp_test 3.2.4 { SELECT * FROM c1 WHERE a=1 AND (c, d)>(1, 'c') } {
- 0 0 0 {SEARCH TABLE c1 USING INDEX c1ab (a=?)}
- }
- do_eqp_test 3.2.5 { SELECT * FROM c1 WHERE a=1 AND (c, d)>(1, 'o') } {
- 0 0 0 {SEARCH TABLE c1 USING INDEX c1cd ((c,d)>(?,?))}
- }
- do_eqp_test 3.2.6 { SELECT * FROM c1 WHERE a=1 AND (c, +b)>(1, 'c') } {
- 0 0 0 {SEARCH TABLE c1 USING INDEX c1ab (a=?)}
- }
+ do_eqp_test 3.1.1 { SELECT * FROM c1 WHERE a=1 AND c=2 } \
+ {SEARCH TABLE c1 USING INDEX c1cd (c=?)}
+
+ do_eqp_test 3.1.2 { SELECT * FROM c1 WHERE a=1 AND b>'d' AND c=2 } \
+ {SEARCH TABLE c1 USING INDEX c1cd (c=?)}
+
+ do_eqp_test 3.1.3 { SELECT * FROM c1 WHERE a=1 AND b>'l' AND c=2 } \
+ {SEARCH TABLE c1 USING INDEX c1ab (a=? AND b>?)}
+
+ do_eqp_test 3.2.1 { SELECT * FROM c1 WHERE a=1 AND c>1 } \
+ {SEARCH TABLE c1 USING INDEX c1cd (c>?)}
+
+ do_eqp_test 3.2.2 { SELECT * FROM c1 WHERE a=1 AND c>0 } \
+ {SEARCH TABLE c1 USING INDEX c1ab (a=?)}
+
+ do_eqp_test 3.2.3 { SELECT * FROM c1 WHERE a=1 AND c>=1 } \
+ {SEARCH TABLE c1 USING INDEX c1ab (a=?)}
+
+ do_eqp_test 3.2.4 { SELECT * FROM c1 WHERE a=1 AND (c, d)>(1, 'c') } \
+ {SEARCH TABLE c1 USING INDEX c1ab (a=?)}
+
+ do_eqp_test 3.2.5 { SELECT * FROM c1 WHERE a=1 AND (c, d)>(1, 'o') } \
+ {SEARCH TABLE c1 USING INDEX c1cd ((c,d)>(?,?))}
+
+ do_eqp_test 3.2.6 { SELECT * FROM c1 WHERE a=1 AND (c, +b)>(1, 'c') } \
+ {SEARCH TABLE c1 USING INDEX c1ab (a=?)}
+
}
#------------------------------------------------------------------------
do_execsql_test 5.0 {
@@ -232,15 +231,16 @@
do_eqp_test 5.1 {
SELECT * FROM d2 WHERE
(a, b) IN (SELECT x, y FROM d1) AND
(c) IN (SELECT y FROM d1)
} {
- 0 0 0 {SEARCH TABLE d2 USING INDEX d2ab (a=? AND b=?)}
- 0 0 0 {EXECUTE LIST SUBQUERY 1}
- 1 0 0 {SCAN TABLE d1}
- 0 0 0 {EXECUTE LIST SUBQUERY 2}
- 2 0 0 {SCAN TABLE d1}
+ QUERY PLAN
+ |--SEARCH TABLE d2 USING INDEX d2ab (a=? AND b=?)
+ |--LIST SUBQUERY
+ | `--SCAN TABLE d1
+ `--LIST SUBQUERY
+ `--SCAN TABLE d1
}
do_execsql_test 6.0 {
CREATE TABLE e1(a, b, c, d, e);
CREATE INDEX e1ab ON e1(a, b);
@@ -247,35 +247,27 @@
CREATE INDEX e1cde ON e1(c, d, e);
}
do_eqp_test 6.1 {
SELECT * FROM e1 WHERE (a, b) > (?, ?)
-} {
- 0 0 0 {SEARCH TABLE e1 USING INDEX e1ab ((a,b)>(?,?))}
-}
+} {SEARCH TABLE e1 USING INDEX e1ab ((a,b)>(?,?))}
+
do_eqp_test 6.2 {
SELECT * FROM e1 WHERE (a, b) < (?, ?)
-} {
- 0 0 0 {SEARCH TABLE e1 USING INDEX e1ab ((a,b)<(?,?))}
-}
+} {SEARCH TABLE e1 USING INDEX e1ab ((a,b)<(?,?))}
+
do_eqp_test 6.3 {
SELECT * FROM e1 WHERE c = ? AND (d, e) > (?, ?)
-} {
- 0 0 0 {SEARCH TABLE e1 USING INDEX e1cde (c=? AND (d,e)>(?,?))}
-}
+} {SEARCH TABLE e1 USING INDEX e1cde (c=? AND (d,e)>(?,?))}
+
do_eqp_test 6.4 {
SELECT * FROM e1 WHERE c = ? AND (d, e) < (?, ?)
-} {
- 0 0 0 {SEARCH TABLE e1 USING INDEX e1cde (c=? AND (d,e)<(?,?))}
-}
+} {SEARCH TABLE e1 USING INDEX e1cde (c=? AND (d,e)<(?,?))}
do_eqp_test 6.5 {
SELECT * FROM e1 WHERE (d, e) BETWEEN (?, ?) AND (?, ?) AND c = ?
-} {
- 0 0 0
- {SEARCH TABLE e1 USING INDEX e1cde (c=? AND (d,e)>(?,?) AND (d,e)<(?,?))}
-}
+} {SEARCH TABLE e1 USING INDEX e1cde (c=? AND (d,e)>(?,?) AND (d,e)<(?,?))}
#-------------------------------------------------------------------------
do_execsql_test 7.1 {
CREATE TABLE f1(a, b, c);
Index: test/scanstatus.test
==================================================================
--- test/scanstatus.test
+++ test/scanstatus.test
@@ -326,11 +326,11 @@
zExplain {SEARCH TABLE t1 USING COVERING INDEX sqlite_autoindex_t1_1 (a=?)}
}
do_eqp_test 5.3.1 {
SELECT count(*) FROM t2 WHERE y = 'j';
-} {0 0 0 {SEARCH TABLE t2 USING COVERING INDEX t2xy (ANY(x) AND y=?)}}
+} {SEARCH TABLE t2 USING COVERING INDEX t2xy (ANY(x) AND y=?)}
do_execsql_test 5.3.2 {
SELECT count(*) FROM t2 WHERE y = 'j';
} {19}
do_scanstatus_test 5.3.3 {
nLoop 1 nVisit 19 nEst 56.0 zName t2xy zExplain
@@ -338,12 +338,13 @@
}
do_eqp_test 5.4.1 {
SELECT count(*) FROM t1, t2 WHERE y = c;
} {
- 0 0 0 {SCAN TABLE t1 USING COVERING INDEX t1bc}
- 0 1 1 {SEARCH TABLE t2 USING COVERING INDEX t2xy (ANY(x) AND y=?)}
+ QUERY PLAN
+ |--SCAN TABLE t1 USING COVERING INDEX t1bc
+ `--SEARCH TABLE t2 USING COVERING INDEX t2xy (ANY(x) AND y=?)
}
do_execsql_test 5.4.2 {
SELECT count(*) FROM t1, t2 WHERE y = c;
} {200}
do_scanstatus_test 5.4.3 {
@@ -354,12 +355,13 @@
}
do_eqp_test 5.5.1 {
SELECT count(*) FROM t1, t3 WHERE y = c;
} {
- 0 0 1 {SCAN TABLE t3}
- 0 1 0 {SEARCH TABLE t1 USING AUTOMATIC COVERING INDEX (c=?)}
+ QUERY PLAN
+ |--SCAN TABLE t3
+ `--SEARCH TABLE t1 USING AUTOMATIC COVERING INDEX (c=?)
}
do_execsql_test 5.5.2 {
SELECT count(*) FROM t1, t3 WHERE y = c;
} {200}
do_scanstatus_test 5.5.3 {
Index: test/selectA.test
==================================================================
--- test/selectA.test
+++ test/selectA.test
@@ -1334,15 +1334,18 @@
SELECT c, d FROM t5
UNION ALL
SELECT a, b FROM t4 WHERE f()==f()
ORDER BY 1,2
} {
- 1 0 0 {SCAN TABLE t5 USING INDEX i2}
- 1 0 0 {USE TEMP B-TREE FOR RIGHT PART OF ORDER BY}
- 2 0 0 {SCAN TABLE t4 USING INDEX i1}
- 2 0 0 {USE TEMP B-TREE FOR RIGHT PART OF ORDER BY}
- 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (UNION ALL)}
+ QUERY PLAN
+ `--MERGE (UNION ALL)
+ |--LEFT
+ | |--SCAN TABLE t5 USING INDEX i2
+ | `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY
+ `--RIGHT
+ |--SCAN TABLE t4 USING INDEX i1
+ `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY
}
do_execsql_test 4.1.3 {
SELECT c, d FROM t5
UNION ALL
Index: test/selectD.test
==================================================================
--- test/selectD.test
+++ test/selectD.test
@@ -167,8 +167,8 @@
LEFT JOIN (SELECT count(*) AS cnt, x1.d
FROM (t42 INNER JOIN t43 ON d=g) AS x1
WHERE x1.d>5
GROUP BY x1.d) AS x2
ON t41.b=x2.d;
-} {/.*SEARCH SUBQUERY 1 AS x2 USING AUTOMATIC.*/}
+} {/*SEARCH SUBQUERY 0x* AS x2 USING AUTOMATIC*/}
finish_test
Index: test/skipscan2.test
==================================================================
--- test/skipscan2.test
+++ test/skipscan2.test
@@ -197,9 +197,9 @@
}
execsql { ANALYZE }
} {}
do_eqp_test skipscan2-3.3eqp {
SELECT * FROM t3 WHERE b=42;
-} {0 0 0 {SEARCH TABLE t3 USING PRIMARY KEY (ANY(a) AND b=?)}}
+} {SEARCH TABLE t3 USING PRIMARY KEY (ANY(a) AND b=?)}
finish_test
Index: test/skipscan6.test
==================================================================
--- test/skipscan6.test
+++ test/skipscan6.test
@@ -177,24 +177,15 @@
# Use index "t3_a", as (a=?) is expected to match only a single row.
#
do_eqp_test 3.1 {
SELECT * FROM t3 WHERE a = ? AND c = ?
-} {
- 0 0 0 {SEARCH TABLE t3 USING INDEX t3_a (a=?)}
-}
+} {SEARCH TABLE t3 USING INDEX t3_a (a=?)}
# The same query on table t2. This should use index "t2_a", for the
# same reason. At one point though, it was mistakenly using a skip-scan.
#
do_eqp_test 3.2 {
SELECT * FROM t2 WHERE a = ? AND c = ?
-} {
- 0 0 0 {SEARCH TABLE t2 USING INDEX t2_a (a=?)}
-}
-
-finish_test
-
-
-
+} {SEARCH TABLE t2 USING INDEX t2_a (a=?)}
finish_test
Index: test/soak.test
==================================================================
--- test/soak.test
+++ test/soak.test
@@ -65,16 +65,16 @@
corruptC.test
}
set G(isquick) 1
-set soak_starttime [clock seconds]
+set soak_starttime [clock_seconds]
set soak_finishtime [expr {$soak_starttime + $TIMEOUT}]
# Loop until the timeout is reached or an error occurs.
#
-for {set iRun 0} {[clock seconds] < $soak_finishtime} {incr iRun} {
+for {set iRun 0} {[clock_seconds] < $soak_finishtime} {incr iRun} {
set iIdx [expr {$iRun % [llength $SOAKTESTS]}]
source [file join $testdir [lindex $SOAKTESTS $iIdx]]
catch {db close}
ADDED test/sorterref.test
Index: test/sorterref.test
==================================================================
--- /dev/null
+++ test/sorterref.test
@@ -0,0 +1,50 @@
+# 2018 April 14.
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix sorterref
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a, b, c);
+ INSERT INTO t1 VALUES(1, 2, 3);
+ INSERT INTO t1 VALUES(4, 5, 6);
+ ALTER TABLE t1 ADD COLUMN d DEFAULT 'string';
+ INSERT INTO t1 VALUES(7, 8, 9, 'text');
+}
+
+do_execsql_test 1.1 {
+ SELECT * FROM t1 ORDER BY b;
+} {
+ 1 2 3 string 4 5 6 string 7 8 9 text
+}
+
+do_execsql_test 2.0 {
+ DROP TABLE IF EXISTS t1;
+ CREATE TABLE t1(a, b);
+ CREATE TABLE t2(c, d, PRIMARY KEY(c)) WITHOUT ROWID;
+
+ INSERT INTO t1 VALUES(1, 2);
+ INSERT INTO t1 VALUES(2, 3);
+ INSERT INTO t1 VALUES(3, 4);
+
+ INSERT INTO t2 VALUES(1, 'one');
+ INSERT INTO t2 VALUES(3, 'three');
+}
+
+do_execsql_test 2.1 {
+ SELECT * FROM t1 LEFT JOIN t2 ON (a=c) ORDER BY b;
+} {1 2 1 one 2 3 {} {} 3 4 3 three}
+
+
+
+finish_test
Index: test/tester.tcl
==================================================================
--- test/tester.tcl
+++ test/tester.tcl
@@ -982,13 +982,84 @@
proc do_timed_execsql_test {testname sql {result {}}} {
fix_testname testname
uplevel do_test [list $testname] [list "execsql_timed {$sql}"]\
[list [list {*}$result]]
}
+
+# Run an EXPLAIN QUERY PLAN $sql in database "db". Then rewrite the output
+# as an ASCII-art graph and return a string that is that graph.
+#
+# Hexadecimal literals in the output text are converted into "xxxxxx" since those
+# literals are pointer values that might very from one run of the test to the
+# next, yet we want the output to be consistent.
+#
+proc query_plan_graph {sql} {
+ db eval "EXPLAIN QUERY PLAN $sql" {
+ set dx($id) $detail
+ lappend cx($parent) $id
+ }
+ set a "\n QUERY PLAN\n"
+ append a [append_graph " " dx cx 0]
+ return [regsub -all { 0x[A-F0-9]+\y} $a { xxxxxx}]
+}
+
+# Helper routine for [query_plan_graph SQL]:
+#
+# Output rows of the graph that are children of $level.
+#
+# prefix: Prepend to every output line
+#
+# dxname: Name of an array variable that stores text describe
+# The description for $id is $dx($id)
+#
+# cxname: Name of an array variable holding children of item.
+# Children of $id are $cx($id)
+#
+# level: Render all lines that are children of $level
+#
+proc append_graph {prefix dxname cxname level} {
+ upvar $dxname dx $cxname cx
+ set a ""
+ set x $cx($level)
+ set n [llength $x]
+ for {set i 0} {$i<$n} {incr i} {
+ set id [lindex $x $i]
+ if {$i==$n-1} {
+ set p1 "`--"
+ set p2 " "
+ } else {
+ set p1 "|--"
+ set p2 "| "
+ }
+ append a $prefix$p1$dx($id)\n
+ if {[info exists cx($id)]} {
+ append a [append_graph "$prefix$p2" dx cx $id]
+ }
+ }
+ return $a
+}
+
+# Do an EXPLAIN QUERY PLAN test on input $sql with expected results $res
+#
+# If $res begins with a "\s+QUERY PLAN\n" then it is assumed to be the
+# complete graph which must match the output of [query_plan_graph $sql]
+# exactly.
+#
+# If $res does not begin with "\s+QUERY PLAN\n" then take it is a string
+# that must be found somewhere in the query plan output.
+#
proc do_eqp_test {name sql res} {
- uplevel do_execsql_test $name [list "EXPLAIN QUERY PLAN $sql"] [list $res]
+ if {[regexp {^\s+QUERY PLAN\n} $res]} {
+ uplevel do_test $name [list [list query_plan_graph $sql]] [list $res]
+ } else {
+ if {[string index $res 0]!="/"} {
+ set res "/*$res*/"
+ }
+ uplevel do_execsql_test $name [list "EXPLAIN QUERY PLAN $sql"] [list $res]
+ }
}
+
#-------------------------------------------------------------------------
# Usage: do_select_tests PREFIX ?SWITCHES? TESTLIST
#
# Where switches are:
Index: test/tkt-385a5b56b9.test
==================================================================
--- test/tkt-385a5b56b9.test
+++ test/tkt-385a5b56b9.test
@@ -32,22 +32,19 @@
CREATE TABLE t2(x, y NOT NULL);
CREATE UNIQUE INDEX t2x ON t2(x);
CREATE UNIQUE INDEX t2y ON t2(y);
}
-do_eqp_test 2.1 { SELECT DISTINCT x FROM t2 } {
- 0 0 0 {SCAN TABLE t2 USING COVERING INDEX t2x}
-}
-
-do_eqp_test 2.2 { SELECT DISTINCT y FROM t2 } {
- 0 0 0 {SCAN TABLE t2 USING COVERING INDEX t2y}
-}
-
-do_eqp_test 2.3 { SELECT DISTINCT x, y FROM t2 WHERE y=10 } {
- 0 0 0 {SEARCH TABLE t2 USING INDEX t2y (y=?)}
-}
-
-do_eqp_test 2.4 { SELECT DISTINCT x, y FROM t2 WHERE x=10 } {
- 0 0 0 {SEARCH TABLE t2 USING INDEX t2x (x=?)}
-}
+do_eqp_test 2.1 { SELECT DISTINCT x FROM t2 } \
+ {SCAN TABLE t2 USING COVERING INDEX t2x}
+
+do_eqp_test 2.2 { SELECT DISTINCT y FROM t2 } \
+ {SCAN TABLE t2 USING COVERING INDEX t2y}
+
+do_eqp_test 2.3 { SELECT DISTINCT x, y FROM t2 WHERE y=10 } \
+ {SEARCH TABLE t2 USING INDEX t2y (y=?)}
+
+do_eqp_test 2.4 { SELECT DISTINCT x, y FROM t2 WHERE x=10 } \
+ {SEARCH TABLE t2 USING INDEX t2x (x=?)}
+
finish_test
Index: test/tkt-78e04e52ea.test
==================================================================
--- test/tkt-78e04e52ea.test
+++ test/tkt-78e04e52ea.test
@@ -39,14 +39,12 @@
execsql {
CREATE INDEX i1 ON ""("" COLLATE nocase);
}
} {}
do_test tkt-78e04-1.4 {
- execsql {
- EXPLAIN QUERY PLAN SELECT "" FROM "" WHERE "" LIKE '1abc%';
- }
-} {0 0 0 {SCAN TABLE USING COVERING INDEX i1}}
+ db eval {EXPLAIN QUERY PLAN SELECT "" FROM "" WHERE "" LIKE '1abc%';}
+} {/*SCAN TABLE USING COVERING INDEX i1*/}
do_test tkt-78e04-1.5 {
execsql {
DROP TABLE "";
SELECT name FROM sqlite_master;
}
@@ -55,14 +53,14 @@
do_test tkt-78e04-2.1 {
execsql {
CREATE INDEX "" ON t2(x);
EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE x=5;
}
-} {0 0 0 {SEARCH TABLE t2 USING COVERING INDEX (x=?)}}
+} {/*SEARCH TABLE t2 USING COVERING INDEX (x=?)*/}
do_test tkt-78e04-2.2 {
execsql {
DROP INDEX "";
EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE x=2;
}
-} {0 0 0 {SCAN TABLE t2}}
+} {/*SCAN TABLE t2*/}
finish_test
Index: test/tkt-b75a9ca6b0.test
==================================================================
--- test/tkt-b75a9ca6b0.test
+++ test/tkt-b75a9ca6b0.test
@@ -30,45 +30,45 @@
do_execsql_test 1.1 {
CREATE INDEX i1 ON t1(x, y);
}
-set idxscan {0 0 0 {SCAN TABLE t1 USING COVERING INDEX i1}}
-set tblscan {0 0 0 {SCAN TABLE t1}}
-set grpsort {0 0 0 {USE TEMP B-TREE FOR GROUP BY}}
-set sort {0 0 0 {USE TEMP B-TREE FOR ORDER BY}}
+set idxscan {SCAN TABLE t1 USING COVERING INDEX i1}
+set tblscan {SCAN TABLE t1}
+set grpsort {USE TEMP B-TREE FOR GROUP BY}
+set sort {USE TEMP B-TREE FOR ORDER BY}
foreach {tn q res eqp} [subst -nocommands {
1 "SELECT * FROM t1 GROUP BY x, y ORDER BY x,y"
{1 3 2 2 3 1} {$idxscan}
2 "SELECT * FROM t1 GROUP BY x, y ORDER BY x"
- {1 3 2 2 3 1} {$idxscan $sort}
+ {1 3 2 2 3 1} {$idxscan*$sort}
3 "SELECT * FROM t1 GROUP BY y, x ORDER BY y, x"
- {3 1 2 2 1 3} {$idxscan $sort}
+ {3 1 2 2 1 3} {$idxscan*$sort}
4 "SELECT * FROM t1 GROUP BY x ORDER BY x"
{1 3 2 2 3 1} {$idxscan}
5 "SELECT * FROM t1 GROUP BY y ORDER BY y"
- {3 1 2 2 1 3} {$tblscan $grpsort}
+ {3 1 2 2 1 3} {$tblscan*$grpsort}
6 "SELECT * FROM t1 GROUP BY y ORDER BY x"
- {1 3 2 2 3 1} {$tblscan $grpsort $sort}
+ {1 3 2 2 3 1} {$tblscan*$grpsort*$sort}
7 "SELECT * FROM t1 GROUP BY x, y ORDER BY x, y DESC"
- {1 3 2 2 3 1} {$idxscan $sort}
+ {1 3 2 2 3 1} {$idxscan*$sort}
8 "SELECT * FROM t1 GROUP BY x, y ORDER BY x DESC, y DESC"
- {3 1 2 2 1 3} {$idxscan $sort}
+ {3 1 2 2 1 3} {$idxscan*$sort}
9 "SELECT * FROM t1 GROUP BY x, y ORDER BY x ASC, y ASC"
{1 3 2 2 3 1} {$idxscan}
10 "SELECT * FROM t1 GROUP BY x, y ORDER BY x COLLATE nocase, y"
- {1 3 2 2 3 1} {$idxscan $sort}
+ {1 3 2 2 3 1} {$idxscan*$sort}
}] {
do_execsql_test 1.$tn.1 $q $res
do_eqp_test 1.$tn.2 $q $eqp
}
Index: test/tkt3442.test
==================================================================
--- test/tkt3442.test
+++ test/tkt3442.test
@@ -32,39 +32,28 @@
);
CREATE UNIQUE INDEX ididx ON listhash(id);
}
} {}
-
-# Explain Query Plan
-#
-proc EQP {sql} {
- uplevel "execsql {EXPLAIN QUERY PLAN $sql}"
-}
-
-
# These tests perform an EXPLAIN QUERY PLAN on both versions of the
# SELECT referenced in ticket #3442 (both '5000' and "5000")
# and verify that the query plan is the same.
#
-ifcapable explain {
- do_test tkt3442-1.2 {
- EQP { SELECT node FROM listhash WHERE id='5000' LIMIT 1; }
- } {0 0 0 {SEARCH TABLE listhash USING INDEX ididx (id=?)}}
- do_test tkt3442-1.3 {
- EQP { SELECT node FROM listhash WHERE id="5000" LIMIT 1; }
- } {0 0 0 {SEARCH TABLE listhash USING INDEX ididx (id=?)}}
-}
+do_eqp_test tkt3442-1.2 {
+ SELECT node FROM listhash WHERE id='5000' LIMIT 1;
+} {SEARCH TABLE listhash USING INDEX ididx (id=?)}
+do_eqp_test tkt3442-1.3 {
+ SELECT node FROM listhash WHERE id="5000" LIMIT 1;
+} {SEARCH TABLE listhash USING INDEX ididx (id=?)}
# Some extra tests testing other permutations of 5000.
#
-ifcapable explain {
- do_test tkt3442-1.4 {
- EQP { SELECT node FROM listhash WHERE id=5000 LIMIT 1; }
- } {0 0 0 {SEARCH TABLE listhash USING INDEX ididx (id=?)}}
-}
+do_eqp_test tkt3442-1.4 {
+ SELECT node FROM listhash WHERE id=5000 LIMIT 1;
+} {SEARCH TABLE listhash USING INDEX ididx (id=?)}
+
do_test tkt3442-1.5 {
catchsql {
SELECT node FROM listhash WHERE id=[5000] LIMIT 1;
}
} {1 {no such column: 5000}}
Index: test/tpch01.test
==================================================================
--- test/tpch01.test
+++ test/tpch01.test
@@ -163,11 +163,11 @@
group by
o_year
order by
o_year;}]
set ::eqpres
-} {/0 0 0 {SEARCH TABLE part USING INDEX bootleg_pti .P_TYPE=..} 0 1 2 {SEARCH TABLE lineitem USING INDEX lpki2 .L_PARTKEY=..}.*/}
+} {/*SEARCH TABLE part USING INDEX bootleg_pti *SEARCH TABLE lineitem USING INDEX lpki2*/}
do_test tpch01-1.1b {
set ::eqpres
} {/.* customer .* nation AS n1 .*/}
do_test tpch01-1.1c {
set ::eqpres
@@ -185,8 +185,16 @@
and l_returnflag = 'R' and c_nationkey = n_nationkey
group by
c_custkey, c_name, c_acctbal, c_phone, n_name, c_address, c_comment
order by
revenue desc;
-} {0 0 1 {SEARCH TABLE orders USING INDEX odi (O_ORDERDATE>? AND O_ORDERDATE)} 0 1 0 {SEARCH TABLE customer USING INDEX cpki (C_CUSTKEY=?)} 0 2 3 {SEARCH TABLE nation USING INDEX npki (N_NATIONKEY=?)} 0 3 2 {SEARCH TABLE lineitem USING INDEX lpki (L_ORDERKEY=?)} 0 0 0 {USE TEMP B-TREE FOR GROUP BY} 0 0 0 {USE TEMP B-TREE FOR ORDER BY}}
+} {
+ QUERY PLAN
+ |--SEARCH TABLE orders USING INDEX odi (O_ORDERDATE>? AND O_ORDERDATE)
+ |--SEARCH TABLE customer USING INDEX cpki (C_CUSTKEY=?)
+ |--SEARCH TABLE nation USING INDEX npki (N_NATIONKEY=?)
+ |--SEARCH TABLE lineitem USING INDEX lpki (L_ORDERKEY=?)
+ |--USE TEMP B-TREE FOR GROUP BY
+ `--USE TEMP B-TREE FOR ORDER BY
+}
finish_test
Index: test/trigger1.test
==================================================================
--- test/trigger1.test
+++ test/trigger1.test
@@ -725,7 +725,47 @@
UPDATE t17b SET ss = 4;
END;
INSERT INTO t17a(ii) VALUES('1');
PRAGMA integrity_check;
} {ok}
+
+# 2018-04-26
+# When a BEFORE UPDATE trigger changes a column value in a row being
+# updated, and that column value is used by the UPDATE to change other
+# column, the value used to compute the update is from before the trigger.
+# In the example that follows, the value of "b" in "c=b" is 2 (the value
+# prior to running the BEFORE UPDATE trigger) not 1000.
+#
+do_execsql_test trigger1-18.0 {
+ CREATE TABLE t18(a PRIMARY KEY,b,c);
+ INSERT INTO t18(a,b,c) VALUES(1,2,3);
+ CREATE TRIGGER t18r1 BEFORE UPDATE ON t18 BEGIN
+ UPDATE t18 SET b=1000 WHERE a=old.a;
+ END;
+ UPDATE t18 SET c=b WHERE a=1;
+ SELECT * FROM t18;
+} {1 1000 2} ;# Not: 1 1000 1000
+do_execsql_test trigger1-18.1 {
+ DELETE FROM t18;
+ INSERT INTO t18(a,b,c) VALUES(1,2,3);
+ UPDATE t18 SET c=b, b=b+1 WHERE a=1;
+ SELECT * FROM t18;
+} {1 3 2} ;# Not: 1 1001 1000
+
+# 2018-04-26 ticket [https://www.sqlite.org/src/tktview/d85fffd6ffe856092e]
+# VDBE Program uses an expired value.
+#
+do_execsql_test trigger1-19.0 {
+ CREATE TABLE t19(a INT PRIMARY KEY, b, c)WITHOUT ROWID;
+ INSERT INTO t19(a,b,c) VALUES(1,2,3);
+ CREATE TRIGGER t19r3 BEFORE UPDATE ON t19 BEGIN SELECT new.b; END;
+ UPDATE t19 SET c=b WHERE a=1;
+ SELECT * FROM t19;
+} {1 2 2}
+do_execsql_test trigger1-19.1 {
+ DELETE FROM t19;
+ INSERT INTO t19(a,b,c) VALUES(1,2,3);
+ UPDATE t19 SET c=CASE WHEN b=2 THEN b ELSE b+99 END WHERE a=1;
+ SELECT * FROM t19;
+} {1 2 2}
finish_test
Index: test/triggerE.test
==================================================================
--- test/triggerE.test
+++ test/triggerE.test
@@ -55,10 +55,11 @@
5 { BEFORE DELETE ON t1 BEGIN SELECT * FROM t2 GROUP BY ?; END; }
6 { BEFORE DELETE ON t1 BEGIN SELECT * FROM t2 LIMIT ?; END; }
7 { BEFORE DELETE ON t1 BEGIN SELECT * FROM t2 ORDER BY ?; END; }
8 { BEFORE UPDATE ON t1 BEGIN UPDATE t2 SET c = ?; END; }
9 { BEFORE UPDATE ON t1 BEGIN UPDATE t2 SET c = 1 WHERE d = ?; END; }
+ 10 { AFTER INSERT ON t1 BEGIN SELECT * FROM pragma_stats(?); END; }
} {
catchsql {drop trigger tr1}
do_catchsql_test 1.1.$tn "CREATE TRIGGER tr1 $defn" [list 1 $errmsg]
do_catchsql_test 1.2.$tn "CREATE TEMP TRIGGER tr1 $defn" [list 1 $errmsg]
}
Index: test/unordered.test
==================================================================
--- test/unordered.test
+++ test/unordered.test
@@ -38,32 +38,31 @@
}
db close
sqlite3 db test.db
foreach {tn sql r(ordered) r(unordered)} {
1 "SELECT * FROM t1 ORDER BY a"
- {0 0 0 {SCAN TABLE t1 USING INDEX i1}}
- {0 0 0 {SCAN TABLE t1} 0 0 0 {USE TEMP B-TREE FOR ORDER BY}}
+ {SCAN TABLE t1 USING INDEX i1}
+ {SCAN TABLE t1*USE TEMP B-TREE FOR ORDER BY}
2 "SELECT * FROM t1 WHERE a > 100"
- {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a>?)}}
- {0 0 0 {SCAN TABLE t1}}
+ {SEARCH TABLE t1 USING INDEX i1 (a>?)}
+ {SCAN TABLE t1}
3 "SELECT * FROM t1 WHERE a = ? ORDER BY rowid"
- {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}}
- {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}}
+ {SEARCH TABLE t1 USING INDEX i1 (a=?)}
+ {SEARCH TABLE t1 USING INDEX i1 (a=?)*USE TEMP B-TREE FOR ORDER BY}
4 "SELECT max(a) FROM t1"
- {0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i1}}
- {0 0 0 {SEARCH TABLE t1}}
+ {SEARCH TABLE t1 USING COVERING INDEX i1}
+ {SEARCH TABLE t1}
5 "SELECT group_concat(b) FROM t1 GROUP BY a"
- {0 0 0 {SCAN TABLE t1 USING INDEX i1}}
- {0 0 0 {SCAN TABLE t1} 0 0 0 {USE TEMP B-TREE FOR GROUP BY}}
+ {SCAN TABLE t1 USING INDEX i1}
+ {SCAN TABLE t1*USE TEMP B-TREE FOR GROUP BY}
6 "SELECT * FROM t1 WHERE a = ?"
- {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}}
- {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}}
+ {SEARCH TABLE t1 USING INDEX i1 (a=?)}
+ {SEARCH TABLE t1 USING INDEX i1 (a=?)}
7 "SELECT count(*) FROM t1"
- {0 0 0 {SCAN TABLE t1 USING COVERING INDEX i1}}
- {0 0 0 {SCAN TABLE t1}}
+ {SCAN TABLE t1 USING COVERING INDEX i1}
+ {SCAN TABLE t1}
} {
do_eqp_test 1.$idxmode.$tn $sql $r($idxmode)
}
}
Index: test/update.test
==================================================================
--- test/update.test
+++ test/update.test
@@ -506,10 +506,22 @@
execsql {
UPDATE t1 SET e=e+1 WHERE a IN (SELECT a FROM t1);
SELECT a,e FROM t1;
}
} {1 15 2 8}
+ do_test update-11.3 {
+ execsql {
+ UPDATE t1 AS xyz SET e=e+1 WHERE xyz.a IN (SELECT a FROM t1);
+ SELECT a,e FROM t1;
+ }
+ } {1 16 2 9}
+ do_test update-11.4 {
+ execsql {
+ UPDATE t1 AS xyz SET e=e+1 WHERE EXISTS(SELECT 1 FROM t1 WHERE t1.a10;
+ INSERT INTO t1(a,b) VALUES(1,2),(3,2) ON CONFLICT(b) DO NOTHING;
+ SELECT * FROM t1;
+} {1 {ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint}}
+do_catchsql_test upsert1-310 {
+ DELETE FROM t1;
+ INSERT INTO t1(a,b) VALUES(1,2),(3,2) ON CONFLICT(b) WHERE b!=10 DO NOTHING;
+ SELECT * FROM t1;
+} {1 {ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint}}
+do_execsql_test upsert1-320 {
+ DELETE FROM t1;
+ INSERT INTO t1(a,b) VALUES(1,2),(3,2),(4,20),(5,20)
+ ON CONFLICT(b) WHERE b>10 DO NOTHING;
+ SELECT *, 'x' FROM t1 ORDER BY b, a;
+} {1 2 0 x 3 2 0 x 4 20 0 x}
+
+# Upsert works with count_changes=on;
+do_execsql_test upsert1-400 {
+ DROP TABLE IF EXISTS t2;
+ CREATE TABLE t2(a TEXT UNIQUE, b INT DEFAULT 1);
+ INSERT INTO t2(a) VALUES('one'),('two'),('three');
+ PRAGMA count_changes=ON;
+ INSERT INTO t2(a) VALUES('one'),('one'),('three'),('four')
+ ON CONFLICT(a) DO UPDATE SET b=b+1;
+} {1}
+do_execsql_test upsert1-410 {
+ PRAGMA count_changes=OFF;
+ SELECT a, b FROM t2 ORDER BY a;
+} {four 1 one 3 three 2 two 1}
+
+# Problem found by AFL prior to any release
+do_execsql_test upsert1-500 {
+ DROP TABLE t1;
+ CREATE TABLE t1(x INTEGER PRIMARY KEY, y INT UNIQUE);
+ INSERT INTO t1(x,y) SELECT 1,2 WHERE true
+ ON CONFLICT(x) DO UPDATE SET y=max(t1.y,excluded.y) AND true;
+ SELECT * FROM t1;
+} {1 2}
+
+finish_test
ADDED test/upsert2.test
Index: test/upsert2.test
==================================================================
--- /dev/null
+++ test/upsert2.test
@@ -0,0 +1,170 @@
+# 2018-04-17
+#
+# 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.
+#
+#***********************************************************************
+#
+# Test cases for UPSERT
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix zipfile
+
+do_execsql_test upsert2-100 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b int, c DEFAULT 0);
+ INSERT INTO t1(a,b) VALUES(1,2),(3,4);
+ INSERT INTO t1(a,b) VALUES(1,8),(2,11),(3,1)
+ ON CONFLICT(a) DO UPDATE SET b=excluded.b, c=c+1 WHERE t1.b0;
+ CREATE UNIQUE INDEX abc2 ON abc(y) WHERE x='xyz' COLLATE nocase;
+ }
+} {
+ reset_db
+ execsql $sql
+ do_execsql_test 4.$tn.1 {
+ INSERT INTO abc VALUES(1, 'one', 1);
+ INSERT INTO abc VALUES(2, 'two', 2);
+ INSERT INTO abc VALUES(3, 'xyz', 3);
+ INSERT INTO abc VALUES(4, 'XYZ', 4);
+ }
+
+ foreach {tn2 oc res} {
+ 1 "ON CONFLICT DO NOTHING" 0
+ 2 "ON CONFLICT(x) WHERE y>0 DO NOTHING" 0
+ 3 "ON CONFLICT(x) DO NOTHING" 2
+ 4 "ON CONFLICT(x) WHERE y>=0 DO NOTHING" 2
+ 5 "ON CONFLICT(y) WHERE x='xyz' COLLATE nocase DO NOTHING" 1
+ } {
+ do_catchsql_test 4.$tn.2.$tn2 "
+ INSERT INTO abc VALUES(5, 'one', 10) $oc
+ " $rtbl($res)
+ }
+
+ do_execsql_test 4.$tn.3 {
+ SELECT * FROM abc
+ } {1 one 1 2 two 2 3 xyz 3 4 XYZ 4}
+
+ foreach {tn2 oc res} {
+ 1 "ON CONFLICT DO NOTHING" 0
+ 2 "ON CONFLICT(y) WHERE x='xyz' COLLATE nocase DO NOTHING" 0
+ 3 "ON CONFLICT(y) WHERE x='xyz' COLLATE binary DO NOTHING" 2
+ 4 "ON CONFLICT(x) WHERE y>0 DO NOTHING" 1
+ } {
+ do_catchsql_test 4.$tn.2.$tn2 "
+ INSERT INTO abc VALUES(5, 'xYz', 3) $oc
+ " $rtbl($res)
+ }
+}
+
+do_catchsql_test 5.0 {
+ CREATE TABLE w1(a INT PRIMARY KEY, x, y);
+ CREATE UNIQUE INDEX w1expr ON w1(('x' || x));
+ INSERT INTO w1 VALUES(2, 'one', NULL)
+ ON CONFLICT (('x' || x) COLLATE nocase) DO NOTHING;
+} {1 {ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint}}
+
+#-------------------------------------------------------------------------
+# Test that ON CONFLICT constraint processing occurs before any REPLACE
+# constraint processing.
+#
+foreach {tn sql} {
+ 1 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE, c);
+ }
+ 2 {
+ CREATE TABLE t1(a INT PRIMARY KEY, b UNIQUE, c);
+ }
+ 3 {
+ CREATE TABLE t1(a INT PRIMARY KEY, b UNIQUE, c) WITHOUT ROWID;
+ }
+} {
+ reset_db
+ execsql $sql
+ do_execsql_test 6.1.$tn {
+ INSERT INTO t1 VALUES(1, 1, 'one');
+ INSERT INTO t1 VALUES(2, 2, 'two');
+ INSERT OR REPLACE INTO t1 VALUES(1, 2, 'two') ON CONFLICT(b) DO NOTHING;
+ PRAGMA integrity_check;
+ } {ok}
+}
+
+foreach {tn sql} {
+ 1 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE, c UNIQUE);
+ }
+} {
+ reset_db
+ execsql $sql
+
+ do_execsql_test 6.2.$tn.1 {
+ INSERT INTO t1 VALUES(1, 1, 1);
+ INSERT INTO t1 VALUES(2, 2, 2);
+ }
+
+ do_execsql_test 6.2.$tn.2 {
+ INSERT OR REPLACE INTO t1 VALUES(3, 1, 1) ON CONFLICT(b) DO NOTHING;
+ SELECT * FROM t1;
+ PRAGMA integrity_check;
+ } {1 1 1 2 2 2 ok}
+
+ do_execsql_test 6.2.$tn.3 {
+ INSERT OR REPLACE INTO t1 VALUES(3, 2, 2) ON CONFLICT(c) DO NOTHING;
+ SELECT * FROM t1;
+ PRAGMA integrity_check;
+ } {1 1 1 2 2 2 ok}
+
+ do_execsql_test 6.2.$tn.2 {
+ INSERT OR REPLACE INTO t1 VALUES(3, 1, 1) ON CONFLICT(b)
+ DO UPDATE SET b=b||'x';
+ SELECT * FROM t1;
+ PRAGMA integrity_check;
+ } {1 1x 1 2 2 2 ok}
+
+ do_execsql_test 6.2.$tn.2 {
+ INSERT OR REPLACE INTO t1 VALUES(3, 2, 2) ON CONFLICT(c)
+ DO UPDATE SET c=c||'x';
+ SELECT * FROM t1;
+ PRAGMA integrity_check;
+ } {1 1x 1 2 2 2x ok}
+}
+
+#-------------------------------------------------------------------------
+# Test references to "excluded". And using an alias in an INSERT
+# statement.
+#
+foreach {tn sql} {
+ 1 {
+ CREATE TABLE t1(w, x, y, z, PRIMARY KEY(x, y));
+ CREATE UNIQUE INDEX zz ON t1(z);
+ }
+ 2 {
+ CREATE TABLE t1(w, x, y, z, PRIMARY KEY(x, y)) WITHOUT ROWID;
+ CREATE UNIQUE INDEX zz ON t1(z);
+ }
+} {
+ reset_db
+ execsql $sql
+ do_execsql_test 7.$tn.0 {
+ INSERT INTO t1 VALUES('a', 1, 1, 1);
+ INSERT INTO t1 VALUES('b', 2, 2, 2);
+ }
+
+ do_execsql_test 7.$tn.1 {
+ INSERT INTO t1 VALUES('c', 3, 3, 1) ON CONFLICT(z)
+ DO UPDATE SET w = excluded.w;
+ SELECT * FROM t1;
+ } {c 1 1 1 b 2 2 2}
+
+ do_execsql_test 7.$tn.2 {
+ INSERT INTO t1 VALUES('c', 2, 2, 3) ON CONFLICT(y, x)
+ DO UPDATE SET w = w||w;
+ SELECT * FROM t1;
+ } {c 1 1 1 bb 2 2 2}
+
+ do_execsql_test 7.$tn.3 {
+ INSERT INTO t1 VALUES('c', 2, 2, 3) ON CONFLICT(y, x)
+ DO UPDATE SET w = w||t1.w;
+ SELECT * FROM t1;
+ } {c 1 1 1 bbbb 2 2 2}
+
+ do_execsql_test 7.$tn.4 {
+ INSERT INTO t1 AS tbl VALUES('c', 2, 2, 3) ON CONFLICT(y, x)
+ DO UPDATE SET w = w||tbl.w;
+ SELECT * FROM t1;
+ } {c 1 1 1 bbbbbbbb 2 2 2}
+}
+
+foreach {tn sql} {
+ 1 {
+ CREATE TABLE excluded(w, x INTEGER, 'a b', z, PRIMARY KEY(x, 'a b'));
+ CREATE UNIQUE INDEX zz ON excluded(z);
+ CREATE INDEX zz2 ON excluded(z);
+ }
+ 2 {
+ CREATE TABLE excluded(w, x, 'a b', z, PRIMARY KEY(x, 'a b')) WITHOUT ROWID;
+ CREATE UNIQUE INDEX zz ON excluded(z);
+ CREATE INDEX zz2 ON excluded(z);
+ }
+} {
+ reset_db
+ execsql $sql
+ do_execsql_test 8.$tn.0 {
+ INSERT INTO excluded VALUES('a', 1, 1, 1);
+ INSERT INTO excluded VALUES('b', 2, 2, 2);
+ }
+
+ # Note: An error in Postgres: "table reference "excluded" is ambiguous".
+ #
+ do_execsql_test 8.$tn.1 {
+ INSERT INTO excluded VALUES('hello', 1, 1, NULL) ON CONFLICT(x, "a b")
+ DO UPDATE SET w=excluded.w;
+ SELECT * FROM excluded;
+ } {a 1 1 1 b 2 2 2}
+
+ do_execsql_test 8.$tn.2 {
+ INSERT INTO excluded AS x1 VALUES('hello', 1, 1, NULL) ON CONFLICT(x, [a b])
+ DO UPDATE SET w=excluded.w;
+ SELECT * FROM excluded;
+ } {hello 1 1 1 b 2 2 2}
+
+ do_execsql_test 8.$tn.3 {
+ INSERT INTO excluded AS x1 VALUES('hello', 1, 1, NULL) ON CONFLICT(x, [a b])
+ DO UPDATE SET w=w||w WHERE excluded.w!='hello';
+ SELECT * FROM excluded;
+ } {hello 1 1 1 b 2 2 2}
+
+ do_execsql_test 8.$tn.4 {
+ INSERT INTO excluded AS x1 VALUES('hello', 1, 1, NULL) ON CONFLICT(x, [a b])
+ DO UPDATE SET w=w||w WHERE excluded.x=1;
+ SELECT * FROM excluded;
+ } {hellohello 1 1 1 b 2 2 2}
+
+ do_catchsql_test 8.$tn.5 {
+ INSERT INTO excluded AS x1 VALUES('hello', 1, 1, NULL)
+ ON CONFLICT(x, [a b]) WHERE y=1
+ DO UPDATE SET w=w||w WHERE excluded.x=1;
+ } {1 {no such column: y}}
+}
+
+#--------------------------------------------------------------------------
+#
+do_execsql_test 9.0 {
+ CREATE TABLE v(x INTEGER);
+ CREATE TABLE hist(x INTEGER PRIMARY KEY, cnt INTEGER);
+ CREATE TRIGGER vt AFTER INSERT ON v BEGIN
+ INSERT INTO hist VALUES(new.x, 1) ON CONFLICT(x) DO
+ UPDATE SET cnt=cnt+1;
+ END;
+}
+
+do_execsql_test 9.1 {
+ INSERT INTO v VALUES(1), (4), (1), (5), (5), (8), (9), (1);
+ SELECT * FROM hist;
+} {
+ 1 3
+ 4 1
+ 5 2
+ 8 1
+ 9 1
+}
+
+
+finish_test
ADDED test/upsertfault.test
Index: test/upsertfault.test
==================================================================
--- /dev/null
+++ test/upsertfault.test
@@ -0,0 +1,38 @@
+# 2018-04-17
+#
+# 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.
+#
+#***********************************************************************
+#
+# Test cases for UPSERT
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix upsertfault
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a PRIMARY KEY, b, c, d, UNIQUE(b, c));
+ INSERT INTO t1 VALUES(1, 1, 1, 1);
+ INSERT INTO t1 VALUES(2, 2, 2, 2);
+}
+faultsim_save_and_close
+
+do_faultsim_test 1 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ db eval { SELECT * FROM sqlite_master }
+} -body {
+ execsql {
+ INSERT INTO t1 VALUES(3, 2, 2, NULL) ON CONFLICT(b, c) DO
+ UPDATE SET d=d+1;
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+
+finish_test
Index: test/where.test
==================================================================
--- test/where.test
+++ test/where.test
@@ -1365,7 +1365,52 @@
do_execsql_test where-18.6 {
INSERT INTO t181 VALUES(2);
SELECT DISTINCT a FROM t181 LEFT JOIN t182 ON a=b ORDER BY +a, +c IS NULL;
} {1 2}
+# Make sure the OR optimization works on a JOIN
+#
+do_execsql_test where-19.0 {
+ CREATE TABLE t191(a INT UNIQUE NOT NULL, b INT UNIQUE NOT NULL,c,d);
+ CREATE INDEX t191a ON t1(a);
+ CREATE INDEX t191b ON t1(b);
+ CREATE TABLE t192(x INTEGER PRIMARY KEY,y INT, z INT);
+
+ EXPLAIN QUERY PLAN
+ SELECT t191.rowid FROM t192, t191 WHERE (a=y OR b=y) AND x=?1;
+} {/.* sqlite_autoindex_t191_1 .* sqlite_autoindex_t191_2 .*/}
+
+# 2018-04-24 ticket [https://www.sqlite.org/src/info/4ba5abf65c5b0f9a]
+# Index on expressions leads to an incorrect answer for a LEFT JOIN
+#
+do_execsql_test where-20.0 {
+ CREATE TABLE t201(x);
+ CREATE TABLE t202(y, z);
+ INSERT INTO t201 VALUES('key');
+ INSERT INTO t202 VALUES('key', -1);
+ CREATE INDEX t202i ON t202(y, ifnull(z, 0));
+ SELECT count(*) FROM t201 LEFT JOIN t202 ON (x=y) WHERE ifnull(z, 0) >=0;
+} {0}
+
+do_execsql_test where-21.0 {
+ CREATE TABLE t12(a, b, c);
+ CREATE TABLE t13(x);
+ CREATE INDEX t12ab ON t12(b, a);
+ CREATE INDEX t12ac ON t12(c, a);
+
+ INSERT INTO t12 VALUES(4, 0, 1);
+ INSERT INTO t12 VALUES(4, 1, 0);
+ INSERT INTO t12 VALUES(5, 0, 1);
+ INSERT INTO t12 VALUES(5, 1, 0);
+
+ INSERT INTO t13 VALUES(1), (2), (3), (4);
+}
+do_execsql_test where-21.1 {
+ SELECT * FROM t12 WHERE
+ a = (SELECT * FROM (SELECT count(*) FROM t13 LIMIT 5) ORDER BY 1 LIMIT 10)
+ AND (b=1 OR c=1);
+} {
+ 4 1 0
+ 4 0 1
+}
finish_test
Index: test/where3.test
==================================================================
--- test/where3.test
+++ test/where3.test
@@ -233,21 +233,24 @@
INSERT INTO t301 VALUES(1,2,3);
INSERT INTO t301 VALUES(2,2,3);
CREATE TABLE t302(x, y);
INSERT INTO t302 VALUES(4,5);
ANALYZE;
- explain query plan SELECT * FROM t302, t301 WHERE t302.x=5 AND t301.a=t302.y;
+}
+do_eqp_test where3-3.0a {
+ SELECT * FROM t302, t301 WHERE t302.x=5 AND t301.a=t302.y;
} {
- 0 0 0 {SCAN TABLE t302}
- 0 1 1 {SEARCH TABLE t301 USING INTEGER PRIMARY KEY (rowid=?)}
+ QUERY PLAN
+ |--SCAN TABLE t302
+ `--SEARCH TABLE t301 USING INTEGER PRIMARY KEY (rowid=?)
}
-do_execsql_test where3-3.1 {
- explain query plan
+do_eqp_test where3-3.1 {
SELECT * FROM t301, t302 WHERE t302.x=5 AND t301.a=t302.y;
} {
- 0 0 1 {SCAN TABLE t302}
- 0 1 0 {SEARCH TABLE t301 USING INTEGER PRIMARY KEY (rowid=?)}
+ QUERY PLAN
+ |--SCAN TABLE t302
+ `--SEARCH TABLE t301 USING INTEGER PRIMARY KEY (rowid=?)
}
do_execsql_test where3-3.2 {
SELECT * FROM t301 WHERE c=3 AND a IS NULL;
} {}
do_execsql_test where3-3.3 {
@@ -306,61 +309,62 @@
keyword_id INTEGER, folder_type TEXT,
dateAdded INTEGER, lastModified INTEGER);
CREATE INDEX bbb_111 ON bbb (fk, type);
CREATE INDEX bbb_222 ON bbb (parent, position);
CREATE INDEX bbb_333 ON bbb (fk, lastModified);
-
- EXPLAIN QUERY PLAN
+}
+do_eqp_test where3-5.0a {
SELECT bbb.title AS tag_title
FROM aaa JOIN bbb ON bbb.id = aaa.parent
WHERE aaa.fk = 'constant'
AND LENGTH(bbb.title) > 0
AND bbb.parent = 4
ORDER BY bbb.title COLLATE NOCASE ASC;
} {
- 0 0 0 {SEARCH TABLE aaa USING INDEX aaa_333 (fk=?)}
- 0 1 1 {SEARCH TABLE bbb USING INTEGER PRIMARY KEY (rowid=?)}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SEARCH TABLE aaa USING INDEX aaa_333 (fk=?)
+ |--SEARCH TABLE bbb USING INTEGER PRIMARY KEY (rowid=?)
+ `--USE TEMP B-TREE FOR ORDER BY
}
-do_execsql_test where3-5.1 {
- EXPLAIN QUERY PLAN
+do_eqp_test where3-5.1 {
SELECT bbb.title AS tag_title
FROM aaa JOIN aaa AS bbb ON bbb.id = aaa.parent
WHERE aaa.fk = 'constant'
AND LENGTH(bbb.title) > 0
AND bbb.parent = 4
ORDER BY bbb.title COLLATE NOCASE ASC;
} {
- 0 0 0 {SEARCH TABLE aaa USING INDEX aaa_333 (fk=?)}
- 0 1 1 {SEARCH TABLE aaa AS bbb USING INTEGER PRIMARY KEY (rowid=?)}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SEARCH TABLE aaa USING INDEX aaa_333 (fk=?)
+ |--SEARCH TABLE aaa AS bbb USING INTEGER PRIMARY KEY (rowid=?)
+ `--USE TEMP B-TREE FOR ORDER BY
}
-do_execsql_test where3-5.2 {
- EXPLAIN QUERY PLAN
+do_eqp_test where3-5.2 {
SELECT bbb.title AS tag_title
FROM bbb JOIN aaa ON bbb.id = aaa.parent
WHERE aaa.fk = 'constant'
AND LENGTH(bbb.title) > 0
AND bbb.parent = 4
ORDER BY bbb.title COLLATE NOCASE ASC;
} {
- 0 0 1 {SEARCH TABLE aaa USING INDEX aaa_333 (fk=?)}
- 0 1 0 {SEARCH TABLE bbb USING INTEGER PRIMARY KEY (rowid=?)}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SEARCH TABLE aaa USING INDEX aaa_333 (fk=?)
+ |--SEARCH TABLE bbb USING INTEGER PRIMARY KEY (rowid=?)
+ `--USE TEMP B-TREE FOR ORDER BY
}
-do_execsql_test where3-5.3 {
- EXPLAIN QUERY PLAN
+do_eqp_test where3-5.3 {
SELECT bbb.title AS tag_title
FROM aaa AS bbb JOIN aaa ON bbb.id = aaa.parent
WHERE aaa.fk = 'constant'
AND LENGTH(bbb.title) > 0
AND bbb.parent = 4
ORDER BY bbb.title COLLATE NOCASE ASC;
} {
- 0 0 1 {SEARCH TABLE aaa USING INDEX aaa_333 (fk=?)}
- 0 1 0 {SEARCH TABLE aaa AS bbb USING INTEGER PRIMARY KEY (rowid=?)}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--SEARCH TABLE aaa USING INDEX aaa_333 (fk=?)
+ |--SEARCH TABLE aaa AS bbb USING INTEGER PRIMARY KEY (rowid=?)
+ `--USE TEMP B-TREE FOR ORDER BY
}
# Name resolution with NATURAL JOIN and USING
#
do_test where3-6.setup {
Index: test/where7.test
==================================================================
--- test/where7.test
+++ test/where7.test
@@ -23339,22 +23339,24 @@
FOREIGN KEY (c8) REFERENCES t301(c8)
);
CREATE INDEX t302_c3 on t302(c3);
CREATE INDEX t302_c8_c3 on t302(c8, c3);
CREATE INDEX t302_c5 on t302(c5);
-
- EXPLAIN QUERY PLAN
+}
+do_eqp_test where7-3.2 {
SELECT t302.c1
FROM t302 JOIN t301 ON t302.c8 = +t301.c8
WHERE t302.c2 = 19571
AND t302.c3 > 1287603136
AND (t301.c4 = 1407449685622784
OR t301.c8 = 1407424651264000)
ORDER BY t302.c5 LIMIT 200;
} {
- 0 0 1 {SEARCH TABLE t301 USING COVERING INDEX t301_c4 (c4=?)}
- 0 0 1 {SEARCH TABLE t301 USING INTEGER PRIMARY KEY (rowid=?)}
- 0 1 0 {SEARCH TABLE t302 USING INDEX t302_c8_c3 (c8=? AND c3>?)}
- 0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+ QUERY PLAN
+ |--MULTI-INDEX OR
+ | |--SEARCH TABLE t301 USING COVERING INDEX t301_c4 (c4=?)
+ | `--SEARCH TABLE t301 USING INTEGER PRIMARY KEY (rowid=?)
+ |--SEARCH TABLE t302 USING INDEX t302_c8_c3 (c8=? AND c3>?)
+ `--USE TEMP B-TREE FOR ORDER BY
}
finish_test
Index: test/where8.test
==================================================================
--- test/where8.test
+++ test/where8.test
@@ -13,10 +13,15 @@
# of WHERE clauses that feature the OR operator.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
+
+if {[permutation]=="sorterref"} {
+ finish_test
+ return
+}
# Test organization:
#
# where8-1.*: Tests to demonstrate simple cases work with a single table
# in the FROM clause.
Index: test/where9.test
==================================================================
--- test/where9.test
+++ test/where9.test
@@ -355,29 +355,31 @@
}
} {1 80 2 1 80 28 1 80 54 1 80 80 2 80 2 2 80 28 2 80 54 2 80 80 scan 1 sort 1}
ifcapable explain {
- do_execsql_test where9-3.1 {
- EXPLAIN QUERY PLAN
+ do_eqp_test where9-3.1 {
SELECT t2.a FROM t1, t2
WHERE t1.a=80 AND ((t1.c=t2.c AND t1.d=t2.d) OR t1.f=t2.f)
- } {
- 0 0 0 {SEARCH TABLE t1 USING INTEGER PRIMARY KEY (rowid=?)}
- 0 1 1 {SEARCH TABLE t2 USING INDEX t2d (d=?)}
- 0 1 1 {SEARCH TABLE t2 USING COVERING INDEX t2f (f=?)}
- }
- do_execsql_test where9-3.2 {
- EXPLAIN QUERY PLAN
+ } [string map {"\n " \n} {
+ QUERY PLAN
+ |--SEARCH TABLE t1 USING INTEGER PRIMARY KEY (rowid=?)
+ `--MULTI-INDEX OR
+ |--SEARCH TABLE t2 USING INDEX t2d (d=?)
+ `--SEARCH TABLE t2 USING COVERING INDEX t2f (f=?)
+ }]
+ do_eqp_test where9-3.2 {
SELECT coalesce(t2.a,9999)
FROM t1 LEFT JOIN t2 ON (t1.c+1=t2.c AND t1.d=t2.d) OR (t1.f||'x')=t2.f
WHERE t1.a=80
- } {
- 0 0 0 {SEARCH TABLE t1 USING INTEGER PRIMARY KEY (rowid=?)}
- 0 1 1 {SEARCH TABLE t2 USING INDEX t2d (d=?)}
- 0 1 1 {SEARCH TABLE t2 USING COVERING INDEX t2f (f=?)}
- }
+ } [string map {"\n " \n} {
+ QUERY PLAN
+ |--SEARCH TABLE t1 USING INTEGER PRIMARY KEY (rowid=?)
+ `--MULTI-INDEX OR
+ |--SEARCH TABLE t2 USING INDEX t2d (d=?)
+ `--SEARCH TABLE t2 USING COVERING INDEX t2f (f=?)
+ }]
}
# Make sure that INDEXED BY and multi-index OR clauses play well with
# one another.
#
@@ -444,38 +446,34 @@
AND (c=31031 OR d IS NULL)
ORDER BY +a
}
} {1 {no query solution}}
-ifcapable explain {
- # The (c=31031 OR d IS NULL) clause is preferred over b>1000 because
- # the former is an equality test which is expected to return fewer rows.
- #
- do_execsql_test where9-5.1 {
- EXPLAIN QUERY PLAN SELECT a FROM t1 WHERE b>1000 AND (c=31031 OR d IS NULL)
- } {
- 0 0 0 {SEARCH TABLE t1 USING INDEX t1c (c=?)}
- 0 0 0 {SEARCH TABLE t1 USING INDEX t1d (d=?)}
- }
-
- # In contrast, b=1000 is preferred over any OR-clause.
- #
- do_execsql_test where9-5.2 {
- EXPLAIN QUERY PLAN SELECT a FROM t1 WHERE b=1000 AND (c=31031 OR d IS NULL)
- } {
- 0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b=?)}
- }
-
- # Likewise, inequalities in an AND are preferred over inequalities in
- # an OR.
- #
- do_execsql_test where9-5.3 {
- EXPLAIN QUERY PLAN SELECT a FROM t1 WHERE b>1000 AND (c>=31031 OR d IS NULL)
- } {
- 0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b>?)}
- }
-}
+# The (c=31031 OR d IS NULL) clause is preferred over b>1000 because
+# the former is an equality test which is expected to return fewer rows.
+#
+do_eqp_test where9-5.1 {
+ SELECT a FROM t1 WHERE b>1000 AND (c=31031 OR d IS NULL)
+} {
+ QUERY PLAN
+ `--MULTI-INDEX OR
+ |--SEARCH TABLE t1 USING INDEX t1c (c=?)
+ `--SEARCH TABLE t1 USING INDEX t1d (d=?)
+}
+
+# In contrast, b=1000 is preferred over any OR-clause.
+#
+do_eqp_test where9-5.2 {
+ SELECT a FROM t1 WHERE b=1000 AND (c=31031 OR d IS NULL)
+} {SEARCH TABLE t1 USING INDEX t1b (b=?)}
+
+# Likewise, inequalities in an AND are preferred over inequalities in
+# an OR.
+#
+do_eqp_test where9-5.3 {
+ SELECT a FROM t1 WHERE b>1000 AND (c>=31031 OR d IS NULL)
+} {SEARCH TABLE t1 USING INDEX t1b (b>?)}
############################################################################
# Make sure OR-clauses work correctly on UPDATE and DELETE statements.
do_test where9-6.2.1 {
Index: test/whereG.test
==================================================================
--- test/whereG.test
+++ test/whereG.test
@@ -64,11 +64,11 @@
SELECT DISTINCT aname
FROM album, composer, track
WHERE unlikely(cname LIKE '%bach%')
AND composer.cid=track.cid
AND album.aid=track.aid;
-} {/.*composer.*track.*album.*/}
+} {composer*track*album}
do_execsql_test whereG-1.2 {
SELECT DISTINCT aname
FROM album, composer, track
WHERE unlikely(cname LIKE '%bach%')
AND composer.cid=track.cid
@@ -193,17 +193,17 @@
CREATE TABLE t1(a, b, c);
CREATE INDEX i1 ON t1(a, b);
}
do_eqp_test 5.1.2 {
SELECT * FROM t1 WHERE a>?
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a>?)}}
+} {SEARCH TABLE t1 USING INDEX i1 (a>?)}
do_eqp_test 5.1.3 {
SELECT * FROM t1 WHERE likelihood(a>?, 0.9)
-} {0 0 0 {SCAN TABLE t1}}
+} {SCAN TABLE t1}
do_eqp_test 5.1.4 {
SELECT * FROM t1 WHERE likely(a>?)
-} {0 0 0 {SCAN TABLE t1}}
+} {SCAN TABLE t1}
do_test 5.2 {
for {set i 0} {$i < 100} {incr i} {
execsql { INSERT INTO t1 VALUES('abc', $i, $i); }
}
@@ -210,27 +210,27 @@
execsql { INSERT INTO t1 SELECT 'def', b, c FROM t1; }
execsql { ANALYZE }
} {}
do_eqp_test 5.2.2 {
SELECT * FROM t1 WHERE likelihood(b>?, 0.01)
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (ANY(a) AND b>?)}}
+} {SEARCH TABLE t1 USING INDEX i1 (ANY(a) AND b>?)}
do_eqp_test 5.2.3 {
SELECT * FROM t1 WHERE likelihood(b>?, 0.9)
-} {0 0 0 {SCAN TABLE t1}}
+} {SCAN TABLE t1}
do_eqp_test 5.2.4 {
SELECT * FROM t1 WHERE likely(b>?)
-} {0 0 0 {SCAN TABLE t1}}
+} {SCAN TABLE t1}
do_eqp_test 5.3.1 {
SELECT * FROM t1 WHERE a=?
-} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}}
+} {SEARCH TABLE t1 USING INDEX i1 (a=?)}
do_eqp_test 5.3.2 {
SELECT * FROM t1 WHERE likelihood(a=?, 0.9)
-} {0 0 0 {SCAN TABLE t1}}
+} {SCAN TABLE t1}
do_eqp_test 5.3.3 {
SELECT * FROM t1 WHERE likely(a=?)
-} {0 0 0 {SCAN TABLE t1}}
+} {SCAN TABLE t1}
# 2015-06-18
# Ticket [https://www.sqlite.org/see/tktview/472f0742a1868fb58862bc588ed70]
#
do_execsql_test 6.0 {
Index: test/whereI.test
==================================================================
--- test/whereI.test
+++ test/whereI.test
@@ -27,12 +27,14 @@
}
do_eqp_test 1.1 {
SELECT a FROM t1 WHERE b='b' OR c='x'
} {
- 0 0 0 {SEARCH TABLE t1 USING INDEX i1 (b=?)}
- 0 0 0 {SEARCH TABLE t1 USING INDEX i2 (c=?)}
+ QUERY PLAN
+ `--MULTI-INDEX OR
+ |--SEARCH TABLE t1 USING INDEX i1 (b=?)
+ `--SEARCH TABLE t1 USING INDEX i2 (c=?)
}
do_execsql_test 1.2 {
SELECT a FROM t1 WHERE b='b' OR c='x'
} {2 3}
@@ -55,12 +57,14 @@
}
do_eqp_test 2.1 {
SELECT a FROM t2 WHERE b='b' OR c='x'
} {
- 0 0 0 {SEARCH TABLE t2 USING INDEX i3 (b=?)}
- 0 0 0 {SEARCH TABLE t2 USING INDEX i4 (c=?)}
+ QUERY PLAN
+ `--MULTI-INDEX OR
+ |--SEARCH TABLE t2 USING INDEX i3 (b=?)
+ `--SEARCH TABLE t2 USING INDEX i4 (c=?)
}
do_execsql_test 2.2 {
SELECT a FROM t2 WHERE b='b' OR c='x'
} {ii iii}
Index: test/whereJ.test
==================================================================
--- test/whereJ.test
+++ test/whereJ.test
@@ -400,23 +400,19 @@
do_eqp_test 3.4 {
SELECT * FROM t1 WHERE
a = 4 AND b BETWEEN 20 AND 80 -- Matches 80 rows
AND
c BETWEEN 150 AND 160 -- Matches 10 rows
-} {
- 0 0 0 {SEARCH TABLE t1 USING INDEX idx_c (c>? AND c)}
-}
+} {SEARCH TABLE t1 USING INDEX idx_c (c>? AND c)}
# This one should use index "idx_ab".
do_eqp_test 3.5 {
SELECT * FROM t1 WHERE
a = 5 AND b BETWEEN 20 AND 80 -- Matches 1 row
AND
c BETWEEN 150 AND 160 -- Matches 10 rows
-} {
- 0 0 0 {SEARCH TABLE t1 USING INDEX idx_ab (a=? AND b>? AND b)}
-}
+} {SEARCH TABLE t1 USING INDEX idx_ab (a=? AND b>? AND b)}
###########################################################################################
# Reset the database and setup for a test case derived from actual SQLite users
#
Index: test/with1.test
==================================================================
--- test/with1.test
+++ test/with1.test
@@ -990,20 +990,27 @@
SELECT 1 FROM xyz;
} 1
# EXPLAIN QUERY PLAN on a self-join of a CTE
#
-do_execsql_test 19.1 {
+do_execsql_test 19.1a {
DROP TABLE IF EXISTS t1;
CREATE TABLE t1(x);
- EXPLAIN QUERY PLAN
+}
+do_eqp_test 19.1b {
WITH
x1(a) AS (values(100))
INSERT INTO t1(x)
SELECT * FROM (WITH x2(y) AS (SELECT * FROM x1) SELECT y+a FROM x1, x2);
SELECT * FROM t1;
-} {0 0 0 {SCAN SUBQUERY 1} 0 1 1 {SCAN SUBQUERY 1}}
+} {
+ QUERY PLAN
+ |--MATERIALIZE xxxxxx
+ | `--SCAN CONSTANT ROW
+ |--SCAN SUBQUERY xxxxxx
+ `--SCAN SUBQUERY xxxxxx
+}
# 2017-10-28.
# See check-in https://sqlite.org/src/info/0926df095faf72c2
# Tried to optimize co-routine processing by changing a Copy opcode
# into SCopy. But OSSFuzz found two (similar) cases where that optimization
Index: test/with3.test
==================================================================
--- test/with3.test
+++ test/with3.test
@@ -77,26 +77,34 @@
}
do_eqp_test 3.1.2 {
WITH cnt(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM cnt LIMIT 1)
SELECT * FROM cnt, y1 WHERE i=a
- } {
- 3 0 0 {SCAN TABLE cnt}
- 1 0 0 {COMPOUND SUBQUERIES 0 AND 0 (UNION ALL)}
- 0 0 0 {SCAN SUBQUERY 1}
- 0 1 1 {SEARCH TABLE y1 USING INDEX y1a (a=?)}
- }
+ } [string map {"\n " \n} {
+ QUERY PLAN
+ |--MATERIALIZE xxxxxx
+ | |--SETUP
+ | | `--SCAN CONSTANT ROW
+ | `--RECURSIVE STEP
+ | `--SCAN TABLE cnt
+ |--SCAN SUBQUERY xxxxxx
+ `--SEARCH TABLE y1 USING INDEX y1a (a=?)
+ }]
do_eqp_test 3.1.3 {
WITH cnt(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM cnt LIMIT 1000000)
SELECT * FROM cnt, y1 WHERE i=a
- } {
- 3 0 0 {SCAN TABLE cnt}
- 1 0 0 {COMPOUND SUBQUERIES 0 AND 0 (UNION ALL)}
- 0 0 1 {SCAN TABLE y1}
- 0 1 0 {SEARCH SUBQUERY 1 USING AUTOMATIC COVERING INDEX (i=?)}
- }
+ } [string map {"\n " \n} {
+ QUERY PLAN
+ |--MATERIALIZE xxxxxx
+ | |--SETUP
+ | | `--SCAN CONSTANT ROW
+ | `--RECURSIVE STEP
+ | `--SCAN TABLE cnt
+ |--SCAN TABLE y1
+ `--SEARCH SUBQUERY xxxxxx USING AUTOMATIC COVERING INDEX (i=?)
+ }]
}
do_execsql_test 3.2.1 {
CREATE TABLE w1(pk INTEGER PRIMARY KEY, x INTEGER);
CREATE TABLE w2(pk INTEGER PRIMARY KEY);
@@ -106,15 +114,20 @@
WITH RECURSIVE c(w,id) AS (SELECT 0, (SELECT pk FROM w2 LIMIT 1)
UNION ALL SELECT c.w + 1, x FROM w1, c LIMIT 1)
SELECT * FROM c, w2, w1
WHERE c.id=w2.pk AND c.id=w1.pk;
} {
- 2 0 0 {EXECUTE SCALAR SUBQUERY 3}
- 3 0 0 {SCAN TABLE w2}
- 4 0 0 {SCAN TABLE w1}
- 4 1 1 {SCAN TABLE c}
- 1 0 0 {COMPOUND SUBQUERIES 0 AND 0 (UNION ALL)} 0 0 0 {SCAN SUBQUERY 1}
- 0 1 1 {SEARCH TABLE w2 USING INTEGER PRIMARY KEY (rowid=?)}
- 0 2 2 {SEARCH TABLE w1 USING INTEGER PRIMARY KEY (rowid=?)}
+ QUERY PLAN
+ |--MATERIALIZE xxxxxx
+ | |--SETUP
+ | | |--SCAN CONSTANT ROW
+ | | `--SCALAR SUBQUERY
+ | | `--SCAN TABLE w2
+ | `--RECURSIVE STEP
+ | |--SCAN TABLE w1
+ | `--SCAN TABLE c
+ |--SCAN SUBQUERY xxxxxx
+ |--SEARCH TABLE w2 USING INTEGER PRIMARY KEY (rowid=?)
+ `--SEARCH TABLE w1 USING INTEGER PRIMARY KEY (rowid=?)
}
finish_test
Index: test/without_rowid1.test
==================================================================
--- test/without_rowid1.test
+++ test/without_rowid1.test
@@ -236,11 +236,11 @@
INSERT INTO t45 VALUES(9, 'two', 'x');
}
do_eqp_test 5.1 {
SELECT * FROM t45 WHERE b=? AND a>?
-} {/*USING INDEX i45 (b=? AND a>?)*/}
+} {USING INDEX i45 (b=? AND a>?)}
do_execsql_test 5.2 {
SELECT * FROM t45 WHERE b='two' AND a>4
} {5 two x 7 two x 9 two x}
@@ -255,15 +255,15 @@
)
INSERT INTO t46 SELECT x / 20, x % 20, x % 10, x FROM r;
}
set queries {
- 1 2 "c = 5 AND a = 1" {/*i46 (c=? AND a=?)*/}
- 2 6 "c = 4 AND a < 3" {/*i46 (c=? AND a)*/}
- 3 4 "c = 2 AND a >= 3" {/*i46 (c=? AND a>?)*/}
- 4 1 "c = 2 AND a = 1 AND b<10" {/*i46 (c=? AND a=? AND b)*/}
- 5 1 "c = 0 AND a = 0 AND b>5" {/*i46 (c=? AND a=? AND b>?)*/}
+ 1 2 "c = 5 AND a = 1" {i46 (c=? AND a=?)}
+ 2 6 "c = 4 AND a < 3" {i46 (c=? AND a)}
+ 3 4 "c = 2 AND a >= 3" {i46 (c=? AND a>?)}
+ 4 1 "c = 2 AND a = 1 AND b<10" {i46 (c=? AND a=? AND b)}
+ 5 1 "c = 0 AND a = 0 AND b>5" {i46 (c=? AND a=? AND b>?)}
}
foreach {tn cnt where eqp} $queries {
do_execsql_test 5.5.$tn.1 "SELECT count(*) FROM t46 WHERE $where" $cnt
}
Index: test/wordcount.c
==================================================================
--- test/wordcount.c
+++ test/wordcount.c
@@ -26,10 +26,14 @@
**
** Replace mode means:
** (1) REPLACE INTO wordcount
** VALUES($new,ifnull((SELECT cnt FROM wordcount WHERE word=$new),0)+1);
**
+** Upsert mode means:
+** (1) INSERT INTO wordcount VALUES($new,1)
+** ON CONFLICT(word) DO UPDATE SET cnt=cnt+1
+**
** Select mode means:
** (1) SELECT 1 FROM wordcount WHERE word=$new
** (2) INSERT INTO wordcount VALUES($new,1) -- if (1) returns nothing
** (3) UPDATE wordcount SET cnt=cnt+1 WHERE word=$new --if (1) return TRUE
**
@@ -88,10 +92,11 @@
" --summary Show summary information on the collected data.\n"
" --tag NAME Tag all output using NAME. Use only stdout.\n"
" --timer Time the operation of this program\n"
" --trace Enable sqlite3_trace() output.\n"
" --update Use UPDATE mode\n"
+" --upsert Use UPSERT mode\n"
" --without-rowid Use a WITHOUT ROWID table to store the words.\n"
;
/* Output tag */
char *zTag = "--";
@@ -206,21 +211,23 @@
}
/* Define operating modes */
#define MODE_INSERT 0
#define MODE_REPLACE 1
-#define MODE_SELECT 2
-#define MODE_UPDATE 3
-#define MODE_DELETE 4
-#define MODE_QUERY 5
-#define MODE_COUNT 6
+#define MODE_UPSERT 2
+#define MODE_SELECT 3
+#define MODE_UPDATE 4
+#define MODE_DELETE 5
+#define MODE_QUERY 6
+#define MODE_COUNT 7
#define MODE_ALL (-1)
/* Mode names */
static const char *azMode[] = {
"--insert",
"--replace",
+ "--upsert",
"--select",
"--update",
"--delete",
"--query"
};
@@ -290,10 +297,12 @@
do{ z++; }while( z[0]=='-' );
if( strcmp(z,"without-rowid")==0 ){
useWithoutRowid = 1;
}else if( strcmp(z,"replace")==0 ){
iMode = MODE_REPLACE;
+ }else if( strcmp(z,"upsert")==0 ){
+ iMode = MODE_UPSERT;
}else if( strcmp(z,"select")==0 ){
iMode = MODE_SELECT;
}else if( strcmp(z,"insert")==0 ){
iMode = MODE_INSERT;
}else if( strcmp(z,"update")==0 ){
@@ -465,10 +474,18 @@
"VALUES(?1,coalesce((SELECT cnt FROM wordcount WHERE word=?1),0)+1)",
-1, &pInsert, 0);
if( rc ) fatal_error("Could not prepare the REPLACE statement: %s\n",
sqlite3_errmsg(db));
}
+ if( iMode2==MODE_UPSERT ){
+ rc = sqlite3_prepare_v2(db,
+ "INSERT INTO wordcount(word,cnt) VALUES(?1,1) "
+ "ON CONFLICT(word) DO UPDATE SET cnt=cnt+1",
+ -1, &pInsert, 0);
+ if( rc ) fatal_error("Could not prepare the UPSERT statement: %s\n",
+ sqlite3_errmsg(db));
+ }
if( iMode2==MODE_DELETE ){
rc = sqlite3_prepare_v2(db,
"DELETE FROM wordcount WHERE word=?1",
-1, &pDelete, 0);
if( rc ) fatal_error("Could not prepare the DELETE statement: %s\n",
Index: test/zipfile2.test
==================================================================
--- test/zipfile2.test
+++ test/zipfile2.test
@@ -50,21 +50,19 @@
CREATE VIRTUAL TABLE ddd USING zipfile([testzip]);
CREATE VIRTUAL TABLE eee USING zipfile(testzip);
CREATE VIRTUAL TABLE fff USING zipfile('test''zip');
}
-if {$::tcl_platform(platform)=="windows"} {
- set res {1 {cannot open file: testdir}}
-} else {
- set res {1 {error in fread()}}
-}
do_test 2.0 {
forcedelete testdir
file mkdir testdir
execsql { CREATE VIRTUAL TABLE hhh USING zipfile('testdir') }
- catchsql { SELECT * FROM hhh }
-} $res
+ lindex [catchsql {
+ SELECT * FROM hhh;
+ INSERT INTO hhh(name, data) VALUES('1.txt', 'file data');
+ }] 0
+} 1
set archive {
504B0304140000080000D4A52BEC09F3B6E0110000001100000005000900612E
747874555405000140420F00636F6E74656E7473206F6620612E747874504B03
Index: tool/lemon.c
==================================================================
--- tool/lemon.c
+++ tool/lemon.c
@@ -268,10 +268,12 @@
char *datatype; /* The data type of information held by this
** object. Only used if type==NONTERMINAL */
int dtnum; /* The data type number. In the parser, the value
** stack is a union. The .yy%d element of this
** union is the correct data type for this object */
+ int bContent; /* True if this symbol ever carries content - if
+ ** it is ever more than just syntax */
/* The following fields are used by MULTITERMINALs only */
int nsubsym; /* Number of constituent symbols in the MULTI */
struct symbol **subsym; /* Array of constituent symbols */
};
@@ -394,10 +396,11 @@
int errorcnt; /* Number of errors */
struct symbol *errsym; /* The error symbol */
struct symbol *wildcard; /* Token that matches anything */
char *name; /* Name of the generated parser */
char *arg; /* Declaration of the 3th argument to parser */
+ char *ctx; /* Declaration of 2nd argument to constructor */
char *tokentype; /* Type of terminal symbols in the parser stack */
char *vartype; /* The default type of non-terminal symbols */
char *start; /* Name of the start symbol for the grammar */
char *stacksize; /* Size of the parser stack */
char *include; /* Code to put at the start of the C file */
@@ -1532,10 +1535,22 @@
}
lemon_strcpy(*paz, z);
for(z=*paz; *z && *z!='='; z++){}
*z = 0;
}
+
+/* Rember the name of the output directory
+*/
+static char *outputDir = NULL;
+static void handle_d_option(char *z){
+ outputDir = (char *) malloc( lemonStrlen(z)+1 );
+ if( outputDir==0 ){
+ fprintf(stderr,"out of memory\n");
+ exit(1);
+ }
+ lemon_strcpy(outputDir, z);
+}
static char *user_templatename = NULL;
static void handle_T_option(char *z){
user_templatename = (char *) malloc( lemonStrlen(z)+1 );
if( user_templatename==0 ){
@@ -1614,13 +1629,15 @@
static int quiet = 0;
static int statistics = 0;
static int mhflag = 0;
static int nolinenosflag = 0;
static int noResort = 0;
+
static struct s_options options[] = {
{OPT_FLAG, "b", (char*)&basisflag, "Print only the basis in report."},
{OPT_FLAG, "c", (char*)&compress, "Don't compress the action table."},
+ {OPT_FSTR, "d", (char*)&handle_d_option, "Output directory. Default '.'"},
{OPT_FSTR, "D", (char*)handle_D_option, "Define an %ifdef macro."},
{OPT_FSTR, "f", 0, "Ignored. (Placeholder for -f compiler options.)"},
{OPT_FLAG, "g", (char*)&rpflag, "Print grammar without actions."},
{OPT_FSTR, "I", 0, "Ignored. (Placeholder for '-I' compiler options.)"},
{OPT_FLAG, "m", (char*)&mhflag, "Output a makeheaders compatible file."},
@@ -1661,20 +1678,19 @@
lem.argv0 = argv[0];
lem.filename = OptArg(0);
lem.basisflag = basisflag;
lem.nolinenosflag = nolinenosflag;
Symbol_new("$");
- lem.errsym = Symbol_new("error");
- lem.errsym->useCnt = 0;
/* Parse the input file */
Parse(&lem);
if( lem.errorcnt ) exit(lem.errorcnt);
if( lem.nrule==0 ){
fprintf(stderr,"Empty grammar.\n");
exit(1);
}
+ lem.errsym = Symbol_find("error");
/* Count and index the symbols of the grammar */
Symbol_new("{default}");
lem.nsymbol = Symbol_count();
lem.symbols = Symbol_arrayof();
@@ -2361,10 +2377,11 @@
rp->rhs = (struct symbol**)&rp[1];
rp->rhsalias = (const char**)&(rp->rhs[psp->nrhs]);
for(i=0; inrhs; i++){
rp->rhs[i] = psp->rhs[i];
rp->rhsalias[i] = psp->alias[i];
+ if( rp->rhsalias[i]!=0 ){ rp->rhs[i]->bContent = 1; }
}
rp->lhs = psp->lhs;
rp->lhsalias = psp->lhsalias;
rp->nrhs = psp->nrhs;
rp->code = 0;
@@ -2477,10 +2494,13 @@
psp->declargslot = &(psp->gp->failure);
}else if( strcmp(x,"stack_overflow")==0 ){
psp->declargslot = &(psp->gp->overflow);
}else if( strcmp(x,"extra_argument")==0 ){
psp->declargslot = &(psp->gp->arg);
+ psp->insertLineMacro = 0;
+ }else if( strcmp(x,"extra_context")==0 ){
+ psp->declargslot = &(psp->gp->ctx);
psp->insertLineMacro = 0;
}else if( strcmp(x,"token_type")==0 ){
psp->declargslot = &(psp->gp->tokentype);
psp->insertLineMacro = 0;
}else if( strcmp(x,"default_type")==0 ){
@@ -3024,17 +3044,32 @@
*/
PRIVATE char *file_makename(struct lemon *lemp, const char *suffix)
{
char *name;
char *cp;
+ char *filename = lemp->filename;
+ int sz;
- name = (char*)malloc( lemonStrlen(lemp->filename) + lemonStrlen(suffix) + 5 );
+ if( outputDir ){
+ cp = strrchr(filename, '/');
+ if( cp ) filename = cp + 1;
+ }
+ sz = lemonStrlen(filename);
+ sz += lemonStrlen(suffix);
+ if( outputDir ) sz += lemonStrlen(outputDir) + 1;
+ sz += 5;
+ name = (char*)malloc( sz );
if( name==0 ){
fprintf(stderr,"Can't allocate space for a filename.\n");
exit(1);
}
- lemon_strcpy(name,lemp->filename);
+ name[0] = 0;
+ if( outputDir ){
+ lemon_strcpy(name, outputDir);
+ lemon_strcat(name, "/");
+ }
+ lemon_strcat(name,filename);
cp = strrchr(name,'.');
if( cp ) *cp = 0;
lemon_strcat(name,suffix);
return name;
}
@@ -3248,11 +3283,11 @@
}
/* Generate the "*.out" log file */
void ReportOutput(struct lemon *lemp)
{
- int i;
+ int i, n;
struct state *stp;
struct config *cfp;
struct action *ap;
struct rule *rp;
FILE *fp;
@@ -3288,10 +3323,11 @@
}
fprintf(fp,"\n");
}
fprintf(fp, "----------------------------------------------------\n");
fprintf(fp, "Symbols:\n");
+ fprintf(fp, "The first-set of non-terminals is shown after the name.\n\n");
for(i=0; insymbol; i++){
int j;
struct symbol *sp;
sp = lemp->symbols[i];
@@ -3308,10 +3344,30 @@
}
}
if( sp->prec>=0 ) fprintf(fp," (precedence=%d)", sp->prec);
fprintf(fp, "\n");
}
+ fprintf(fp, "----------------------------------------------------\n");
+ fprintf(fp, "Syntax-only Symbols:\n");
+ fprintf(fp, "The following symbols never carry semantic content.\n\n");
+ for(i=n=0; insymbol; i++){
+ int w;
+ struct symbol *sp = lemp->symbols[i];
+ if( sp->bContent ) continue;
+ w = (int)strlen(sp->name);
+ if( n>0 && n+w>75 ){
+ fprintf(fp,"\n");
+ n = 0;
+ }
+ if( n>0 ){
+ fprintf(fp, " ");
+ n++;
+ }
+ fprintf(fp, "%s", sp->name);
+ n += w;
+ }
+ if( n>0 ) fprintf(fp, "\n");
fprintf(fp, "----------------------------------------------------\n");
fprintf(fp, "Rules:\n");
for(rp=lemp->rule; rp; rp=rp->next){
fprintf(fp, "%4d: ", rp->iRule);
rule_print(fp, rp);
@@ -3992,11 +4048,11 @@
for(i=0; ierrsym->useCnt ){
+ if( lemp->errsym && lemp->errsym->useCnt ){
fprintf(out," int yy%d;\n",lemp->errsym->dtnum); lineno++;
}
free(stddt);
free(types);
fprintf(out,"} YYMINORTYPE;\n"); lineno++;
@@ -4142,12 +4198,12 @@
}
tplt_xfer(lemp->name,in,out,&lineno);
/* Generate the defines */
fprintf(out,"#define YYCODETYPE %s\n",
- minimum_size_type(0, lemp->nsymbol+1, &szCodeType)); lineno++;
- fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol+1); lineno++;
+ minimum_size_type(0, lemp->nsymbol, &szCodeType)); lineno++;
+ fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol); lineno++;
fprintf(out,"#define YYACTIONTYPE %s\n",
minimum_size_type(0,lemp->maxAction,&szActionType)); lineno++;
if( lemp->wildcard ){
fprintf(out,"#define YYWILDCARD %d\n",
lemp->wildcard->index); lineno++;
@@ -4168,24 +4224,44 @@
i = lemonStrlen(lemp->arg);
while( i>=1 && ISSPACE(lemp->arg[i-1]) ) i--;
while( i>=1 && (ISALNUM(lemp->arg[i-1]) || lemp->arg[i-1]=='_') ) i--;
fprintf(out,"#define %sARG_SDECL %s;\n",name,lemp->arg); lineno++;
fprintf(out,"#define %sARG_PDECL ,%s\n",name,lemp->arg); lineno++;
- fprintf(out,"#define %sARG_FETCH %s = yypParser->%s\n",
+ fprintf(out,"#define %sARG_PARAM ,%s\n",name,&lemp->arg[i]); lineno++;
+ fprintf(out,"#define %sARG_FETCH %s=yypParser->%s;\n",
name,lemp->arg,&lemp->arg[i]); lineno++;
- fprintf(out,"#define %sARG_STORE yypParser->%s = %s\n",
+ fprintf(out,"#define %sARG_STORE yypParser->%s=%s;\n",
name,&lemp->arg[i],&lemp->arg[i]); lineno++;
}else{
- fprintf(out,"#define %sARG_SDECL\n",name); lineno++;
- fprintf(out,"#define %sARG_PDECL\n",name); lineno++;
+ fprintf(out,"#define %sARG_SDECL\n",name); lineno++;
+ fprintf(out,"#define %sARG_PDECL\n",name); lineno++;
+ fprintf(out,"#define %sARG_PARAM\n",name); lineno++;
fprintf(out,"#define %sARG_FETCH\n",name); lineno++;
fprintf(out,"#define %sARG_STORE\n",name); lineno++;
}
+ if( lemp->ctx && lemp->ctx[0] ){
+ i = lemonStrlen(lemp->ctx);
+ while( i>=1 && ISSPACE(lemp->ctx[i-1]) ) i--;
+ while( i>=1 && (ISALNUM(lemp->ctx[i-1]) || lemp->ctx[i-1]=='_') ) i--;
+ fprintf(out,"#define %sCTX_SDECL %s;\n",name,lemp->ctx); lineno++;
+ fprintf(out,"#define %sCTX_PDECL ,%s\n",name,lemp->ctx); lineno++;
+ fprintf(out,"#define %sCTX_PARAM ,%s\n",name,&lemp->ctx[i]); lineno++;
+ fprintf(out,"#define %sCTX_FETCH %s=yypParser->%s;\n",
+ name,lemp->ctx,&lemp->ctx[i]); lineno++;
+ fprintf(out,"#define %sCTX_STORE yypParser->%s=%s;\n",
+ name,&lemp->ctx[i],&lemp->ctx[i]); lineno++;
+ }else{
+ fprintf(out,"#define %sCTX_SDECL\n",name); lineno++;
+ fprintf(out,"#define %sCTX_PDECL\n",name); lineno++;
+ fprintf(out,"#define %sCTX_PARAM\n",name); lineno++;
+ fprintf(out,"#define %sCTX_FETCH\n",name); lineno++;
+ fprintf(out,"#define %sCTX_STORE\n",name); lineno++;
+ }
if( mhflag ){
fprintf(out,"#endif\n"); lineno++;
}
- if( lemp->errsym->useCnt ){
+ if( lemp->errsym && lemp->errsym->useCnt ){
fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index); lineno++;
fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum); lineno++;
}
if( lemp->has_fallback ){
fprintf(out,"#define YYFALLBACK 1\n"); lineno++;
Index: tool/lempar.c
==================================================================
--- tool/lempar.c
+++ tool/lempar.c
@@ -64,12 +64,14 @@
** for terminal symbols is called "yy0".
** YYSTACKDEPTH is the maximum depth of the parser's stack. If
** zero the stack is dynamically sized using realloc()
** ParseARG_SDECL A static variable declaration for the %extra_argument
** ParseARG_PDECL A parameter declaration for the %extra_argument
+** ParseARG_PARAM Code to pass %extra_argument as a subroutine parameter
** ParseARG_STORE Code to store %extra_argument into yypParser
** ParseARG_FETCH Code to extract %extra_argument from yypParser
+** ParseCTX_* As ParseARG_ except for %extra_context
** YYERRORSYMBOL is the code number of the error symbol. If not
** defined, then do no error processing.
** YYNSTATE the combined number of states.
** YYNRULE the number of rules in the grammar
** YYNTOKEN Number of terminal symbols
@@ -209,10 +211,11 @@
#endif
#ifndef YYNOERRORRECOVERY
int yyerrcnt; /* Shifts left before out of the error */
#endif
ParseARG_SDECL /* A place to hold %extra_argument */
+ ParseCTX_SDECL /* A place to hold %extra_context */
#if YYSTACKDEPTH<=0
int yystksz; /* Current side of the stack */
yyStackEntry *yystack; /* The parser's stack */
yyStackEntry yystk0; /* First stack entry */
#else
@@ -313,32 +316,33 @@
# define YYMALLOCARGTYPE size_t
#endif
/* Initialize a new parser that has already been allocated.
*/
-void ParseInit(void *yypParser){
- yyParser *pParser = (yyParser*)yypParser;
+void ParseInit(void *yypRawParser ParseCTX_PDECL){
+ yyParser *yypParser = (yyParser*)yypRawParser;
+ ParseCTX_STORE
#ifdef YYTRACKMAXSTACKDEPTH
- pParser->yyhwm = 0;
+ yypParser->yyhwm = 0;
#endif
#if YYSTACKDEPTH<=0
- pParser->yytos = NULL;
- pParser->yystack = NULL;
- pParser->yystksz = 0;
- if( yyGrowStack(pParser) ){
- pParser->yystack = &pParser->yystk0;
- pParser->yystksz = 1;
+ yypParser->yytos = NULL;
+ yypParser->yystack = NULL;
+ yypParser->yystksz = 0;
+ if( yyGrowStack(yypParser) ){
+ yypParser->yystack = &yypParser->yystk0;
+ yypParser->yystksz = 1;
}
#endif
#ifndef YYNOERRORRECOVERY
- pParser->yyerrcnt = -1;
+ yypParser->yyerrcnt = -1;
#endif
- pParser->yytos = pParser->yystack;
- pParser->yystack[0].stateno = 0;
- pParser->yystack[0].major = 0;
+ yypParser->yytos = yypParser->yystack;
+ yypParser->yystack[0].stateno = 0;
+ yypParser->yystack[0].major = 0;
#if YYSTACKDEPTH>0
- pParser->yystackEnd = &pParser->yystack[YYSTACKDEPTH-1];
+ yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1];
#endif
}
#ifndef Parse_ENGINEALWAYSONSTACK
/*
@@ -351,15 +355,18 @@
**
** Outputs:
** A pointer to a parser. This pointer is used in subsequent calls
** to Parse and ParseFree.
*/
-void *ParseAlloc(void *(*mallocProc)(YYMALLOCARGTYPE)){
- yyParser *pParser;
- pParser = (yyParser*)(*mallocProc)( (YYMALLOCARGTYPE)sizeof(yyParser) );
- if( pParser ) ParseInit(pParser);
- return pParser;
+void *ParseAlloc(void *(*mallocProc)(YYMALLOCARGTYPE) ParseCTX_PDECL){
+ yyParser *yypParser;
+ yypParser = (yyParser*)(*mallocProc)( (YYMALLOCARGTYPE)sizeof(yyParser) );
+ if( yypParser ){
+ ParseCTX_STORE
+ ParseInit(yypParser ParseCTX_PARAM);
+ }
+ return (void*)yypParser;
}
#endif /* Parse_ENGINEALWAYSONSTACK */
/* The following function deletes the "minor type" or semantic value
@@ -372,11 +379,12 @@
static void yy_destructor(
yyParser *yypParser, /* The parser */
YYCODETYPE yymajor, /* Type code for object to destroy */
YYMINORTYPE *yypminor /* The object to be destroyed */
){
- ParseARG_FETCH;
+ ParseARG_FETCH
+ ParseCTX_FETCH
switch( yymajor ){
/* Here is inserted the actions which take place when a
** terminal or non-terminal is destroyed. This can happen
** when the symbol is popped from the stack during a
** reduce or during error processing or when a parser is
@@ -495,17 +503,16 @@
/*
** Find the appropriate action for a parser given the terminal
** look-ahead token iLookAhead.
*/
-static unsigned int yy_find_shift_action(
- yyParser *pParser, /* The parser */
- YYCODETYPE iLookAhead /* The look-ahead token */
+static YYACTIONTYPE yy_find_shift_action(
+ YYCODETYPE iLookAhead, /* The look-ahead token */
+ YYACTIONTYPE stateno /* Current state number */
){
int i;
- int stateno = pParser->yytos->stateno;
-
+
if( stateno>YY_MAX_SHIFT ) return stateno;
assert( stateno <= YY_SHIFT_COUNT );
#if defined(YYCOVERAGE)
yycoverage[stateno][iLookAhead] = 1;
#endif
@@ -565,11 +572,11 @@
/*
** Find the appropriate action for a parser given the non-terminal
** look-ahead token iLookAhead.
*/
static int yy_find_reduce_action(
- int stateno, /* Current state number */
+ YYACTIONTYPE stateno, /* Current state number */
YYCODETYPE iLookAhead /* The look-ahead token */
){
int i;
#ifdef YYERRORSYMBOL
if( stateno>YY_REDUCE_COUNT ){
@@ -594,11 +601,12 @@
/*
** The following routine is called if the stack overflows.
*/
static void yyStackOverflow(yyParser *yypParser){
- ParseARG_FETCH;
+ ParseARG_FETCH
+ ParseCTX_FETCH
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
}
#endif
@@ -606,11 +614,12 @@
/* Here code is inserted which will execute if the parser
** stack every overflows */
/******** Begin %stack_overflow code ******************************************/
%%
/******** End %stack_overflow code ********************************************/
- ParseARG_STORE; /* Suppress warning about unused %extra_argument var */
+ ParseARG_STORE /* Suppress warning about unused %extra_argument var */
+ ParseCTX_STORE
}
/*
** Print tracing information for a SHIFT action
*/
@@ -635,12 +644,12 @@
/*
** Perform a shift action.
*/
static void yy_shift(
yyParser *yypParser, /* The parser to be shifted */
- int yyNewState, /* The new state to shift in */
- int yyMajor, /* The major token to shift in */
+ YYACTIONTYPE yyNewState, /* The new state to shift in */
+ YYCODETYPE yyMajor, /* The major token to shift in */
ParseTOKENTYPE yyMinor /* The minor token to shift in */
){
yyStackEntry *yytos;
yypParser->yytos++;
#ifdef YYTRACKMAXSTACKDEPTH
@@ -666,12 +675,12 @@
#endif
if( yyNewState > YY_MAX_SHIFT ){
yyNewState += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE;
}
yytos = yypParser->yytos;
- yytos->stateno = (YYACTIONTYPE)yyNewState;
- yytos->major = (YYCODETYPE)yyMajor;
+ yytos->stateno = yyNewState;
+ yytos->major = yyMajor;
yytos->minor.yy0 = yyMinor;
yyTraceShift(yypParser, yyNewState, "Shift");
}
/* The following table contains information about every rule that
@@ -694,21 +703,22 @@
** access to the lookahead token (if any). The yyLookahead will be YYNOCODE
** if the lookahead token has already been consumed. As this procedure is
** only called from one place, optimizing compilers will in-line it, which
** means that the extra parameters have no performance impact.
*/
-static void yy_reduce(
+static YYACTIONTYPE yy_reduce(
yyParser *yypParser, /* The parser */
unsigned int yyruleno, /* Number of the rule by which to reduce */
int yyLookahead, /* Lookahead token, or YYNOCODE if none */
ParseTOKENTYPE yyLookaheadToken /* Value of the lookahead token */
+ ParseCTX_PDECL /* %extra_context */
){
int yygoto; /* The next state */
int yyact; /* The next action */
yyStackEntry *yymsp; /* The top of the parser's stack */
int yysize; /* Amount to pop the stack */
- ParseARG_FETCH;
+ ParseARG_FETCH
(void)yyLookahead;
(void)yyLookaheadToken;
yymsp = yypParser->yytos;
#ifndef NDEBUG
if( yyTraceFILE && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){
@@ -735,17 +745,23 @@
}
#endif
#if YYSTACKDEPTH>0
if( yypParser->yytos>=yypParser->yystackEnd ){
yyStackOverflow(yypParser);
- return;
+ /* The call to yyStackOverflow() above pops the stack until it is
+ ** empty, causing the main parser loop to exit. So the return value
+ ** is never used and does not matter. */
+ return 0;
}
#else
if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){
if( yyGrowStack(yypParser) ){
yyStackOverflow(yypParser);
- return;
+ /* The call to yyStackOverflow() above pops the stack until it is
+ ** empty, causing the main parser loop to exit. So the return value
+ ** is never used and does not matter. */
+ return 0;
}
yymsp = yypParser->yytos;
}
#endif
}
@@ -778,20 +794,22 @@
yymsp += yysize+1;
yypParser->yytos = yymsp;
yymsp->stateno = (YYACTIONTYPE)yyact;
yymsp->major = (YYCODETYPE)yygoto;
yyTraceShift(yypParser, yyact, "... then shift");
+ return yyact;
}
/*
** The following code executes when the parse fails
*/
#ifndef YYNOERRORRECOVERY
static void yy_parse_failed(
yyParser *yypParser /* The parser */
){
- ParseARG_FETCH;
+ ParseARG_FETCH
+ ParseCTX_FETCH
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt);
}
#endif
@@ -799,11 +817,12 @@
/* Here code is inserted which will be executed whenever the
** parser fails */
/************ Begin %parse_failure code ***************************************/
%%
/************ End %parse_failure code *****************************************/
- ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+ ParseARG_STORE /* Suppress warning about unused %extra_argument variable */
+ ParseCTX_STORE
}
#endif /* YYNOERRORRECOVERY */
/*
** The following code executes when a syntax error first occurs.
@@ -811,25 +830,28 @@
static void yy_syntax_error(
yyParser *yypParser, /* The parser */
int yymajor, /* The major type of the error token */
ParseTOKENTYPE yyminor /* The minor type of the error token */
){
- ParseARG_FETCH;
+ ParseARG_FETCH
+ ParseCTX_FETCH
#define TOKEN yyminor
/************ Begin %syntax_error code ****************************************/
%%
/************ End %syntax_error code ******************************************/
- ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+ ParseARG_STORE /* Suppress warning about unused %extra_argument variable */
+ ParseCTX_STORE
}
/*
** The following is executed when the parser accepts
*/
static void yy_accept(
yyParser *yypParser /* The parser */
){
- ParseARG_FETCH;
+ ParseARG_FETCH
+ ParseCTX_FETCH
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt);
}
#endif
@@ -840,11 +862,12 @@
/* Here code is inserted which will be executed whenever the
** parser accepts */
/*********** Begin %parse_accept code *****************************************/
%%
/*********** End %parse_accept code *******************************************/
- ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+ ParseARG_STORE /* Suppress warning about unused %extra_argument variable */
+ ParseCTX_STORE
}
/* The main parser program.
** The first argument is a pointer to a structure obtained from
** "ParseAlloc" which describes the current state of the parser.
@@ -869,49 +892,51 @@
int yymajor, /* The major token code number */
ParseTOKENTYPE yyminor /* The value for the token */
ParseARG_PDECL /* Optional %extra_argument parameter */
){
YYMINORTYPE yyminorunion;
- unsigned int yyact; /* The parser action. */
+ YYACTIONTYPE yyact; /* The parser action. */
#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY)
int yyendofinput; /* True if we are at the end of input */
#endif
#ifdef YYERRORSYMBOL
int yyerrorhit = 0; /* True if yymajor has invoked an error */
#endif
- yyParser *yypParser; /* The parser */
+ yyParser *yypParser = (yyParser*)yyp; /* The parser */
+ ParseCTX_FETCH
+ ParseARG_STORE
- yypParser = (yyParser*)yyp;
assert( yypParser->yytos!=0 );
#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY)
yyendofinput = (yymajor==0);
#endif
- ParseARG_STORE;
+ yyact = yypParser->yytos->stateno;
#ifndef NDEBUG
if( yyTraceFILE ){
- int stateno = yypParser->yytos->stateno;
- if( stateno < YY_MIN_REDUCE ){
+ if( yyact < YY_MIN_REDUCE ){
fprintf(yyTraceFILE,"%sInput '%s' in state %d\n",
- yyTracePrompt,yyTokenName[yymajor],stateno);
+ yyTracePrompt,yyTokenName[yymajor],yyact);
}else{
fprintf(yyTraceFILE,"%sInput '%s' with pending reduce %d\n",
- yyTracePrompt,yyTokenName[yymajor],stateno-YY_MIN_REDUCE);
+ yyTracePrompt,yyTokenName[yymajor],yyact-YY_MIN_REDUCE);
}
}
#endif
do{
- yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor);
+ assert( yyact==yypParser->yytos->stateno );
+ yyact = yy_find_shift_action(yymajor,yyact);
if( yyact >= YY_MIN_REDUCE ){
- yy_reduce(yypParser,yyact-YY_MIN_REDUCE,yymajor,yyminor);
+ yyact = yy_reduce(yypParser,yyact-YY_MIN_REDUCE,yymajor,
+ yyminor ParseCTX_PARAM);
}else if( yyact <= YY_MAX_SHIFTREDUCE ){
yy_shift(yypParser,yyact,yymajor,yyminor);
#ifndef YYNOERRORRECOVERY
yypParser->yyerrcnt--;
#endif
- yymajor = YYNOCODE;
+ break;
}else if( yyact==YY_ACCEPT_ACTION ){
yypParser->yytos--;
yy_accept(yypParser);
return;
}else{
@@ -978,10 +1003,12 @@
yy_shift(yypParser,yyact,YYERRORSYMBOL,yyminor);
}
}
yypParser->yyerrcnt = 3;
yyerrorhit = 1;
+ if( yymajor==YYNOCODE ) break;
+ yyact = yypParser->yytos->stateno;
#elif defined(YYNOERRORRECOVERY)
/* If the YYNOERRORRECOVERY macro is defined, then do not attempt to
** do any kind of error recovery. Instead, simply invoke the syntax
** error routine and continue going as if nothing had happened.
**
@@ -988,12 +1015,11 @@
** Applications can set this macro (for example inside %include) if
** they intend to abandon the parse upon the first syntax error seen.
*/
yy_syntax_error(yypParser,yymajor, yyminor);
yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
- yymajor = YYNOCODE;
-
+ break;
#else /* YYERRORSYMBOL is not defined */
/* This is what we do if the grammar does not define ERROR:
**
** * Report an error message, and throw away the input token.
**
@@ -1011,14 +1037,14 @@
yy_parse_failed(yypParser);
#ifndef YYNOERRORRECOVERY
yypParser->yyerrcnt = -1;
#endif
}
- yymajor = YYNOCODE;
+ break;
#endif
}
- }while( yymajor!=YYNOCODE && yypParser->yytos>yypParser->yystack );
+ }while( yypParser->yytos>yypParser->yystack );
#ifndef NDEBUG
if( yyTraceFILE ){
yyStackEntry *i;
char cDiv = '[';
fprintf(yyTraceFILE,"%sReturn. Stack=",yyTracePrompt);
Index: tool/mkkeywordhash.c
==================================================================
--- tool/mkkeywordhash.c
+++ tool/mkkeywordhash.c
@@ -141,10 +141,15 @@
#ifdef SQLITE_OMIT_CTE
# define CTE 0
#else
# define CTE 0x00040000
#endif
+#ifdef SQLITE_OMIT_UPSERT
+# define UPSERT 0
+#else
+# define UPSERT 0x00080000
+#endif
/*
** These are the keywords
*/
static Keyword aKeywordTable[] = {
@@ -184,10 +189,11 @@
{ "DEFERRABLE", "TK_DEFERRABLE", FKEY },
{ "DELETE", "TK_DELETE", ALWAYS },
{ "DESC", "TK_DESC", ALWAYS },
{ "DETACH", "TK_DETACH", ATTACH },
{ "DISTINCT", "TK_DISTINCT", ALWAYS },
+ { "DO", "TK_DO", UPSERT },
{ "DROP", "TK_DROP", ALWAYS },
{ "END", "TK_END", ALWAYS },
{ "EACH", "TK_EACH", TRIGGER },
{ "ELSE", "TK_ELSE", ALWAYS },
{ "ESCAPE", "TK_ESCAPE", ALWAYS },
@@ -224,10 +230,11 @@
{ "LIMIT", "TK_LIMIT", ALWAYS },
{ "MATCH", "TK_MATCH", ALWAYS },
{ "NATURAL", "TK_JOIN_KW", ALWAYS },
{ "NO", "TK_NO", FKEY },
{ "NOT", "TK_NOT", ALWAYS },
+ { "NOTHING", "TK_NOTHING", UPSERT },
{ "NOTNULL", "TK_NOTNULL", ALWAYS },
{ "NULL", "TK_NULL", ALWAYS },
{ "OF", "TK_OF", ALWAYS },
{ "OFFSET", "TK_OFFSET", ALWAYS },
{ "ON", "TK_ON", ALWAYS },
@@ -608,8 +615,18 @@
printf(" int id = TK_ID;\n");
printf(" keywordCode((char*)z, n, &id);\n");
printf(" return id;\n");
printf("}\n");
printf("#define SQLITE_N_KEYWORD %d\n", nKeyword);
+ printf("int sqlite3_keyword_name(int i,const char **pzName,int *pnName){\n");
+ printf(" if( i<0 || i>=SQLITE_N_KEYWORD ) return SQLITE_ERROR;\n");
+ printf(" *pzName = zKWText + aKWOffset[i];\n");
+ printf(" *pnName = aKWLen[i];\n");
+ printf(" return SQLITE_OK;\n");
+ printf("}\n");
+ printf("int sqlite3_keyword_count(void){ return SQLITE_N_KEYWORD; }\n");
+ printf("int sqlite3_keyword_check(const char *zName, int nName){\n");
+ printf(" return TK_ID!=sqlite3KeywordCode((const u8*)zName, nName);\n");
+ printf("}\n");
return 0;
}
Index: tool/mkmsvcmin.tcl
==================================================================
--- tool/mkmsvcmin.tcl
+++ tool/mkmsvcmin.tcl
@@ -81,11 +81,11 @@
$(CSC) /target:exe $(TOP)\Replace.cs
sqlite3.def: Replace.exe $(LIBOBJ)
echo EXPORTS > sqlite3.def
dumpbin /all $(LIBOBJ) \\
- | .\Replace.exe "^\s+/EXPORT:_?(sqlite3(?:session|changeset|changegroup)?_[^@,]*)(?:@\d+|,DATA)?$$" $$1 true \\
+ | .\Replace.exe "^\s+/EXPORT:_?(sqlite3(?:session|changeset|changegroup|rebaser)?_[^@,]*)(?:@\d+|,DATA)?$$" $$1 true \\
| sort >> sqlite3.def
}]]
set data "#### DO NOT EDIT ####\n"
append data "# This makefile is automatically "
Index: tool/mksqlite3c.tcl
==================================================================
--- tool/mksqlite3c.tcl
+++ tool/mksqlite3c.tcl
@@ -362,10 +362,11 @@
prepare.c
select.c
table.c
trigger.c
update.c
+ upsert.c
vacuum.c
vtab.c
wherecode.c
whereexpr.c
where.c
Index: tool/sqldiff.c
==================================================================
--- tool/sqldiff.c
+++ tool/sqldiff.c
@@ -132,32 +132,12 @@
** Space to hold the returned string is obtained from sqlite3_malloc(). The
** caller is responsible for ensuring this space is freed when no longer
** needed.
*/
static char *safeId(const char *zId){
- /* All SQLite keywords, in alphabetical order */
- static const char *azKeywords[] = {
- "ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ANALYZE", "AND", "AS",
- "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "BY",
- "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT",
- "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT_DATE",
- "CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT", "DEFERRABLE",
- "DEFERRED", "DELETE", "DESC", "DETACH", "DISTINCT", "DROP", "EACH",
- "ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUSIVE", "EXISTS", "EXPLAIN",
- "FAIL", "FOR", "FOREIGN", "FROM", "FULL", "GLOB", "GROUP", "HAVING", "IF",
- "IGNORE", "IMMEDIATE", "IN", "INDEX", "INDEXED", "INITIALLY", "INNER",
- "INSERT", "INSTEAD", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY",
- "LEFT", "LIKE", "LIMIT", "MATCH", "NATURAL", "NO", "NOT", "NOTNULL",
- "NULL", "OF", "OFFSET", "ON", "OR", "ORDER", "OUTER", "PLAN", "PRAGMA",
- "PRIMARY", "QUERY", "RAISE", "RECURSIVE", "REFERENCES", "REGEXP",
- "REINDEX", "RELEASE", "RENAME", "REPLACE", "RESTRICT", "RIGHT",
- "ROLLBACK", "ROW", "SAVEPOINT", "SELECT", "SET", "TABLE", "TEMP",
- "TEMPORARY", "THEN", "TO", "TRANSACTION", "TRIGGER", "UNION", "UNIQUE",
- "UPDATE", "USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", "WHERE",
- "WITH", "WITHOUT",
- };
- int lwr, upr, mid, c, i, x;
+ int i, x;
+ char c;
if( zId[0]==0 ) return sqlite3_mprintf("\"\"");
for(i=x=0; (c = zId[i])!=0; i++){
if( !isalpha(c) && c!='_' ){
if( i>0 && isdigit(c) ){
x++;
@@ -164,24 +144,14 @@
}else{
return sqlite3_mprintf("\"%w\"", zId);
}
}
}
- if( x ) return sqlite3_mprintf("%s", zId);
- lwr = 0;
- upr = sizeof(azKeywords)/sizeof(azKeywords[0]) - 1;
- while( lwr<=upr ){
- mid = (lwr+upr)/2;
- c = sqlite3_stricmp(azKeywords[mid], zId);
- if( c==0 ) return sqlite3_mprintf("\"%w\"", zId);
- if( c<0 ){
- lwr = mid+1;
- }else{
- upr = mid-1;
- }
- }
- return sqlite3_mprintf("%s", zId);
+ if( x || !sqlite3_keyword_check(zId,i) ){
+ return sqlite3_mprintf("%s", zId);
+ }
+ return sqlite3_mprintf("\"%w\"", zId);
}
/*
** Prepare a new SQL statement. Print an error and abort if anything
** goes wrong.