summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2018-04-10 07:11:31 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2018-04-10 07:11:43 +0000
commitacc1e9e10afe76857e39efcece2711136d140e68 (patch)
tree5a70633b9666b15ab9be4d532fafc5fdb26feb3e
parentReleasing progress-linux version 3.22.0-2~dschinn1. (diff)
downloadsqlite3-acc1e9e10afe76857e39efcece2711136d140e68.zip
sqlite3-acc1e9e10afe76857e39efcece2711136d140e68.tar.xz
Merging upstream version 3.23.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--Makefile.in20
-rw-r--r--Makefile.msc30
-rw-r--r--README.md16
-rw-r--r--VERSION2
-rw-r--r--autoconf/Makefile.am2
-rw-r--r--autoconf/Makefile.msc1
-rw-r--r--autoconf/configure.ac3
-rwxr-xr-xconfigure18
-rw-r--r--ext/expert/expert.c3
-rw-r--r--ext/fts3/fts3_write.c9
-rw-r--r--ext/fts5/fts5_expr.c2
-rw-r--r--ext/fts5/fts5_index.c4
-rw-r--r--ext/fts5/fts5_storage.c2
-rw-r--r--ext/fts5/test/fts5aa.test2
-rw-r--r--ext/icu/README.txt4
-rw-r--r--ext/misc/btreeinfo.c3
-rw-r--r--ext/misc/completion.c5
-rw-r--r--ext/misc/dbdump.c29
-rw-r--r--ext/misc/fileio.c119
-rw-r--r--ext/misc/normalize.c707
-rw-r--r--ext/misc/scrub.c4
-rw-r--r--ext/misc/spellfix.c957
-rw-r--r--ext/misc/zipfile.c1776
-rw-r--r--ext/misc/zorder.c102
-rw-r--r--ext/rbu/rbu_common.tcl1
-rw-r--r--ext/rbu/rbucollate.test63
-rw-r--r--ext/rbu/sqlite3rbu.c4
-rw-r--r--ext/rbu/test_rbu.c6
-rw-r--r--ext/repair/sqlite3_checker.tcl2
-rw-r--r--ext/rtree/rtree.c1
-rw-r--r--ext/rtree/rtree1.test1
-rw-r--r--ext/rtree/rtree4.test1
-rw-r--r--ext/rtree/rtree5.test1
-rw-r--r--ext/rtree/rtree6.test2
-rw-r--r--ext/rtree/rtreeG.test1
-rw-r--r--ext/session/session4.test79
-rw-r--r--ext/session/session_common.tcl1
-rw-r--r--ext/session/sessionfault2.test178
-rw-r--r--ext/session/sessionrebase.test477
-rw-r--r--ext/session/sqlite3session.c723
-rw-r--r--ext/session/sqlite3session.h264
-rw-r--r--ext/session/test_session.c191
-rw-r--r--main.mk18
-rw-r--r--manifest301
-rw-r--r--manifest.uuid2
-rw-r--r--src/analyze.c2
-rw-r--r--src/attach.c174
-rw-r--r--src/btree.c30
-rw-r--r--src/btreeInt.h20
-rw-r--r--src/build.c30
-rw-r--r--src/ctime.c2
-rw-r--r--src/dbstat.c2
-rw-r--r--src/expr.c161
-rw-r--r--src/func.c61
-rw-r--r--src/global.c7
-rw-r--r--src/insert.c34
-rw-r--r--src/main.c85
-rw-r--r--src/memdb.c589
-rw-r--r--src/mutex_unix.c39
-rw-r--r--src/mutex_w32.c34
-rw-r--r--src/os.c7
-rw-r--r--src/os_unix.c68
-rw-r--r--src/os_win.c16
-rw-r--r--src/pager.c148
-rw-r--r--src/pager.h7
-rw-r--r--src/parse.y7
-rw-r--r--src/pcache.c2
-rw-r--r--src/prepare.c2
-rw-r--r--src/printf.c101
-rw-r--r--src/resolve.c35
-rw-r--r--src/select.c143
-rw-r--r--src/shell.c.in424
-rw-r--r--src/sqlite.h.in236
-rw-r--r--src/sqlite3ext.h4
-rw-r--r--src/sqliteInt.h32
-rw-r--r--src/status.c3
-rw-r--r--src/tclsqlite.c198
-rw-r--r--src/test1.c30
-rw-r--r--src/test_config.c12
-rw-r--r--src/test_malloc.c1
-rw-r--r--src/test_windirent.h3
-rw-r--r--src/treeview.c28
-rw-r--r--src/util.c52
-rw-r--r--src/vacuum.c4
-rw-r--r--src/vdbe.c144
-rw-r--r--src/vdbeInt.h4
-rw-r--r--src/vdbeapi.c16
-rw-r--r--src/vdbemem.c71
-rw-r--r--src/wal.c73
-rw-r--r--src/where.c9
-rw-r--r--src/wherecode.c23
-rw-r--r--src/whereexpr.c5
-rw-r--r--test/analyze.test18
-rw-r--r--test/avtrans.test3
-rw-r--r--test/cast.test45
-rw-r--r--test/crash8.test6
-rw-r--r--test/cursorhint2.test77
-rw-r--r--test/dbstatus.test39
-rw-r--r--test/dbstatus2.test13
-rw-r--r--test/e_select.test2
-rw-r--r--test/expr.test57
-rw-r--r--test/fts3aa.test1
-rw-r--r--test/fts3rank.test15
-rw-r--r--test/fts4onepass.test14
-rw-r--r--test/func.test11
-rw-r--r--test/func6.test164
-rw-r--r--test/fuzzcheck.c8
-rw-r--r--test/indexexpr1.test21
-rw-r--r--test/ioerr.test4
-rw-r--r--test/istrue.test146
-rw-r--r--test/join.test54
-rw-r--r--test/join2.test93
-rw-r--r--test/join5.test4
-rw-r--r--test/json101.test25
-rw-r--r--test/kvtest.c8
-rw-r--r--test/malloc.test2
-rw-r--r--test/malloc3.test11
-rw-r--r--test/memdb1.test163
-rw-r--r--test/misc7.test24
-rw-r--r--test/nockpt.test81
-rw-r--r--test/normalize.test72
-rw-r--r--test/notnull.test45
-rw-r--r--test/optfuzz-db01.c948
-rw-r--r--test/optfuzz-db01.txt142
-rw-r--r--test/optfuzz.c309
-rw-r--r--test/ossfuzz.c11
-rw-r--r--test/ossshell.c8
-rw-r--r--test/pagerfault.test14
-rw-r--r--test/printf2.test57
-rwxr-xr-xtest/releasetest.tcl1
-rw-r--r--test/rowvalue.test152
-rw-r--r--test/sessionfuzz-data1.dbbin0 -> 258048 bytes
-rw-r--r--test/sessionfuzz.c1018
-rw-r--r--test/shell1.test42
-rw-r--r--test/speed4p.test1
-rw-r--r--test/spellfix.test2
-rw-r--r--test/spellfix4.test353
-rw-r--r--test/subquery2.test49
-rw-r--r--test/tclsqlite.test6
-rw-r--r--test/tester.tcl10
-rw-r--r--test/thread001.test1
-rw-r--r--test/trace3.test21
-rw-r--r--test/walro2.test18
-rw-r--r--test/walthread.test98
-rw-r--r--test/whereF.test94
-rw-r--r--test/with4.test52
-rw-r--r--test/zipfile.test562
-rw-r--r--test/zipfile2.test246
-rw-r--r--test/zipfilefault.test166
-rw-r--r--tool/addopcodes.tcl2
-rw-r--r--tool/lempar.c3
-rw-r--r--tool/mkopts.tcl4
-rw-r--r--tool/mksqlite3c.tcl1
-rw-r--r--tool/mksqlite3h.tcl6
-rw-r--r--tool/speed-check.sh20
-rw-r--r--www/about.html8
-rw-r--r--www/appfileformat.html2
-rw-r--r--www/assert.html343
-rw-r--r--www/books.html2
-rw-r--r--www/c3ref/c_dbconfig_enable_fkey.html23
-rw-r--r--www/c3ref/c_dbstatus_options.html15
-rw-r--r--www/c3ref/c_deserialize_freeonclose.html125
-rw-r--r--www/c3ref/c_fcntl_begin_atomic_write.html12
-rw-r--r--www/c3ref/c_serialize_nocopy.html116
-rw-r--r--www/c3ref/c_source_id.html6
-rw-r--r--www/c3ref/constlist.html36
-rw-r--r--www/c3ref/context.html30
-rw-r--r--www/c3ref/deserialize.html138
-rw-r--r--www/c3ref/funclist.html34
-rw-r--r--www/c3ref/mprintf.html78
-rw-r--r--www/c3ref/objlist.html30
-rw-r--r--www/c3ref/prepare.html4
-rw-r--r--www/c3ref/serialize.html143
-rw-r--r--www/c3ref/sqlite3.html30
-rw-r--r--www/c3ref/stmt.html60
-rw-r--r--www/c3ref/value.html30
-rw-r--r--www/capi3ref.html510
-rw-r--r--www/changes.html172
-rw-r--r--www/chronology.html6
-rw-r--r--www/cli.html72
-rw-r--r--www/codeofconduct.html224
-rw-r--r--www/compile.html9
-rw-r--r--www/copyright.html93
-rw-r--r--www/crew.html21
-rw-r--r--www/doc_backlink_crossref.html476
-rw-r--r--www/doc_keyword_crossref.html2
-rw-r--r--www/doc_pagelink_crossref.html94
-rw-r--r--www/doc_target_crossref.html47
-rw-r--r--www/doclist.html5
-rw-r--r--www/fts5.html40
-rw-r--r--www/howtocorrupt.html6
-rw-r--r--www/images/syntax/literal-value.gifbin8249 -> 9724 bytes
-rw-r--r--www/index.html2
-rw-r--r--www/keyword_index.html61
-rw-r--r--www/lang.html30
-rw-r--r--www/lang_aggfunc.html30
-rw-r--r--www/lang_corefunc.html33
-rw-r--r--www/lang_expr.html26
-rw-r--r--www/news.html6
-rw-r--r--www/opcode.html32
-rw-r--r--www/optoverview.html1153
-rw-r--r--www/pragma.html34
-rw-r--r--www/printf.html508
-rw-r--r--www/privatebranch.html2
-rw-r--r--www/qmplan.html483
-rw-r--r--www/releaselog/3_13_0.html71
-rw-r--r--www/releaselog/3_22_0.html2
-rw-r--r--www/releaselog/3_23_0.html199
-rw-r--r--www/releaselog/current.html183
-rw-r--r--www/requirements.html268
-rw-r--r--www/rescode.html60
-rwxr-xr-xwww/search1325
-rwxr-xr-xwww/search.d/admin1140
-rw-r--r--www/search.d/search.dbbin0 -> 7806976 bytes
-rw-r--r--www/session.html1245
-rw-r--r--www/session/c_changeset_abort.html32
-rw-r--r--www/session/c_changeset_conflict.html64
-rw-r--r--www/session/changegroup.html10
-rw-r--r--www/session/changeset_iter.html53
-rw-r--r--www/session/constlist.html30
-rw-r--r--www/session/funclist.html37
-rw-r--r--www/session/objlist.html31
-rw-r--r--www/session/rebaser.html192
-rw-r--r--www/session/session.html53
-rw-r--r--www/session/sqlite3changegroup_add.html80
-rw-r--r--www/session/sqlite3changegroup_add_strm.html95
-rw-r--r--www/session/sqlite3changegroup_new.html16
-rw-r--r--www/session/sqlite3changegroup_output.html2
-rw-r--r--www/session/sqlite3changeset_apply.html213
-rw-r--r--www/session/sqlite3changeset_concat.html28
-rw-r--r--www/session/sqlite3changeset_conflict.html2
-rw-r--r--www/session/sqlite3changeset_finalize.html20
-rw-r--r--www/session/sqlite3changeset_invert.html8
-rw-r--r--www/session/sqlite3changeset_new.html10
-rw-r--r--www/session/sqlite3changeset_next.html6
-rw-r--r--www/session/sqlite3changeset_old.html6
-rw-r--r--www/session/sqlite3changeset_op.html8
-rw-r--r--www/session/sqlite3changeset_pk.html4
-rw-r--r--www/session/sqlite3changeset_start.html22
-rw-r--r--www/session/sqlite3rebaser_configure.html106
-rw-r--r--www/session/sqlite3rebaser_create.html103
-rw-r--r--www/session/sqlite3rebaser_delete.html102
-rw-r--r--www/session/sqlite3rebaser_rebase.html112
-rw-r--r--www/session/sqlite3session_attach.html28
-rw-r--r--www/session/sqlite3session_changeset.html38
-rw-r--r--www/session/sqlite3session_create.html2
-rw-r--r--www/session/sqlite3session_delete.html4
-rw-r--r--www/session/sqlite3session_diff.html36
-rw-r--r--www/session/sqlite3session_enable.html4
-rw-r--r--www/session/sqlite3session_indirect.html12
-rw-r--r--www/session/sqlite3session_isempty.html10
-rw-r--r--www/session/sqlite3session_patchset.html16
-rw-r--r--www/session/sqlite3session_table_filter.html6
-rw-r--r--www/sessionintro.html3
-rw-r--r--www/sitemap.html13
-rw-r--r--www/spellfix1.html14
-rw-r--r--www/sqlanalyze.html4
-rw-r--r--www/sqlar.html397
-rw-r--r--www/syntax.html30
-rw-r--r--www/tclsqlite.html114
-rw-r--r--www/testing.html26
-rw-r--r--www/th3.html6
-rw-r--r--www/toc.dbbin77824 -> 77824 bytes
-rw-r--r--www/vtab.html3
-rw-r--r--www/whentouse.html34
-rw-r--r--www/whyc.html148
-rw-r--r--www/zipfile.html69
267 files changed, 22532 insertions, 4823 deletions
diff --git a/Makefile.in b/Makefile.in
index f340178..96ef340 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -180,7 +180,7 @@ LIBOBJS0 = alter.lo analyze.lo attach.lo auth.lo \
func.lo global.lo hash.lo \
icu.lo insert.lo json1.lo legacy.lo loadext.lo \
main.lo malloc.lo mem0.lo mem1.lo mem2.lo mem3.lo mem5.lo \
- memjournal.lo \
+ memdb.lo memjournal.lo \
mutex.lo mutex_noop.lo mutex_unix.lo mutex_w32.lo \
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 \
@@ -240,6 +240,7 @@ SRC = \
$(TOP)/src/mem2.c \
$(TOP)/src/mem3.c \
$(TOP)/src/mem5.c \
+ $(TOP)/src/memdb.c \
$(TOP)/src/memjournal.c \
$(TOP)/src/msvc.h \
$(TOP)/src/mutex.c \
@@ -440,6 +441,7 @@ TESTSRC += \
$(TOP)/ext/misc/ieee754.c \
$(TOP)/ext/misc/mmapwarm.c \
$(TOP)/ext/misc/nextchar.c \
+ $(TOP)/ext/misc/normalize.c \
$(TOP)/ext/misc/percentile.c \
$(TOP)/ext/misc/regexp.c \
$(TOP)/ext/misc/remember.c \
@@ -590,6 +592,7 @@ SHELL_OPT += -DSQLITE_INTROSPECTION_PRAGMAS
FUZZERSHELL_OPT = -DSQLITE_ENABLE_JSON1
FUZZCHECK_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ
FUZZCHECK_OPT += -DSQLITE_MAX_MEMORY=50000000
+FUZZCHECK_OPT += -DSQLITE_PRINTF_PRECISION_LIMIT=1000
FUZZCHECK_SRC = $(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c
DBFUZZ_OPT =
@@ -647,6 +650,9 @@ ossshell$(TEXE): $(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c sqlite3.
$(LTLINK) -o $@ $(FUZZCHECK_OPT) $(TOP)/test/ossshell.c \
$(TOP)/test/ossfuzz.c sqlite3.c $(TLIBS)
+sessionfuzz$(TEXE): $(TOP)/test/sessionfuzz.c sqlite3.c sqlite3.h
+ $(CC) $(CFLAGS) -I. -o $@ $(TOP)/test/sessionfuzz.c $(TLIBS)
+
dbfuzz$(TEXE): $(TOP)/test/dbfuzz.c sqlite3.c sqlite3.h
$(LTLINK) -o $@ $(DBFUZZ_OPT) $(TOP)/test/dbfuzz.c sqlite3.c $(TLIBS)
@@ -827,6 +833,9 @@ mem3.lo: $(TOP)/src/mem3.c $(HDR)
mem5.lo: $(TOP)/src/mem5.c $(HDR)
$(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/mem5.c
+memdb.lo: $(TOP)/src/memdb.c $(HDR)
+ $(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/memdb.c
+
memjournal.lo: $(TOP)/src/memjournal.c $(HDR)
$(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/memjournal.c
@@ -1160,14 +1169,17 @@ fulltestonly: $(TESTPROGS) fuzztest
./testfixture$(TEXE) $(TOP)/test/full.test
# Fuzz testing
-fuzztest: fuzzcheck$(TEXE) $(FUZZDATA)
+fuzztest: fuzzcheck$(TEXE) $(FUZZDATA) sessionfuzz$(TEXE) $(TOP)/test/sessionfuzz-data1.db
./fuzzcheck$(TEXE) $(FUZZDATA)
+ ./sessionfuzz$(TEXE) run $(TOP)/test/sessionfuzz-data1.db
-fastfuzztest: fuzzcheck$(TEXE) $(FUZZDATA)
+fastfuzztest: fuzzcheck$(TEXE) $(FUZZDATA) sessionfuzz$(TEXE) $(TOP)/test/sessionfuzz-data1.db
./fuzzcheck$(TEXE) --limit-mem 100M $(FUZZDATA)
+ ./sessionfuzz$(TEXE) run $(TOP)/test/sessionfuzz-data1.db
-valgrindfuzz: fuzzcheck$(TEXT) $(FUZZDATA)
+valgrindfuzz: fuzzcheck$(TEXT) $(FUZZDATA) sessionfuzz$(TEXE) $(TOP)/test/sessionfuzz-data1.db
valgrind ./fuzzcheck$(TEXE) --cell-size-check --limit-mem 10M --timeout 600 $(FUZZDATA)
+ valgrind ./sessionfuzz$(TEXE) run $(TOP)/test/sessionfuzz-data1.db
# The veryquick.test TCL tests.
#
diff --git a/Makefile.msc b/Makefile.msc
index 73c8df2..43d4a37 100644
--- a/Makefile.msc
+++ b/Makefile.msc
@@ -1186,7 +1186,7 @@ LIBOBJS0 = vdbe.lo parse.lo alter.lo analyze.lo attach.lo auth.lo \
func.lo global.lo hash.lo \
icu.lo insert.lo legacy.lo loadext.lo \
main.lo malloc.lo mem0.lo mem1.lo mem2.lo mem3.lo mem5.lo \
- memjournal.lo \
+ memdb.lo memjournal.lo \
mutex.lo mutex_noop.lo mutex_unix.lo mutex_w32.lo \
notify.lo opcodes.lo os.lo os_unix.lo os_win.lo \
pager.lo pcache.lo pcache1.lo pragma.lo prepare.lo printf.lo \
@@ -1259,6 +1259,7 @@ SRC00 = \
$(TOP)\src\mem2.c \
$(TOP)\src\mem3.c \
$(TOP)\src\mem5.c \
+ $(TOP)\src\memdb.c \
$(TOP)\src\memjournal.c \
$(TOP)\src\mutex.c \
$(TOP)\src\mutex_noop.c \
@@ -1500,6 +1501,7 @@ TESTEXT = \
$(TOP)\ext\misc\ieee754.c \
$(TOP)\ext\misc\mmapwarm.c \
$(TOP)\ext\misc\nextchar.c \
+ $(TOP)\ext\misc\normalize.c \
$(TOP)\ext\misc\percentile.c \
$(TOP)\ext\misc\regexp.c \
$(TOP)\ext\misc\remember.c \
@@ -1614,12 +1616,11 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_RTREE
#
MPTESTER_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5
FUZZERSHELL_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1
-FUZZCHECK_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ -DSQLITE_MAX_MEMORY=50000000
+FUZZCHECK_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ -DSQLITE_MAX_MEMORY=50000000 -DSQLITE_PRINTF_PRECISION_LIMIT=1000
FUZZCHECK_SRC = $(TOP)\test\fuzzcheck.c $(TOP)\test\ossfuzz.c
OSSSHELL_SRC = $(TOP)\test\ossshell.c $(TOP)\test\ossfuzz.c
DBFUZZ_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION
KV_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_DIRECT_OVERFLOW_READ
-DBSELFTEST_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS5
ST_COMPILE_OPTS = -DSQLITE_THREADSAFE=0
# Standard options to testfixture.
@@ -1687,7 +1688,7 @@ dbhash.exe: $(TOP)\tool\dbhash.c $(SQLITE3C) $(SQLITE3H)
$(LTLINK) $(NO_WARN) $(TOP)\tool\dbhash.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
scrub.exe: $(TOP)\ext\misc\scrub.c $(SQLITE3C) $(SQLITE3H)
- $(LTLINK) $(NO_WARN) $(TOP)\ext\misc\scrub.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
+ $(LTLINK) $(NO_WARN) -DSCRUB_STANDALONE=1 $(TOP)\ext\misc\scrub.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
srcck1.exe: $(TOP)\tool\srcck1.c
$(BCC) $(NO_WARN) -Fe$@ $(TOP)\tool\srcck1.c
@@ -1707,6 +1708,9 @@ fuzzcheck.exe: $(FUZZCHECK_SRC) $(SQLITE3C) $(SQLITE3H)
ossshell.exe: $(OSSSHELL_SRC) $(SQLITE3C) $(SQLITE3H)
$(LTLINK) $(NO_WARN) $(FUZZCHECK_COMPILE_OPTS) $(OSSSHELL_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
+sessionfuzz.exe: zlib $(TOP)\test\sessionfuzz.c $(SQLITE3C) $(SQLITE3H)
+ $(LTLINK) $(NO_WARN) -I$(ZLIBINCDIR) $(TOP)\test\sessionfuzz.c /link $(LDFLAGS) $(LTLINKOPTS) /LIBPATH:$(ZLIBLIBDIR) $(ZLIBLIB)
+
mptester.exe: $(TOP)\mptest\mptest.c $(SQLITE3C) $(SQLITE3H)
$(LTLINK) $(NO_WARN) $(MPTESTER_COMPILE_OPTS) $(TOP)\mptest\mptest.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
@@ -1908,6 +1912,9 @@ mem3.lo: $(TOP)\src\mem3.c $(HDR)
mem5.lo: $(TOP)\src\mem5.c $(HDR)
$(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\mem5.c
+memdb.lo: $(TOP)\src\memdb.c $(HDR)
+ $(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\memdb.c
+
memjournal.lo: $(TOP)\src\memjournal.c $(HDR)
$(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\memjournal.c
@@ -2084,7 +2091,7 @@ keywordhash.h: $(TOP)\tool\mkkeywordhash.c mkkeywordhash.exe
# Source files that go into making shell.c
SHELL_SRC = \
$(TOP)\src\shell.c.in \
- $(TOP)\ext\misc\appendvfs.c \
+ $(TOP)\ext\misc\appendvfs.c \
$(TOP)\ext\misc\shathree.c \
$(TOP)\ext\misc\fileio.c \
$(TOP)\ext\misc\completion.c \
@@ -2251,6 +2258,7 @@ TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_SERIES_CONSTRAINT_VERIFY=1
TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_DEFAULT_PAGE_SIZE=1024
TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_STMTVTAB
TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_DBPAGE_VTAB
+TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_JSON1
TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) $(TEST_CCONV_OPTS)
TESTFIXTURE_SRC0 = $(TESTEXT) $(TESTSRC2)
@@ -2364,11 +2372,11 @@ sqlite3_checker.exe: sqlite3_checker.c $(LIBRESOBJS)
$(LTLINK) $(NO_WARN) -DBUILD_sqlite -I$(TCLINCDIR) sqlite3_checker.c \
/link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS)
-dbdump.exe: $(TOP)\ext\misc\dbdump.c $(SQLITE3C) $(SQLITE3H)
+dbdump.exe: $(TOP)\ext\misc\dbdump.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS)
$(LTLINK) $(NO_WARN) -DDBDUMP_STANDALONE $(TOP)\ext\misc\dbdump.c $(SQLITE3C) \
/link $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LTLIBS)
-testloadext.lo: $(TOP)\src\test_loadext.c
+testloadext.lo: $(TOP)\src\test_loadext.c $(SQLITE3H)
$(LTCOMPILE) $(NO_WARN) -c $(TOP)\src\test_loadext.c
testloadext.dll: testloadext.lo
@@ -2421,9 +2429,6 @@ kvtest.exe: $(TOP)\test\kvtest.c $(SQLITE3C) $(SQLITE3H)
$(LTLINK) $(NO_WARN) $(KV_COMPILE_OPTS) \
$(TOP)\test\kvtest.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
-dbselftest.exe: $(TOP)\test\dbselftest.c $(SQLITE3C) $(SQLITE3H)
- $(LTLINK) $(NO_WARN) $(DBSELFTEST_COMPILE_OPTS) $(TOP)\test\dbselftest.c $(SQLITE3C)
-
rbu.exe: $(TOP)\ext\rbu\rbu.c $(TOP)\ext\rbu\sqlite3rbu.c $(SQLITE3C) $(SQLITE3H)
$(LTLINK) $(NO_WARN) -DSQLITE_ENABLE_RBU \
$(TOP)\ext\rbu\rbu.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
@@ -2463,7 +2468,10 @@ clean:
del /Q sqlite3_analyzer.exe sqlite3_analyzer.c 2>NUL
del /Q sqlite-*-output.vsix 2>NUL
del /Q fuzzershell.exe fuzzcheck.exe sqldiff.exe dbhash.exe 2>NUL
- del /Q sqltclsh.exe 2>NUL
+ del /Q sqltclsh.* 2>NUL
+ del /Q dbfuzz.exe sessionfuzz.exe 2>NUL
+ del /Q kvtest.exe ossshell.exe scrub.exe 2>NUL
+ del /Q showshm.exe sqlite3_checker.* sqlite3_expert.exe 2>NUL
del /Q fts5.* fts5parse.* 2>NUL
del /Q lsm.h lsm1.c 2>NUL
# <</mark>>
diff --git a/README.md b/README.md
index e5fa2ac..a92e5ae 100644
--- a/README.md
+++ b/README.md
@@ -14,15 +14,17 @@ SQLite sources are managed using the
[Fossil](https://www.fossil-scm.org/), a distributed version control system
that was specifically designed to support SQLite development.
If you do not want to use Fossil, you can download tarballs or ZIP
-archives as follows:
+archives or [SQLite archives](https://sqlite.org/cli.html#sqlar) as follows:
- * Lastest trunk check-in:
- <https://www.sqlite.org/src/tarball/sqlite.tar.gz> or
- <https://www.sqlite.org/src/zip/sqlite.zip>.
+ * Lastest trunk check-in as
+ [Tarball](https://www.sqlite.org/src/tarball/sqlite.tar.gz),
+ [ZIP-archive](https://www.sqlite.org/src/zip/sqlite.zip), or
+ [SQLite-archive](https://www.sqlite.org/src/sqlar/sqlite.sqlar).
- * Latest release:
- <https://www.sqlite.org/src/tarball/sqlite.tar.gz?r=release> or
- <https://www.sqlite.org/src/zip/sqlite.zip?r=release>.
+ * Latest release as
+ [Tarball](https://www.sqlite.org/src/tarball/sqlite.tar.gz?r=release),
+ [ZIP-archive](https://www.sqlite.org/src/zip/sqlite.zip?r=release), or
+ [SQLite-archive](https://www.sqlite.org/src/sqlar/sqlite.sqlar?r=release).
* For other check-ins, substitute an appropriate branch name or
tag or hash prefix for "release" in the URLs of the previous
diff --git a/VERSION b/VERSION
index a7e7070..ee893b7 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.22.0
+3.23.0
diff --git a/autoconf/Makefile.am b/autoconf/Makefile.am
index 0f07068..db24692 100644
--- a/autoconf/Makefile.am
+++ b/autoconf/Makefile.am
@@ -10,7 +10,7 @@ sqlite3_SOURCES = shell.c sqlite3.h
EXTRA_sqlite3_SOURCES = sqlite3.c
sqlite3_LDADD = @EXTRA_SHELL_OBJ@ @READLINE_LIBS@
sqlite3_DEPENDENCIES = @EXTRA_SHELL_OBJ@
-sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_STMTVTAB -DSQLITE_ENABLE_DBSTAT_VTAB
+sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_STMTVTAB -DSQLITE_ENABLE_DBSTAT_VTAB $(SHELL_CFLAGS)
include_HEADERS = sqlite3.h sqlite3ext.h
diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc
index 5f7c693..d99549b 100644
--- a/autoconf/Makefile.msc
+++ b/autoconf/Makefile.msc
@@ -931,6 +931,7 @@ LIBRESOBJS =
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_STMTVTAB
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_DBSTAT_VTAB
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC -DSQLITE_INTROSPECTION_PRAGMAS
+SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_RTREE
!ENDIF
diff --git a/autoconf/configure.ac b/autoconf/configure.ac
index 19975e9..2680bec 100644
--- a/autoconf/configure.ac
+++ b/autoconf/configure.ac
@@ -169,6 +169,9 @@ AC_CHECK_HEADERS(zlib.h,[
])
AC_SUBST(ZLIB_FLAGS)
+AC_SEARCH_LIBS(system,,,[SHELL_CFLAGS="-DSQLITE_NOHAVE_SYSTEM"])
+AC_SUBST(SHELL_CFLAGS)
+
#-----------------------------------------------------------------------
# UPDATE: Maybe it's better if users just set CFLAGS before invoking
# configure. This option doesn't really add much...
diff --git a/configure b/configure
index 8e735c6..3df19e8 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for sqlite 3.22.0.
+# Generated by GNU Autoconf 2.69 for sqlite 3.23.0.
#
#
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
@@ -726,8 +726,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='sqlite'
PACKAGE_TARNAME='sqlite'
-PACKAGE_VERSION='3.22.0'
-PACKAGE_STRING='sqlite 3.22.0'
+PACKAGE_VERSION='3.23.0'
+PACKAGE_STRING='sqlite 3.23.0'
PACKAGE_BUGREPORT=''
PACKAGE_URL=''
@@ -1465,7 +1465,7 @@ 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.22.0 to adapt to many kinds of systems.
+\`configure' configures sqlite 3.23.0 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1530,7 +1530,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of sqlite 3.22.0:";;
+ short | recursive ) echo "Configuration of sqlite 3.23.0:";;
esac
cat <<\_ACEOF
@@ -1655,7 +1655,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-sqlite configure 3.22.0
+sqlite configure 3.23.0
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2074,7 +2074,7 @@ 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.22.0, which was
+It was created by sqlite $as_me 3.23.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@@ -12242,7 +12242,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# 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.22.0, which was
+This file was extended by sqlite $as_me 3.23.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -12308,7 +12308,7 @@ _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.22.0
+sqlite config.status 3.23.0
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
diff --git a/ext/expert/expert.c b/ext/expert/expert.c
index 13fc87e..051480f 100644
--- a/ext/expert/expert.c
+++ b/ext/expert/expert.c
@@ -93,8 +93,9 @@ int main(int argc, char **argv){
}else{
for(i=1; i<(argc-1); i++){
char *zArg = argv[i];
+ int nArg;
if( zArg[0]=='-' && zArg[1]=='-' && zArg[2]!=0 ) zArg++;
- int nArg = (int)strlen(zArg);
+ nArg = (int)strlen(zArg);
if( nArg>=2 && 0==sqlite3_strnicmp(zArg, "-file", nArg) ){
if( ++i==(argc-1) ) option_requires_argument("-file");
rc = readSqlFromFile(p, argv[i], &zErr);
diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c
index daf3399..0baf82b 100644
--- a/ext/fts3/fts3_write.c
+++ b/ext/fts3/fts3_write.c
@@ -1908,6 +1908,7 @@ static int fts3WriteSegment(
sqlite3_bind_blob(pStmt, 2, z, n, SQLITE_STATIC);
sqlite3_step(pStmt);
rc = sqlite3_reset(pStmt);
+ sqlite3_bind_null(pStmt, 2);
}
return rc;
}
@@ -1964,6 +1965,7 @@ static int fts3WriteSegdir(
sqlite3_bind_blob(pStmt, 6, zRoot, nRoot, SQLITE_STATIC);
sqlite3_step(pStmt);
rc = sqlite3_reset(pStmt);
+ sqlite3_bind_null(pStmt, 6);
}
return rc;
}
@@ -3443,6 +3445,7 @@ static void fts3UpdateDocTotals(
sqlite3_bind_blob(pStmt, 2, pBlob, nBlob, SQLITE_STATIC);
sqlite3_step(pStmt);
*pRC = sqlite3_reset(pStmt);
+ sqlite3_bind_null(pStmt, 2);
sqlite3_free(a);
}
@@ -4631,6 +4634,7 @@ static int fts3TruncateSegment(
sqlite3_bind_int(pChomp, 4, iIdx);
sqlite3_step(pChomp);
rc = sqlite3_reset(pChomp);
+ sqlite3_bind_null(pChomp, 2);
}
}
@@ -4710,6 +4714,7 @@ static int fts3IncrmergeHintStore(Fts3Table *p, Blob *pHint){
sqlite3_bind_blob(pReplace, 2, pHint->a, pHint->n, SQLITE_STATIC);
sqlite3_step(pReplace);
rc = sqlite3_reset(pReplace);
+ sqlite3_bind_null(pReplace, 2);
}
return rc;
@@ -5524,7 +5529,6 @@ int sqlite3Fts3UpdateMethod(
){
Fts3Table *p = (Fts3Table *)pVtab;
int rc = SQLITE_OK; /* Return Code */
- int isRemove = 0; /* True for an UPDATE or DELETE */
u32 *aSzIns = 0; /* Sizes of inserted documents */
u32 *aSzDel = 0; /* Sizes of deleted documents */
int nChng = 0; /* Net change in number of documents */
@@ -5622,7 +5626,6 @@ int sqlite3Fts3UpdateMethod(
if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){
assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER );
rc = fts3DeleteByRowid(p, apVal[0], &nChng, aSzDel);
- isRemove = 1;
}
/* If this is an INSERT or UPDATE operation, insert the new record. */
@@ -5634,7 +5637,7 @@ int sqlite3Fts3UpdateMethod(
rc = FTS_CORRUPT_VTAB;
}
}
- if( rc==SQLITE_OK && (!isRemove || *pRowid!=p->iPrevDocid ) ){
+ if( rc==SQLITE_OK ){
rc = fts3PendingTermsDocid(p, 0, iLangid, *pRowid);
}
if( rc==SQLITE_OK ){
diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c
index a86dbeb..03c3d70 100644
--- a/ext/fts5/fts5_expr.c
+++ b/ext/fts5/fts5_expr.c
@@ -1676,7 +1676,7 @@ Fts5ExprPhrase *sqlite3Fts5ParseTerm(
** no token characters at all. (e.g ... MATCH '""'). */
sCtx.pPhrase = sqlite3Fts5MallocZero(&pParse->rc, sizeof(Fts5ExprPhrase));
}else if( sCtx.pPhrase->nTerm ){
- sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = bPrefix;
+ sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = (u8)bPrefix;
}
pParse->apPhrase[pParse->nPhrase-1] = sCtx.pPhrase;
}
diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c
index a75bf0f..412a04f 100644
--- a/ext/fts5/fts5_index.c
+++ b/ext/fts5/fts5_index.c
@@ -758,6 +758,7 @@ static void fts5DataWrite(Fts5Index *p, i64 iRowid, const u8 *pData, int nData){
sqlite3_bind_blob(p->pWriter, 2, pData, nData, SQLITE_STATIC);
sqlite3_step(p->pWriter);
p->rc = sqlite3_reset(p->pWriter);
+ sqlite3_bind_null(p->pWriter, 2);
}
/*
@@ -2386,6 +2387,7 @@ static void fts5SegIterSeekInit(
bDlidx = (val & 0x0001);
}
p->rc = sqlite3_reset(pIdxSelect);
+ sqlite3_bind_null(pIdxSelect, 2);
if( iPg<pSeg->pgnoFirst ){
iPg = pSeg->pgnoFirst;
@@ -3598,6 +3600,7 @@ static int fts5AllocateSegid(Fts5Index *p, Fts5Structure *pStruct){
sqlite3_bind_blob(pIdxSelect, 2, aBlob, 2, SQLITE_STATIC);
assert( sqlite3_step(pIdxSelect)!=SQLITE_ROW );
p->rc = sqlite3_reset(pIdxSelect);
+ sqlite3_bind_null(pIdxSelect, 2);
}
}
#endif
@@ -3724,6 +3727,7 @@ static void fts5WriteFlushBtree(Fts5Index *p, Fts5SegWriter *pWriter){
sqlite3_bind_int64(p->pIdxWriter, 3, bFlag + ((i64)pWriter->iBtPage<<1));
sqlite3_step(p->pIdxWriter);
p->rc = sqlite3_reset(p->pIdxWriter);
+ sqlite3_bind_null(p->pIdxWriter, 2);
}
pWriter->iBtPage = 0;
}
diff --git a/ext/fts5/fts5_storage.c b/ext/fts5/fts5_storage.c
index 59336fc..70d7135 100644
--- a/ext/fts5/fts5_storage.c
+++ b/ext/fts5/fts5_storage.c
@@ -458,6 +458,7 @@ static int fts5StorageInsertDocsize(
sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC);
sqlite3_step(pReplace);
rc = sqlite3_reset(pReplace);
+ sqlite3_bind_null(pReplace, 2);
}
}
return rc;
@@ -1118,6 +1119,7 @@ int sqlite3Fts5StorageConfigValue(
}
sqlite3_step(pReplace);
rc = sqlite3_reset(pReplace);
+ sqlite3_bind_null(pReplace, 1);
}
if( rc==SQLITE_OK && pVal ){
int iNew = p->pConfig->iCookie + 1;
diff --git a/ext/fts5/test/fts5aa.test b/ext/fts5/test/fts5aa.test
index a3ea0af..67cb620 100644
--- a/ext/fts5/test/fts5aa.test
+++ b/ext/fts5/test/fts5aa.test
@@ -593,5 +593,5 @@ do_execsql_test 22.1 {
}
-
+expand_all_sql db
finish_test
diff --git a/ext/icu/README.txt b/ext/icu/README.txt
index d744f74..af75d22 100644
--- a/ext/icu/README.txt
+++ b/ext/icu/README.txt
@@ -39,8 +39,8 @@ SQLite. Documentation follows.
To utilise "general" case mapping, the upper() or lower() scalar
functions are invoked with one argument:
- upper('ABC') -> 'abc'
- lower('abc') -> 'ABC'
+ upper('abc') -> 'ABC'
+ lower('ABC') -> 'abc'
To access ICU "language specific" case mapping, upper() or lower()
should be invoked with two arguments. The second argument is the name
diff --git a/ext/misc/btreeinfo.c b/ext/misc/btreeinfo.c
index d75f062..131b210 100644
--- a/ext/misc/btreeinfo.c
+++ b/ext/misc/btreeinfo.c
@@ -339,7 +339,8 @@ static int binfoColumn(
sqlite3 *db = sqlite3_context_db_handle(ctx);
int rc = binfoCompute(db, pgno, pCsr);
if( rc ){
- return rc;
+ pCursor->pVtab->zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ return SQLITE_ERROR;
}
}
switch( i ){
diff --git a/ext/misc/completion.c b/ext/misc/completion.c
index 79f889a..7809635 100644
--- a/ext/misc/completion.c
+++ b/ext/misc/completion.c
@@ -78,7 +78,7 @@ struct completion_cursor {
#define COMPLETION_INDEXES 5
#define COMPLETION_TRIGGERS 6
#define COMPLETION_DATABASES 7
-#define COMPLETION_TABLES 8
+#define COMPLETION_TABLES 8 /* Also VIEWs and TRIGGERs */
#define COMPLETION_COLUMNS 9
#define COMPLETION_MODULES 10
#define COMPLETION_EOF 11
@@ -250,8 +250,7 @@ static int completionNext(sqlite3_vtab_cursor *cur){
const char *zDb = (const char*)sqlite3_column_text(pS2, 1);
zSql = sqlite3_mprintf(
"%z%s"
- "SELECT name FROM \"%w\".sqlite_master"
- " WHERE type='table'",
+ "SELECT name FROM \"%w\".sqlite_master",
zSql, zSep, zDb
);
if( zSql==0 ) return SQLITE_NOMEM;
diff --git a/ext/misc/dbdump.c b/ext/misc/dbdump.c
index 90c2dd4..b4d6422 100644
--- a/ext/misc/dbdump.c
+++ b/ext/misc/dbdump.c
@@ -293,7 +293,6 @@ static char **tableColumnList(DState *p, const char *zTab){
** ordinary column in the table. Verify that azRowid[j] is a valid
** name for the rowid before adding it to azCol[0]. WITHOUT ROWID
** tables will fail this last check */
- int rc;
rc = sqlite3_table_column_metadata(p->db,0,zTab,azRowid[j],0,0,0,0,0);
if( rc==SQLITE_OK ) azCol[0] = azRowid[j];
break;
@@ -455,12 +454,12 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){
if( strcmp(zType, "table")==0 ){
DText sSelect;
DText sTable;
- char **azCol;
+ char **azTCol;
int i;
int nCol;
- azCol = tableColumnList(p, zTable);
- if( azCol==0 ) return 0;
+ azTCol = tableColumnList(p, zTable);
+ if( azTCol==0 ) return 0;
initText(&sTable);
appendText(&sTable, "INSERT INTO ", 0);
@@ -473,12 +472,12 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){
** In other words: "INSERT INTO tab(rowid,a,b,c,...) VALUES(...)"
** instead of the usual "INSERT INTO tab VALUES(...)".
*/
- if( azCol[0] ){
+ if( azTCol[0] ){
appendText(&sTable, "(", 0);
- appendText(&sTable, azCol[0], 0);
- for(i=1; azCol[i]; i++){
+ appendText(&sTable, azTCol[0], 0);
+ for(i=1; azTCol[i]; i++){
appendText(&sTable, ",", 0);
- appendText(&sTable, azCol[i], quoteChar(azCol[i]));
+ appendText(&sTable, azTCol[i], quoteChar(azTCol[i]));
}
appendText(&sTable, ")", 0);
}
@@ -487,19 +486,19 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){
/* Build an appropriate SELECT statement */
initText(&sSelect);
appendText(&sSelect, "SELECT ", 0);
- if( azCol[0] ){
- appendText(&sSelect, azCol[0], 0);
+ if( azTCol[0] ){
+ appendText(&sSelect, azTCol[0], 0);
appendText(&sSelect, ",", 0);
}
- for(i=1; azCol[i]; i++){
- appendText(&sSelect, azCol[i], quoteChar(azCol[i]));
- if( azCol[i+1] ){
+ for(i=1; azTCol[i]; i++){
+ appendText(&sSelect, azTCol[i], quoteChar(azTCol[i]));
+ if( azTCol[i+1] ){
appendText(&sSelect, ",", 0);
}
}
nCol = i;
- if( azCol[0]==0 ) nCol--;
- freeColumnList(azCol);
+ if( azTCol[0]==0 ) nCol--;
+ freeColumnList(azTCol);
appendText(&sSelect, " FROM ", 0);
appendText(&sSelect, zTable, quoteChar(zTable));
diff --git a/ext/misc/fileio.c b/ext/misc/fileio.c
index 60a960f..b734ca0 100644
--- a/ext/misc/fileio.c
+++ b/ext/misc/fileio.c
@@ -93,6 +93,9 @@ SQLITE_EXTENSION_INIT1
# include <direct.h>
# include "test_windirent.h"
# define dirent DIRENT
+# ifndef chmod
+# define chmod _chmod
+# endif
# ifndef stat
# define stat _stat
# endif
@@ -159,6 +162,97 @@ static void ctxErrorMsg(sqlite3_context *ctx, const char *zFmt, ...){
va_end(ap);
}
+#if defined(_WIN32)
+/*
+** This function is designed to convert a Win32 FILETIME structure into the
+** number of seconds since the Unix Epoch (1970-01-01 00:00:00 UTC).
+*/
+static sqlite3_uint64 fileTimeToUnixTime(
+ LPFILETIME pFileTime
+){
+ SYSTEMTIME epochSystemTime;
+ ULARGE_INTEGER epochIntervals;
+ FILETIME epochFileTime;
+ ULARGE_INTEGER fileIntervals;
+
+ memset(&epochSystemTime, 0, sizeof(SYSTEMTIME));
+ epochSystemTime.wYear = 1970;
+ epochSystemTime.wMonth = 1;
+ epochSystemTime.wDay = 1;
+ SystemTimeToFileTime(&epochSystemTime, &epochFileTime);
+ epochIntervals.LowPart = epochFileTime.dwLowDateTime;
+ epochIntervals.HighPart = epochFileTime.dwHighDateTime;
+
+ fileIntervals.LowPart = pFileTime->dwLowDateTime;
+ fileIntervals.HighPart = pFileTime->dwHighDateTime;
+
+ return (fileIntervals.QuadPart - epochIntervals.QuadPart) / 10000000;
+}
+
+/*
+** This function attempts to normalize the time values found in the stat()
+** buffer to UTC. This is necessary on Win32, where the runtime library
+** appears to return these values as local times.
+*/
+static void statTimesToUtc(
+ const char *zPath,
+ struct stat *pStatBuf
+){
+ HANDLE hFindFile;
+ WIN32_FIND_DATAW fd;
+ LPWSTR zUnicodeName;
+ extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*);
+ zUnicodeName = sqlite3_win32_utf8_to_unicode(zPath);
+ if( zUnicodeName ){
+ memset(&fd, 0, sizeof(WIN32_FIND_DATA));
+ hFindFile = FindFirstFileW(zUnicodeName, &fd);
+ if( hFindFile!=NULL ){
+ pStatBuf->st_ctime = (time_t)fileTimeToUnixTime(&fd.ftCreationTime);
+ pStatBuf->st_atime = (time_t)fileTimeToUnixTime(&fd.ftLastAccessTime);
+ pStatBuf->st_mtime = (time_t)fileTimeToUnixTime(&fd.ftLastWriteTime);
+ FindClose(hFindFile);
+ }
+ sqlite3_free(zUnicodeName);
+ }
+}
+#endif
+
+/*
+** This function is used in place of stat(). On Windows, special handling
+** is required in order for the included time to be returned as UTC. On all
+** other systems, this function simply calls stat().
+*/
+static int fileStat(
+ const char *zPath,
+ struct stat *pStatBuf
+){
+#if defined(_WIN32)
+ int rc = stat(zPath, pStatBuf);
+ if( rc==0 ) statTimesToUtc(zPath, pStatBuf);
+ return rc;
+#else
+ return stat(zPath, pStatBuf);
+#endif
+}
+
+/*
+** This function is used in place of lstat(). On Windows, special handling
+** is required in order for the included time to be returned as UTC. On all
+** other systems, this function simply calls lstat().
+*/
+static int fileLinkStat(
+ const char *zPath,
+ struct stat *pStatBuf
+){
+#if defined(_WIN32)
+ int rc = lstat(zPath, pStatBuf);
+ if( rc==0 ) statTimesToUtc(zPath, pStatBuf);
+ return rc;
+#else
+ return lstat(zPath, pStatBuf);
+#endif
+}
+
/*
** Argument zFile is the name of a file that will be created and/or written
** by SQL function writefile(). This function ensures that the directory
@@ -190,7 +284,7 @@ static int makeDirectory(
if( i==nCopy ) break;
zCopy[i] = '\0';
- rc2 = stat(zCopy, &sStat);
+ rc2 = fileStat(zCopy, &sStat);
if( rc2!=0 ){
if( mkdir(zCopy, mode & 0777) ) rc = SQLITE_ERROR;
}else{
@@ -232,7 +326,7 @@ static int writeFile(
** to do so using chmod(), it is not an error. */
struct stat sStat;
if( errno!=EEXIST
- || 0!=stat(zFile, &sStat)
+ || 0!=fileStat(zFile, &sStat)
|| !S_ISDIR(sStat.st_mode)
|| ((sStat.st_mode&0777)!=(mode&0777) && 0!=chmod(zFile, mode&0777))
){
@@ -270,15 +364,23 @@ static int writeFile(
SYSTEMTIME currentTime;
LONGLONG intervals;
HANDLE hFile;
+ LPWSTR zUnicodeName;
+ extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*);
+
GetSystemTime(&currentTime);
SystemTimeToFileTime(&currentTime, &lastAccess);
intervals = Int32x32To64(mtime, 10000000) + 116444736000000000;
lastWrite.dwLowDateTime = (DWORD)intervals;
lastWrite.dwHighDateTime = intervals >> 32;
- hFile = CreateFile(
- zFile, FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING,
+ zUnicodeName = sqlite3_win32_utf8_to_unicode(zFile);
+ if( zUnicodeName==0 ){
+ return 1;
+ }
+ hFile = CreateFileW(
+ zUnicodeName, FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, NULL
);
+ sqlite3_free(zUnicodeName);
if( hFile!=INVALID_HANDLE_VALUE ){
BOOL bResult = SetFileTime(hFile, NULL, &lastAccess, &lastWrite);
CloseHandle(hFile);
@@ -286,7 +388,7 @@ static int writeFile(
}else{
return 1;
}
-#elif defined(AT_FDCWD) && 0 /* utimensat() is not univerally available */
+#elif defined(AT_FDCWD) && 0 /* utimensat() is not universally available */
/* Recent unix */
struct timespec times[2];
times[0].tv_nsec = times[1].tv_nsec = 0;
@@ -486,10 +588,12 @@ static void fsdirResetCursor(fsdir_cursor *pCur){
sqlite3_free(pLvl->zDir);
}
sqlite3_free(pCur->zPath);
+ sqlite3_free(pCur->aLvl);
pCur->aLvl = 0;
pCur->zPath = 0;
pCur->zBase = 0;
pCur->nBase = 0;
+ pCur->nLvl = 0;
pCur->iLvl = -1;
pCur->iRowid = 1;
}
@@ -501,7 +605,6 @@ static int fsdirClose(sqlite3_vtab_cursor *cur){
fsdir_cursor *pCur = (fsdir_cursor*)cur;
fsdirResetCursor(pCur);
- sqlite3_free(pCur->aLvl);
sqlite3_free(pCur);
return SQLITE_OK;
}
@@ -562,7 +665,7 @@ static int fsdirNext(sqlite3_vtab_cursor *cur){
sqlite3_free(pCur->zPath);
pCur->zPath = sqlite3_mprintf("%s/%s", pLvl->zDir, pEntry->d_name);
if( pCur->zPath==0 ) return SQLITE_NOMEM;
- if( lstat(pCur->zPath, &pCur->sStat) ){
+ if( fileLinkStat(pCur->zPath, &pCur->sStat) ){
fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath);
return SQLITE_ERROR;
}
@@ -696,7 +799,7 @@ static int fsdirFilter(
if( pCur->zPath==0 ){
return SQLITE_NOMEM;
}
- if( lstat(pCur->zPath, &pCur->sStat) ){
+ if( fileLinkStat(pCur->zPath, &pCur->sStat) ){
fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath);
return SQLITE_ERROR;
}
diff --git a/ext/misc/normalize.c b/ext/misc/normalize.c
new file mode 100644
index 0000000..fd656f1
--- /dev/null
+++ b/ext/misc/normalize.c
@@ -0,0 +1,707 @@
+/*
+** 2018-01-08
+**
+** 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 the sqlite3_normalize() function.
+**
+** char *sqlite3_normalize(const char *zSql);
+**
+** This function takes an SQL string as input and returns a "normalized"
+** version of that string in memory obtained from sqlite3_malloc64(). The
+** caller is responsible for ensuring that the returned memory is freed.
+**
+** If a memory allocation error occurs, this routine returns NULL.
+**
+** The normalization consists of the following transformations:
+**
+** (1) Convert every literal (string, blob literal, numeric constant,
+** or "NULL" constant) into a ?
+**
+** (2) Remove all superfluous whitespace, including comments. Change
+** all required whitespace to a single space character.
+**
+** (3) Lowercase all ASCII characters.
+**
+** (4) If an IN or NOT IN operator is followed by a list of 1 or more
+** values, convert that list into "(?,?,?)".
+**
+** The purpose of normalization is two-fold:
+**
+** (1) Sanitize queries by removing potentially private or sensitive
+** information contained in literals.
+**
+** (2) Identify structurally identical queries by comparing their
+** normalized forms.
+**
+** Command-Line Utility
+** --------------------
+**
+** This file also contains code for a command-line utility that converts
+** SQL queries in text files into their normalized forms. To build the
+** command-line program, compile this file with -DSQLITE_NORMALIZE_CLI
+** and link it against the SQLite library.
+*/
+#include <sqlite3.h>
+#include <string.h>
+
+/*
+** Implementation note:
+**
+** Much of the tokenizer logic is copied out of the tokenize.c source file
+** of SQLite. That logic could be simplified for this particular application,
+** but that would impose a risk of introducing subtle errors. It is best to
+** keep the code as close to the original as possible.
+**
+** The tokenize code is in sync with the SQLite core as of 2018-01-08.
+** Any future changes to the core tokenizer might require corresponding
+** adjustments to the tokenizer logic in this module.
+*/
+
+
+/* Character classes for tokenizing
+**
+** In the sqlite3GetToken() function, a switch() on aiClass[c] is implemented
+** using a lookup table, whereas a switch() directly on c uses a binary search.
+** The lookup table is much faster. To maximize speed, and to ensure that
+** a lookup table is used, all of the classes need to be small integers and
+** all of them need to be used within the switch.
+*/
+#define CC_X 0 /* The letter 'x', or start of BLOB literal */
+#define CC_KYWD 1 /* Alphabetics or '_'. Usable in a keyword */
+#define CC_ID 2 /* unicode characters usable in IDs */
+#define CC_DIGIT 3 /* Digits */
+#define CC_DOLLAR 4 /* '$' */
+#define CC_VARALPHA 5 /* '@', '#', ':'. Alphabetic SQL variables */
+#define CC_VARNUM 6 /* '?'. Numeric SQL variables */
+#define CC_SPACE 7 /* Space characters */
+#define CC_QUOTE 8 /* '"', '\'', or '`'. String literals, quoted ids */
+#define CC_QUOTE2 9 /* '['. [...] style quoted ids */
+#define CC_PIPE 10 /* '|'. Bitwise OR or concatenate */
+#define CC_MINUS 11 /* '-'. Minus or SQL-style comment */
+#define CC_LT 12 /* '<'. Part of < or <= or <> */
+#define CC_GT 13 /* '>'. Part of > or >= */
+#define CC_EQ 14 /* '='. Part of = or == */
+#define CC_BANG 15 /* '!'. Part of != */
+#define CC_SLASH 16 /* '/'. / or c-style comment */
+#define CC_LP 17 /* '(' */
+#define CC_RP 18 /* ')' */
+#define CC_SEMI 19 /* ';' */
+#define CC_PLUS 20 /* '+' */
+#define CC_STAR 21 /* '*' */
+#define CC_PERCENT 22 /* '%' */
+#define CC_COMMA 23 /* ',' */
+#define CC_AND 24 /* '&' */
+#define CC_TILDA 25 /* '~' */
+#define CC_DOT 26 /* '.' */
+#define CC_ILLEGAL 27 /* Illegal character */
+
+static const unsigned char aiClass[] = {
+/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */
+/* 0x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 7, 7, 27, 7, 7, 27, 27,
+/* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* 2x */ 7, 15, 8, 5, 4, 22, 24, 8, 17, 18, 21, 20, 23, 11, 26, 16,
+/* 3x */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 19, 12, 14, 13, 6,
+/* 4x */ 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+/* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 9, 27, 27, 27, 1,
+/* 6x */ 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 27, 10, 27, 25, 27,
+/* 8x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+/* 9x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+/* Ax */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+/* Bx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+/* Cx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+/* Dx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+/* Ex */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+/* Fx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
+};
+
+/* An array to map all upper-case characters into their corresponding
+** lower-case character.
+**
+** SQLite only considers US-ASCII (or EBCDIC) characters. We do not
+** handle case conversions for the UTF character set since the tables
+** involved are nearly as big or bigger than SQLite itself.
+*/
+static const unsigned char sqlite3UpperToLower[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+ 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
+ 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
+ 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99,100,101,102,103,
+ 104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,
+ 122, 91, 92, 93, 94, 95, 96, 97, 98, 99,100,101,102,103,104,105,106,107,
+ 108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,
+ 126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
+ 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,
+ 162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,
+ 180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,
+ 198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,
+ 216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,
+ 234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,
+ 252,253,254,255
+};
+
+/*
+** The following 256 byte lookup table is used to support SQLites built-in
+** equivalents to the following standard library functions:
+**
+** isspace() 0x01
+** isalpha() 0x02
+** isdigit() 0x04
+** isalnum() 0x06
+** isxdigit() 0x08
+** toupper() 0x20
+** SQLite identifier character 0x40
+** Quote character 0x80
+**
+** Bit 0x20 is set if the mapped character requires translation to upper
+** case. i.e. if the character is a lower-case ASCII character.
+** If x is a lower-case ASCII character, then its upper-case equivalent
+** is (x - 0x20). Therefore toupper() can be implemented as:
+**
+** (x & ~(map[x]&0x20))
+**
+** The equivalent of tolower() is implemented using the sqlite3UpperToLower[]
+** array. tolower() is used more often than toupper() by SQLite.
+**
+** Bit 0x40 is set if the character is non-alphanumeric and can be used in an
+** SQLite identifier. Identifiers are alphanumerics, "_", "$", and any
+** non-ASCII UTF character. Hence the test for whether or not a character is
+** part of an identifier is 0x46.
+*/
+static const unsigned char sqlite3CtypeMap[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 00..07 ........ */
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, /* 08..0f ........ */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10..17 ........ */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 18..1f ........ */
+ 0x01, 0x00, 0x80, 0x00, 0x40, 0x00, 0x00, 0x80, /* 20..27 !"#$%&' */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 28..2f ()*+,-./ */
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, /* 30..37 01234567 */
+ 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 38..3f 89:;<=>? */
+
+ 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x02, /* 40..47 @ABCDEFG */
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 48..4f HIJKLMNO */
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 50..57 PQRSTUVW */
+ 0x02, 0x02, 0x02, 0x80, 0x00, 0x00, 0x00, 0x40, /* 58..5f XYZ[\]^_ */
+ 0x80, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x22, /* 60..67 `abcdefg */
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, /* 68..6f hijklmno */
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, /* 70..77 pqrstuvw */
+ 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, /* 78..7f xyz{|}~. */
+
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 80..87 ........ */
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 88..8f ........ */
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 90..97 ........ */
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 98..9f ........ */
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* a0..a7 ........ */
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* a8..af ........ */
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* b0..b7 ........ */
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* b8..bf ........ */
+
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* c0..c7 ........ */
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* c8..cf ........ */
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* d0..d7 ........ */
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* d8..df ........ */
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* e0..e7 ........ */
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* e8..ef ........ */
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* f0..f7 ........ */
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40 /* f8..ff ........ */
+};
+#define sqlite3Toupper(x) ((x)&~(sqlite3CtypeMap[(unsigned char)(x)]&0x20))
+#define sqlite3Isspace(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x01)
+#define sqlite3Isalnum(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x06)
+#define sqlite3Isalpha(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x02)
+#define sqlite3Isdigit(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x04)
+#define sqlite3Isxdigit(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x08)
+#define sqlite3Tolower(x) (sqlite3UpperToLower[(unsigned char)(x)])
+#define sqlite3Isquote(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x80)
+
+
+/*
+** If X is a character that can be used in an identifier then
+** IdChar(X) will be true. Otherwise it is false.
+**
+** For ASCII, any character with the high-order bit set is
+** allowed in an identifier. For 7-bit characters,
+** sqlite3IsIdChar[X] must be 1.
+**
+** For EBCDIC, the rules are more complex but have the same
+** end result.
+**
+** Ticket #1066. the SQL standard does not allow '$' in the
+** middle of identifiers. But many SQL implementations do.
+** SQLite will allow '$' in identifiers for compatibility.
+** But the feature is undocumented.
+*/
+#define IdChar(C) ((sqlite3CtypeMap[(unsigned char)C]&0x46)!=0)
+
+/*
+** Ignore testcase() macros
+*/
+#define testcase(X)
+
+/*
+** Token values
+*/
+#define TK_SPACE 0
+#define TK_NAME 1
+#define TK_LITERAL 2
+#define TK_PUNCT 3
+#define TK_ERROR 4
+
+#define TK_MINUS TK_PUNCT
+#define TK_LP TK_PUNCT
+#define TK_RP TK_PUNCT
+#define TK_SEMI TK_PUNCT
+#define TK_PLUS TK_PUNCT
+#define TK_STAR TK_PUNCT
+#define TK_SLASH TK_PUNCT
+#define TK_REM TK_PUNCT
+#define TK_EQ TK_PUNCT
+#define TK_LE TK_PUNCT
+#define TK_NE TK_PUNCT
+#define TK_LSHIFT TK_PUNCT
+#define TK_LT TK_PUNCT
+#define TK_GE TK_PUNCT
+#define TK_RSHIFT TK_PUNCT
+#define TK_GT TK_PUNCT
+#define TK_GE TK_PUNCT
+#define TK_BITOR TK_PUNCT
+#define TK_CONCAT TK_PUNCT
+#define TK_COMMA TK_PUNCT
+#define TK_BITAND TK_PUNCT
+#define TK_BITNOT TK_PUNCT
+#define TK_STRING TK_LITERAL
+#define TK_ID TK_NAME
+#define TK_ILLEGAL TK_ERROR
+#define TK_DOT TK_PUNCT
+#define TK_INTEGER TK_LITERAL
+#define TK_FLOAT TK_LITERAL
+#define TK_VARIABLE TK_LITERAL
+#define TK_BLOB TK_LITERAL
+
+/*
+** Return the length (in bytes) of the token that begins at z[0].
+** Store the token type in *tokenType before returning.
+*/
+static int sqlite3GetToken(const unsigned char *z, int *tokenType){
+ int i, c;
+ switch( aiClass[*z] ){ /* Switch on the character-class of the first byte
+ ** of the token. See the comment on the CC_ defines
+ ** above. */
+ case CC_SPACE: {
+ for(i=1; sqlite3Isspace(z[i]); i++){}
+ *tokenType = TK_SPACE;
+ return i;
+ }
+ case CC_MINUS: {
+ if( z[1]=='-' ){
+ for(i=2; (c=z[i])!=0 && c!='\n'; i++){}
+ *tokenType = TK_SPACE;
+ return i;
+ }
+ *tokenType = TK_MINUS;
+ return 1;
+ }
+ case CC_LP: {
+ *tokenType = TK_LP;
+ return 1;
+ }
+ case CC_RP: {
+ *tokenType = TK_RP;
+ return 1;
+ }
+ case CC_SEMI: {
+ *tokenType = TK_SEMI;
+ return 1;
+ }
+ case CC_PLUS: {
+ *tokenType = TK_PLUS;
+ return 1;
+ }
+ case CC_STAR: {
+ *tokenType = TK_STAR;
+ return 1;
+ }
+ case CC_SLASH: {
+ if( z[1]!='*' || z[2]==0 ){
+ *tokenType = TK_SLASH;
+ return 1;
+ }
+ for(i=3, c=z[2]; (c!='*' || z[i]!='/') && (c=z[i])!=0; i++){}
+ if( c ) i++;
+ *tokenType = TK_SPACE;
+ return i;
+ }
+ case CC_PERCENT: {
+ *tokenType = TK_REM;
+ return 1;
+ }
+ case CC_EQ: {
+ *tokenType = TK_EQ;
+ return 1 + (z[1]=='=');
+ }
+ case CC_LT: {
+ if( (c=z[1])=='=' ){
+ *tokenType = TK_LE;
+ return 2;
+ }else if( c=='>' ){
+ *tokenType = TK_NE;
+ return 2;
+ }else if( c=='<' ){
+ *tokenType = TK_LSHIFT;
+ return 2;
+ }else{
+ *tokenType = TK_LT;
+ return 1;
+ }
+ }
+ case CC_GT: {
+ if( (c=z[1])=='=' ){
+ *tokenType = TK_GE;
+ return 2;
+ }else if( c=='>' ){
+ *tokenType = TK_RSHIFT;
+ return 2;
+ }else{
+ *tokenType = TK_GT;
+ return 1;
+ }
+ }
+ case CC_BANG: {
+ if( z[1]!='=' ){
+ *tokenType = TK_ILLEGAL;
+ return 1;
+ }else{
+ *tokenType = TK_NE;
+ return 2;
+ }
+ }
+ case CC_PIPE: {
+ if( z[1]!='|' ){
+ *tokenType = TK_BITOR;
+ return 1;
+ }else{
+ *tokenType = TK_CONCAT;
+ return 2;
+ }
+ }
+ case CC_COMMA: {
+ *tokenType = TK_COMMA;
+ return 1;
+ }
+ case CC_AND: {
+ *tokenType = TK_BITAND;
+ return 1;
+ }
+ case CC_TILDA: {
+ *tokenType = TK_BITNOT;
+ return 1;
+ }
+ case CC_QUOTE: {
+ int delim = z[0];
+ testcase( delim=='`' );
+ testcase( delim=='\'' );
+ testcase( delim=='"' );
+ for(i=1; (c=z[i])!=0; i++){
+ if( c==delim ){
+ if( z[i+1]==delim ){
+ i++;
+ }else{
+ break;
+ }
+ }
+ }
+ if( c=='\'' ){
+ *tokenType = TK_STRING;
+ return i+1;
+ }else if( c!=0 ){
+ *tokenType = TK_ID;
+ return i+1;
+ }else{
+ *tokenType = TK_ILLEGAL;
+ return i;
+ }
+ }
+ case CC_DOT: {
+ if( !sqlite3Isdigit(z[1]) ){
+ *tokenType = TK_DOT;
+ return 1;
+ }
+ /* If the next character is a digit, this is a floating point
+ ** number that begins with ".". Fall thru into the next case */
+ }
+ case CC_DIGIT: {
+ *tokenType = TK_INTEGER;
+ if( z[0]=='0' && (z[1]=='x' || z[1]=='X') && sqlite3Isxdigit(z[2]) ){
+ for(i=3; sqlite3Isxdigit(z[i]); i++){}
+ return i;
+ }
+ for(i=0; sqlite3Isdigit(z[i]); i++){}
+ if( z[i]=='.' ){
+ i++;
+ while( sqlite3Isdigit(z[i]) ){ i++; }
+ *tokenType = TK_FLOAT;
+ }
+ if( (z[i]=='e' || z[i]=='E') &&
+ ( sqlite3Isdigit(z[i+1])
+ || ((z[i+1]=='+' || z[i+1]=='-') && sqlite3Isdigit(z[i+2]))
+ )
+ ){
+ i += 2;
+ while( sqlite3Isdigit(z[i]) ){ i++; }
+ *tokenType = TK_FLOAT;
+ }
+ while( IdChar(z[i]) ){
+ *tokenType = TK_ILLEGAL;
+ i++;
+ }
+ return i;
+ }
+ case CC_QUOTE2: {
+ for(i=1, c=z[0]; c!=']' && (c=z[i])!=0; i++){}
+ *tokenType = c==']' ? TK_ID : TK_ILLEGAL;
+ return i;
+ }
+ case CC_VARNUM: {
+ *tokenType = TK_VARIABLE;
+ for(i=1; sqlite3Isdigit(z[i]); i++){}
+ return i;
+ }
+ case CC_DOLLAR:
+ case CC_VARALPHA: {
+ int n = 0;
+ testcase( z[0]=='$' ); testcase( z[0]=='@' );
+ testcase( z[0]==':' ); testcase( z[0]=='#' );
+ *tokenType = TK_VARIABLE;
+ for(i=1; (c=z[i])!=0; i++){
+ if( IdChar(c) ){
+ n++;
+ }else if( c=='(' && n>0 ){
+ do{
+ i++;
+ }while( (c=z[i])!=0 && !sqlite3Isspace(c) && c!=')' );
+ if( c==')' ){
+ i++;
+ }else{
+ *tokenType = TK_ILLEGAL;
+ }
+ break;
+ }else if( c==':' && z[i+1]==':' ){
+ i++;
+ }else{
+ break;
+ }
+ }
+ if( n==0 ) *tokenType = TK_ILLEGAL;
+ return i;
+ }
+ case CC_KYWD: {
+ for(i=1; aiClass[z[i]]<=CC_KYWD; i++){}
+ if( IdChar(z[i]) ){
+ /* This token started out using characters that can appear in keywords,
+ ** but z[i] is a character not allowed within keywords, so this must
+ ** be an identifier instead */
+ i++;
+ break;
+ }
+ *tokenType = TK_ID;
+ return i;
+ }
+ case CC_X: {
+ testcase( z[0]=='x' ); testcase( z[0]=='X' );
+ if( z[1]=='\'' ){
+ *tokenType = TK_BLOB;
+ for(i=2; sqlite3Isxdigit(z[i]); i++){}
+ if( z[i]!='\'' || i%2 ){
+ *tokenType = TK_ILLEGAL;
+ while( z[i] && z[i]!='\'' ){ i++; }
+ }
+ if( z[i] ) i++;
+ return i;
+ }
+ /* If it is not a BLOB literal, then it must be an ID, since no
+ ** SQL keywords start with the letter 'x'. Fall through */
+ }
+ case CC_ID: {
+ i = 1;
+ break;
+ }
+ default: {
+ *tokenType = TK_ILLEGAL;
+ return 1;
+ }
+ }
+ while( IdChar(z[i]) ){ i++; }
+ *tokenType = TK_ID;
+ return i;
+}
+
+char *sqlite3_normalize(const char *zSql){
+ char *z; /* The output string */
+ sqlite3_int64 nZ; /* Size of the output string in bytes */
+ sqlite3_int64 nSql; /* Size of the input string in bytes */
+ int i; /* Next character to read from zSql[] */
+ int j; /* Next slot to fill in on z[] */
+ int tokenType; /* Type of the next token */
+ int n; /* Size of the next token */
+ int k; /* Loop counter */
+
+ nSql = strlen(zSql);
+ nZ = nSql;
+ z = sqlite3_malloc64( nZ+2 );
+ if( z==0 ) return 0;
+ for(i=j=0; zSql[i]; i += n){
+ n = sqlite3GetToken((unsigned char*)zSql+i, &tokenType);
+ switch( tokenType ){
+ case TK_SPACE: {
+ break;
+ }
+ case TK_ERROR: {
+ sqlite3_free(z);
+ return 0;
+ }
+ case TK_LITERAL: {
+ z[j++] = '?';
+ break;
+ }
+ case TK_PUNCT:
+ case TK_NAME: {
+ if( n==4 && sqlite3_strnicmp(zSql+i,"NULL",4)==0 ){
+ if( (j>=3 && strncmp(z+j-2,"is",2)==0 && !IdChar(z[j-3]))
+ || (j>=4 && strncmp(z+j-3,"not",3)==0 && !IdChar(z[j-4]))
+ ){
+ /* NULL is a keyword in this case, not a literal value */
+ }else{
+ /* Here the NULL is a literal value */
+ z[j++] = '?';
+ break;
+ }
+ }
+ if( j>0 && IdChar(z[j-1]) && IdChar(zSql[i]) ) z[j++] = ' ';
+ for(k=0; k<n; k++){
+ z[j++] = sqlite3Tolower(zSql[i+k]);
+ }
+ break;
+ }
+ }
+ }
+ while( j>0 && z[j-1]==' ' ){ j--; }
+ if( i>0 && z[j-1]!=';' ){ z[j++] = ';'; }
+ z[j] = 0;
+
+ /* Make a second pass converting "in(...)" where the "..." is not a
+ ** SELECT statement into "in(?,?,?)" */
+ for(i=0; i<j; i=n){
+ char *zIn = strstr(z+i, "in(");
+ int nParen;
+ if( zIn==0 ) break;
+ n = (int)(zIn-z)+3; /* Index of first char past "in(" */
+ if( n && IdChar(zIn[-1]) ) continue;
+ if( strncmp(zIn, "in(select",9)==0 && !IdChar(zIn[9]) ) continue;
+ if( strncmp(zIn, "in(with",7)==0 && !IdChar(zIn[7]) ) continue;
+ for(nParen=1, k=0; z[n+k]; k++){
+ if( z[n+k]=='(' ) nParen++;
+ if( z[n+k]==')' ){
+ nParen--;
+ if( nParen==0 ) break;
+ }
+ }
+ /* k is the number of bytes in the "..." within "in(...)" */
+ if( k<5 ){
+ z = sqlite3_realloc64(z, j+(5-k)+1);
+ if( z==0 ) return 0;
+ memmove(z+n+5, z+n+k, j-(n+k));
+ }else if( k>5 ){
+ memmove(z+n+5, z+n+k, j-(n+k));
+ }
+ j = j-k+5;
+ z[j] = 0;
+ memcpy(z+n, "?,?,?", 5);
+ }
+ return z;
+}
+
+/*
+** For testing purposes, or to build a stand-alone SQL normalizer program,
+** compile this one source file with the -DSQLITE_NORMALIZE_CLI and link
+** it against any SQLite library. The resulting command-line program will
+** run sqlite3_normalize() over the text of all files named on the command-
+** line and show the result on standard output.
+*/
+#ifdef SQLITE_NORMALIZE_CLI
+#include <stdio.h>
+#include <stdlib.h>
+
+/*
+** Break zIn up into separate SQL statements and run sqlite3_normalize()
+** on each one. Print the result of each run.
+*/
+static void normalizeFile(char *zIn){
+ int i;
+ if( zIn==0 ) return;
+ for(i=0; zIn[i]; i++){
+ char cSaved;
+ if( zIn[i]!=';' ) continue;
+ cSaved = zIn[i+1];
+ zIn[i+1] = 0;
+ if( sqlite3_complete(zIn) ){
+ char *zOut = sqlite3_normalize(zIn);
+ if( zOut ){
+ printf("%s\n", zOut);
+ sqlite3_free(zOut);
+ }else{
+ fprintf(stderr, "ERROR: %s\n", zIn);
+ }
+ zIn[i+1] = cSaved;
+ zIn += i+1;
+ i = -1;
+ }else{
+ zIn[i+1] = cSaved;
+ }
+ }
+}
+
+/*
+** The main routine for "sql_normalize". Read files named on the
+** command-line and run the text of each through sqlite3_normalize().
+*/
+int main(int argc, char **argv){
+ int i;
+ FILE *in;
+ char *zBuf = 0;
+ sqlite3_int64 sz, got;
+
+ for(i=1; i<argc; i++){
+ in = fopen(argv[i], "rb");
+ if( in==0 ){
+ fprintf(stderr, "cannot open \"%s\"\n", argv[i]);
+ continue;
+ }
+ fseek(in, 0, SEEK_END);
+ sz = ftell(in);
+ rewind(in);
+ zBuf = sqlite3_realloc64(zBuf, sz+1);
+ if( zBuf==0 ){
+ fprintf(stderr, "failed to malloc for %lld bytes\n", sz);
+ exit(1);
+ }
+ got = fread(zBuf, 1, sz, in);
+ fclose(in);
+ if( got!=sz ){
+ fprintf(stderr, "only able to read %lld of %lld bytes from \"%s\"\n",
+ got, sz, argv[i]);
+ }else{
+ zBuf[got] = 0;
+ normalizeFile(zBuf);
+ }
+ }
+ sqlite3_free(zBuf);
+}
+#endif /* SQLITE_NORMALIZE_CLI */
diff --git a/ext/misc/scrub.c b/ext/misc/scrub.c
index 92718e2..4eb56b0 100644
--- a/ext/misc/scrub.c
+++ b/ext/misc/scrub.c
@@ -131,7 +131,7 @@ static void scrubBackupWrite(ScrubState *p, int pgno, const u8 *pData){
scrubBackupErr(p, "write failed for page %d", pgno);
p->rcErr = SQLITE_IOERR;
}
- if( pgno>p->iLastPage ) p->iLastPage = pgno;
+ if( (u32)pgno>p->iLastPage ) p->iLastPage = pgno;
}
/* Prepare a statement against the "db" database. */
@@ -459,7 +459,7 @@ static void scrubBackupBtree(ScrubState *p, int pgno, int iDepth){
nLocal = K<=X ? K : M;
if( pc+nLocal > p->szUsable-4 ){ ln=__LINE__; goto btree_corrupt; }
iChild = scrubBackupInt32(&a[pc+nLocal]);
- scrubBackupOverflow(p, iChild, P-nLocal);
+ scrubBackupOverflow(p, iChild, (u32)(P-nLocal));
}
/* Walk the right-most tree */
diff --git a/ext/misc/spellfix.c b/ext/misc/spellfix.c
index 4f17b88..faceea1 100644
--- a/ext/misc/spellfix.c
+++ b/ext/misc/spellfix.c
@@ -18,6 +18,12 @@
SQLITE_EXTENSION_INIT1
#ifndef SQLITE_AMALGAMATION
+# if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
+# define NDEBUG 1
+# endif
+# if defined(NDEBUG) && defined(SQLITE_DEBUG)
+# undef NDEBUG
+# endif
# include <string.h>
# include <stdio.h>
# include <stdlib.h>
@@ -652,6 +658,79 @@ static void editDist3ConfigDelete(void *pIn){
sqlite3_free(p);
}
+/* Compare the FROM values of two EditDist3Cost objects, for sorting.
+** Return negative, zero, or positive if the A is less than, equal to,
+** or greater than B.
+*/
+static int editDist3CostCompare(EditDist3Cost *pA, EditDist3Cost *pB){
+ int n = pA->nFrom;
+ int rc;
+ if( n>pB->nFrom ) n = pB->nFrom;
+ rc = strncmp(pA->a, pB->a, n);
+ if( rc==0 ) rc = pA->nFrom - pB->nFrom;
+ return rc;
+}
+
+/*
+** Merge together two sorted lists of EditDist3Cost objects, in order
+** of increasing FROM.
+*/
+static EditDist3Cost *editDist3CostMerge(
+ EditDist3Cost *pA,
+ EditDist3Cost *pB
+){
+ EditDist3Cost *pHead = 0;
+ EditDist3Cost **ppTail = &pHead;
+ EditDist3Cost *p;
+ while( pA && pB ){
+ if( editDist3CostCompare(pA,pB)<=0 ){
+ p = pA;
+ pA = pA->pNext;
+ }else{
+ p = pB;
+ pB = pB->pNext;
+ }
+ *ppTail = p;
+ ppTail = &p->pNext;
+ }
+ if( pA ){
+ *ppTail = pA;
+ }else{
+ *ppTail = pB;
+ }
+ return pHead;
+}
+
+/*
+** Sort a list of EditDist3Cost objects into order of increasing FROM
+*/
+static EditDist3Cost *editDist3CostSort(EditDist3Cost *pList){
+ EditDist3Cost *ap[60], *p;
+ int i;
+ int mx = 0;
+ ap[0] = 0;
+ ap[1] = 0;
+ while( pList ){
+ p = pList;
+ pList = p->pNext;
+ p->pNext = 0;
+ for(i=0; ap[i]; i++){
+ p = editDist3CostMerge(ap[i],p);
+ ap[i] = 0;
+ }
+ ap[i] = p;
+ if( i>mx ){
+ mx = i;
+ ap[i+1] = 0;
+ }
+ }
+ p = 0;
+ for(i=0; i<=mx; i++){
+ if( ap[i] ) p = editDist3CostMerge(p,ap[i]);
+ }
+ return p;
+}
+
/*
** Load all edit-distance weights from a table.
*/
@@ -685,6 +764,7 @@ static int editDist3ConfigLoad(
assert( zTo!=0 || nTo==0 );
if( nFrom>100 || nTo>100 ) continue;
if( iCost<0 ) continue;
+ if( iCost>=10000 ) continue; /* Costs above 10K are considered infinite */
if( pLang==0 || iLang!=iLangPrev ){
EditDist3Lang *pNew;
pNew = sqlite3_realloc64(p->a, (p->nLang+1)*sizeof(p->a[0]));
@@ -722,6 +802,12 @@ static int editDist3ConfigLoad(
}
rc2 = sqlite3_finalize(pStmt);
if( rc==SQLITE_OK ) rc = rc2;
+ if( rc==SQLITE_OK ){
+ int iLang;
+ for(iLang=0; iLang<p->nLang; iLang++){
+ p->a[iLang].pCost = editDist3CostSort(p->a[iLang].pCost);
+ }
+ }
return rc;
}
@@ -749,6 +835,8 @@ static int utf8Len(unsigned char c, int N){
** the given string.
*/
static int matchTo(EditDist3Cost *p, const char *z, int n){
+ assert( n>0 );
+ if( p->a[p->nFrom]!=z[0] ) return 0;
if( p->nTo>n ) return 0;
if( strncmp(p->a+p->nFrom, z, p->nTo)!=0 ) return 0;
return 1;
@@ -760,7 +848,10 @@ static int matchTo(EditDist3Cost *p, const char *z, int n){
*/
static int matchFrom(EditDist3Cost *p, const char *z, int n){
assert( p->nFrom<=n );
- if( strncmp(p->a, z, p->nFrom)!=0 ) return 0;
+ if( p->nFrom ){
+ if( p->a[0]!=z[0] ) return 0;
+ if( strncmp(p->a, z, p->nFrom)!=0 ) return 0;
+ }
return 1;
}
@@ -776,7 +867,9 @@ static int matchFromTo(
){
int b1 = pStr->a[n1].nByte;
if( b1>n2 ) return 0;
- if( memcmp(pStr->z+n1, z2, b1)!=0 ) return 0;
+ assert( b1>0 );
+ if( pStr->z[n1]!=z2[0] ) return 0;
+ if( strncmp(pStr->z+n1, z2, b1)!=0 ) return 0;
return 1;
}
@@ -858,9 +951,6 @@ static EditDist3FromString *editDist3FromStringNew(
/*
** Update entry m[i] such that it is the minimum of its current value
** and m[j]+iCost.
-**
-** If the iCost is 1,000,000 or greater, then consider the cost to be
-** infinite and skip the update.
*/
static void updateCost(
unsigned int *m,
@@ -868,11 +958,11 @@ static void updateCost(
int j,
int iCost
){
+ unsigned int b;
assert( iCost>=0 );
- if( iCost<10000 ){
- unsigned int b = m[j] + iCost;
- if( b<m[i] ) m[i] = b;
- }
+ assert( iCost<10000 );
+ b = m[j] + iCost;
+ if( b<m[i] ) m[i] = b;
}
/*
@@ -936,8 +1026,9 @@ static int editDist3Core(
a2[i2].nByte = utf8Len((unsigned char)z2[i2], n2-i2);
for(p=pLang->pCost; p; p=p->pNext){
EditDist3Cost **apNew;
- if( p->nFrom>0 ) continue;
+ if( p->nFrom>0 ) break;
if( i2+p->nTo>n2 ) continue;
+ if( p->a[0]>z2[i2] ) break;
if( matchTo(p, z2+i2, n2-i2)==0 ) continue;
a2[i2].nIns++;
apNew = sqlite3_realloc64(a2[i2].apIns, sizeof(*apNew)*a2[i2].nIns);
@@ -1200,404 +1291,412 @@ static int utf8Charlen(const char *zIn, int nIn){
return nChar;
}
+typedef struct Transliteration Transliteration;
+struct Transliteration {
+ unsigned short int cFrom;
+ unsigned char cTo0, cTo1, cTo2, cTo3;
+};
+
/*
** Table of translations from unicode characters into ASCII.
*/
-static const struct {
- unsigned short int cFrom;
- unsigned char cTo0, cTo1;
-} translit[] = {
- { 0x00A0, 0x20, 0x00 }, /*   to */
- { 0x00B5, 0x75, 0x00 }, /* µ to u */
- { 0x00C0, 0x41, 0x00 }, /* À to A */
- { 0x00C1, 0x41, 0x00 }, /* Á to A */
- { 0x00C2, 0x41, 0x00 }, /* Â to A */
- { 0x00C3, 0x41, 0x00 }, /* Ã to A */
- { 0x00C4, 0x41, 0x65 }, /* Ä to Ae */
- { 0x00C5, 0x41, 0x61 }, /* Å to Aa */
- { 0x00C6, 0x41, 0x45 }, /* Æ to AE */
- { 0x00C7, 0x43, 0x00 }, /* Ç to C */
- { 0x00C8, 0x45, 0x00 }, /* È to E */
- { 0x00C9, 0x45, 0x00 }, /* É to E */
- { 0x00CA, 0x45, 0x00 }, /* Ê to E */
- { 0x00CB, 0x45, 0x00 }, /* Ë to E */
- { 0x00CC, 0x49, 0x00 }, /* Ì to I */
- { 0x00CD, 0x49, 0x00 }, /* Í to I */
- { 0x00CE, 0x49, 0x00 }, /* Î to I */
- { 0x00CF, 0x49, 0x00 }, /* Ï to I */
- { 0x00D0, 0x44, 0x00 }, /* Ð to D */
- { 0x00D1, 0x4E, 0x00 }, /* Ñ to N */
- { 0x00D2, 0x4F, 0x00 }, /* Ò to O */
- { 0x00D3, 0x4F, 0x00 }, /* Ó to O */
- { 0x00D4, 0x4F, 0x00 }, /* Ô to O */
- { 0x00D5, 0x4F, 0x00 }, /* Õ to O */
- { 0x00D6, 0x4F, 0x65 }, /* Ö to Oe */
- { 0x00D7, 0x78, 0x00 }, /* × to x */
- { 0x00D8, 0x4F, 0x00 }, /* Ø to O */
- { 0x00D9, 0x55, 0x00 }, /* Ù to U */
- { 0x00DA, 0x55, 0x00 }, /* Ú to U */
- { 0x00DB, 0x55, 0x00 }, /* Û to U */
- { 0x00DC, 0x55, 0x65 }, /* Ü to Ue */
- { 0x00DD, 0x59, 0x00 }, /* Ý to Y */
- { 0x00DE, 0x54, 0x68 }, /* Þ to Th */
- { 0x00DF, 0x73, 0x73 }, /* ß to ss */
- { 0x00E0, 0x61, 0x00 }, /* à to a */
- { 0x00E1, 0x61, 0x00 }, /* á to a */
- { 0x00E2, 0x61, 0x00 }, /* â to a */
- { 0x00E3, 0x61, 0x00 }, /* ã to a */
- { 0x00E4, 0x61, 0x65 }, /* ä to ae */
- { 0x00E5, 0x61, 0x61 }, /* å to aa */
- { 0x00E6, 0x61, 0x65 }, /* æ to ae */
- { 0x00E7, 0x63, 0x00 }, /* ç to c */
- { 0x00E8, 0x65, 0x00 }, /* è to e */
- { 0x00E9, 0x65, 0x00 }, /* é to e */
- { 0x00EA, 0x65, 0x00 }, /* ê to e */
- { 0x00EB, 0x65, 0x00 }, /* ë to e */
- { 0x00EC, 0x69, 0x00 }, /* ì to i */
- { 0x00ED, 0x69, 0x00 }, /* í to i */
- { 0x00EE, 0x69, 0x00 }, /* î to i */
- { 0x00EF, 0x69, 0x00 }, /* ï to i */
- { 0x00F0, 0x64, 0x00 }, /* ð to d */
- { 0x00F1, 0x6E, 0x00 }, /* ñ to n */
- { 0x00F2, 0x6F, 0x00 }, /* ò to o */
- { 0x00F3, 0x6F, 0x00 }, /* ó to o */
- { 0x00F4, 0x6F, 0x00 }, /* ô to o */
- { 0x00F5, 0x6F, 0x00 }, /* õ to o */
- { 0x00F6, 0x6F, 0x65 }, /* ö to oe */
- { 0x00F7, 0x3A, 0x00 }, /* ÷ to : */
- { 0x00F8, 0x6F, 0x00 }, /* ø to o */
- { 0x00F9, 0x75, 0x00 }, /* ù to u */
- { 0x00FA, 0x75, 0x00 }, /* ú to u */
- { 0x00FB, 0x75, 0x00 }, /* û to u */
- { 0x00FC, 0x75, 0x65 }, /* ü to ue */
- { 0x00FD, 0x79, 0x00 }, /* ý to y */
- { 0x00FE, 0x74, 0x68 }, /* þ to th */
- { 0x00FF, 0x79, 0x00 }, /* ÿ to y */
- { 0x0100, 0x41, 0x00 }, /* Ā to A */
- { 0x0101, 0x61, 0x00 }, /* ā to a */
- { 0x0102, 0x41, 0x00 }, /* Ă to A */
- { 0x0103, 0x61, 0x00 }, /* ă to a */
- { 0x0104, 0x41, 0x00 }, /* Ą to A */
- { 0x0105, 0x61, 0x00 }, /* ą to a */
- { 0x0106, 0x43, 0x00 }, /* Ć to C */
- { 0x0107, 0x63, 0x00 }, /* ć to c */
- { 0x0108, 0x43, 0x68 }, /* Ĉ to Ch */
- { 0x0109, 0x63, 0x68 }, /* ĉ to ch */
- { 0x010A, 0x43, 0x00 }, /* Ċ to C */
- { 0x010B, 0x63, 0x00 }, /* ċ to c */
- { 0x010C, 0x43, 0x00 }, /* Č to C */
- { 0x010D, 0x63, 0x00 }, /* č to c */
- { 0x010E, 0x44, 0x00 }, /* Ď to D */
- { 0x010F, 0x64, 0x00 }, /* ď to d */
- { 0x0110, 0x44, 0x00 }, /* Đ to D */
- { 0x0111, 0x64, 0x00 }, /* đ to d */
- { 0x0112, 0x45, 0x00 }, /* Ē to E */
- { 0x0113, 0x65, 0x00 }, /* ē to e */
- { 0x0114, 0x45, 0x00 }, /* Ĕ to E */
- { 0x0115, 0x65, 0x00 }, /* ĕ to e */
- { 0x0116, 0x45, 0x00 }, /* Ė to E */
- { 0x0117, 0x65, 0x00 }, /* ė to e */
- { 0x0118, 0x45, 0x00 }, /* Ę to E */
- { 0x0119, 0x65, 0x00 }, /* ę to e */
- { 0x011A, 0x45, 0x00 }, /* Ě to E */
- { 0x011B, 0x65, 0x00 }, /* ě to e */
- { 0x011C, 0x47, 0x68 }, /* Ĝ to Gh */
- { 0x011D, 0x67, 0x68 }, /* ĝ to gh */
- { 0x011E, 0x47, 0x00 }, /* Ğ to G */
- { 0x011F, 0x67, 0x00 }, /* ğ to g */
- { 0x0120, 0x47, 0x00 }, /* Ġ to G */
- { 0x0121, 0x67, 0x00 }, /* ġ to g */
- { 0x0122, 0x47, 0x00 }, /* Ģ to G */
- { 0x0123, 0x67, 0x00 }, /* ģ to g */
- { 0x0124, 0x48, 0x68 }, /* Ĥ to Hh */
- { 0x0125, 0x68, 0x68 }, /* ĥ to hh */
- { 0x0126, 0x48, 0x00 }, /* Ħ to H */
- { 0x0127, 0x68, 0x00 }, /* ħ to h */
- { 0x0128, 0x49, 0x00 }, /* Ĩ to I */
- { 0x0129, 0x69, 0x00 }, /* ĩ to i */
- { 0x012A, 0x49, 0x00 }, /* Ī to I */
- { 0x012B, 0x69, 0x00 }, /* ī to i */
- { 0x012C, 0x49, 0x00 }, /* Ĭ to I */
- { 0x012D, 0x69, 0x00 }, /* ĭ to i */
- { 0x012E, 0x49, 0x00 }, /* Į to I */
- { 0x012F, 0x69, 0x00 }, /* į to i */
- { 0x0130, 0x49, 0x00 }, /* İ to I */
- { 0x0131, 0x69, 0x00 }, /* ı to i */
- { 0x0132, 0x49, 0x4A }, /* IJ to IJ */
- { 0x0133, 0x69, 0x6A }, /* ij to ij */
- { 0x0134, 0x4A, 0x68 }, /* Ĵ to Jh */
- { 0x0135, 0x6A, 0x68 }, /* ĵ to jh */
- { 0x0136, 0x4B, 0x00 }, /* Ķ to K */
- { 0x0137, 0x6B, 0x00 }, /* ķ to k */
- { 0x0138, 0x6B, 0x00 }, /* ĸ to k */
- { 0x0139, 0x4C, 0x00 }, /* Ĺ to L */
- { 0x013A, 0x6C, 0x00 }, /* ĺ to l */
- { 0x013B, 0x4C, 0x00 }, /* Ļ to L */
- { 0x013C, 0x6C, 0x00 }, /* ļ to l */
- { 0x013D, 0x4C, 0x00 }, /* Ľ to L */
- { 0x013E, 0x6C, 0x00 }, /* ľ to l */
- { 0x013F, 0x4C, 0x2E }, /* Ŀ to L. */
- { 0x0140, 0x6C, 0x2E }, /* ŀ to l. */
- { 0x0141, 0x4C, 0x00 }, /* Ł to L */
- { 0x0142, 0x6C, 0x00 }, /* ł to l */
- { 0x0143, 0x4E, 0x00 }, /* Ń to N */
- { 0x0144, 0x6E, 0x00 }, /* ń to n */
- { 0x0145, 0x4E, 0x00 }, /* Ņ to N */
- { 0x0146, 0x6E, 0x00 }, /* ņ to n */
- { 0x0147, 0x4E, 0x00 }, /* Ň to N */
- { 0x0148, 0x6E, 0x00 }, /* ň to n */
- { 0x0149, 0x27, 0x6E }, /* ʼn to 'n */
- { 0x014A, 0x4E, 0x47 }, /* Ŋ to NG */
- { 0x014B, 0x6E, 0x67 }, /* ŋ to ng */
- { 0x014C, 0x4F, 0x00 }, /* Ō to O */
- { 0x014D, 0x6F, 0x00 }, /* ō to o */
- { 0x014E, 0x4F, 0x00 }, /* Ŏ to O */
- { 0x014F, 0x6F, 0x00 }, /* ŏ to o */
- { 0x0150, 0x4F, 0x00 }, /* Ő to O */
- { 0x0151, 0x6F, 0x00 }, /* ő to o */
- { 0x0152, 0x4F, 0x45 }, /* Πto OE */
- { 0x0153, 0x6F, 0x65 }, /* œ to oe */
- { 0x0154, 0x52, 0x00 }, /* Ŕ to R */
- { 0x0155, 0x72, 0x00 }, /* ŕ to r */
- { 0x0156, 0x52, 0x00 }, /* Ŗ to R */
- { 0x0157, 0x72, 0x00 }, /* ŗ to r */
- { 0x0158, 0x52, 0x00 }, /* Ř to R */
- { 0x0159, 0x72, 0x00 }, /* ř to r */
- { 0x015A, 0x53, 0x00 }, /* Ś to S */
- { 0x015B, 0x73, 0x00 }, /* ś to s */
- { 0x015C, 0x53, 0x68 }, /* Ŝ to Sh */
- { 0x015D, 0x73, 0x68 }, /* ŝ to sh */
- { 0x015E, 0x53, 0x00 }, /* Ş to S */
- { 0x015F, 0x73, 0x00 }, /* ş to s */
- { 0x0160, 0x53, 0x00 }, /* Š to S */
- { 0x0161, 0x73, 0x00 }, /* š to s */
- { 0x0162, 0x54, 0x00 }, /* Ţ to T */
- { 0x0163, 0x74, 0x00 }, /* ţ to t */
- { 0x0164, 0x54, 0x00 }, /* Ť to T */
- { 0x0165, 0x74, 0x00 }, /* ť to t */
- { 0x0166, 0x54, 0x00 }, /* Ŧ to T */
- { 0x0167, 0x74, 0x00 }, /* ŧ to t */
- { 0x0168, 0x55, 0x00 }, /* Ũ to U */
- { 0x0169, 0x75, 0x00 }, /* ũ to u */
- { 0x016A, 0x55, 0x00 }, /* Ū to U */
- { 0x016B, 0x75, 0x00 }, /* ū to u */
- { 0x016C, 0x55, 0x00 }, /* Ŭ to U */
- { 0x016D, 0x75, 0x00 }, /* ŭ to u */
- { 0x016E, 0x55, 0x00 }, /* Ů to U */
- { 0x016F, 0x75, 0x00 }, /* ů to u */
- { 0x0170, 0x55, 0x00 }, /* Ű to U */
- { 0x0171, 0x75, 0x00 }, /* ű to u */
- { 0x0172, 0x55, 0x00 }, /* Ų to U */
- { 0x0173, 0x75, 0x00 }, /* ų to u */
- { 0x0174, 0x57, 0x00 }, /* Ŵ to W */
- { 0x0175, 0x77, 0x00 }, /* ŵ to w */
- { 0x0176, 0x59, 0x00 }, /* Ŷ to Y */
- { 0x0177, 0x79, 0x00 }, /* ŷ to y */
- { 0x0178, 0x59, 0x00 }, /* Ÿ to Y */
- { 0x0179, 0x5A, 0x00 }, /* Ź to Z */
- { 0x017A, 0x7A, 0x00 }, /* ź to z */
- { 0x017B, 0x5A, 0x00 }, /* Ż to Z */
- { 0x017C, 0x7A, 0x00 }, /* ż to z */
- { 0x017D, 0x5A, 0x00 }, /* Ž to Z */
- { 0x017E, 0x7A, 0x00 }, /* ž to z */
- { 0x017F, 0x73, 0x00 }, /* ſ to s */
- { 0x0192, 0x66, 0x00 }, /* ƒ to f */
- { 0x0218, 0x53, 0x00 }, /* Ș to S */
- { 0x0219, 0x73, 0x00 }, /* ș to s */
- { 0x021A, 0x54, 0x00 }, /* Ț to T */
- { 0x021B, 0x74, 0x00 }, /* ț to t */
- { 0x0386, 0x41, 0x00 }, /* Ά to A */
- { 0x0388, 0x45, 0x00 }, /* Έ to E */
- { 0x0389, 0x49, 0x00 }, /* Ή to I */
- { 0x038A, 0x49, 0x00 }, /* Ί to I */
- { 0x038C, 0x4f, 0x00 }, /* Ό to O */
- { 0x038E, 0x59, 0x00 }, /* Ύ to Y */
- { 0x038F, 0x4f, 0x00 }, /* Ώ to O */
- { 0x0390, 0x69, 0x00 }, /* ΐ to i */
- { 0x0391, 0x41, 0x00 }, /* Α to A */
- { 0x0392, 0x42, 0x00 }, /* Β to B */
- { 0x0393, 0x47, 0x00 }, /* Γ to G */
- { 0x0394, 0x44, 0x00 }, /* Δ to D */
- { 0x0395, 0x45, 0x00 }, /* Ε to E */
- { 0x0396, 0x5a, 0x00 }, /* Ζ to Z */
- { 0x0397, 0x49, 0x00 }, /* Η to I */
- { 0x0398, 0x54, 0x68 }, /* Θ to Th */
- { 0x0399, 0x49, 0x00 }, /* Ι to I */
- { 0x039A, 0x4b, 0x00 }, /* Κ to K */
- { 0x039B, 0x4c, 0x00 }, /* Λ to L */
- { 0x039C, 0x4d, 0x00 }, /* Μ to M */
- { 0x039D, 0x4e, 0x00 }, /* Ν to N */
- { 0x039E, 0x58, 0x00 }, /* Ξ to X */
- { 0x039F, 0x4f, 0x00 }, /* Ο to O */
- { 0x03A0, 0x50, 0x00 }, /* Π to P */
- { 0x03A1, 0x52, 0x00 }, /* Ρ to R */
- { 0x03A3, 0x53, 0x00 }, /* Σ to S */
- { 0x03A4, 0x54, 0x00 }, /* Τ to T */
- { 0x03A5, 0x59, 0x00 }, /* Υ to Y */
- { 0x03A6, 0x46, 0x00 }, /* Φ to F */
- { 0x03A7, 0x43, 0x68 }, /* Χ to Ch */
- { 0x03A8, 0x50, 0x73 }, /* Ψ to Ps */
- { 0x03A9, 0x4f, 0x00 }, /* Ω to O */
- { 0x03AA, 0x49, 0x00 }, /* Ϊ to I */
- { 0x03AB, 0x59, 0x00 }, /* Ϋ to Y */
- { 0x03AC, 0x61, 0x00 }, /* ά to a */
- { 0x03AD, 0x65, 0x00 }, /* έ to e */
- { 0x03AE, 0x69, 0x00 }, /* ή to i */
- { 0x03AF, 0x69, 0x00 }, /* ί to i */
- { 0x03B1, 0x61, 0x00 }, /* α to a */
- { 0x03B2, 0x62, 0x00 }, /* β to b */
- { 0x03B3, 0x67, 0x00 }, /* γ to g */
- { 0x03B4, 0x64, 0x00 }, /* δ to d */
- { 0x03B5, 0x65, 0x00 }, /* ε to e */
- { 0x03B6, 0x7a, 0x00 }, /* ζ to z */
- { 0x03B7, 0x69, 0x00 }, /* η to i */
- { 0x03B8, 0x74, 0x68 }, /* θ to th */
- { 0x03B9, 0x69, 0x00 }, /* ι to i */
- { 0x03BA, 0x6b, 0x00 }, /* κ to k */
- { 0x03BB, 0x6c, 0x00 }, /* λ to l */
- { 0x03BC, 0x6d, 0x00 }, /* μ to m */
- { 0x03BD, 0x6e, 0x00 }, /* ν to n */
- { 0x03BE, 0x78, 0x00 }, /* ξ to x */
- { 0x03BF, 0x6f, 0x00 }, /* ο to o */
- { 0x03C0, 0x70, 0x00 }, /* π to p */
- { 0x03C1, 0x72, 0x00 }, /* ρ to r */
- { 0x03C3, 0x73, 0x00 }, /* σ to s */
- { 0x03C4, 0x74, 0x00 }, /* τ to t */
- { 0x03C5, 0x79, 0x00 }, /* υ to y */
- { 0x03C6, 0x66, 0x00 }, /* φ to f */
- { 0x03C7, 0x63, 0x68 }, /* χ to ch */
- { 0x03C8, 0x70, 0x73 }, /* ψ to ps */
- { 0x03C9, 0x6f, 0x00 }, /* ω to o */
- { 0x03CA, 0x69, 0x00 }, /* ϊ to i */
- { 0x03CB, 0x79, 0x00 }, /* ϋ to y */
- { 0x03CC, 0x6f, 0x00 }, /* ό to o */
- { 0x03CD, 0x79, 0x00 }, /* ύ to y */
- { 0x03CE, 0x69, 0x00 }, /* ώ to i */
- { 0x0400, 0x45, 0x00 }, /* Ѐ to E */
- { 0x0401, 0x45, 0x00 }, /* Ё to E */
- { 0x0402, 0x44, 0x00 }, /* Ђ to D */
- { 0x0403, 0x47, 0x00 }, /* Ѓ to G */
- { 0x0404, 0x45, 0x00 }, /* Є to E */
- { 0x0405, 0x5a, 0x00 }, /* Ѕ to Z */
- { 0x0406, 0x49, 0x00 }, /* І to I */
- { 0x0407, 0x49, 0x00 }, /* Ї to I */
- { 0x0408, 0x4a, 0x00 }, /* Ј to J */
- { 0x0409, 0x49, 0x00 }, /* Љ to I */
- { 0x040A, 0x4e, 0x00 }, /* Њ to N */
- { 0x040B, 0x44, 0x00 }, /* Ћ to D */
- { 0x040C, 0x4b, 0x00 }, /* Ќ to K */
- { 0x040D, 0x49, 0x00 }, /* Ѝ to I */
- { 0x040E, 0x55, 0x00 }, /* Ў to U */
- { 0x040F, 0x44, 0x00 }, /* Џ to D */
- { 0x0410, 0x41, 0x00 }, /* А to A */
- { 0x0411, 0x42, 0x00 }, /* Б to B */
- { 0x0412, 0x56, 0x00 }, /* В to V */
- { 0x0413, 0x47, 0x00 }, /* Г to G */
- { 0x0414, 0x44, 0x00 }, /* Д to D */
- { 0x0415, 0x45, 0x00 }, /* Е to E */
- { 0x0416, 0x5a, 0x68 }, /* Ж to Zh */
- { 0x0417, 0x5a, 0x00 }, /* З to Z */
- { 0x0418, 0x49, 0x00 }, /* И to I */
- { 0x0419, 0x49, 0x00 }, /* Й to I */
- { 0x041A, 0x4b, 0x00 }, /* К to K */
- { 0x041B, 0x4c, 0x00 }, /* Л to L */
- { 0x041C, 0x4d, 0x00 }, /* М to M */
- { 0x041D, 0x4e, 0x00 }, /* Н to N */
- { 0x041E, 0x4f, 0x00 }, /* О to O */
- { 0x041F, 0x50, 0x00 }, /* П to P */
- { 0x0420, 0x52, 0x00 }, /* Р to R */
- { 0x0421, 0x53, 0x00 }, /* С to S */
- { 0x0422, 0x54, 0x00 }, /* Т to T */
- { 0x0423, 0x55, 0x00 }, /* У to U */
- { 0x0424, 0x46, 0x00 }, /* Ф to F */
- { 0x0425, 0x4b, 0x68 }, /* Х to Kh */
- { 0x0426, 0x54, 0x63 }, /* Ц to Tc */
- { 0x0427, 0x43, 0x68 }, /* Ч to Ch */
- { 0x0428, 0x53, 0x68 }, /* Ш to Sh */
- { 0x0429, 0x53, 0x68 }, /* Щ to Shch */
- { 0x042A, 0x61, 0x00 }, /* to A */
- { 0x042B, 0x59, 0x00 }, /* Ы to Y */
- { 0x042C, 0x59, 0x00 }, /* to Y */
- { 0x042D, 0x45, 0x00 }, /* Э to E */
- { 0x042E, 0x49, 0x75 }, /* Ю to Iu */
- { 0x042F, 0x49, 0x61 }, /* Я to Ia */
- { 0x0430, 0x61, 0x00 }, /* а to a */
- { 0x0431, 0x62, 0x00 }, /* б to b */
- { 0x0432, 0x76, 0x00 }, /* в to v */
- { 0x0433, 0x67, 0x00 }, /* г to g */
- { 0x0434, 0x64, 0x00 }, /* д to d */
- { 0x0435, 0x65, 0x00 }, /* е to e */
- { 0x0436, 0x7a, 0x68 }, /* ж to zh */
- { 0x0437, 0x7a, 0x00 }, /* з to z */
- { 0x0438, 0x69, 0x00 }, /* и to i */
- { 0x0439, 0x69, 0x00 }, /* й to i */
- { 0x043A, 0x6b, 0x00 }, /* к to k */
- { 0x043B, 0x6c, 0x00 }, /* л to l */
- { 0x043C, 0x6d, 0x00 }, /* м to m */
- { 0x043D, 0x6e, 0x00 }, /* н to n */
- { 0x043E, 0x6f, 0x00 }, /* о to o */
- { 0x043F, 0x70, 0x00 }, /* п to p */
- { 0x0440, 0x72, 0x00 }, /* р to r */
- { 0x0441, 0x73, 0x00 }, /* с to s */
- { 0x0442, 0x74, 0x00 }, /* т to t */
- { 0x0443, 0x75, 0x00 }, /* у to u */
- { 0x0444, 0x66, 0x00 }, /* ф to f */
- { 0x0445, 0x6b, 0x68 }, /* х to kh */
- { 0x0446, 0x74, 0x63 }, /* ц to tc */
- { 0x0447, 0x63, 0x68 }, /* ч to ch */
- { 0x0448, 0x73, 0x68 }, /* ш to sh */
- { 0x0449, 0x73, 0x68 }, /* щ to shch */
- { 0x044A, 0x61, 0x00 }, /* to a */
- { 0x044B, 0x79, 0x00 }, /* ы to y */
- { 0x044C, 0x79, 0x00 }, /* to y */
- { 0x044D, 0x65, 0x00 }, /* э to e */
- { 0x044E, 0x69, 0x75 }, /* ю to iu */
- { 0x044F, 0x69, 0x61 }, /* я to ia */
- { 0x0450, 0x65, 0x00 }, /* ѐ to e */
- { 0x0451, 0x65, 0x00 }, /* ё to e */
- { 0x0452, 0x64, 0x00 }, /* ђ to d */
- { 0x0453, 0x67, 0x00 }, /* ѓ to g */
- { 0x0454, 0x65, 0x00 }, /* є to e */
- { 0x0455, 0x7a, 0x00 }, /* ѕ to z */
- { 0x0456, 0x69, 0x00 }, /* і to i */
- { 0x0457, 0x69, 0x00 }, /* ї to i */
- { 0x0458, 0x6a, 0x00 }, /* ј to j */
- { 0x0459, 0x69, 0x00 }, /* љ to i */
- { 0x045A, 0x6e, 0x00 }, /* њ to n */
- { 0x045B, 0x64, 0x00 }, /* ћ to d */
- { 0x045C, 0x6b, 0x00 }, /* ќ to k */
- { 0x045D, 0x69, 0x00 }, /* ѝ to i */
- { 0x045E, 0x75, 0x00 }, /* ў to u */
- { 0x045F, 0x64, 0x00 }, /* џ to d */
- { 0x1E02, 0x42, 0x00 }, /* Ḃ to B */
- { 0x1E03, 0x62, 0x00 }, /* ḃ to b */
- { 0x1E0A, 0x44, 0x00 }, /* Ḋ to D */
- { 0x1E0B, 0x64, 0x00 }, /* ḋ to d */
- { 0x1E1E, 0x46, 0x00 }, /* Ḟ to F */
- { 0x1E1F, 0x66, 0x00 }, /* ḟ to f */
- { 0x1E40, 0x4D, 0x00 }, /* Ṁ to M */
- { 0x1E41, 0x6D, 0x00 }, /* ṁ to m */
- { 0x1E56, 0x50, 0x00 }, /* Ṗ to P */
- { 0x1E57, 0x70, 0x00 }, /* ṗ to p */
- { 0x1E60, 0x53, 0x00 }, /* Ṡ to S */
- { 0x1E61, 0x73, 0x00 }, /* ṡ to s */
- { 0x1E6A, 0x54, 0x00 }, /* Ṫ to T */
- { 0x1E6B, 0x74, 0x00 }, /* ṫ to t */
- { 0x1E80, 0x57, 0x00 }, /* Ẁ to W */
- { 0x1E81, 0x77, 0x00 }, /* ẁ to w */
- { 0x1E82, 0x57, 0x00 }, /* Ẃ to W */
- { 0x1E83, 0x77, 0x00 }, /* ẃ to w */
- { 0x1E84, 0x57, 0x00 }, /* Ẅ to W */
- { 0x1E85, 0x77, 0x00 }, /* ẅ to w */
- { 0x1EF2, 0x59, 0x00 }, /* Ỳ to Y */
- { 0x1EF3, 0x79, 0x00 }, /* ỳ to y */
- { 0xFB00, 0x66, 0x66 }, /* ff to ff */
- { 0xFB01, 0x66, 0x69 }, /* fi to fi */
- { 0xFB02, 0x66, 0x6C }, /* fl to fl */
- { 0xFB05, 0x73, 0x74 }, /* ſt to st */
- { 0xFB06, 0x73, 0x74 }, /* st to st */
+static const Transliteration translit[] = {
+ { 0x00A0, 0x20, 0x00, 0x00, 0x00 }, /*   to */
+ { 0x00B5, 0x75, 0x00, 0x00, 0x00 }, /* µ to u */
+ { 0x00C0, 0x41, 0x00, 0x00, 0x00 }, /* À to A */
+ { 0x00C1, 0x41, 0x00, 0x00, 0x00 }, /* Á to A */
+ { 0x00C2, 0x41, 0x00, 0x00, 0x00 }, /* Â to A */
+ { 0x00C3, 0x41, 0x00, 0x00, 0x00 }, /* Ã to A */
+ { 0x00C4, 0x41, 0x65, 0x00, 0x00 }, /* Ä to Ae */
+ { 0x00C5, 0x41, 0x61, 0x00, 0x00 }, /* Å to Aa */
+ { 0x00C6, 0x41, 0x45, 0x00, 0x00 }, /* Æ to AE */
+ { 0x00C7, 0x43, 0x00, 0x00, 0x00 }, /* Ç to C */
+ { 0x00C8, 0x45, 0x00, 0x00, 0x00 }, /* È to E */
+ { 0x00C9, 0x45, 0x00, 0x00, 0x00 }, /* É to E */
+ { 0x00CA, 0x45, 0x00, 0x00, 0x00 }, /* Ê to E */
+ { 0x00CB, 0x45, 0x00, 0x00, 0x00 }, /* Ë to E */
+ { 0x00CC, 0x49, 0x00, 0x00, 0x00 }, /* Ì to I */
+ { 0x00CD, 0x49, 0x00, 0x00, 0x00 }, /* Í to I */
+ { 0x00CE, 0x49, 0x00, 0x00, 0x00 }, /* Î to I */
+ { 0x00CF, 0x49, 0x00, 0x00, 0x00 }, /* Ï to I */
+ { 0x00D0, 0x44, 0x00, 0x00, 0x00 }, /* Ð to D */
+ { 0x00D1, 0x4E, 0x00, 0x00, 0x00 }, /* Ñ to N */
+ { 0x00D2, 0x4F, 0x00, 0x00, 0x00 }, /* Ò to O */
+ { 0x00D3, 0x4F, 0x00, 0x00, 0x00 }, /* Ó to O */
+ { 0x00D4, 0x4F, 0x00, 0x00, 0x00 }, /* Ô to O */
+ { 0x00D5, 0x4F, 0x00, 0x00, 0x00 }, /* Õ to O */
+ { 0x00D6, 0x4F, 0x65, 0x00, 0x00 }, /* Ö to Oe */
+ { 0x00D7, 0x78, 0x00, 0x00, 0x00 }, /* × to x */
+ { 0x00D8, 0x4F, 0x00, 0x00, 0x00 }, /* Ø to O */
+ { 0x00D9, 0x55, 0x00, 0x00, 0x00 }, /* Ù to U */
+ { 0x00DA, 0x55, 0x00, 0x00, 0x00 }, /* Ú to U */
+ { 0x00DB, 0x55, 0x00, 0x00, 0x00 }, /* Û to U */
+ { 0x00DC, 0x55, 0x65, 0x00, 0x00 }, /* Ü to Ue */
+ { 0x00DD, 0x59, 0x00, 0x00, 0x00 }, /* Ý to Y */
+ { 0x00DE, 0x54, 0x68, 0x00, 0x00 }, /* Þ to Th */
+ { 0x00DF, 0x73, 0x73, 0x00, 0x00 }, /* ß to ss */
+ { 0x00E0, 0x61, 0x00, 0x00, 0x00 }, /* à to a */
+ { 0x00E1, 0x61, 0x00, 0x00, 0x00 }, /* á to a */
+ { 0x00E2, 0x61, 0x00, 0x00, 0x00 }, /* â to a */
+ { 0x00E3, 0x61, 0x00, 0x00, 0x00 }, /* ã to a */
+ { 0x00E4, 0x61, 0x65, 0x00, 0x00 }, /* ä to ae */
+ { 0x00E5, 0x61, 0x61, 0x00, 0x00 }, /* å to aa */
+ { 0x00E6, 0x61, 0x65, 0x00, 0x00 }, /* æ to ae */
+ { 0x00E7, 0x63, 0x00, 0x00, 0x00 }, /* ç to c */
+ { 0x00E8, 0x65, 0x00, 0x00, 0x00 }, /* è to e */
+ { 0x00E9, 0x65, 0x00, 0x00, 0x00 }, /* é to e */
+ { 0x00EA, 0x65, 0x00, 0x00, 0x00 }, /* ê to e */
+ { 0x00EB, 0x65, 0x00, 0x00, 0x00 }, /* ë to e */
+ { 0x00EC, 0x69, 0x00, 0x00, 0x00 }, /* ì to i */
+ { 0x00ED, 0x69, 0x00, 0x00, 0x00 }, /* í to i */
+ { 0x00EE, 0x69, 0x00, 0x00, 0x00 }, /* î to i */
+ { 0x00EF, 0x69, 0x00, 0x00, 0x00 }, /* ï to i */
+ { 0x00F0, 0x64, 0x00, 0x00, 0x00 }, /* ð to d */
+ { 0x00F1, 0x6E, 0x00, 0x00, 0x00 }, /* ñ to n */
+ { 0x00F2, 0x6F, 0x00, 0x00, 0x00 }, /* ò to o */
+ { 0x00F3, 0x6F, 0x00, 0x00, 0x00 }, /* ó to o */
+ { 0x00F4, 0x6F, 0x00, 0x00, 0x00 }, /* ô to o */
+ { 0x00F5, 0x6F, 0x00, 0x00, 0x00 }, /* õ to o */
+ { 0x00F6, 0x6F, 0x65, 0x00, 0x00 }, /* ö to oe */
+ { 0x00F7, 0x3A, 0x00, 0x00, 0x00 }, /* ÷ to : */
+ { 0x00F8, 0x6F, 0x00, 0x00, 0x00 }, /* ø to o */
+ { 0x00F9, 0x75, 0x00, 0x00, 0x00 }, /* ù to u */
+ { 0x00FA, 0x75, 0x00, 0x00, 0x00 }, /* ú to u */
+ { 0x00FB, 0x75, 0x00, 0x00, 0x00 }, /* û to u */
+ { 0x00FC, 0x75, 0x65, 0x00, 0x00 }, /* ü to ue */
+ { 0x00FD, 0x79, 0x00, 0x00, 0x00 }, /* ý to y */
+ { 0x00FE, 0x74, 0x68, 0x00, 0x00 }, /* þ to th */
+ { 0x00FF, 0x79, 0x00, 0x00, 0x00 }, /* ÿ to y */
+ { 0x0100, 0x41, 0x00, 0x00, 0x00 }, /* Ā to A */
+ { 0x0101, 0x61, 0x00, 0x00, 0x00 }, /* ā to a */
+ { 0x0102, 0x41, 0x00, 0x00, 0x00 }, /* Ă to A */
+ { 0x0103, 0x61, 0x00, 0x00, 0x00 }, /* ă to a */
+ { 0x0104, 0x41, 0x00, 0x00, 0x00 }, /* Ą to A */
+ { 0x0105, 0x61, 0x00, 0x00, 0x00 }, /* ą to a */
+ { 0x0106, 0x43, 0x00, 0x00, 0x00 }, /* Ć to C */
+ { 0x0107, 0x63, 0x00, 0x00, 0x00 }, /* ć to c */
+ { 0x0108, 0x43, 0x68, 0x00, 0x00 }, /* Ĉ to Ch */
+ { 0x0109, 0x63, 0x68, 0x00, 0x00 }, /* ĉ to ch */
+ { 0x010A, 0x43, 0x00, 0x00, 0x00 }, /* Ċ to C */
+ { 0x010B, 0x63, 0x00, 0x00, 0x00 }, /* ċ to c */
+ { 0x010C, 0x43, 0x00, 0x00, 0x00 }, /* Č to C */
+ { 0x010D, 0x63, 0x00, 0x00, 0x00 }, /* č to c */
+ { 0x010E, 0x44, 0x00, 0x00, 0x00 }, /* Ď to D */
+ { 0x010F, 0x64, 0x00, 0x00, 0x00 }, /* ď to d */
+ { 0x0110, 0x44, 0x00, 0x00, 0x00 }, /* Đ to D */
+ { 0x0111, 0x64, 0x00, 0x00, 0x00 }, /* đ to d */
+ { 0x0112, 0x45, 0x00, 0x00, 0x00 }, /* Ē to E */
+ { 0x0113, 0x65, 0x00, 0x00, 0x00 }, /* ē to e */
+ { 0x0114, 0x45, 0x00, 0x00, 0x00 }, /* Ĕ to E */
+ { 0x0115, 0x65, 0x00, 0x00, 0x00 }, /* ĕ to e */
+ { 0x0116, 0x45, 0x00, 0x00, 0x00 }, /* Ė to E */
+ { 0x0117, 0x65, 0x00, 0x00, 0x00 }, /* ė to e */
+ { 0x0118, 0x45, 0x00, 0x00, 0x00 }, /* Ę to E */
+ { 0x0119, 0x65, 0x00, 0x00, 0x00 }, /* ę to e */
+ { 0x011A, 0x45, 0x00, 0x00, 0x00 }, /* Ě to E */
+ { 0x011B, 0x65, 0x00, 0x00, 0x00 }, /* ě to e */
+ { 0x011C, 0x47, 0x68, 0x00, 0x00 }, /* Ĝ to Gh */
+ { 0x011D, 0x67, 0x68, 0x00, 0x00 }, /* ĝ to gh */
+ { 0x011E, 0x47, 0x00, 0x00, 0x00 }, /* Ğ to G */
+ { 0x011F, 0x67, 0x00, 0x00, 0x00 }, /* ğ to g */
+ { 0x0120, 0x47, 0x00, 0x00, 0x00 }, /* Ġ to G */
+ { 0x0121, 0x67, 0x00, 0x00, 0x00 }, /* ġ to g */
+ { 0x0122, 0x47, 0x00, 0x00, 0x00 }, /* Ģ to G */
+ { 0x0123, 0x67, 0x00, 0x00, 0x00 }, /* ģ to g */
+ { 0x0124, 0x48, 0x68, 0x00, 0x00 }, /* Ĥ to Hh */
+ { 0x0125, 0x68, 0x68, 0x00, 0x00 }, /* ĥ to hh */
+ { 0x0126, 0x48, 0x00, 0x00, 0x00 }, /* Ħ to H */
+ { 0x0127, 0x68, 0x00, 0x00, 0x00 }, /* ħ to h */
+ { 0x0128, 0x49, 0x00, 0x00, 0x00 }, /* Ĩ to I */
+ { 0x0129, 0x69, 0x00, 0x00, 0x00 }, /* ĩ to i */
+ { 0x012A, 0x49, 0x00, 0x00, 0x00 }, /* Ī to I */
+ { 0x012B, 0x69, 0x00, 0x00, 0x00 }, /* ī to i */
+ { 0x012C, 0x49, 0x00, 0x00, 0x00 }, /* Ĭ to I */
+ { 0x012D, 0x69, 0x00, 0x00, 0x00 }, /* ĭ to i */
+ { 0x012E, 0x49, 0x00, 0x00, 0x00 }, /* Į to I */
+ { 0x012F, 0x69, 0x00, 0x00, 0x00 }, /* į to i */
+ { 0x0130, 0x49, 0x00, 0x00, 0x00 }, /* İ to I */
+ { 0x0131, 0x69, 0x00, 0x00, 0x00 }, /* ı to i */
+ { 0x0132, 0x49, 0x4A, 0x00, 0x00 }, /* IJ to IJ */
+ { 0x0133, 0x69, 0x6A, 0x00, 0x00 }, /* ij to ij */
+ { 0x0134, 0x4A, 0x68, 0x00, 0x00 }, /* Ĵ to Jh */
+ { 0x0135, 0x6A, 0x68, 0x00, 0x00 }, /* ĵ to jh */
+ { 0x0136, 0x4B, 0x00, 0x00, 0x00 }, /* Ķ to K */
+ { 0x0137, 0x6B, 0x00, 0x00, 0x00 }, /* ķ to k */
+ { 0x0138, 0x6B, 0x00, 0x00, 0x00 }, /* ĸ to k */
+ { 0x0139, 0x4C, 0x00, 0x00, 0x00 }, /* Ĺ to L */
+ { 0x013A, 0x6C, 0x00, 0x00, 0x00 }, /* ĺ to l */
+ { 0x013B, 0x4C, 0x00, 0x00, 0x00 }, /* Ļ to L */
+ { 0x013C, 0x6C, 0x00, 0x00, 0x00 }, /* ļ to l */
+ { 0x013D, 0x4C, 0x00, 0x00, 0x00 }, /* Ľ to L */
+ { 0x013E, 0x6C, 0x00, 0x00, 0x00 }, /* ľ to l */
+ { 0x013F, 0x4C, 0x2E, 0x00, 0x00 }, /* Ŀ to L. */
+ { 0x0140, 0x6C, 0x2E, 0x00, 0x00 }, /* ŀ to l. */
+ { 0x0141, 0x4C, 0x00, 0x00, 0x00 }, /* Ł to L */
+ { 0x0142, 0x6C, 0x00, 0x00, 0x00 }, /* ł to l */
+ { 0x0143, 0x4E, 0x00, 0x00, 0x00 }, /* Ń to N */
+ { 0x0144, 0x6E, 0x00, 0x00, 0x00 }, /* ń to n */
+ { 0x0145, 0x4E, 0x00, 0x00, 0x00 }, /* Ņ to N */
+ { 0x0146, 0x6E, 0x00, 0x00, 0x00 }, /* ņ to n */
+ { 0x0147, 0x4E, 0x00, 0x00, 0x00 }, /* Ň to N */
+ { 0x0148, 0x6E, 0x00, 0x00, 0x00 }, /* ň to n */
+ { 0x0149, 0x27, 0x6E, 0x00, 0x00 }, /* ʼn to 'n */
+ { 0x014A, 0x4E, 0x47, 0x00, 0x00 }, /* Ŋ to NG */
+ { 0x014B, 0x6E, 0x67, 0x00, 0x00 }, /* ŋ to ng */
+ { 0x014C, 0x4F, 0x00, 0x00, 0x00 }, /* Ō to O */
+ { 0x014D, 0x6F, 0x00, 0x00, 0x00 }, /* ō to o */
+ { 0x014E, 0x4F, 0x00, 0x00, 0x00 }, /* Ŏ to O */
+ { 0x014F, 0x6F, 0x00, 0x00, 0x00 }, /* ŏ to o */
+ { 0x0150, 0x4F, 0x00, 0x00, 0x00 }, /* Ő to O */
+ { 0x0151, 0x6F, 0x00, 0x00, 0x00 }, /* ő to o */
+ { 0x0152, 0x4F, 0x45, 0x00, 0x00 }, /* Πto OE */
+ { 0x0153, 0x6F, 0x65, 0x00, 0x00 }, /* œ to oe */
+ { 0x0154, 0x52, 0x00, 0x00, 0x00 }, /* Ŕ to R */
+ { 0x0155, 0x72, 0x00, 0x00, 0x00 }, /* ŕ to r */
+ { 0x0156, 0x52, 0x00, 0x00, 0x00 }, /* Ŗ to R */
+ { 0x0157, 0x72, 0x00, 0x00, 0x00 }, /* ŗ to r */
+ { 0x0158, 0x52, 0x00, 0x00, 0x00 }, /* Ř to R */
+ { 0x0159, 0x72, 0x00, 0x00, 0x00 }, /* ř to r */
+ { 0x015A, 0x53, 0x00, 0x00, 0x00 }, /* Ś to S */
+ { 0x015B, 0x73, 0x00, 0x00, 0x00 }, /* ś to s */
+ { 0x015C, 0x53, 0x68, 0x00, 0x00 }, /* Ŝ to Sh */
+ { 0x015D, 0x73, 0x68, 0x00, 0x00 }, /* ŝ to sh */
+ { 0x015E, 0x53, 0x00, 0x00, 0x00 }, /* Ş to S */
+ { 0x015F, 0x73, 0x00, 0x00, 0x00 }, /* ş to s */
+ { 0x0160, 0x53, 0x00, 0x00, 0x00 }, /* Š to S */
+ { 0x0161, 0x73, 0x00, 0x00, 0x00 }, /* š to s */
+ { 0x0162, 0x54, 0x00, 0x00, 0x00 }, /* Ţ to T */
+ { 0x0163, 0x74, 0x00, 0x00, 0x00 }, /* ţ to t */
+ { 0x0164, 0x54, 0x00, 0x00, 0x00 }, /* Ť to T */
+ { 0x0165, 0x74, 0x00, 0x00, 0x00 }, /* ť to t */
+ { 0x0166, 0x54, 0x00, 0x00, 0x00 }, /* Ŧ to T */
+ { 0x0167, 0x74, 0x00, 0x00, 0x00 }, /* ŧ to t */
+ { 0x0168, 0x55, 0x00, 0x00, 0x00 }, /* Ũ to U */
+ { 0x0169, 0x75, 0x00, 0x00, 0x00 }, /* ũ to u */
+ { 0x016A, 0x55, 0x00, 0x00, 0x00 }, /* Ū to U */
+ { 0x016B, 0x75, 0x00, 0x00, 0x00 }, /* ū to u */
+ { 0x016C, 0x55, 0x00, 0x00, 0x00 }, /* Ŭ to U */
+ { 0x016D, 0x75, 0x00, 0x00, 0x00 }, /* ŭ to u */
+ { 0x016E, 0x55, 0x00, 0x00, 0x00 }, /* Ů to U */
+ { 0x016F, 0x75, 0x00, 0x00, 0x00 }, /* ů to u */
+ { 0x0170, 0x55, 0x00, 0x00, 0x00 }, /* Ű to U */
+ { 0x0171, 0x75, 0x00, 0x00, 0x00 }, /* ű to u */
+ { 0x0172, 0x55, 0x00, 0x00, 0x00 }, /* Ų to U */
+ { 0x0173, 0x75, 0x00, 0x00, 0x00 }, /* ų to u */
+ { 0x0174, 0x57, 0x00, 0x00, 0x00 }, /* Ŵ to W */
+ { 0x0175, 0x77, 0x00, 0x00, 0x00 }, /* ŵ to w */
+ { 0x0176, 0x59, 0x00, 0x00, 0x00 }, /* Ŷ to Y */
+ { 0x0177, 0x79, 0x00, 0x00, 0x00 }, /* ŷ to y */
+ { 0x0178, 0x59, 0x00, 0x00, 0x00 }, /* Ÿ to Y */
+ { 0x0179, 0x5A, 0x00, 0x00, 0x00 }, /* Ź to Z */
+ { 0x017A, 0x7A, 0x00, 0x00, 0x00 }, /* ź to z */
+ { 0x017B, 0x5A, 0x00, 0x00, 0x00 }, /* Ż to Z */
+ { 0x017C, 0x7A, 0x00, 0x00, 0x00 }, /* ż to z */
+ { 0x017D, 0x5A, 0x00, 0x00, 0x00 }, /* Ž to Z */
+ { 0x017E, 0x7A, 0x00, 0x00, 0x00 }, /* ž to z */
+ { 0x017F, 0x73, 0x00, 0x00, 0x00 }, /* ſ to s */
+ { 0x0192, 0x66, 0x00, 0x00, 0x00 }, /* ƒ to f */
+ { 0x0218, 0x53, 0x00, 0x00, 0x00 }, /* Ș to S */
+ { 0x0219, 0x73, 0x00, 0x00, 0x00 }, /* ș to s */
+ { 0x021A, 0x54, 0x00, 0x00, 0x00 }, /* Ț to T */
+ { 0x021B, 0x74, 0x00, 0x00, 0x00 }, /* ț to t */
+ { 0x0386, 0x41, 0x00, 0x00, 0x00 }, /* Ά to A */
+ { 0x0388, 0x45, 0x00, 0x00, 0x00 }, /* Έ to E */
+ { 0x0389, 0x49, 0x00, 0x00, 0x00 }, /* Ή to I */
+ { 0x038A, 0x49, 0x00, 0x00, 0x00 }, /* Ί to I */
+ { 0x038C, 0x4f, 0x00, 0x00, 0x00 }, /* Ό to O */
+ { 0x038E, 0x59, 0x00, 0x00, 0x00 }, /* Ύ to Y */
+ { 0x038F, 0x4f, 0x00, 0x00, 0x00 }, /* Ώ to O */
+ { 0x0390, 0x69, 0x00, 0x00, 0x00 }, /* ΐ to i */
+ { 0x0391, 0x41, 0x00, 0x00, 0x00 }, /* Α to A */
+ { 0x0392, 0x42, 0x00, 0x00, 0x00 }, /* Β to B */
+ { 0x0393, 0x47, 0x00, 0x00, 0x00 }, /* Γ to G */
+ { 0x0394, 0x44, 0x00, 0x00, 0x00 }, /* Δ to D */
+ { 0x0395, 0x45, 0x00, 0x00, 0x00 }, /* Ε to E */
+ { 0x0396, 0x5a, 0x00, 0x00, 0x00 }, /* Ζ to Z */
+ { 0x0397, 0x49, 0x00, 0x00, 0x00 }, /* Η to I */
+ { 0x0398, 0x54, 0x68, 0x00, 0x00 }, /* Θ to Th */
+ { 0x0399, 0x49, 0x00, 0x00, 0x00 }, /* Ι to I */
+ { 0x039A, 0x4b, 0x00, 0x00, 0x00 }, /* Κ to K */
+ { 0x039B, 0x4c, 0x00, 0x00, 0x00 }, /* Λ to L */
+ { 0x039C, 0x4d, 0x00, 0x00, 0x00 }, /* Μ to M */
+ { 0x039D, 0x4e, 0x00, 0x00, 0x00 }, /* Ν to N */
+ { 0x039E, 0x58, 0x00, 0x00, 0x00 }, /* Ξ to X */
+ { 0x039F, 0x4f, 0x00, 0x00, 0x00 }, /* Ο to O */
+ { 0x03A0, 0x50, 0x00, 0x00, 0x00 }, /* Π to P */
+ { 0x03A1, 0x52, 0x00, 0x00, 0x00 }, /* Ρ to R */
+ { 0x03A3, 0x53, 0x00, 0x00, 0x00 }, /* Σ to S */
+ { 0x03A4, 0x54, 0x00, 0x00, 0x00 }, /* Τ to T */
+ { 0x03A5, 0x59, 0x00, 0x00, 0x00 }, /* Υ to Y */
+ { 0x03A6, 0x46, 0x00, 0x00, 0x00 }, /* Φ to F */
+ { 0x03A7, 0x43, 0x68, 0x00, 0x00 }, /* Χ to Ch */
+ { 0x03A8, 0x50, 0x73, 0x00, 0x00 }, /* Ψ to Ps */
+ { 0x03A9, 0x4f, 0x00, 0x00, 0x00 }, /* Ω to O */
+ { 0x03AA, 0x49, 0x00, 0x00, 0x00 }, /* Ϊ to I */
+ { 0x03AB, 0x59, 0x00, 0x00, 0x00 }, /* Ϋ to Y */
+ { 0x03AC, 0x61, 0x00, 0x00, 0x00 }, /* ά to a */
+ { 0x03AD, 0x65, 0x00, 0x00, 0x00 }, /* έ to e */
+ { 0x03AE, 0x69, 0x00, 0x00, 0x00 }, /* ή to i */
+ { 0x03AF, 0x69, 0x00, 0x00, 0x00 }, /* ί to i */
+ { 0x03B1, 0x61, 0x00, 0x00, 0x00 }, /* α to a */
+ { 0x03B2, 0x62, 0x00, 0x00, 0x00 }, /* β to b */
+ { 0x03B3, 0x67, 0x00, 0x00, 0x00 }, /* γ to g */
+ { 0x03B4, 0x64, 0x00, 0x00, 0x00 }, /* δ to d */
+ { 0x03B5, 0x65, 0x00, 0x00, 0x00 }, /* ε to e */
+ { 0x03B6, 0x7a, 0x00, 0x00, 0x00 }, /* ζ to z */
+ { 0x03B7, 0x69, 0x00, 0x00, 0x00 }, /* η to i */
+ { 0x03B8, 0x74, 0x68, 0x00, 0x00 }, /* θ to th */
+ { 0x03B9, 0x69, 0x00, 0x00, 0x00 }, /* ι to i */
+ { 0x03BA, 0x6b, 0x00, 0x00, 0x00 }, /* κ to k */
+ { 0x03BB, 0x6c, 0x00, 0x00, 0x00 }, /* λ to l */
+ { 0x03BC, 0x6d, 0x00, 0x00, 0x00 }, /* μ to m */
+ { 0x03BD, 0x6e, 0x00, 0x00, 0x00 }, /* ν to n */
+ { 0x03BE, 0x78, 0x00, 0x00, 0x00 }, /* ξ to x */
+ { 0x03BF, 0x6f, 0x00, 0x00, 0x00 }, /* ο to o */
+ { 0x03C0, 0x70, 0x00, 0x00, 0x00 }, /* π to p */
+ { 0x03C1, 0x72, 0x00, 0x00, 0x00 }, /* ρ to r */
+ { 0x03C3, 0x73, 0x00, 0x00, 0x00 }, /* σ to s */
+ { 0x03C4, 0x74, 0x00, 0x00, 0x00 }, /* τ to t */
+ { 0x03C5, 0x79, 0x00, 0x00, 0x00 }, /* υ to y */
+ { 0x03C6, 0x66, 0x00, 0x00, 0x00 }, /* φ to f */
+ { 0x03C7, 0x63, 0x68, 0x00, 0x00 }, /* χ to ch */
+ { 0x03C8, 0x70, 0x73, 0x00, 0x00 }, /* ψ to ps */
+ { 0x03C9, 0x6f, 0x00, 0x00, 0x00 }, /* ω to o */
+ { 0x03CA, 0x69, 0x00, 0x00, 0x00 }, /* ϊ to i */
+ { 0x03CB, 0x79, 0x00, 0x00, 0x00 }, /* ϋ to y */
+ { 0x03CC, 0x6f, 0x00, 0x00, 0x00 }, /* ό to o */
+ { 0x03CD, 0x79, 0x00, 0x00, 0x00 }, /* ύ to y */
+ { 0x03CE, 0x69, 0x00, 0x00, 0x00 }, /* ώ to i */
+ { 0x0400, 0x45, 0x00, 0x00, 0x00 }, /* Ѐ to E */
+ { 0x0401, 0x45, 0x00, 0x00, 0x00 }, /* Ё to E */
+ { 0x0402, 0x44, 0x00, 0x00, 0x00 }, /* Ђ to D */
+ { 0x0403, 0x47, 0x00, 0x00, 0x00 }, /* Ѓ to G */
+ { 0x0404, 0x45, 0x00, 0x00, 0x00 }, /* Є to E */
+ { 0x0405, 0x5a, 0x00, 0x00, 0x00 }, /* Ѕ to Z */
+ { 0x0406, 0x49, 0x00, 0x00, 0x00 }, /* І to I */
+ { 0x0407, 0x49, 0x00, 0x00, 0x00 }, /* Ї to I */
+ { 0x0408, 0x4a, 0x00, 0x00, 0x00 }, /* Ј to J */
+ { 0x0409, 0x49, 0x00, 0x00, 0x00 }, /* Љ to I */
+ { 0x040A, 0x4e, 0x00, 0x00, 0x00 }, /* Њ to N */
+ { 0x040B, 0x44, 0x00, 0x00, 0x00 }, /* Ћ to D */
+ { 0x040C, 0x4b, 0x00, 0x00, 0x00 }, /* Ќ to K */
+ { 0x040D, 0x49, 0x00, 0x00, 0x00 }, /* Ѝ to I */
+ { 0x040E, 0x55, 0x00, 0x00, 0x00 }, /* Ў to U */
+ { 0x040F, 0x44, 0x00, 0x00, 0x00 }, /* Џ to D */
+ { 0x0410, 0x41, 0x00, 0x00, 0x00 }, /* А to A */
+ { 0x0411, 0x42, 0x00, 0x00, 0x00 }, /* Б to B */
+ { 0x0412, 0x56, 0x00, 0x00, 0x00 }, /* В to V */
+ { 0x0413, 0x47, 0x00, 0x00, 0x00 }, /* Г to G */
+ { 0x0414, 0x44, 0x00, 0x00, 0x00 }, /* Д to D */
+ { 0x0415, 0x45, 0x00, 0x00, 0x00 }, /* Е to E */
+ { 0x0416, 0x5a, 0x68, 0x00, 0x00 }, /* Ж to Zh */
+ { 0x0417, 0x5a, 0x00, 0x00, 0x00 }, /* З to Z */
+ { 0x0418, 0x49, 0x00, 0x00, 0x00 }, /* И to I */
+ { 0x0419, 0x49, 0x00, 0x00, 0x00 }, /* Й to I */
+ { 0x041A, 0x4b, 0x00, 0x00, 0x00 }, /* К to K */
+ { 0x041B, 0x4c, 0x00, 0x00, 0x00 }, /* Л to L */
+ { 0x041C, 0x4d, 0x00, 0x00, 0x00 }, /* М to M */
+ { 0x041D, 0x4e, 0x00, 0x00, 0x00 }, /* Н to N */
+ { 0x041E, 0x4f, 0x00, 0x00, 0x00 }, /* О to O */
+ { 0x041F, 0x50, 0x00, 0x00, 0x00 }, /* П to P */
+ { 0x0420, 0x52, 0x00, 0x00, 0x00 }, /* Р to R */
+ { 0x0421, 0x53, 0x00, 0x00, 0x00 }, /* С to S */
+ { 0x0422, 0x54, 0x00, 0x00, 0x00 }, /* Т to T */
+ { 0x0423, 0x55, 0x00, 0x00, 0x00 }, /* У to U */
+ { 0x0424, 0x46, 0x00, 0x00, 0x00 }, /* Ф to F */
+ { 0x0425, 0x4b, 0x68, 0x00, 0x00 }, /* Х to Kh */
+ { 0x0426, 0x54, 0x63, 0x00, 0x00 }, /* Ц to Tc */
+ { 0x0427, 0x43, 0x68, 0x00, 0x00 }, /* Ч to Ch */
+ { 0x0428, 0x53, 0x68, 0x00, 0x00 }, /* Ш to Sh */
+ { 0x0429, 0x53, 0x68, 0x63, 0x68 }, /* Щ to Shch */
+ { 0x042A, 0x61, 0x00, 0x00, 0x00 }, /* to A */
+ { 0x042B, 0x59, 0x00, 0x00, 0x00 }, /* Ы to Y */
+ { 0x042C, 0x59, 0x00, 0x00, 0x00 }, /* to Y */
+ { 0x042D, 0x45, 0x00, 0x00, 0x00 }, /* Э to E */
+ { 0x042E, 0x49, 0x75, 0x00, 0x00 }, /* Ю to Iu */
+ { 0x042F, 0x49, 0x61, 0x00, 0x00 }, /* Я to Ia */
+ { 0x0430, 0x61, 0x00, 0x00, 0x00 }, /* а to a */
+ { 0x0431, 0x62, 0x00, 0x00, 0x00 }, /* б to b */
+ { 0x0432, 0x76, 0x00, 0x00, 0x00 }, /* в to v */
+ { 0x0433, 0x67, 0x00, 0x00, 0x00 }, /* г to g */
+ { 0x0434, 0x64, 0x00, 0x00, 0x00 }, /* д to d */
+ { 0x0435, 0x65, 0x00, 0x00, 0x00 }, /* е to e */
+ { 0x0436, 0x7a, 0x68, 0x00, 0x00 }, /* ж to zh */
+ { 0x0437, 0x7a, 0x00, 0x00, 0x00 }, /* з to z */
+ { 0x0438, 0x69, 0x00, 0x00, 0x00 }, /* и to i */
+ { 0x0439, 0x69, 0x00, 0x00, 0x00 }, /* й to i */
+ { 0x043A, 0x6b, 0x00, 0x00, 0x00 }, /* к to k */
+ { 0x043B, 0x6c, 0x00, 0x00, 0x00 }, /* л to l */
+ { 0x043C, 0x6d, 0x00, 0x00, 0x00 }, /* м to m */
+ { 0x043D, 0x6e, 0x00, 0x00, 0x00 }, /* н to n */
+ { 0x043E, 0x6f, 0x00, 0x00, 0x00 }, /* о to o */
+ { 0x043F, 0x70, 0x00, 0x00, 0x00 }, /* п to p */
+ { 0x0440, 0x72, 0x00, 0x00, 0x00 }, /* р to r */
+ { 0x0441, 0x73, 0x00, 0x00, 0x00 }, /* с to s */
+ { 0x0442, 0x74, 0x00, 0x00, 0x00 }, /* т to t */
+ { 0x0443, 0x75, 0x00, 0x00, 0x00 }, /* у to u */
+ { 0x0444, 0x66, 0x00, 0x00, 0x00 }, /* ф to f */
+ { 0x0445, 0x6b, 0x68, 0x00, 0x00 }, /* х to kh */
+ { 0x0446, 0x74, 0x63, 0x00, 0x00 }, /* ц to tc */
+ { 0x0447, 0x63, 0x68, 0x00, 0x00 }, /* ч to ch */
+ { 0x0448, 0x73, 0x68, 0x00, 0x00 }, /* ш to sh */
+ { 0x0449, 0x73, 0x68, 0x63, 0x68 }, /* щ to shch */
+ { 0x044A, 0x61, 0x00, 0x00, 0x00 }, /* to a */
+ { 0x044B, 0x79, 0x00, 0x00, 0x00 }, /* ы to y */
+ { 0x044C, 0x79, 0x00, 0x00, 0x00 }, /* to y */
+ { 0x044D, 0x65, 0x00, 0x00, 0x00 }, /* э to e */
+ { 0x044E, 0x69, 0x75, 0x00, 0x00 }, /* ю to iu */
+ { 0x044F, 0x69, 0x61, 0x00, 0x00 }, /* я to ia */
+ { 0x0450, 0x65, 0x00, 0x00, 0x00 }, /* ѐ to e */
+ { 0x0451, 0x65, 0x00, 0x00, 0x00 }, /* ё to e */
+ { 0x0452, 0x64, 0x00, 0x00, 0x00 }, /* ђ to d */
+ { 0x0453, 0x67, 0x00, 0x00, 0x00 }, /* ѓ to g */
+ { 0x0454, 0x65, 0x00, 0x00, 0x00 }, /* є to e */
+ { 0x0455, 0x7a, 0x00, 0x00, 0x00 }, /* ѕ to z */
+ { 0x0456, 0x69, 0x00, 0x00, 0x00 }, /* і to i */
+ { 0x0457, 0x69, 0x00, 0x00, 0x00 }, /* ї to i */
+ { 0x0458, 0x6a, 0x00, 0x00, 0x00 }, /* ј to j */
+ { 0x0459, 0x69, 0x00, 0x00, 0x00 }, /* љ to i */
+ { 0x045A, 0x6e, 0x00, 0x00, 0x00 }, /* њ to n */
+ { 0x045B, 0x64, 0x00, 0x00, 0x00 }, /* ћ to d */
+ { 0x045C, 0x6b, 0x00, 0x00, 0x00 }, /* ќ to k */
+ { 0x045D, 0x69, 0x00, 0x00, 0x00 }, /* ѝ to i */
+ { 0x045E, 0x75, 0x00, 0x00, 0x00 }, /* ў to u */
+ { 0x045F, 0x64, 0x00, 0x00, 0x00 }, /* џ to d */
+ { 0x1E02, 0x42, 0x00, 0x00, 0x00 }, /* Ḃ to B */
+ { 0x1E03, 0x62, 0x00, 0x00, 0x00 }, /* ḃ to b */
+ { 0x1E0A, 0x44, 0x00, 0x00, 0x00 }, /* Ḋ to D */
+ { 0x1E0B, 0x64, 0x00, 0x00, 0x00 }, /* ḋ to d */
+ { 0x1E1E, 0x46, 0x00, 0x00, 0x00 }, /* Ḟ to F */
+ { 0x1E1F, 0x66, 0x00, 0x00, 0x00 }, /* ḟ to f */
+ { 0x1E40, 0x4D, 0x00, 0x00, 0x00 }, /* Ṁ to M */
+ { 0x1E41, 0x6D, 0x00, 0x00, 0x00 }, /* ṁ to m */
+ { 0x1E56, 0x50, 0x00, 0x00, 0x00 }, /* Ṗ to P */
+ { 0x1E57, 0x70, 0x00, 0x00, 0x00 }, /* ṗ to p */
+ { 0x1E60, 0x53, 0x00, 0x00, 0x00 }, /* Ṡ to S */
+ { 0x1E61, 0x73, 0x00, 0x00, 0x00 }, /* ṡ to s */
+ { 0x1E6A, 0x54, 0x00, 0x00, 0x00 }, /* Ṫ to T */
+ { 0x1E6B, 0x74, 0x00, 0x00, 0x00 }, /* ṫ to t */
+ { 0x1E80, 0x57, 0x00, 0x00, 0x00 }, /* Ẁ to W */
+ { 0x1E81, 0x77, 0x00, 0x00, 0x00 }, /* ẁ to w */
+ { 0x1E82, 0x57, 0x00, 0x00, 0x00 }, /* Ẃ to W */
+ { 0x1E83, 0x77, 0x00, 0x00, 0x00 }, /* ẃ to w */
+ { 0x1E84, 0x57, 0x00, 0x00, 0x00 }, /* Ẅ to W */
+ { 0x1E85, 0x77, 0x00, 0x00, 0x00 }, /* ẅ to w */
+ { 0x1EF2, 0x59, 0x00, 0x00, 0x00 }, /* Ỳ to Y */
+ { 0x1EF3, 0x79, 0x00, 0x00, 0x00 }, /* ỳ to y */
+ { 0xFB00, 0x66, 0x66, 0x00, 0x00 }, /* ff to ff */
+ { 0xFB01, 0x66, 0x69, 0x00, 0x00 }, /* fi to fi */
+ { 0xFB02, 0x66, 0x6C, 0x00, 0x00 }, /* fl to fl */
+ { 0xFB05, 0x73, 0x74, 0x00, 0x00 }, /* ſt to st */
+ { 0xFB06, 0x73, 0x74, 0x00, 0x00 }, /* st to st */
};
+static const Transliteration *spellfixFindTranslit(int c, int *pxTop){
+ *pxTop = (sizeof(translit)/sizeof(translit[0])) - 1;
+ return translit;
+}
+
/*
** Convert the input string from UTF-8 into pure ASCII by converting
** all non-ASCII characters to some combination of characters in the
@@ -1621,23 +1720,24 @@ static unsigned char *transliterate(const unsigned char *zIn, int nIn){
zOut[nOut++] = (unsigned char)c;
}else{
int xTop, xBtm, x;
- xTop = sizeof(translit)/sizeof(translit[0]) - 1;
+ const Transliteration *tbl = spellfixFindTranslit(c, &xTop);
xBtm = 0;
while( xTop>=xBtm ){
x = (xTop + xBtm)/2;
- if( translit[x].cFrom==c ){
- zOut[nOut++] = translit[x].cTo0;
- if( translit[x].cTo1 ){
- zOut[nOut++] = translit[x].cTo1;
- /* Add an extra "ch" after the "sh" for Щ and щ */
- if( c==0x0429 || c== 0x0449 ){
- zOut[nOut++] = 'c';
- zOut[nOut++] = 'h';
+ if( tbl[x].cFrom==c ){
+ zOut[nOut++] = tbl[x].cTo0;
+ if( tbl[x].cTo1 ){
+ zOut[nOut++] = tbl[x].cTo1;
+ if( tbl[x].cTo2 ){
+ zOut[nOut++] = tbl[x].cTo2;
+ if( tbl[x].cTo3 ){
+ zOut[nOut++] = tbl[x].cTo3;
+ }
}
}
c = 0;
break;
- }else if( translit[x].cFrom>c ){
+ }else if( tbl[x].cFrom>c ){
xTop = x-1;
}else{
xBtm = x+1;
@@ -1668,15 +1768,22 @@ static int translen_to_charlen(const char *zIn, int nIn, int nTrans){
nOut++;
if( c>=128 ){
int xTop, xBtm, x;
- xTop = sizeof(translit)/sizeof(translit[0]) - 1;
+ const Transliteration *tbl = spellfixFindTranslit(c, &xTop);
xBtm = 0;
while( xTop>=xBtm ){
x = (xTop + xBtm)/2;
- if( translit[x].cFrom==c ){
- if( translit[x].cTo1 ) nOut++;
- if( c==0x0429 || c== 0x0449 ) nOut += 2;
+ if( tbl[x].cFrom==c ){
+ if( tbl[x].cTo1 ){
+ nOut++;
+ if( tbl[x].cTo2 ){
+ nOut++;
+ if( tbl[x].cTo3 ){
+ nOut++;
+ }
+ }
+ }
break;
- }else if( translit[x].cFrom>c ){
+ }else if( tbl[x].cFrom>c ){
xTop = x-1;
}else{
xBtm = x+1;
@@ -2476,7 +2583,7 @@ static int spellfix1FilterForMatch(
nPattern = (int)strlen(zPattern);
if( zPattern[nPattern-1]=='*' ) nPattern--;
zSql = sqlite3_mprintf(
- "SELECT id, word, rank, k1"
+ "SELECT id, word, rank, coalesce(k1,word)"
" FROM \"%w\".\"%w_vocab\""
" WHERE langid=%d AND k2>=?1 AND k2<?2",
p->zDbName, p->zTableName, iLang
@@ -2810,17 +2917,17 @@ static int spellfix1Update(
if( sqlite3_value_type(argv[1])==SQLITE_NULL ){
spellfix1DbExec(&rc, db,
"INSERT INTO \"%w\".\"%w_vocab\"(rank,langid,word,k1,k2) "
- "VALUES(%d,%d,%Q,%Q,%Q)",
+ "VALUES(%d,%d,%Q,nullif(%Q,%Q),%Q)",
p->zDbName, p->zTableName,
- iRank, iLang, zWord, zK1, zK2
+ iRank, iLang, zWord, zK1, zWord, zK2
);
}else{
newRowid = sqlite3_value_int64(argv[1]);
spellfix1DbExec(&rc, db,
"INSERT OR %s INTO \"%w\".\"%w_vocab\"(id,rank,langid,word,k1,k2) "
- "VALUES(%lld,%d,%d,%Q,%Q,%Q)",
+ "VALUES(%lld,%d,%d,%Q,nullif(%Q,%Q),%Q)",
zConflict, p->zDbName, p->zTableName,
- newRowid, iRank, iLang, zWord, zK1, zK2
+ newRowid, iRank, iLang, zWord, zK1, zWord, zK2
);
}
*pRowid = sqlite3_last_insert_rowid(db);
@@ -2829,9 +2936,9 @@ static int spellfix1Update(
newRowid = *pRowid = sqlite3_value_int64(argv[1]);
spellfix1DbExec(&rc, db,
"UPDATE OR %s \"%w\".\"%w_vocab\" SET id=%lld, rank=%d, langid=%d,"
- " word=%Q, k1=%Q, k2=%Q WHERE id=%lld",
+ " word=%Q, k1=nullif(%Q,%Q), k2=%Q WHERE id=%lld",
zConflict, p->zDbName, p->zTableName, newRowid, iRank, iLang,
- zWord, zK1, zK2, rowid
+ zWord, zK1, zWord, zK2, rowid
);
}
sqlite3_free(zK1);
diff --git a/ext/misc/zipfile.c b/ext/misc/zipfile.c
index fa31d62..9f2258e 100644
--- a/ext/misc/zipfile.c
+++ b/ext/misc/zipfile.c
@@ -30,29 +30,48 @@ SQLITE_EXTENSION_INIT1
#include <string.h>
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#if !defined(_WIN32) && !defined(WIN32)
-# include <unistd.h>
-# include <dirent.h>
-# include <utime.h>
-#else
-# include <io.h>
-#endif
-#include <time.h>
-#include <errno.h>
-
#include <zlib.h>
#ifndef SQLITE_OMIT_VIRTUALTABLE
#ifndef SQLITE_AMALGAMATION
+
typedef sqlite3_int64 i64;
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;
#define MIN(a,b) ((a)<(b) ? (a) : (b))
+
+#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST)
+# define ALWAYS(X) (1)
+# define NEVER(X) (0)
+#elif !defined(NDEBUG)
+# define ALWAYS(X) ((X)?1:(assert(0),0))
+# define NEVER(X) ((X)?(assert(0),1):0)
+#else
+# define ALWAYS(X) (X)
+# define NEVER(X) (X)
+#endif
+
+#endif /* SQLITE_AMALGAMATION */
+
+/*
+** Definitions for mode bitmasks S_IFDIR, S_IFREG and S_IFLNK.
+**
+** In some ways it would be better to obtain these values from system
+** header files. But, the dependency is undesirable and (a) these
+** have been stable for decades, (b) the values are part of POSIX and
+** are also made explicit in [man stat], and (c) are part of the
+** file format for zip archives.
+*/
+#ifndef S_IFDIR
+# define S_IFDIR 0040000
+#endif
+#ifndef S_IFREG
+# define S_IFREG 0100000
+#endif
+#ifndef S_IFLNK
+# define S_IFLNK 0120000
#endif
static const char ZIPFILE_SCHEMA[] =
@@ -93,6 +112,9 @@ static const char ZIPFILE_SCHEMA[] =
**
** ZIPFILE_SIGNATURE_LFH:
** First 4 bytes of a valid LFH record.
+**
+** ZIPFILE_SIGNATURE_EOCD
+** First 4 bytes of a valid EOCD record.
*/
#define ZIPFILE_EXTRA_TIMESTAMP 0x5455
#define ZIPFILE_NEWENTRY_MADEBY ((3<<8) + 30)
@@ -101,22 +123,14 @@ static const char ZIPFILE_SCHEMA[] =
#define ZIPFILE_SIGNATURE_CDS 0x02014b50
#define ZIPFILE_SIGNATURE_LFH 0x04034b50
#define ZIPFILE_SIGNATURE_EOCD 0x06054b50
-#define ZIPFILE_LFH_FIXED_SZ 30
/*
-** Set the error message contained in context ctx to the results of
-** vprintf(zFmt, ...).
+** The sizes of the fixed-size part of each of the three main data
+** structures in a zip archive.
*/
-static void zipfileCtxErrorMsg(sqlite3_context *ctx, const char *zFmt, ...){
- char *zMsg = 0;
- va_list ap;
- va_start(ap, zFmt);
- zMsg = sqlite3_vmprintf(zFmt, ap);
- sqlite3_result_error(ctx, zMsg, -1);
- sqlite3_free(zMsg);
- va_end(ap);
-}
-
+#define ZIPFILE_LFH_FIXED_SZ 30
+#define ZIPFILE_EOCD_FIXED_SZ 22
+#define ZIPFILE_CDS_FIXED_SZ 46
/*
*** 4.3.16 End of central directory record:
@@ -222,47 +236,39 @@ struct ZipfileLFH {
typedef struct ZipfileEntry ZipfileEntry;
struct ZipfileEntry {
- char *zPath; /* Path of zipfile entry */
- u8 *aCdsEntry; /* Buffer containing entire CDS entry */
- int nCdsEntry; /* Size of buffer aCdsEntry[] in bytes */
- int bDeleted; /* True if entry has been deleted */
+ ZipfileCDS cds; /* Parsed CDS record */
+ u32 mUnixTime; /* Modification time, in UNIX format */
+ u8 *aExtra; /* cds.nExtra+cds.nComment bytes of extra data */
+ i64 iDataOff; /* Offset to data in file (if aData==0) */
+ u8 *aData; /* cds.szCompressed bytes of compressed data */
ZipfileEntry *pNext; /* Next element in in-memory CDS */
};
/*
-** Cursor type for recursively iterating through a directory structure.
+** Cursor type for zipfile tables.
*/
typedef struct ZipfileCsr ZipfileCsr;
struct ZipfileCsr {
sqlite3_vtab_cursor base; /* Base class - must be first */
i64 iId; /* Cursor ID */
- int bEof; /* True when at EOF */
+ u8 bEof; /* True when at EOF */
+ u8 bNoop; /* If next xNext() call is no-op */
/* Used outside of write transactions */
FILE *pFile; /* Zip file */
i64 iNextOff; /* Offset of next record in central directory */
ZipfileEOCD eocd; /* Parse of central directory record */
- /* Used inside write transactions */
- ZipfileEntry *pCurrent;
-
- ZipfileCDS cds; /* Central Directory Structure */
- ZipfileLFH lfh; /* Local File Header for current entry */
- i64 iDataOff; /* Offset in zipfile to data */
- u32 mTime; /* Extended mtime value */
- int flags; /* Flags byte (see below for bits) */
+ ZipfileEntry *pFreeEntry; /* Free this list when cursor is closed or reset */
+ ZipfileEntry *pCurrent; /* Current entry */
ZipfileCsr *pCsrNext; /* Next cursor on same virtual table */
};
-/*
-** Values for ZipfileCsr.flags.
-*/
-#define ZIPFILE_MTIME_VALID 0x0001
-
typedef struct ZipfileTab ZipfileTab;
struct ZipfileTab {
sqlite3_vtab base; /* Base class - must be first */
char *zFile; /* Zip file this table accesses (may be NULL) */
+ sqlite3 *db; /* Host database connection */
u8 *aBuffer; /* Temporary buffer used for various tasks */
ZipfileCsr *pCsrList; /* List of cursors */
@@ -276,17 +282,33 @@ struct ZipfileTab {
i64 szOrig; /* Size of archive at start of transaction */
};
+/*
+** Set the error message contained in context ctx to the results of
+** vprintf(zFmt, ...).
+*/
+static void zipfileCtxErrorMsg(sqlite3_context *ctx, const char *zFmt, ...){
+ char *zMsg = 0;
+ va_list ap;
+ va_start(ap, zFmt);
+ zMsg = sqlite3_vmprintf(zFmt, ap);
+ sqlite3_result_error(ctx, zMsg, -1);
+ sqlite3_free(zMsg);
+ va_end(ap);
+}
+
+/*
+** If string zIn is quoted, dequote it in place. Otherwise, if the string
+** is not quoted, do nothing.
+*/
static void zipfileDequote(char *zIn){
char q = zIn[0];
if( q=='"' || q=='\'' || q=='`' || q=='[' ){
- char c;
int iIn = 1;
int iOut = 0;
if( q=='[' ) q = ']';
- while( (c = zIn[iIn++]) ){
- if( c==q ){
- if( zIn[iIn++]!=q ) break;
- }
+ while( ALWAYS(zIn[iIn]) ){
+ char c = zIn[iIn++];
+ if( c==q && zIn[iIn++]!=q ) break;
zIn[iOut++] = c;
}
zIn[iOut] = '\0';
@@ -314,6 +336,21 @@ static int zipfileConnect(
ZipfileTab *pNew = 0;
int rc;
+ /* If the table name is not "zipfile", require that the argument be
+ ** specified. This stops zipfile tables from being created as:
+ **
+ ** CREATE VIRTUAL TABLE zzz USING zipfile();
+ **
+ ** It does not prevent:
+ **
+ ** CREATE VIRTUAL TABLE zipfile USING zipfile();
+ */
+ assert( 0==sqlite3_stricmp(argv[0], "zipfile") );
+ if( (0!=sqlite3_stricmp(argv[2], "zipfile") && argc<4) || argc>4 ){
+ *pzErr = sqlite3_mprintf("zipfile constructor requires one argument");
+ return SQLITE_ERROR;
+ }
+
if( argc>3 ){
zFile = argv[3];
nFile = (int)strlen(zFile)+1;
@@ -324,6 +361,7 @@ static int zipfileConnect(
pNew = (ZipfileTab*)sqlite3_malloc(nByte+nFile);
if( pNew==0 ) return SQLITE_NOMEM;
memset(pNew, 0, nByte+nFile);
+ pNew->db = db;
pNew->aBuffer = (u8*)&pNew[1];
if( zFile ){
pNew->zFile = (char*)&pNew->aBuffer[ZIPFILE_BUFFER_SIZE];
@@ -336,9 +374,42 @@ static int zipfileConnect(
}
/*
+** Free the ZipfileEntry structure indicated by the only argument.
+*/
+static void zipfileEntryFree(ZipfileEntry *p){
+ if( p ){
+ sqlite3_free(p->cds.zFile);
+ sqlite3_free(p);
+ }
+}
+
+/*
+** Release resources that should be freed at the end of a write
+** transaction.
+*/
+static void zipfileCleanupTransaction(ZipfileTab *pTab){
+ ZipfileEntry *pEntry;
+ ZipfileEntry *pNext;
+
+ if( pTab->pWriteFd ){
+ fclose(pTab->pWriteFd);
+ pTab->pWriteFd = 0;
+ }
+ for(pEntry=pTab->pFirstEntry; pEntry; pEntry=pNext){
+ pNext = pEntry->pNext;
+ zipfileEntryFree(pEntry);
+ }
+ pTab->pFirstEntry = 0;
+ pTab->pLastEntry = 0;
+ pTab->szCurrent = 0;
+ pTab->szOrig = 0;
+}
+
+/*
** This method is the destructor for zipfile vtab objects.
*/
static int zipfileDisconnect(sqlite3_vtab *pVtab){
+ zipfileCleanupTransaction((ZipfileTab*)pVtab);
sqlite3_free(pVtab);
return SQLITE_OK;
}
@@ -366,12 +437,20 @@ static int zipfileOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCsr){
** by zipfileOpen().
*/
static void zipfileResetCursor(ZipfileCsr *pCsr){
- sqlite3_free(pCsr->cds.zFile);
- pCsr->cds.zFile = 0;
+ ZipfileEntry *p;
+ ZipfileEntry *pNext;
+
pCsr->bEof = 0;
if( pCsr->pFile ){
fclose(pCsr->pFile);
pCsr->pFile = 0;
+ zipfileEntryFree(pCsr->pCurrent);
+ pCsr->pCurrent = 0;
+ }
+
+ for(p=pCsr->pFreeEntry; p; p=pNext){
+ pNext = p->pNext;
+ zipfileEntryFree(p);
}
}
@@ -385,12 +464,8 @@ static int zipfileClose(sqlite3_vtab_cursor *cur){
zipfileResetCursor(pCsr);
/* Remove this cursor from the ZipfileTab.pCsrList list. */
- for(pp=&pTab->pCsrList; *pp; pp=&((*pp)->pCsrNext)){
- if( *pp==pCsr ){
- *pp = pCsr->pCsrNext;
- break;
- }
- }
+ for(pp=&pTab->pCsrList; *pp!=pCsr; pp=&((*pp)->pCsrNext));
+ *pp = pCsr->pCsrNext;
sqlite3_free(pCsr);
return SQLITE_OK;
@@ -400,13 +475,31 @@ static int zipfileClose(sqlite3_vtab_cursor *cur){
** Set the error message for the virtual table associated with cursor
** pCsr to the results of vprintf(zFmt, ...).
*/
-static void zipfileSetErrmsg(ZipfileCsr *pCsr, const char *zFmt, ...){
+static void zipfileTableErr(ZipfileTab *pTab, const char *zFmt, ...){
va_list ap;
va_start(ap, zFmt);
+ sqlite3_free(pTab->base.zErrMsg);
+ pTab->base.zErrMsg = sqlite3_vmprintf(zFmt, ap);
+ va_end(ap);
+}
+static void zipfileCursorErr(ZipfileCsr *pCsr, const char *zFmt, ...){
+ va_list ap;
+ va_start(ap, zFmt);
+ sqlite3_free(pCsr->base.pVtab->zErrMsg);
pCsr->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap);
va_end(ap);
}
+/*
+** Read nRead bytes of data from offset iOff of file pFile into buffer
+** aRead[]. Return SQLITE_OK if successful, or an SQLite error code
+** otherwise.
+**
+** If an error does occur, output variable (*pzErrmsg) may be set to point
+** to an English language error message. It is the responsibility of the
+** caller to eventually free this buffer using
+** sqlite3_free().
+*/
static int zipfileReadData(
FILE *pFile, /* Read from this file */
u8 *aRead, /* Read into this buffer */
@@ -440,9 +533,16 @@ static int zipfileAppendData(
return SQLITE_OK;
}
+/*
+** Read and return a 16-bit little-endian unsigned integer from buffer aBuf.
+*/
static u16 zipfileGetU16(const u8 *aBuf){
return (aBuf[1] << 8) + aBuf[0];
}
+
+/*
+** Read and return a 32-bit little-endian unsigned integer from buffer aBuf.
+*/
static u32 zipfileGetU32(const u8 *aBuf){
return ((u32)(aBuf[3]) << 24)
+ ((u32)(aBuf[2]) << 16)
@@ -450,10 +550,17 @@ static u32 zipfileGetU32(const u8 *aBuf){
+ ((u32)(aBuf[0]) << 0);
}
+/*
+** Write a 16-bit little endiate integer into buffer aBuf.
+*/
static void zipfilePutU16(u8 *aBuf, u16 val){
aBuf[0] = val & 0xFF;
aBuf[1] = (val>>8) & 0xFF;
}
+
+/*
+** Write a 32-bit little endiate integer into buffer aBuf.
+*/
static void zipfilePutU32(u8 *aBuf, u32 val){
aBuf[0] = val & 0xFF;
aBuf[1] = (val>>8) & 0xFF;
@@ -467,15 +574,11 @@ static void zipfilePutU32(u8 *aBuf, u32 val){
#define zipfileWrite32(aBuf,val) { zipfilePutU32(aBuf,val); aBuf+=4; }
#define zipfileWrite16(aBuf,val) { zipfilePutU16(aBuf,val); aBuf+=2; }
-static u8* zipfileCsrBuffer(ZipfileCsr *pCsr){
- return ((ZipfileTab*)(pCsr->base.pVtab))->aBuffer;
-}
-
/*
** Magic numbers used to read CDS records.
*/
-#define ZIPFILE_CDS_FIXED_SZ 46
#define ZIPFILE_CDS_NFILE_OFF 28
+#define ZIPFILE_CDS_SZCOMPRESSED_OFF 20
/*
** Decode the CDS record in buffer aBuf into (*pCDS). Return SQLITE_ERROR
@@ -512,216 +615,321 @@ static int zipfileReadCDS(u8 *aBuf, ZipfileCDS *pCDS){
}
/*
-** Read the CDS record for the current entry from disk into pCsr->cds.
+** Decode the LFH record in buffer aBuf into (*pLFH). Return SQLITE_ERROR
+** if the record is not well-formed, or SQLITE_OK otherwise.
*/
-static int zipfileCsrReadCDS(ZipfileCsr *pCsr){
- char **pzErr = &pCsr->base.pVtab->zErrMsg;
- u8 *aRead;
+static int zipfileReadLFH(
+ u8 *aBuffer,
+ ZipfileLFH *pLFH
+){
+ u8 *aRead = aBuffer;
int rc = SQLITE_OK;
- sqlite3_free(pCsr->cds.zFile);
- pCsr->cds.zFile = 0;
-
- if( pCsr->pCurrent==0 ){
- aRead = zipfileCsrBuffer(pCsr);
- rc = zipfileReadData(
- pCsr->pFile, aRead, ZIPFILE_CDS_FIXED_SZ, pCsr->iNextOff, pzErr
- );
+ u32 sig = zipfileRead32(aRead);
+ if( sig!=ZIPFILE_SIGNATURE_LFH ){
+ rc = SQLITE_ERROR;
}else{
- aRead = pCsr->pCurrent->aCdsEntry;
+ pLFH->iVersionExtract = zipfileRead16(aRead);
+ pLFH->flags = zipfileRead16(aRead);
+ pLFH->iCompression = zipfileRead16(aRead);
+ pLFH->mTime = zipfileRead16(aRead);
+ pLFH->mDate = zipfileRead16(aRead);
+ pLFH->crc32 = zipfileRead32(aRead);
+ pLFH->szCompressed = zipfileRead32(aRead);
+ pLFH->szUncompressed = zipfileRead32(aRead);
+ pLFH->nFile = zipfileRead16(aRead);
+ pLFH->nExtra = zipfileRead16(aRead);
}
+ return rc;
+}
- if( rc==SQLITE_OK ){
- rc = zipfileReadCDS(aRead, &pCsr->cds);
- if( rc!=SQLITE_OK ){
- assert( pCsr->pCurrent==0 );
- zipfileSetErrmsg(pCsr,"failed to read CDS at offset %lld",pCsr->iNextOff);
- }else{
- int nRead;
- if( pCsr->pCurrent==0 ){
- nRead = pCsr->cds.nFile + pCsr->cds.nExtra;
- aRead = zipfileCsrBuffer(pCsr);
- pCsr->iNextOff += ZIPFILE_CDS_FIXED_SZ;
- rc = zipfileReadData(pCsr->pFile, aRead, nRead, pCsr->iNextOff, pzErr);
- }else{
- aRead = &aRead[ZIPFILE_CDS_FIXED_SZ];
- }
-
- if( rc==SQLITE_OK ){
- pCsr->cds.zFile = sqlite3_mprintf("%.*s", (int)pCsr->cds.nFile, aRead);
- pCsr->iNextOff += pCsr->cds.nFile;
- pCsr->iNextOff += pCsr->cds.nExtra;
- pCsr->iNextOff += pCsr->cds.nComment;
- }
- /* Scan the cds.nExtra bytes of "extra" fields for any that can
- ** be interpreted. The general format of an extra field is:
- **
- ** Header ID 2 bytes
- ** Data Size 2 bytes
- ** Data N bytes
- **
- */
- if( rc==SQLITE_OK ){
- u8 *p = &aRead[pCsr->cds.nFile];
- u8 *pEnd = &p[pCsr->cds.nExtra];
-
- while( p<pEnd ){
- u16 id = zipfileRead16(p);
- u16 nByte = zipfileRead16(p);
-
- switch( id ){
- case ZIPFILE_EXTRA_TIMESTAMP: {
- u8 b = p[0];
- if( b & 0x01 ){ /* 0x01 -> modtime is present */
- pCsr->mTime = zipfileGetU32(&p[1]);
- pCsr->flags |= ZIPFILE_MTIME_VALID;
- }
- break;
- }
- }
-
- p += nByte;
+/*
+** Buffer aExtra (size nExtra bytes) contains zip archive "extra" fields.
+** Scan through this buffer to find an "extra-timestamp" field. If one
+** exists, extract the 32-bit modification-timestamp from it and store
+** the value in output parameter *pmTime.
+**
+** Zero is returned if no extra-timestamp record could be found (and so
+** *pmTime is left unchanged), or non-zero otherwise.
+**
+** The general format of an extra field is:
+**
+** Header ID 2 bytes
+** Data Size 2 bytes
+** Data N bytes
+*/
+static int zipfileScanExtra(u8 *aExtra, int nExtra, u32 *pmTime){
+ int ret = 0;
+ u8 *p = aExtra;
+ u8 *pEnd = &aExtra[nExtra];
+
+ while( p<pEnd ){
+ u16 id = zipfileRead16(p);
+ u16 nByte = zipfileRead16(p);
+
+ switch( id ){
+ case ZIPFILE_EXTRA_TIMESTAMP: {
+ u8 b = p[0];
+ if( b & 0x01 ){ /* 0x01 -> modtime is present */
+ *pmTime = zipfileGetU32(&p[1]);
+ ret = 1;
}
+ break;
}
}
+
+ p += nByte;
}
+ return ret;
+}
- return rc;
+/*
+** Convert the standard MS-DOS timestamp stored in the mTime and mDate
+** fields of the CDS structure passed as the only argument to a 32-bit
+** UNIX seconds-since-the-epoch timestamp. Return the result.
+**
+** "Standard" MS-DOS time format:
+**
+** File modification time:
+** Bits 00-04: seconds divided by 2
+** Bits 05-10: minute
+** Bits 11-15: hour
+** File modification date:
+** Bits 00-04: day
+** Bits 05-08: month (1-12)
+** Bits 09-15: years from 1980
+**
+** https://msdn.microsoft.com/en-us/library/9kkf9tah.aspx
+*/
+static u32 zipfileMtime(ZipfileCDS *pCDS){
+ int Y = (1980 + ((pCDS->mDate >> 9) & 0x7F));
+ int M = ((pCDS->mDate >> 5) & 0x0F);
+ int D = (pCDS->mDate & 0x1F);
+ int B = -13;
+
+ int sec = (pCDS->mTime & 0x1F)*2;
+ int min = (pCDS->mTime >> 5) & 0x3F;
+ int hr = (pCDS->mTime >> 11) & 0x1F;
+ i64 JD;
+
+ /* JD = INT(365.25 * (Y+4716)) + INT(30.6001 * (M+1)) + D + B - 1524.5 */
+
+ /* Calculate the JD in seconds for noon on the day in question */
+ if( M<3 ){
+ Y = Y-1;
+ M = M+12;
+ }
+ JD = (i64)(24*60*60) * (
+ (int)(365.25 * (Y + 4716))
+ + (int)(30.6001 * (M + 1))
+ + D + B - 1524
+ );
+
+ /* Correct the JD for the time within the day */
+ JD += (hr-12) * 3600 + min * 60 + sec;
+
+ /* Convert JD to unix timestamp (the JD epoch is 2440587.5) */
+ return (u32)(JD - (i64)(24405875) * 24*60*6);
}
-static FILE *zipfileGetFd(ZipfileCsr *pCsr){
- if( pCsr->pFile ) return pCsr->pFile;
- return ((ZipfileTab*)(pCsr->base.pVtab))->pWriteFd;
+/*
+** The opposite of zipfileMtime(). This function populates the mTime and
+** mDate fields of the CDS structure passed as the first argument according
+** to the UNIX timestamp value passed as the second.
+*/
+static void zipfileMtimeToDos(ZipfileCDS *pCds, u32 mUnixTime){
+ /* Convert unix timestamp to JD (2440588 is noon on 1/1/1970) */
+ i64 JD = (i64)2440588 + mUnixTime / (24*60*60);
+
+ int A, B, C, D, E;
+ int yr, mon, day;
+ int hr, min, sec;
+
+ A = (int)((JD - 1867216.25)/36524.25);
+ A = (int)(JD + 1 + A - (A/4));
+ B = A + 1524;
+ C = (int)((B - 122.1)/365.25);
+ D = (36525*(C&32767))/100;
+ E = (int)((B-D)/30.6001);
+
+ day = B - D - (int)(30.6001*E);
+ mon = (E<14 ? E-1 : E-13);
+ yr = mon>2 ? C-4716 : C-4715;
+
+ hr = (mUnixTime % (24*60*60)) / (60*60);
+ min = (mUnixTime % (60*60)) / 60;
+ sec = (mUnixTime % 60);
+
+ if( yr>=1980 ){
+ pCds->mDate = (u16)(day + (mon << 5) + ((yr-1980) << 9));
+ pCds->mTime = (u16)(sec/2 + (min<<5) + (hr<<11));
+ }else{
+ pCds->mDate = pCds->mTime = 0;
+ }
+
+ assert( mUnixTime<315507600
+ || mUnixTime==zipfileMtime(pCds)
+ || ((mUnixTime % 2) && mUnixTime-1==zipfileMtime(pCds))
+ /* || (mUnixTime % 2) */
+ );
}
-static int zipfileReadLFH(
- FILE *pFd,
- i64 iOffset,
- u8 *aTmp,
- ZipfileLFH *pLFH,
- char **pzErr
+/*
+** If aBlob is not NULL, then it is a pointer to a buffer (nBlob bytes in
+** size) containing an entire zip archive image. Or, if aBlob is NULL,
+** then pFile is a file-handle open on a zip file. In either case, this
+** function creates a ZipfileEntry object based on the zip archive entry
+** for which the CDS record is at offset iOff.
+**
+** If successful, SQLITE_OK is returned and (*ppEntry) set to point to
+** the new object. Otherwise, an SQLite error code is returned and the
+** final value of (*ppEntry) undefined.
+*/
+static int zipfileGetEntry(
+ ZipfileTab *pTab, /* Store any error message here */
+ const u8 *aBlob, /* Pointer to in-memory file image */
+ int nBlob, /* Size of aBlob[] in bytes */
+ FILE *pFile, /* If aBlob==0, read from this file */
+ i64 iOff, /* Offset of CDS record */
+ ZipfileEntry **ppEntry /* OUT: Pointer to new object */
){
- u8 *aRead = aTmp;
- static const int szFix = ZIPFILE_LFH_FIXED_SZ;
- int rc;
+ u8 *aRead;
+ char **pzErr = &pTab->base.zErrMsg;
+ int rc = SQLITE_OK;
+
+ if( aBlob==0 ){
+ aRead = pTab->aBuffer;
+ rc = zipfileReadData(pFile, aRead, ZIPFILE_CDS_FIXED_SZ, iOff, pzErr);
+ }else{
+ aRead = (u8*)&aBlob[iOff];
+ }
- rc = zipfileReadData(pFd, aRead, szFix, iOffset, pzErr);
if( rc==SQLITE_OK ){
- u32 sig = zipfileRead32(aRead);
- if( sig!=ZIPFILE_SIGNATURE_LFH ){
- *pzErr = sqlite3_mprintf("failed to read LFH at offset %d", (int)iOffset);
- rc = SQLITE_ERROR;
+ int nAlloc;
+ ZipfileEntry *pNew;
+
+ int nFile = zipfileGetU16(&aRead[ZIPFILE_CDS_NFILE_OFF]);
+ int nExtra = zipfileGetU16(&aRead[ZIPFILE_CDS_NFILE_OFF+2]);
+ nExtra += zipfileGetU16(&aRead[ZIPFILE_CDS_NFILE_OFF+4]);
+
+ nAlloc = sizeof(ZipfileEntry) + nExtra;
+ if( aBlob ){
+ nAlloc += zipfileGetU32(&aRead[ZIPFILE_CDS_SZCOMPRESSED_OFF]);
+ }
+
+ pNew = (ZipfileEntry*)sqlite3_malloc(nAlloc);
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
}else{
- pLFH->iVersionExtract = zipfileRead16(aRead);
- pLFH->flags = zipfileRead16(aRead);
- pLFH->iCompression = zipfileRead16(aRead);
- pLFH->mTime = zipfileRead16(aRead);
- pLFH->mDate = zipfileRead16(aRead);
- pLFH->crc32 = zipfileRead32(aRead);
- pLFH->szCompressed = zipfileRead32(aRead);
- pLFH->szUncompressed = zipfileRead32(aRead);
- pLFH->nFile = zipfileRead16(aRead);
- pLFH->nExtra = zipfileRead16(aRead);
- assert( aRead==&aTmp[szFix] );
+ memset(pNew, 0, sizeof(ZipfileEntry));
+ rc = zipfileReadCDS(aRead, &pNew->cds);
+ if( rc!=SQLITE_OK ){
+ *pzErr = sqlite3_mprintf("failed to read CDS at offset %lld", iOff);
+ }else if( aBlob==0 ){
+ rc = zipfileReadData(
+ pFile, aRead, nExtra+nFile, iOff+ZIPFILE_CDS_FIXED_SZ, pzErr
+ );
+ }else{
+ aRead = (u8*)&aBlob[iOff + ZIPFILE_CDS_FIXED_SZ];
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ u32 *pt = &pNew->mUnixTime;
+ pNew->cds.zFile = sqlite3_mprintf("%.*s", nFile, aRead);
+ pNew->aExtra = (u8*)&pNew[1];
+ memcpy(pNew->aExtra, &aRead[nFile], nExtra);
+ if( pNew->cds.zFile==0 ){
+ rc = SQLITE_NOMEM;
+ }else if( 0==zipfileScanExtra(&aRead[nFile], pNew->cds.nExtra, pt) ){
+ pNew->mUnixTime = zipfileMtime(&pNew->cds);
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ static const int szFix = ZIPFILE_LFH_FIXED_SZ;
+ ZipfileLFH lfh;
+ if( pFile ){
+ rc = zipfileReadData(pFile, aRead, szFix, pNew->cds.iOffset, pzErr);
+ }else{
+ aRead = (u8*)&aBlob[pNew->cds.iOffset];
+ }
+
+ rc = zipfileReadLFH(aRead, &lfh);
+ if( rc==SQLITE_OK ){
+ pNew->iDataOff = pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ;
+ pNew->iDataOff += lfh.nFile + lfh.nExtra;
+ if( aBlob && pNew->cds.szCompressed ){
+ pNew->aData = &pNew->aExtra[nExtra];
+ memcpy(pNew->aData, &aBlob[pNew->iDataOff], pNew->cds.szCompressed);
+ }
+ }else{
+ *pzErr = sqlite3_mprintf("failed to read LFH at offset %d",
+ (int)pNew->cds.iOffset
+ );
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ zipfileEntryFree(pNew);
+ }else{
+ *ppEntry = pNew;
}
}
- return rc;
-}
-static int zipfileCsrReadLFH(ZipfileCsr *pCsr){
- FILE *pFile = zipfileGetFd(pCsr);
- char **pzErr = &pCsr->base.pVtab->zErrMsg;
- u8 *aRead = zipfileCsrBuffer(pCsr);
- int rc = zipfileReadLFH(pFile, pCsr->cds.iOffset, aRead, &pCsr->lfh, pzErr);
- pCsr->iDataOff = pCsr->cds.iOffset + ZIPFILE_LFH_FIXED_SZ;
- pCsr->iDataOff += pCsr->lfh.nFile+pCsr->lfh.nExtra;
return rc;
}
-
/*
** Advance an ZipfileCsr to its next row of output.
*/
static int zipfileNext(sqlite3_vtab_cursor *cur){
ZipfileCsr *pCsr = (ZipfileCsr*)cur;
int rc = SQLITE_OK;
- pCsr->flags = 0;
- if( pCsr->pCurrent==0 ){
+ if( pCsr->pFile ){
i64 iEof = pCsr->eocd.iOffset + pCsr->eocd.nSize;
+ zipfileEntryFree(pCsr->pCurrent);
+ pCsr->pCurrent = 0;
if( pCsr->iNextOff>=iEof ){
pCsr->bEof = 1;
+ }else{
+ ZipfileEntry *p = 0;
+ ZipfileTab *pTab = (ZipfileTab*)(cur->pVtab);
+ rc = zipfileGetEntry(pTab, 0, 0, pCsr->pFile, pCsr->iNextOff, &p);
+ if( rc==SQLITE_OK ){
+ pCsr->iNextOff += ZIPFILE_CDS_FIXED_SZ;
+ pCsr->iNextOff += (int)p->cds.nExtra + p->cds.nFile + p->cds.nComment;
+ }
+ pCsr->pCurrent = p;
}
}else{
- assert( pCsr->pFile==0 );
- do {
+ if( !pCsr->bNoop ){
pCsr->pCurrent = pCsr->pCurrent->pNext;
- }while( pCsr->pCurrent && pCsr->pCurrent->bDeleted );
+ }
if( pCsr->pCurrent==0 ){
pCsr->bEof = 1;
}
}
- if( pCsr->bEof==0 ){
- rc = zipfileCsrReadCDS(pCsr);
- if( rc==SQLITE_OK ){
- rc = zipfileCsrReadLFH(pCsr);
- }
- }
-
+ pCsr->bNoop = 0;
return rc;
}
+static void zipfileFree(void *p) {
+ sqlite3_free(p);
+}
+
/*
-** "Standard" MS-DOS time format:
+** Buffer aIn (size nIn bytes) contains compressed data. Uncompressed, the
+** size is nOut bytes. This function uncompresses the data and sets the
+** return value in context pCtx to the result (a blob).
**
-** File modification time:
-** Bits 00-04: seconds divided by 2
-** Bits 05-10: minute
-** Bits 11-15: hour
-** File modification date:
-** Bits 00-04: day
-** Bits 05-08: month (1-12)
-** Bits 09-15: years from 1980
+** If an error occurs, an error code is left in pCtx instead.
*/
-static time_t zipfileMtime(ZipfileCsr *pCsr){
- struct tm t;
- memset(&t, 0, sizeof(t));
- t.tm_sec = (pCsr->cds.mTime & 0x1F)*2;
- t.tm_min = (pCsr->cds.mTime >> 5) & 0x2F;
- t.tm_hour = (pCsr->cds.mTime >> 11) & 0x1F;
-
- t.tm_mday = (pCsr->cds.mDate & 0x1F);
- t.tm_mon = ((pCsr->cds.mDate >> 5) & 0x0F) - 1;
- t.tm_year = 80 + ((pCsr->cds.mDate >> 9) & 0x7F);
-
- return mktime(&t);
-}
-
-static void zipfileMtimeToDos(ZipfileCDS *pCds, u32 mTime){
- time_t t = (time_t)mTime;
- struct tm res;
-
-#if !defined(_WIN32) && !defined(WIN32)
- localtime_r(&t, &res);
-#else
- memcpy(&res, localtime(&t), sizeof(struct tm));
-#endif
-
- pCds->mTime = (u16)(
- (res.tm_sec / 2) +
- (res.tm_min << 5) +
- (res.tm_hour << 11));
-
- pCds->mDate = (u16)(
- (res.tm_mday-1) +
- ((res.tm_mon+1) << 5) +
- ((res.tm_year-80) << 9));
-}
-
static void zipfileInflate(
- sqlite3_context *pCtx, /* Store error here, if any */
+ sqlite3_context *pCtx, /* Store result here */
const u8 *aIn, /* Compressed data */
int nIn, /* Size of buffer aIn[] in bytes */
int nOut /* Expected output size */
@@ -747,7 +955,8 @@ static void zipfileInflate(
if( err!=Z_STREAM_END ){
zipfileCtxErrorMsg(pCtx, "inflate() failed (%d)", err);
}else{
- sqlite3_result_blob(pCtx, aRes, nOut, SQLITE_TRANSIENT);
+ sqlite3_result_blob(pCtx, aRes, nOut, zipfileFree);
+ aRes = 0;
}
}
sqlite3_free(aRes);
@@ -755,10 +964,22 @@ static void zipfileInflate(
}
}
+/*
+** Buffer aIn (size nIn bytes) contains uncompressed data. This function
+** compresses it and sets (*ppOut) to point to a buffer containing the
+** compressed data. The caller is responsible for eventually calling
+** sqlite3_free() to release buffer (*ppOut). Before returning, (*pnOut)
+** is set to the size of buffer (*ppOut) in bytes.
+**
+** If no error occurs, SQLITE_OK is returned. Otherwise, an SQLite error
+** code is returned and an error message left in virtual-table handle
+** pTab. The values of (*ppOut) and (*pnOut) are left unchanged in this
+** case.
+*/
static int zipfileDeflate(
- ZipfileTab *pTab, /* Set error message here */
const u8 *aIn, int nIn, /* Input */
- u8 **ppOut, int *pnOut /* Output */
+ u8 **ppOut, int *pnOut, /* Output */
+ char **pzErr /* OUT: Error message */
){
int nAlloc = (int)compressBound(nIn);
u8 *aOut;
@@ -784,7 +1005,7 @@ static int zipfileDeflate(
*pnOut = (int)str.total_out;
}else{
sqlite3_free(aOut);
- pTab->base.zErrMsg = sqlite3_mprintf("zipfile: deflate() error");
+ *pzErr = sqlite3_mprintf("zipfile: deflate() error");
rc = SQLITE_ERROR;
}
deflateEnd(&str);
@@ -804,60 +1025,66 @@ static int zipfileColumn(
int i /* Which column to return */
){
ZipfileCsr *pCsr = (ZipfileCsr*)cur;
+ ZipfileCDS *pCDS = &pCsr->pCurrent->cds;
int rc = SQLITE_OK;
switch( i ){
case 0: /* name */
- sqlite3_result_text(ctx, pCsr->cds.zFile, -1, SQLITE_TRANSIENT);
+ sqlite3_result_text(ctx, pCDS->zFile, -1, SQLITE_TRANSIENT);
break;
case 1: /* mode */
/* TODO: Whether or not the following is correct surely depends on
** the platform on which the archive was created. */
- sqlite3_result_int(ctx, pCsr->cds.iExternalAttr >> 16);
+ sqlite3_result_int(ctx, pCDS->iExternalAttr >> 16);
break;
case 2: { /* mtime */
- if( pCsr->flags & ZIPFILE_MTIME_VALID ){
- sqlite3_result_int64(ctx, pCsr->mTime);
- }else{
- sqlite3_result_int64(ctx, zipfileMtime(pCsr));
- }
+ sqlite3_result_int64(ctx, pCsr->pCurrent->mUnixTime);
break;
}
case 3: { /* sz */
if( sqlite3_vtab_nochange(ctx)==0 ){
- sqlite3_result_int64(ctx, pCsr->cds.szUncompressed);
+ sqlite3_result_int64(ctx, pCDS->szUncompressed);
}
break;
}
case 4: /* rawdata */
if( sqlite3_vtab_nochange(ctx) ) break;
case 5: { /* data */
- if( i==4 || pCsr->cds.iCompression==0 || pCsr->cds.iCompression==8 ){
- int sz = pCsr->cds.szCompressed;
- int szFinal = pCsr->cds.szUncompressed;
+ if( i==4 || pCDS->iCompression==0 || pCDS->iCompression==8 ){
+ int sz = pCDS->szCompressed;
+ int szFinal = pCDS->szUncompressed;
if( szFinal>0 ){
- u8 *aBuf = sqlite3_malloc(sz);
- if( aBuf==0 ){
- rc = SQLITE_NOMEM;
+ u8 *aBuf;
+ u8 *aFree = 0;
+ if( pCsr->pCurrent->aData ){
+ aBuf = pCsr->pCurrent->aData;
}else{
- FILE *pFile = zipfileGetFd(pCsr);
- rc = zipfileReadData(pFile, aBuf, sz, pCsr->iDataOff,
- &pCsr->base.pVtab->zErrMsg
- );
+ aBuf = aFree = sqlite3_malloc(sz);
+ if( aBuf==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ FILE *pFile = pCsr->pFile;
+ if( pFile==0 ){
+ pFile = ((ZipfileTab*)(pCsr->base.pVtab))->pWriteFd;
+ }
+ rc = zipfileReadData(pFile, aBuf, sz, pCsr->pCurrent->iDataOff,
+ &pCsr->base.pVtab->zErrMsg
+ );
+ }
}
if( rc==SQLITE_OK ){
- if( i==5 && pCsr->cds.iCompression ){
+ if( i==5 && pCDS->iCompression ){
zipfileInflate(ctx, aBuf, sz, szFinal);
}else{
sqlite3_result_blob(ctx, aBuf, sz, SQLITE_TRANSIENT);
}
- sqlite3_free(aBuf);
}
+ sqlite3_free(aFree);
}else{
/* Figure out if this is a directory or a zero-sized file. Consider
** it to be a directory either if the mode suggests so, or if
** the final character in the name is '/'. */
- u32 mode = pCsr->cds.iExternalAttr >> 16;
- if( !(mode & S_IFDIR) && pCsr->cds.zFile[pCsr->cds.nFile-1]!='/' ){
+ u32 mode = pCDS->iExternalAttr >> 16;
+ if( !(mode & S_IFDIR) && pCDS->zFile[pCDS->nFile-1]!='/' ){
sqlite3_result_blob(ctx, "", 0, SQLITE_STATIC);
}
}
@@ -865,9 +1092,10 @@ static int zipfileColumn(
break;
}
case 6: /* method */
- sqlite3_result_int(ctx, pCsr->cds.iCompression);
+ sqlite3_result_int(ctx, pCDS->iCompression);
break;
- case 7: /* z */
+ default: /* z */
+ assert( i==7 );
sqlite3_result_int64(ctx, pCsr->iId);
break;
}
@@ -876,16 +1104,7 @@ static int zipfileColumn(
}
/*
-** Return the rowid for the current row.
-*/
-static int zipfileRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
- assert( 0 );
- return SQLITE_OK;
-}
-
-/*
-** Return TRUE if the cursor has been moved off of the last
-** row of output.
+** Return TRUE if the cursor is at EOF.
*/
static int zipfileEof(sqlite3_vtab_cursor *cur){
ZipfileCsr *pCsr = (ZipfileCsr*)cur;
@@ -893,28 +1112,43 @@ static int zipfileEof(sqlite3_vtab_cursor *cur){
}
/*
+** If aBlob is not NULL, then it points to a buffer nBlob bytes in size
+** containing an entire zip archive image. Or, if aBlob is NULL, then pFile
+** is guaranteed to be a file-handle open on a zip file.
+**
+** This function attempts to locate the EOCD record within the zip archive
+** and populate *pEOCD with the results of decoding it. SQLITE_OK is
+** returned if successful. Otherwise, an SQLite error code is returned and
+** an English language error message may be left in virtual-table pTab.
*/
static int zipfileReadEOCD(
ZipfileTab *pTab, /* Return errors here */
- FILE *pFile, /* Read from this file */
+ const u8 *aBlob, /* Pointer to in-memory file image */
+ int nBlob, /* Size of aBlob[] in bytes */
+ FILE *pFile, /* Read from this file if aBlob==0 */
ZipfileEOCD *pEOCD /* Object to populate */
){
u8 *aRead = pTab->aBuffer; /* Temporary buffer */
- i64 szFile; /* Total size of file in bytes */
int nRead; /* Bytes to read from file */
- i64 iOff; /* Offset to read from */
- int rc;
+ int rc = SQLITE_OK;
- fseek(pFile, 0, SEEK_END);
- szFile = (i64)ftell(pFile);
- if( szFile==0 ){
- memset(pEOCD, 0, sizeof(ZipfileEOCD));
- return SQLITE_OK;
+ if( aBlob==0 ){
+ i64 iOff; /* Offset to read from */
+ i64 szFile; /* Total size of file in bytes */
+ fseek(pFile, 0, SEEK_END);
+ szFile = (i64)ftell(pFile);
+ if( szFile==0 ){
+ memset(pEOCD, 0, sizeof(ZipfileEOCD));
+ return SQLITE_OK;
+ }
+ nRead = (int)(MIN(szFile, ZIPFILE_BUFFER_SIZE));
+ iOff = szFile - nRead;
+ rc = zipfileReadData(pFile, aRead, nRead, iOff, &pTab->base.zErrMsg);
+ }else{
+ nRead = (int)(MIN(nBlob, ZIPFILE_BUFFER_SIZE));
+ aRead = (u8*)&aBlob[nBlob-nRead];
}
- nRead = (int)(MIN(szFile, ZIPFILE_BUFFER_SIZE));
- iOff = szFile - nRead;
- rc = zipfileReadData(pFile, aRead, nRead, iOff, &pTab->base.zErrMsg);
if( rc==SQLITE_OK ){
int i;
@@ -940,17 +1174,59 @@ static int zipfileReadEOCD(
pEOCD->nEntryTotal = zipfileRead16(aRead);
pEOCD->nSize = zipfileRead32(aRead);
pEOCD->iOffset = zipfileRead32(aRead);
+ }
-#if 0
- printf("iDisk=%d iFirstDisk=%d nEntry=%d "
- "nEntryTotal=%d nSize=%d iOffset=%d",
- (int)pEOCD->iDisk, (int)pEOCD->iFirstDisk, (int)pEOCD->nEntry,
- (int)pEOCD->nEntryTotal, (int)pEOCD->nSize, (int)pEOCD->iOffset
- );
-#endif
+ return rc;
+}
+
+/*
+** Add object pNew to the linked list that begins at ZipfileTab.pFirstEntry
+** and ends with pLastEntry. If argument pBefore is NULL, then pNew is added
+** to the end of the list. Otherwise, it is added to the list immediately
+** before pBefore (which is guaranteed to be a part of said list).
+*/
+static void zipfileAddEntry(
+ ZipfileTab *pTab,
+ ZipfileEntry *pBefore,
+ ZipfileEntry *pNew
+){
+ assert( (pTab->pFirstEntry==0)==(pTab->pLastEntry==0) );
+ assert( pNew->pNext==0 );
+ if( pBefore==0 ){
+ if( pTab->pFirstEntry==0 ){
+ pTab->pFirstEntry = pTab->pLastEntry = pNew;
+ }else{
+ assert( pTab->pLastEntry->pNext==0 );
+ pTab->pLastEntry->pNext = pNew;
+ pTab->pLastEntry = pNew;
+ }
+ }else{
+ ZipfileEntry **pp;
+ for(pp=&pTab->pFirstEntry; *pp!=pBefore; pp=&((*pp)->pNext));
+ pNew->pNext = pBefore;
+ *pp = pNew;
}
+}
- return SQLITE_OK;
+static int zipfileLoadDirectory(ZipfileTab *pTab, const u8 *aBlob, int nBlob){
+ ZipfileEOCD eocd;
+ int rc;
+ int i;
+ i64 iOff;
+
+ rc = zipfileReadEOCD(pTab, aBlob, nBlob, pTab->pWriteFd, &eocd);
+ iOff = eocd.iOffset;
+ for(i=0; rc==SQLITE_OK && i<eocd.nEntry; i++){
+ ZipfileEntry *pNew = 0;
+ rc = zipfileGetEntry(pTab, aBlob, nBlob, pTab->pWriteFd, iOff, &pNew);
+
+ if( rc==SQLITE_OK ){
+ zipfileAddEntry(pTab, 0, pNew);
+ iOff += ZIPFILE_CDS_FIXED_SZ;
+ iOff += (int)pNew->cds.nExtra + pNew->cds.nFile + pNew->cds.nComment;
+ }
+ }
+ return rc;
}
/*
@@ -963,29 +1239,37 @@ static int zipfileFilter(
){
ZipfileTab *pTab = (ZipfileTab*)cur->pVtab;
ZipfileCsr *pCsr = (ZipfileCsr*)cur;
- const char *zFile; /* Zip file to scan */
+ const char *zFile = 0; /* Zip file to scan */
int rc = SQLITE_OK; /* Return Code */
+ int bInMemory = 0; /* True for an in-memory zipfile */
zipfileResetCursor(pCsr);
if( pTab->zFile ){
zFile = pTab->zFile;
}else if( idxNum==0 ){
- /* Error. This is an eponymous virtual table and the user has not
- ** supplied a file name. */
- zipfileSetErrmsg(pCsr, "table function zipfile() requires an argument");
+ zipfileCursorErr(pCsr, "zipfile() function requires an argument");
return SQLITE_ERROR;
+ }else if( sqlite3_value_type(argv[0])==SQLITE_BLOB ){
+ const u8 *aBlob = (const u8*)sqlite3_value_blob(argv[0]);
+ int nBlob = sqlite3_value_bytes(argv[0]);
+ assert( pTab->pFirstEntry==0 );
+ rc = zipfileLoadDirectory(pTab, aBlob, nBlob);
+ pCsr->pFreeEntry = pTab->pFirstEntry;
+ pTab->pFirstEntry = pTab->pLastEntry = 0;
+ if( rc!=SQLITE_OK ) return rc;
+ bInMemory = 1;
}else{
zFile = (const char*)sqlite3_value_text(argv[0]);
}
- if( pTab->pWriteFd==0 ){
+ if( 0==pTab->pWriteFd && 0==bInMemory ){
pCsr->pFile = fopen(zFile, "rb");
if( pCsr->pFile==0 ){
- zipfileSetErrmsg(pCsr, "cannot open file: %s", zFile);
+ zipfileCursorErr(pCsr, "cannot open file: %s", zFile);
rc = SQLITE_ERROR;
}else{
- rc = zipfileReadEOCD(pTab, pCsr->pFile, &pCsr->eocd);
+ rc = zipfileReadEOCD(pTab, 0, 0, pCsr->pFile, &pCsr->eocd);
if( rc==SQLITE_OK ){
if( pCsr->eocd.nEntry==0 ){
pCsr->bEof = 1;
@@ -996,12 +1280,9 @@ static int zipfileFilter(
}
}
}else{
- ZipfileEntry e;
- memset(&e, 0, sizeof(e));
- e.pNext = pTab->pFirstEntry;
- pCsr->pCurrent = &e;
+ pCsr->bNoop = 1;
+ pCsr->pCurrent = pCsr->pFreeEntry ? pCsr->pFreeEntry : pTab->pFirstEntry;
rc = zipfileNext(cur);
- assert( pCsr->pCurrent!=&e );
}
return rc;
@@ -1037,182 +1318,67 @@ static int zipfileBestIndex(
return SQLITE_OK;
}
-/*
-** Add object pNew to the end of the linked list that begins at
-** ZipfileTab.pFirstEntry and ends with pLastEntry.
-*/
-static void zipfileAddEntry(
- ZipfileTab *pTab,
- ZipfileEntry *pBefore,
- ZipfileEntry *pNew
-){
- assert( (pTab->pFirstEntry==0)==(pTab->pLastEntry==0) );
- assert( pNew->pNext==0 );
- if( pBefore==0 ){
- if( pTab->pFirstEntry==0 ){
- pTab->pFirstEntry = pTab->pLastEntry = pNew;
- }else{
- assert( pTab->pLastEntry->pNext==0 );
- pTab->pLastEntry->pNext = pNew;
- pTab->pLastEntry = pNew;
- }
- }else{
- ZipfileEntry **pp;
- for(pp=&pTab->pFirstEntry; *pp!=pBefore; pp=&((*pp)->pNext));
- pNew->pNext = pBefore;
- *pp = pNew;
- }
-}
-
-static int zipfileLoadDirectory(ZipfileTab *pTab){
- ZipfileEOCD eocd;
- int rc;
-
- rc = zipfileReadEOCD(pTab, pTab->pWriteFd, &eocd);
- if( rc==SQLITE_OK && eocd.nEntry>0 ){
- int i;
- int iOff = 0;
- u8 *aBuf = sqlite3_malloc(eocd.nSize);
- if( aBuf==0 ){
- rc = SQLITE_NOMEM;
- }else{
- rc = zipfileReadData(
- pTab->pWriteFd, aBuf, eocd.nSize, eocd.iOffset, &pTab->base.zErrMsg
- );
- }
-
- for(i=0; rc==SQLITE_OK && i<eocd.nEntry; i++){
- u16 nFile;
- u16 nExtra;
- u16 nComment;
- ZipfileEntry *pNew;
- u8 *aRec = &aBuf[iOff];
-
- nFile = zipfileGetU16(&aRec[ZIPFILE_CDS_NFILE_OFF]);
- nExtra = zipfileGetU16(&aRec[ZIPFILE_CDS_NFILE_OFF+2]);
- nComment = zipfileGetU16(&aRec[ZIPFILE_CDS_NFILE_OFF+4]);
-
- pNew = sqlite3_malloc(
- sizeof(ZipfileEntry)
- + nFile+1
- + ZIPFILE_CDS_FIXED_SZ+nFile+nExtra+nComment
- );
- if( pNew==0 ){
- rc = SQLITE_NOMEM;
- }else{
- memset(pNew, 0, sizeof(ZipfileEntry));
- pNew->zPath = (char*)&pNew[1];
- memcpy(pNew->zPath, &aRec[ZIPFILE_CDS_FIXED_SZ], nFile);
- pNew->zPath[nFile] = '\0';
- pNew->aCdsEntry = (u8*)&pNew->zPath[nFile+1];
- pNew->nCdsEntry = ZIPFILE_CDS_FIXED_SZ+nFile+nExtra+nComment;
- memcpy(pNew->aCdsEntry, aRec, pNew->nCdsEntry);
- zipfileAddEntry(pTab, 0, pNew);
- }
-
- iOff += ZIPFILE_CDS_FIXED_SZ+nFile+nExtra+nComment;
- }
-
- sqlite3_free(aBuf);
- }
-
- return rc;
-}
-
-static ZipfileEntry *zipfileNewEntry(
- ZipfileCDS *pCds, /* Values for fixed size part of CDS */
- const char *zPath, /* Path for new entry */
- int nPath, /* strlen(zPath) */
- u32 mTime /* Modification time (or 0) */
-){
- u8 *aWrite;
+static ZipfileEntry *zipfileNewEntry(const char *zPath){
ZipfileEntry *pNew;
- pCds->nFile = (u16)nPath;
- pCds->nExtra = mTime ? 9 : 0;
- pNew = (ZipfileEntry*)sqlite3_malloc(
- sizeof(ZipfileEntry) +
- nPath+1 +
- ZIPFILE_CDS_FIXED_SZ + nPath + pCds->nExtra
- );
-
+ pNew = sqlite3_malloc(sizeof(ZipfileEntry));
if( pNew ){
memset(pNew, 0, sizeof(ZipfileEntry));
- pNew->zPath = (char*)&pNew[1];
- pNew->aCdsEntry = (u8*)&pNew->zPath[nPath+1];
- pNew->nCdsEntry = ZIPFILE_CDS_FIXED_SZ + nPath + pCds->nExtra;
- memcpy(pNew->zPath, zPath, nPath+1);
-
- aWrite = pNew->aCdsEntry;
- zipfileWrite32(aWrite, ZIPFILE_SIGNATURE_CDS);
- zipfileWrite16(aWrite, pCds->iVersionMadeBy);
- zipfileWrite16(aWrite, pCds->iVersionExtract);
- zipfileWrite16(aWrite, pCds->flags);
- zipfileWrite16(aWrite, pCds->iCompression);
- zipfileWrite16(aWrite, pCds->mTime);
- zipfileWrite16(aWrite, pCds->mDate);
- zipfileWrite32(aWrite, pCds->crc32);
- zipfileWrite32(aWrite, pCds->szCompressed);
- zipfileWrite32(aWrite, pCds->szUncompressed);
- zipfileWrite16(aWrite, pCds->nFile);
- zipfileWrite16(aWrite, pCds->nExtra);
- zipfileWrite16(aWrite, pCds->nComment); assert( pCds->nComment==0 );
- zipfileWrite16(aWrite, pCds->iDiskStart);
- zipfileWrite16(aWrite, pCds->iInternalAttr);
- zipfileWrite32(aWrite, pCds->iExternalAttr);
- zipfileWrite32(aWrite, pCds->iOffset);
- assert( aWrite==&pNew->aCdsEntry[ZIPFILE_CDS_FIXED_SZ] );
- memcpy(aWrite, zPath, nPath);
- if( pCds->nExtra ){
- aWrite += nPath;
- zipfileWrite16(aWrite, ZIPFILE_EXTRA_TIMESTAMP);
- zipfileWrite16(aWrite, 5);
- *aWrite++ = 0x01;
- zipfileWrite32(aWrite, mTime);
+ pNew->cds.zFile = sqlite3_mprintf("%s", zPath);
+ if( pNew->cds.zFile==0 ){
+ sqlite3_free(pNew);
+ pNew = 0;
}
}
-
return pNew;
}
+static int zipfileSerializeLFH(ZipfileEntry *pEntry, u8 *aBuf){
+ ZipfileCDS *pCds = &pEntry->cds;
+ u8 *a = aBuf;
+
+ pCds->nExtra = 9;
+
+ /* Write the LFH itself */
+ zipfileWrite32(a, ZIPFILE_SIGNATURE_LFH);
+ zipfileWrite16(a, pCds->iVersionExtract);
+ zipfileWrite16(a, pCds->flags);
+ zipfileWrite16(a, pCds->iCompression);
+ zipfileWrite16(a, pCds->mTime);
+ zipfileWrite16(a, pCds->mDate);
+ zipfileWrite32(a, pCds->crc32);
+ zipfileWrite32(a, pCds->szCompressed);
+ zipfileWrite32(a, pCds->szUncompressed);
+ zipfileWrite16(a, (u16)pCds->nFile);
+ zipfileWrite16(a, pCds->nExtra);
+ assert( a==&aBuf[ZIPFILE_LFH_FIXED_SZ] );
+
+ /* Add the file name */
+ memcpy(a, pCds->zFile, (int)pCds->nFile);
+ a += (int)pCds->nFile;
+
+ /* The "extra" data */
+ zipfileWrite16(a, ZIPFILE_EXTRA_TIMESTAMP);
+ zipfileWrite16(a, 5);
+ *a++ = 0x01;
+ zipfileWrite32(a, pEntry->mUnixTime);
+
+ return a-aBuf;
+}
+
static int zipfileAppendEntry(
ZipfileTab *pTab,
- ZipfileCDS *pCds,
- const char *zPath, /* Path for new entry */
- int nPath, /* strlen(zPath) */
+ ZipfileEntry *pEntry,
const u8 *pData,
- int nData,
- u32 mTime
+ int nData
){
u8 *aBuf = pTab->aBuffer;
+ int nBuf;
int rc;
- zipfileWrite32(aBuf, ZIPFILE_SIGNATURE_LFH);
- zipfileWrite16(aBuf, pCds->iVersionExtract);
- zipfileWrite16(aBuf, pCds->flags);
- zipfileWrite16(aBuf, pCds->iCompression);
- zipfileWrite16(aBuf, pCds->mTime);
- zipfileWrite16(aBuf, pCds->mDate);
- zipfileWrite32(aBuf, pCds->crc32);
- zipfileWrite32(aBuf, pCds->szCompressed);
- zipfileWrite32(aBuf, pCds->szUncompressed);
- zipfileWrite16(aBuf, (u16)nPath);
- zipfileWrite16(aBuf, pCds->nExtra);
- assert( aBuf==&pTab->aBuffer[ZIPFILE_LFH_FIXED_SZ] );
- rc = zipfileAppendData(pTab, pTab->aBuffer, (int)(aBuf - pTab->aBuffer));
- if( rc==SQLITE_OK ){
- rc = zipfileAppendData(pTab, (const u8*)zPath, nPath);
- }
-
- if( rc==SQLITE_OK && pCds->nExtra ){
- aBuf = pTab->aBuffer;
- zipfileWrite16(aBuf, ZIPFILE_EXTRA_TIMESTAMP);
- zipfileWrite16(aBuf, 5);
- *aBuf++ = 0x01;
- zipfileWrite32(aBuf, mTime);
- rc = zipfileAppendData(pTab, pTab->aBuffer, 9);
- }
-
+ nBuf = zipfileSerializeLFH(pEntry, aBuf);
+ rc = zipfileAppendData(pTab, aBuf, nBuf);
if( rc==SQLITE_OK ){
+ pEntry->iDataOff = pTab->szCurrent;
rc = zipfileAppendData(pTab, pData, nData);
}
@@ -1220,15 +1386,15 @@ static int zipfileAppendEntry(
}
static int zipfileGetMode(
- ZipfileTab *pTab,
sqlite3_value *pVal,
- u32 defaultMode, /* Value to use if pVal IS NULL */
- u32 *pMode
+ int bIsDir, /* If true, default to directory */
+ u32 *pMode, /* OUT: Mode value */
+ char **pzErr /* OUT: Error message */
){
const char *z = (const char*)sqlite3_value_text(pVal);
u32 mode = 0;
if( z==0 ){
- mode = defaultMode;
+ mode = (bIsDir ? (S_IFDIR + 0755) : (S_IFREG + 0644));
}else if( z[0]>='0' && z[0]<='9' ){
mode = (unsigned int)sqlite3_value_int(pVal);
}else{
@@ -1238,9 +1404,7 @@ static int zipfileGetMode(
switch( z[0] ){
case '-': mode |= S_IFREG; break;
case 'd': mode |= S_IFDIR; break;
-#if !defined(_WIN32) && !defined(WIN32)
case 'l': mode |= S_IFLNK; break;
-#endif
default: goto parse_error;
}
for(i=1; i<10; i++){
@@ -1248,11 +1412,17 @@ static int zipfileGetMode(
else if( z[i]!='-' ) goto parse_error;
}
}
+ if( ((mode & S_IFDIR)==0)==bIsDir ){
+ /* The "mode" attribute is a directory, but data has been specified.
+ ** Or vice-versa - no data but "mode" is a file or symlink. */
+ *pzErr = sqlite3_mprintf("zipfile: mode does not match data");
+ return SQLITE_CONSTRAINT;
+ }
*pMode = mode;
return SQLITE_OK;
parse_error:
- pTab->base.zErrMsg = sqlite3_mprintf("zipfile: parse error in mode: %s", z);
+ *pzErr = sqlite3_mprintf("zipfile: parse error in mode: %s", z);
return SQLITE_ERROR;
}
@@ -1268,6 +1438,81 @@ static int zipfileComparePath(const char *zA, const char *zB, int nB){
return 1;
}
+static int zipfileBegin(sqlite3_vtab *pVtab){
+ ZipfileTab *pTab = (ZipfileTab*)pVtab;
+ int rc = SQLITE_OK;
+
+ assert( pTab->pWriteFd==0 );
+
+ /* Open a write fd on the file. Also load the entire central directory
+ ** structure into memory. During the transaction any new file data is
+ ** appended to the archive file, but the central directory is accumulated
+ ** in main-memory until the transaction is committed. */
+ pTab->pWriteFd = fopen(pTab->zFile, "ab+");
+ if( pTab->pWriteFd==0 ){
+ pTab->base.zErrMsg = sqlite3_mprintf(
+ "zipfile: failed to open file %s for writing", pTab->zFile
+ );
+ rc = SQLITE_ERROR;
+ }else{
+ fseek(pTab->pWriteFd, 0, SEEK_END);
+ pTab->szCurrent = pTab->szOrig = (i64)ftell(pTab->pWriteFd);
+ rc = zipfileLoadDirectory(pTab, 0, 0);
+ }
+
+ if( rc!=SQLITE_OK ){
+ zipfileCleanupTransaction(pTab);
+ }
+
+ return rc;
+}
+
+/*
+** Return the current time as a 32-bit timestamp in UNIX epoch format (like
+** time(2)).
+*/
+static u32 zipfileTime(void){
+ sqlite3_vfs *pVfs = sqlite3_vfs_find(0);
+ u32 ret;
+ if( pVfs->iVersion>=2 && pVfs->xCurrentTimeInt64 ){
+ i64 ms;
+ pVfs->xCurrentTimeInt64(pVfs, &ms);
+ ret = (u32)((ms/1000) - ((i64)24405875 * 8640));
+ }else{
+ double day;
+ pVfs->xCurrentTime(pVfs, &day);
+ ret = (u32)((day - 2440587.5) * 86400);
+ }
+ return ret;
+}
+
+/*
+** Return a 32-bit timestamp in UNIX epoch format.
+**
+** If the value passed as the only argument is either NULL or an SQL NULL,
+** return the current time. Otherwise, return the value stored in (*pVal)
+** cast to a 32-bit unsigned integer.
+*/
+static u32 zipfileGetTime(sqlite3_value *pVal){
+ if( pVal==0 || sqlite3_value_type(pVal)==SQLITE_NULL ){
+ return zipfileTime();
+ }
+ return (u32)sqlite3_value_int64(pVal);
+}
+
+/*
+** Unless it is NULL, entry pOld is currently part of the pTab->pFirstEntry
+** linked list. Remove it from the list and free the object.
+*/
+static void zipfileRemoveEntryFromList(ZipfileTab *pTab, ZipfileEntry *pOld){
+ if( pOld ){
+ ZipfileEntry **pp;
+ for(pp=&pTab->pFirstEntry; (*pp)!=pOld; pp=&((*pp)->pNext));
+ *pp = (*pp)->pNext;
+ zipfileEntryFree(pOld);
+ }
+}
+
/*
** xUpdate method.
*/
@@ -1282,7 +1527,7 @@ static int zipfileUpdate(
ZipfileEntry *pNew = 0; /* New in-memory CDS entry */
u32 mode = 0; /* Mode for new entry */
- i64 mTime = 0; /* Modification time for new entry */
+ u32 mTime = 0; /* Modification time for new entry */
i64 sz = 0; /* Uncompressed size */
const char *zPath = 0; /* Path for new entry */
int nPath = 0; /* strlen(zPath) */
@@ -1291,217 +1536,239 @@ static int zipfileUpdate(
int iMethod = 0; /* Compression method for new entry */
u8 *pFree = 0; /* Free this */
char *zFree = 0; /* Also free this */
- ZipfileCDS cds; /* New Central Directory Structure entry */
ZipfileEntry *pOld = 0;
+ ZipfileEntry *pOld2 = 0;
+ int bUpdate = 0; /* True for an update that modifies "name" */
int bIsDir = 0;
u32 iCrc32 = 0;
- assert( pTab->zFile );
- assert( pTab->pWriteFd );
+ if( pTab->pWriteFd==0 ){
+ rc = zipfileBegin(pVtab);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+ /* If this is a DELETE or UPDATE, find the archive entry to delete. */
if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){
const char *zDelete = (const char*)sqlite3_value_text(apVal[0]);
int nDelete = (int)strlen(zDelete);
+ if( nVal>1 ){
+ const char *zUpdate = (const char*)sqlite3_value_text(apVal[1]);
+ if( zUpdate && zipfileComparePath(zUpdate, zDelete, nDelete)!=0 ){
+ bUpdate = 1;
+ }
+ }
for(pOld=pTab->pFirstEntry; 1; pOld=pOld->pNext){
- if( pOld->bDeleted ) continue;
- if( zipfileComparePath(pOld->zPath, zDelete, nDelete)==0 ){
- pOld->bDeleted = 1;
+ if( zipfileComparePath(pOld->cds.zFile, zDelete, nDelete)==0 ){
break;
}
assert( pOld->pNext );
}
- if( nVal==1 ) return SQLITE_OK;
}
- /* Check that "sz" and "rawdata" are both NULL: */
- if( sqlite3_value_type(apVal[5])!=SQLITE_NULL
- || sqlite3_value_type(apVal[6])!=SQLITE_NULL
- ){
- rc = SQLITE_CONSTRAINT;
- }
+ if( nVal>1 ){
+ /* Check that "sz" and "rawdata" are both NULL: */
+ if( sqlite3_value_type(apVal[5])!=SQLITE_NULL ){
+ zipfileTableErr(pTab, "sz must be NULL");
+ rc = SQLITE_CONSTRAINT;
+ }
+ if( sqlite3_value_type(apVal[6])!=SQLITE_NULL ){
+ zipfileTableErr(pTab, "rawdata must be NULL");
+ rc = SQLITE_CONSTRAINT;
+ }
- if( rc==SQLITE_OK ){
- if( sqlite3_value_type(apVal[7])==SQLITE_NULL ){
- /* data=NULL. A directory */
- bIsDir = 1;
- }else{
- /* Value specified for "data", and possibly "method". This must be
- ** a regular file or a symlink. */
- const u8 *aIn = sqlite3_value_blob(apVal[7]);
- int nIn = sqlite3_value_bytes(apVal[7]);
- int bAuto = sqlite3_value_type(apVal[8])==SQLITE_NULL;
-
- iMethod = sqlite3_value_int(apVal[8]);
- sz = nIn;
- pData = aIn;
- nData = nIn;
- if( iMethod!=0 && iMethod!=8 ){
- rc = SQLITE_CONSTRAINT;
+ if( rc==SQLITE_OK ){
+ if( sqlite3_value_type(apVal[7])==SQLITE_NULL ){
+ /* data=NULL. A directory */
+ bIsDir = 1;
}else{
- if( bAuto || iMethod ){
- int nCmp;
- rc = zipfileDeflate(pTab, aIn, nIn, &pFree, &nCmp);
- if( rc==SQLITE_OK ){
- if( iMethod || nCmp<nIn ){
- iMethod = 8;
- pData = pFree;
- nData = nCmp;
+ /* Value specified for "data", and possibly "method". This must be
+ ** a regular file or a symlink. */
+ const u8 *aIn = sqlite3_value_blob(apVal[7]);
+ int nIn = sqlite3_value_bytes(apVal[7]);
+ int bAuto = sqlite3_value_type(apVal[8])==SQLITE_NULL;
+
+ iMethod = sqlite3_value_int(apVal[8]);
+ sz = nIn;
+ pData = aIn;
+ nData = nIn;
+ if( iMethod!=0 && iMethod!=8 ){
+ zipfileTableErr(pTab, "unknown compression method: %d", iMethod);
+ rc = SQLITE_CONSTRAINT;
+ }else{
+ if( bAuto || iMethod ){
+ int nCmp;
+ rc = zipfileDeflate(aIn, nIn, &pFree, &nCmp, &pTab->base.zErrMsg);
+ if( rc==SQLITE_OK ){
+ if( iMethod || nCmp<nIn ){
+ iMethod = 8;
+ pData = pFree;
+ nData = nCmp;
+ }
}
}
+ iCrc32 = crc32(0, aIn, nIn);
}
- iCrc32 = crc32(0, aIn, nIn);
}
}
- }
- if( rc==SQLITE_OK ){
- rc = zipfileGetMode(pTab, apVal[3],
- (bIsDir ? (S_IFDIR + 0755) : (S_IFREG + 0644)), &mode
- );
- if( rc==SQLITE_OK && (bIsDir == ((mode & S_IFDIR)==0)) ){
- /* The "mode" attribute is a directory, but data has been specified.
- ** Or vice-versa - no data but "mode" is a file or symlink. */
- rc = SQLITE_CONSTRAINT;
+ if( rc==SQLITE_OK ){
+ rc = zipfileGetMode(apVal[3], bIsDir, &mode, &pTab->base.zErrMsg);
}
- }
- if( rc==SQLITE_OK ){
- zPath = (const char*)sqlite3_value_text(apVal[2]);
- nPath = (int)strlen(zPath);
- if( sqlite3_value_type(apVal[4])==SQLITE_NULL ){
- mTime = (sqlite3_int64)time(0);
- }else{
- mTime = sqlite3_value_int64(apVal[4]);
+ if( rc==SQLITE_OK ){
+ zPath = (const char*)sqlite3_value_text(apVal[2]);
+ nPath = (int)strlen(zPath);
+ mTime = zipfileGetTime(apVal[4]);
}
- }
- if( rc==SQLITE_OK && bIsDir ){
- /* For a directory, check that the last character in the path is a
- ** '/'. This appears to be required for compatibility with info-zip
- ** (the unzip command on unix). It does not create directories
- ** otherwise. */
- if( zPath[nPath-1]!='/' ){
- zFree = sqlite3_mprintf("%s/", zPath);
- if( zFree==0 ){ rc = SQLITE_NOMEM; }
- zPath = (const char*)zFree;
- nPath++;
+ if( rc==SQLITE_OK && bIsDir ){
+ /* For a directory, check that the last character in the path is a
+ ** '/'. This appears to be required for compatibility with info-zip
+ ** (the unzip command on unix). It does not create directories
+ ** otherwise. */
+ if( zPath[nPath-1]!='/' ){
+ zFree = sqlite3_mprintf("%s/", zPath);
+ if( zFree==0 ){ rc = SQLITE_NOMEM; }
+ zPath = (const char*)zFree;
+ nPath++;
+ }
}
- }
- /* Check that we're not inserting a duplicate entry */
- if( rc==SQLITE_OK ){
- ZipfileEntry *p;
- for(p=pTab->pFirstEntry; p; p=p->pNext){
- if( p->bDeleted ) continue;
- if( zipfileComparePath(p->zPath, zPath, nPath)==0 ){
- rc = SQLITE_CONSTRAINT;
- break;
+ /* Check that we're not inserting a duplicate entry -OR- updating an
+ ** entry with a path, thereby making it into a duplicate. */
+ if( (pOld==0 || bUpdate) && rc==SQLITE_OK ){
+ ZipfileEntry *p;
+ for(p=pTab->pFirstEntry; p; p=p->pNext){
+ if( zipfileComparePath(p->cds.zFile, zPath, nPath)==0 ){
+ switch( sqlite3_vtab_on_conflict(pTab->db) ){
+ case SQLITE_IGNORE: {
+ goto zipfile_update_done;
+ }
+ case SQLITE_REPLACE: {
+ pOld2 = p;
+ break;
+ }
+ default: {
+ zipfileTableErr(pTab, "duplicate name: \"%s\"", zPath);
+ rc = SQLITE_CONSTRAINT;
+ break;
+ }
+ }
+ break;
+ }
}
}
- }
- if( rc==SQLITE_OK ){
- /* Create the new CDS record. */
- memset(&cds, 0, sizeof(cds));
- cds.iVersionMadeBy = ZIPFILE_NEWENTRY_MADEBY;
- cds.iVersionExtract = ZIPFILE_NEWENTRY_REQUIRED;
- cds.flags = ZIPFILE_NEWENTRY_FLAGS;
- cds.iCompression = (u16)iMethod;
- zipfileMtimeToDos(&cds, (u32)mTime);
- cds.crc32 = iCrc32;
- cds.szCompressed = nData;
- cds.szUncompressed = (u32)sz;
- cds.iExternalAttr = (mode<<16);
- cds.iOffset = (u32)pTab->szCurrent;
- pNew = zipfileNewEntry(&cds, zPath, nPath, (u32)mTime);
- if( pNew==0 ){
- rc = SQLITE_NOMEM;
- }else{
- zipfileAddEntry(pTab, pOld, pNew);
+ if( rc==SQLITE_OK ){
+ /* Create the new CDS record. */
+ pNew = zipfileNewEntry(zPath);
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ pNew->cds.iVersionMadeBy = ZIPFILE_NEWENTRY_MADEBY;
+ pNew->cds.iVersionExtract = ZIPFILE_NEWENTRY_REQUIRED;
+ pNew->cds.flags = ZIPFILE_NEWENTRY_FLAGS;
+ pNew->cds.iCompression = (u16)iMethod;
+ zipfileMtimeToDos(&pNew->cds, mTime);
+ pNew->cds.crc32 = iCrc32;
+ pNew->cds.szCompressed = nData;
+ pNew->cds.szUncompressed = (u32)sz;
+ pNew->cds.iExternalAttr = (mode<<16);
+ pNew->cds.iOffset = (u32)pTab->szCurrent;
+ pNew->cds.nFile = (u16)nPath;
+ pNew->mUnixTime = (u32)mTime;
+ rc = zipfileAppendEntry(pTab, pNew, pData, nData);
+ zipfileAddEntry(pTab, pOld, pNew);
+ }
}
}
- /* Append the new header+file to the archive */
- if( rc==SQLITE_OK ){
- rc = zipfileAppendEntry(pTab, &cds, zPath, nPath, pData, nData, (u32)mTime);
- }
+ if( rc==SQLITE_OK && (pOld || pOld2) ){
+ ZipfileCsr *pCsr;
+ for(pCsr=pTab->pCsrList; pCsr; pCsr=pCsr->pCsrNext){
+ if( pCsr->pCurrent && (pCsr->pCurrent==pOld || pCsr->pCurrent==pOld2) ){
+ pCsr->pCurrent = pCsr->pCurrent->pNext;
+ pCsr->bNoop = 1;
+ }
+ }
- if( rc!=SQLITE_OK && pOld ){
- pOld->bDeleted = 0;
+ zipfileRemoveEntryFromList(pTab, pOld);
+ zipfileRemoveEntryFromList(pTab, pOld2);
}
+
+zipfile_update_done:
sqlite3_free(pFree);
sqlite3_free(zFree);
return rc;
}
-static int zipfileAppendEOCD(ZipfileTab *pTab, ZipfileEOCD *p){
- u8 *aBuf = pTab->aBuffer;
-
- zipfileWrite32(aBuf, ZIPFILE_SIGNATURE_EOCD);
- zipfileWrite16(aBuf, p->iDisk);
- zipfileWrite16(aBuf, p->iFirstDisk);
- zipfileWrite16(aBuf, p->nEntry);
- zipfileWrite16(aBuf, p->nEntryTotal);
- zipfileWrite32(aBuf, p->nSize);
- zipfileWrite32(aBuf, p->iOffset);
- zipfileWrite16(aBuf, 0); /* Size of trailing comment in bytes*/
-
- assert( (aBuf-pTab->aBuffer)==22 );
- return zipfileAppendData(pTab, pTab->aBuffer, (int)(aBuf - pTab->aBuffer));
+static int zipfileSerializeEOCD(ZipfileEOCD *p, u8 *aBuf){
+ u8 *a = aBuf;
+ zipfileWrite32(a, ZIPFILE_SIGNATURE_EOCD);
+ zipfileWrite16(a, p->iDisk);
+ zipfileWrite16(a, p->iFirstDisk);
+ zipfileWrite16(a, p->nEntry);
+ zipfileWrite16(a, p->nEntryTotal);
+ zipfileWrite32(a, p->nSize);
+ zipfileWrite32(a, p->iOffset);
+ zipfileWrite16(a, 0); /* Size of trailing comment in bytes*/
+
+ return a-aBuf;
}
-static void zipfileCleanupTransaction(ZipfileTab *pTab){
- ZipfileEntry *pEntry;
- ZipfileEntry *pNext;
-
- for(pEntry=pTab->pFirstEntry; pEntry; pEntry=pNext){
- pNext = pEntry->pNext;
- sqlite3_free(pEntry);
- }
- pTab->pFirstEntry = 0;
- pTab->pLastEntry = 0;
- fclose(pTab->pWriteFd);
- pTab->pWriteFd = 0;
- pTab->szCurrent = 0;
- pTab->szOrig = 0;
+static int zipfileAppendEOCD(ZipfileTab *pTab, ZipfileEOCD *p){
+ int nBuf = zipfileSerializeEOCD(p, pTab->aBuffer);
+ assert( nBuf==ZIPFILE_EOCD_FIXED_SZ );
+ return zipfileAppendData(pTab, pTab->aBuffer, nBuf);
}
-static int zipfileBegin(sqlite3_vtab *pVtab){
- ZipfileTab *pTab = (ZipfileTab*)pVtab;
- int rc = SQLITE_OK;
-
- assert( pTab->pWriteFd==0 );
+/*
+** Serialize the CDS structure into buffer aBuf[]. Return the number
+** of bytes written.
+*/
+static int zipfileSerializeCDS(ZipfileEntry *pEntry, u8 *aBuf){
+ u8 *a = aBuf;
+ ZipfileCDS *pCDS = &pEntry->cds;
- /* This table is only writable if a default archive path was specified
- ** as part of the CREATE VIRTUAL TABLE statement. */
- if( pTab->zFile==0 ){
- pTab->base.zErrMsg = sqlite3_mprintf(
- "zipfile: writing requires a default archive"
- );
- return SQLITE_ERROR;
+ if( pEntry->aExtra==0 ){
+ pCDS->nExtra = 9;
}
- /* Open a write fd on the file. Also load the entire central directory
- ** structure into memory. During the transaction any new file data is
- ** appended to the archive file, but the central directory is accumulated
- ** in main-memory until the transaction is committed. */
- pTab->pWriteFd = fopen(pTab->zFile, "ab+");
- if( pTab->pWriteFd==0 ){
- pTab->base.zErrMsg = sqlite3_mprintf(
- "zipfile: failed to open file %s for writing", pTab->zFile
- );
- rc = SQLITE_ERROR;
+ zipfileWrite32(a, ZIPFILE_SIGNATURE_CDS);
+ zipfileWrite16(a, pCDS->iVersionMadeBy);
+ zipfileWrite16(a, pCDS->iVersionExtract);
+ zipfileWrite16(a, pCDS->flags);
+ zipfileWrite16(a, pCDS->iCompression);
+ zipfileWrite16(a, pCDS->mTime);
+ zipfileWrite16(a, pCDS->mDate);
+ zipfileWrite32(a, pCDS->crc32);
+ zipfileWrite32(a, pCDS->szCompressed);
+ zipfileWrite32(a, pCDS->szUncompressed);
+ assert( a==&aBuf[ZIPFILE_CDS_NFILE_OFF] );
+ zipfileWrite16(a, pCDS->nFile);
+ zipfileWrite16(a, pCDS->nExtra);
+ zipfileWrite16(a, pCDS->nComment);
+ zipfileWrite16(a, pCDS->iDiskStart);
+ zipfileWrite16(a, pCDS->iInternalAttr);
+ zipfileWrite32(a, pCDS->iExternalAttr);
+ zipfileWrite32(a, pCDS->iOffset);
+
+ memcpy(a, pCDS->zFile, pCDS->nFile);
+ a += pCDS->nFile;
+
+ if( pEntry->aExtra ){
+ int n = (int)pCDS->nExtra + (int)pCDS->nComment;
+ memcpy(a, pEntry->aExtra, n);
+ a += n;
}else{
- fseek(pTab->pWriteFd, 0, SEEK_END);
- pTab->szCurrent = pTab->szOrig = (i64)ftell(pTab->pWriteFd);
- rc = zipfileLoadDirectory(pTab);
+ assert( pCDS->nExtra==9 );
+ zipfileWrite16(a, ZIPFILE_EXTRA_TIMESTAMP);
+ zipfileWrite16(a, 5);
+ *a++ = 0x01;
+ zipfileWrite32(a, pEntry->mUnixTime);
}
- if( rc!=SQLITE_OK ){
- zipfileCleanupTransaction(pTab);
- }
-
- return rc;
+ return a-aBuf;
}
static int zipfileCommit(sqlite3_vtab *pVtab){
@@ -1513,10 +1780,10 @@ static int zipfileCommit(sqlite3_vtab *pVtab){
ZipfileEOCD eocd;
int nEntry = 0;
- /* Write out all undeleted entries */
+ /* Write out all entries */
for(p=pTab->pFirstEntry; rc==SQLITE_OK && p; p=p->pNext){
- if( p->bDeleted ) continue;
- rc = zipfileAppendData(pTab, p->aCdsEntry, p->nCdsEntry);
+ int n = zipfileSerializeCDS(p, pTab->aBuffer);
+ rc = zipfileAppendData(pTab, pTab->aBuffer, n);
nEntry++;
}
@@ -1557,7 +1824,7 @@ static void zipfileFunctionCds(
pCsr = zipfileFindCursor(pTab, sqlite3_value_int64(argv[0]));
if( pCsr ){
- ZipfileCDS *p = &pCsr->cds;
+ ZipfileCDS *p = &pCsr->pCurrent->cds;
char *zRes = sqlite3_mprintf("{"
"\"version-made-by\" : %u, "
"\"version-to-extract\" : %u, "
@@ -1594,7 +1861,6 @@ static void zipfileFunctionCds(
}
}
-
/*
** xFindFunction method.
*/
@@ -1605,18 +1871,259 @@ static int zipfileFindFunction(
void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */
void **ppArg /* OUT: User data for *pxFunc */
){
- if( nArg>0 ){
- if( sqlite3_stricmp("zipfile_cds", zName)==0 ){
- *pxFunc = zipfileFunctionCds;
- *ppArg = (void*)pVtab;
- return 1;
+ if( sqlite3_stricmp("zipfile_cds", zName)==0 ){
+ *pxFunc = zipfileFunctionCds;
+ *ppArg = (void*)pVtab;
+ return 1;
+ }
+ return 0;
+}
+
+typedef struct ZipfileBuffer ZipfileBuffer;
+struct ZipfileBuffer {
+ u8 *a; /* Pointer to buffer */
+ int n; /* Size of buffer in bytes */
+ int nAlloc; /* Byte allocated at a[] */
+};
+
+typedef struct ZipfileCtx ZipfileCtx;
+struct ZipfileCtx {
+ int nEntry;
+ ZipfileBuffer body;
+ ZipfileBuffer cds;
+};
+
+static int zipfileBufferGrow(ZipfileBuffer *pBuf, int nByte){
+ if( pBuf->n+nByte>pBuf->nAlloc ){
+ u8 *aNew;
+ int nNew = pBuf->n ? pBuf->n*2 : 512;
+ int nReq = pBuf->n + nByte;
+
+ while( nNew<nReq ) nNew = nNew*2;
+ aNew = sqlite3_realloc(pBuf->a, nNew);
+ if( aNew==0 ) return SQLITE_NOMEM;
+ pBuf->a = aNew;
+ pBuf->nAlloc = nNew;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** xStep() callback for the zipfile() aggregate. This can be called in
+** any of the following ways:
+**
+** SELECT zipfile(name,data) ...
+** SELECT zipfile(name,mode,mtime,data) ...
+** SELECT zipfile(name,mode,mtime,data,method) ...
+*/
+void zipfileStep(sqlite3_context *pCtx, int nVal, sqlite3_value **apVal){
+ ZipfileCtx *p; /* Aggregate function context */
+ ZipfileEntry e; /* New entry to add to zip archive */
+
+ sqlite3_value *pName = 0;
+ sqlite3_value *pMode = 0;
+ sqlite3_value *pMtime = 0;
+ sqlite3_value *pData = 0;
+ sqlite3_value *pMethod = 0;
+
+ int bIsDir = 0;
+ u32 mode;
+ int rc = SQLITE_OK;
+ char *zErr = 0;
+
+ int iMethod = -1; /* Compression method to use (0 or 8) */
+
+ const u8 *aData = 0; /* Possibly compressed data for new entry */
+ int nData = 0; /* Size of aData[] in bytes */
+ int szUncompressed = 0; /* Size of data before compression */
+ u8 *aFree = 0; /* Free this before returning */
+ u32 iCrc32 = 0; /* crc32 of uncompressed data */
+
+ char *zName = 0; /* Path (name) of new entry */
+ int nName = 0; /* Size of zName in bytes */
+ char *zFree = 0; /* Free this before returning */
+ int nByte;
+
+ memset(&e, 0, sizeof(e));
+ p = (ZipfileCtx*)sqlite3_aggregate_context(pCtx, sizeof(ZipfileCtx));
+ if( p==0 ) return;
+
+ /* Martial the arguments into stack variables */
+ if( nVal!=2 && nVal!=4 && nVal!=5 ){
+ zErr = sqlite3_mprintf("wrong number of arguments to function zipfile()");
+ rc = SQLITE_ERROR;
+ goto zipfile_step_out;
+ }
+ pName = apVal[0];
+ if( nVal==2 ){
+ pData = apVal[1];
+ }else{
+ pMode = apVal[1];
+ pMtime = apVal[2];
+ pData = apVal[3];
+ if( nVal==5 ){
+ pMethod = apVal[4];
}
}
- return 0;
+ /* Check that the 'name' parameter looks ok. */
+ zName = (char*)sqlite3_value_text(pName);
+ nName = sqlite3_value_bytes(pName);
+ if( zName==0 ){
+ zErr = sqlite3_mprintf("first argument to zipfile() must be non-NULL");
+ rc = SQLITE_ERROR;
+ goto zipfile_step_out;
+ }
+
+ /* Inspect the 'method' parameter. This must be either 0 (store), 8 (use
+ ** deflate compression) or NULL (choose automatically). */
+ if( pMethod && SQLITE_NULL!=sqlite3_value_type(pMethod) ){
+ iMethod = (int)sqlite3_value_int64(pMethod);
+ if( iMethod!=0 && iMethod!=8 ){
+ zErr = sqlite3_mprintf("illegal method value: %d", iMethod);
+ rc = SQLITE_ERROR;
+ goto zipfile_step_out;
+ }
+ }
+
+ /* Now inspect the data. If this is NULL, then the new entry must be a
+ ** directory. Otherwise, figure out whether or not the data should
+ ** be deflated or simply stored in the zip archive. */
+ if( sqlite3_value_type(pData)==SQLITE_NULL ){
+ bIsDir = 1;
+ iMethod = 0;
+ }else{
+ aData = sqlite3_value_blob(pData);
+ szUncompressed = nData = sqlite3_value_bytes(pData);
+ iCrc32 = crc32(0, aData, nData);
+ if( iMethod<0 || iMethod==8 ){
+ int nOut = 0;
+ rc = zipfileDeflate(aData, nData, &aFree, &nOut, &zErr);
+ if( rc!=SQLITE_OK ){
+ goto zipfile_step_out;
+ }
+ if( iMethod==8 || nOut<nData ){
+ aData = aFree;
+ nData = nOut;
+ iMethod = 8;
+ }else{
+ iMethod = 0;
+ }
+ }
+ }
+
+ /* Decode the "mode" argument. */
+ rc = zipfileGetMode(pMode, bIsDir, &mode, &zErr);
+ if( rc ) goto zipfile_step_out;
+
+ /* Decode the "mtime" argument. */
+ e.mUnixTime = zipfileGetTime(pMtime);
+
+ /* If this is a directory entry, ensure that there is exactly one '/'
+ ** at the end of the path. Or, if this is not a directory and the path
+ ** ends in '/' it is an error. */
+ if( bIsDir==0 ){
+ if( zName[nName-1]=='/' ){
+ zErr = sqlite3_mprintf("non-directory name must not end with /");
+ rc = SQLITE_ERROR;
+ goto zipfile_step_out;
+ }
+ }else{
+ if( zName[nName-1]!='/' ){
+ zName = zFree = sqlite3_mprintf("%s/", zName);
+ nName++;
+ if( zName==0 ){
+ rc = SQLITE_NOMEM;
+ goto zipfile_step_out;
+ }
+ }else{
+ while( nName>1 && zName[nName-2]=='/' ) nName--;
+ }
+ }
+
+ /* Assemble the ZipfileEntry object for the new zip archive entry */
+ e.cds.iVersionMadeBy = ZIPFILE_NEWENTRY_MADEBY;
+ e.cds.iVersionExtract = ZIPFILE_NEWENTRY_REQUIRED;
+ e.cds.flags = ZIPFILE_NEWENTRY_FLAGS;
+ e.cds.iCompression = (u16)iMethod;
+ zipfileMtimeToDos(&e.cds, (u32)e.mUnixTime);
+ e.cds.crc32 = iCrc32;
+ e.cds.szCompressed = nData;
+ e.cds.szUncompressed = szUncompressed;
+ e.cds.iExternalAttr = (mode<<16);
+ e.cds.iOffset = p->body.n;
+ e.cds.nFile = (u16)nName;
+ e.cds.zFile = zName;
+
+ /* Append the LFH to the body of the new archive */
+ nByte = ZIPFILE_LFH_FIXED_SZ + e.cds.nFile + 9;
+ if( (rc = zipfileBufferGrow(&p->body, nByte)) ) goto zipfile_step_out;
+ p->body.n += zipfileSerializeLFH(&e, &p->body.a[p->body.n]);
+
+ /* Append the data to the body of the new archive */
+ if( nData>0 ){
+ if( (rc = zipfileBufferGrow(&p->body, nData)) ) goto zipfile_step_out;
+ memcpy(&p->body.a[p->body.n], aData, nData);
+ p->body.n += nData;
+ }
+
+ /* Append the CDS record to the directory of the new archive */
+ nByte = ZIPFILE_CDS_FIXED_SZ + e.cds.nFile + 9;
+ if( (rc = zipfileBufferGrow(&p->cds, nByte)) ) goto zipfile_step_out;
+ p->cds.n += zipfileSerializeCDS(&e, &p->cds.a[p->cds.n]);
+
+ /* Increment the count of entries in the archive */
+ p->nEntry++;
+
+ zipfile_step_out:
+ sqlite3_free(aFree);
+ sqlite3_free(zFree);
+ if( rc ){
+ if( zErr ){
+ sqlite3_result_error(pCtx, zErr, -1);
+ }else{
+ sqlite3_result_error_code(pCtx, rc);
+ }
+ }
+ sqlite3_free(zErr);
}
/*
+** xFinalize() callback for zipfile aggregate function.
+*/
+void zipfileFinal(sqlite3_context *pCtx){
+ ZipfileCtx *p;
+ ZipfileEOCD eocd;
+ int nZip;
+ u8 *aZip;
+
+ p = (ZipfileCtx*)sqlite3_aggregate_context(pCtx, sizeof(ZipfileCtx));
+ if( p==0 ) return;
+ if( p->nEntry>0 ){
+ memset(&eocd, 0, sizeof(eocd));
+ eocd.nEntry = (u16)p->nEntry;
+ eocd.nEntryTotal = (u16)p->nEntry;
+ eocd.nSize = p->cds.n;
+ eocd.iOffset = p->body.n;
+
+ nZip = p->body.n + p->cds.n + ZIPFILE_EOCD_FIXED_SZ;
+ aZip = (u8*)sqlite3_malloc(nZip);
+ if( aZip==0 ){
+ sqlite3_result_error_nomem(pCtx);
+ }else{
+ memcpy(aZip, p->body.a, p->body.n);
+ memcpy(&aZip[p->body.n], p->cds.a, p->cds.n);
+ zipfileSerializeEOCD(&eocd, &aZip[p->body.n + p->cds.n]);
+ sqlite3_result_blob(pCtx, aZip, nZip, zipfileFree);
+ }
+ }
+
+ sqlite3_free(p->body.a);
+ sqlite3_free(p->cds.a);
+}
+
+
+/*
** Register the "zipfile" virtual table.
*/
static int zipfileRegister(sqlite3 *db){
@@ -1633,7 +2140,7 @@ static int zipfileRegister(sqlite3 *db){
zipfileNext, /* xNext - advance a cursor */
zipfileEof, /* xEof - check for end of scan */
zipfileColumn, /* xColumn - read data */
- zipfileRowid, /* xRowid - read data */
+ 0, /* xRowid - read data */
zipfileUpdate, /* xUpdate */
zipfileBegin, /* xBegin */
0, /* xSync */
@@ -1644,8 +2151,11 @@ static int zipfileRegister(sqlite3 *db){
};
int rc = sqlite3_create_module(db, "zipfile" , &zipfileModule, 0);
+ if( rc==SQLITE_OK ) rc = sqlite3_overload_function(db, "zipfile_cds", -1);
if( rc==SQLITE_OK ){
- rc = sqlite3_overload_function(db, "zipfile_cds", -1);
+ rc = sqlite3_create_function(db, "zipfile", -1, SQLITE_UTF8, 0, 0,
+ zipfileStep, zipfileFinal
+ );
}
return rc;
}
diff --git a/ext/misc/zorder.c b/ext/misc/zorder.c
new file mode 100644
index 0000000..c385d3c
--- /dev/null
+++ b/ext/misc/zorder.c
@@ -0,0 +1,102 @@
+/*
+** 2018-02-09
+**
+** 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.
+**
+******************************************************************************
+**
+** SQL functions for z-order (Morton code) transformations.
+**
+** zorder(X0,X0,..,xN) Generate an N+1 dimension Morton code
+**
+** unzorder(Z,N,I) Extract the I-th dimension from N-dimensional
+** Morton code Z.
+*/
+#include "sqlite3ext.h"
+SQLITE_EXTENSION_INIT1
+#include <assert.h>
+#include <string.h>
+
+/*
+** Functions: zorder(X0,X1,....)
+**
+** Convert integers X0, X1, ... into morton code.
+**
+** The output is a signed 64-bit integer. If any argument is too large,
+** an error is thrown.
+*/
+static void zorderFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ sqlite3_int64 z, x[63];
+ int i, j;
+ z = 0;
+ for(i=0; i<argc; i++){
+ x[i] = sqlite3_value_int64(argv[i]);
+ }
+ if( argc>0 ){
+ for(i=0; i<63; i++){
+ j = i%argc;
+ z |= (x[j]&1)<<i;
+ x[j] >>= 1;
+ }
+ }
+ sqlite3_result_int64(context, z);
+ for(i=0; i<argc; i++){
+ if( x[i] ){
+ sqlite3_result_error(context, "parameter too large", -1);
+ }
+ }
+}
+
+
+/*
+** Functions: unzorder(Z,N,I)
+**
+** Assuming that Z is an N-dimensional Morton code, extract the I-th
+** dimension.
+*/
+static void unzorderFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ sqlite3_int64 z, n, i, x;
+ int j, k;
+ z = sqlite3_value_int64(argv[0]);
+ n = sqlite3_value_int64(argv[1]);
+ i = sqlite3_value_int64(argv[2]);
+ x = 0;
+ for(k=0, j=i; j<63; j+=n, k++){
+ x |= ((z>>j)&1)<<k;
+ }
+ sqlite3_result_int64(context, x);
+}
+
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_zorder_init(
+ sqlite3 *db,
+ char **pzErrMsg,
+ const sqlite3_api_routines *pApi
+){
+ int rc = SQLITE_OK;
+ SQLITE_EXTENSION_INIT2(pApi);
+ (void)pzErrMsg; /* Unused parameter */
+ rc = sqlite3_create_function(db, "zorder", -1, SQLITE_UTF8, 0,
+ zorderFunc, 0, 0);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_create_function(db, "unzorder", 3, SQLITE_UTF8, 0,
+ unzorderFunc, 0, 0);
+ }
+ return rc;
+}
diff --git a/ext/rbu/rbu_common.tcl b/ext/rbu/rbu_common.tcl
index a58c3aa..b216fe0 100644
--- a/ext/rbu/rbu_common.tcl
+++ b/ext/rbu/rbu_common.tcl
@@ -71,6 +71,7 @@ proc step_rbu {target rbu} {
}
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 }
while 1 {
diff --git a/ext/rbu/rbucollate.test b/ext/rbu/rbucollate.test
new file mode 100644
index 0000000..3b3b022
--- /dev/null
+++ b/ext/rbu/rbucollate.test
@@ -0,0 +1,63 @@
+# 2018 March 22
+#
+# 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 rbucollate
+
+ifcapable !icu_collations {
+ finish_test
+ return
+}
+
+db close
+sqlite3_shutdown
+sqlite3_config_uri 1
+reset_db
+
+# Create a simple RBU database. That expects to write to a table:
+#
+# CREATE TABLE t1(a PRIMARY KEY, b, c);
+#
+proc create_rbu1 {filename} {
+ forcedelete $filename
+ sqlite3 rbu1 $filename
+ rbu1 eval {
+ CREATE TABLE data_t1(a, b, c, rbu_control);
+ INSERT INTO data_t1 VALUES('a', 'one', 1, 0);
+ INSERT INTO data_t1 VALUES('b', 'two', 2, 0);
+ INSERT INTO data_t1 VALUES('c', 'three', 3, 0);
+ }
+ rbu1 close
+ return $filename
+}
+
+do_execsql_test 1.0 {
+ SELECT icu_load_collation('en_US', 'my-collate');
+ CREATE TABLE t1(a COLLATE "my-collate" PRIMARY KEY, b, c);
+} {{}}
+
+do_test 1.2 {
+ create_rbu1 testrbu.db
+ sqlite3rbu rbu test.db testrbu.db
+ rbu dbMain_eval { SELECT icu_load_collation('en_US', 'my-collate') }
+ rbu dbRbu_eval { SELECT icu_load_collation('en_US', 'my-collate') }
+ while 1 {
+ set rc [rbu step]
+ if {$rc!="SQLITE_OK"} break
+ }
+ rbu close
+ db eval { SELECT * FROM t1 }
+} {a one 1 b two 2 c three 3}
+
+#forcedelete testrbu.db
+finish_test
+
diff --git a/ext/rbu/sqlite3rbu.c b/ext/rbu/sqlite3rbu.c
index 0289ef7..a3d54f9 100644
--- a/ext/rbu/sqlite3rbu.c
+++ b/ext/rbu/sqlite3rbu.c
@@ -1806,7 +1806,7 @@ static void rbuCreateImposterTable2(sqlite3rbu *p, RbuObjIter *pIter){
int iCid = sqlite3_column_int(pXInfo, 1);
int bDesc = sqlite3_column_int(pXInfo, 3);
const char *zCollate = (const char*)sqlite3_column_text(pXInfo, 4);
- zCols = rbuMPrintf(p, "%z%sc%d %s COLLATE %s", zCols, zComma,
+ zCols = rbuMPrintf(p, "%z%sc%d %s COLLATE %Q", zCols, zComma,
iCid, pIter->azTblType[iCid], zCollate
);
zPk = rbuMPrintf(p, "%z%sc%d%s", zPk, zComma, iCid, bDesc?" DESC":"");
@@ -1867,7 +1867,7 @@ static void rbuCreateImposterTable(sqlite3rbu *p, RbuObjIter *pIter){
** "PRIMARY KEY" to the imposter table column declaration. */
zPk = "PRIMARY KEY ";
}
- zSql = rbuMPrintf(p, "%z%s\"%w\" %s %sCOLLATE %s%s",
+ zSql = rbuMPrintf(p, "%z%s\"%w\" %s %sCOLLATE %Q%s",
zSql, zComma, zCol, pIter->azTblType[iCol], zPk, zColl,
(pIter->abNotNull[iCol] ? " NOT NULL" : "")
);
diff --git a/ext/rbu/test_rbu.c b/ext/rbu/test_rbu.c
index 631bff2..e0b4d77 100644
--- a/ext/rbu/test_rbu.c
+++ b/ext/rbu/test_rbu.c
@@ -81,6 +81,7 @@ static int SQLITE_TCLAPI test_sqlite3rbu_cmd(
{"close_no_error", 2, ""}, /* 9 */
{"temp_size_limit", 3, "LIMIT"}, /* 10 */
{"temp_size", 2, ""}, /* 11 */
+ {"dbRbu_eval", 3, "SQL"}, /* 12 */
{0,0,0}
};
int iCmd;
@@ -146,8 +147,9 @@ static int SQLITE_TCLAPI test_sqlite3rbu_cmd(
break;
}
- case 4: /* dbMain_eval */ {
- sqlite3 *db = sqlite3rbu_db(pRbu, 0);
+ case 12: /* dbRbu_eval */
+ case 4: /* dbMain_eval */ {
+ sqlite3 *db = sqlite3rbu_db(pRbu, (iCmd==12));
int rc = sqlite3_exec(db, Tcl_GetString(objv[2]), 0, 0, 0);
if( rc!=SQLITE_OK ){
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3_errmsg(db), -1));
diff --git a/ext/repair/sqlite3_checker.tcl b/ext/repair/sqlite3_checker.tcl
index 88c265f..2ae6e15 100644
--- a/ext/repair/sqlite3_checker.tcl
+++ b/ext/repair/sqlite3_checker.tcl
@@ -220,7 +220,9 @@ if {[catch {sqlite3 db $file_to_analyze} res]} {
if {$bFreelistCheck || $bAll} {
puts -nonewline "freelist-check: "
flush stdout
+ db eval BEGIN
puts [db one {SELECT checkfreelist('main')}]
+ db eval END
}
if {$bSummary} {
set scale 0
diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c
index c0fd8c1..00513d4 100644
--- a/ext/rtree/rtree.c
+++ b/ext/rtree/rtree.c
@@ -785,6 +785,7 @@ static int nodeWrite(Rtree *pRtree, RtreeNode *pNode){
sqlite3_step(p);
pNode->isDirty = 0;
rc = sqlite3_reset(p);
+ sqlite3_bind_null(p, 2);
if( pNode->iNode==0 && rc==SQLITE_OK ){
pNode->iNode = sqlite3_last_insert_rowid(pRtree->db);
nodeHashInsert(pRtree, pNode);
diff --git a/ext/rtree/rtree1.test b/ext/rtree/rtree1.test
index 0deee66..ac6e8d9 100644
--- a/ext/rtree/rtree1.test
+++ b/ext/rtree/rtree1.test
@@ -609,4 +609,5 @@ do_execsql_test 15.2 {
COMMIT;
}
+expand_all_sql db
finish_test
diff --git a/ext/rtree/rtree4.test b/ext/rtree/rtree4.test
index af3f8d3..a73921d 100644
--- a/ext/rtree/rtree4.test
+++ b/ext/rtree/rtree4.test
@@ -250,4 +250,5 @@ for {set nDim 1} {$nDim<=5} {incr nDim} {
do_rtree_integrity_test rtree4-$nDim.3 rx
}
+expand_all_sql db
finish_test
diff --git a/ext/rtree/rtree5.test b/ext/rtree/rtree5.test
index 749385e..92bb690 100644
--- a/ext/rtree/rtree5.test
+++ b/ext/rtree/rtree5.test
@@ -79,4 +79,5 @@ do_test rtree5-1.13 {
} {2 2147483643 2147483647 -2147483648 -2147483643}
do_rtree_integrity_test rtree5-1.14 t1
+expand_all_sql db
finish_test
diff --git a/ext/rtree/rtree6.test b/ext/rtree/rtree6.test
index c9c87e8..4066048 100644
--- a/ext/rtree/rtree6.test
+++ b/ext/rtree/rtree6.test
@@ -158,5 +158,5 @@ do_execsql_test rtree6-3.5 {
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>1.1
} {}
-
+expand_all_sql db
finish_test
diff --git a/ext/rtree/rtreeG.test b/ext/rtree/rtreeG.test
index 3bef89c..12225d5 100644
--- a/ext/rtree/rtreeG.test
+++ b/ext/rtree/rtreeG.test
@@ -59,6 +59,7 @@ do_test rtreeG-1.4log {
set ::log
} {}
+expand_all_sql db
db close
sqlite3_shutdown
test_sqlite3_log
diff --git a/ext/session/session4.test b/ext/session/session4.test
index 120a230..de183a6 100644
--- a/ext/session/session4.test
+++ b/ext/session/session4.test
@@ -11,6 +11,8 @@
# This file implements regression tests for the session module.
#
+package require Tcl 8.6
+
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
@@ -64,4 +66,81 @@ do_test 1.3 {
list [catch { sqlite3changeset_apply db $x xConflict } msg] $msg
} {1 SQLITE_CORRUPT}
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 2.0 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY,b,c,d);
+ CREATE TABLE t2(e TEXT PRIMARY KEY NOT NULL,f,g);
+ CREATE TABLE t3(w REAL PRIMARY KEY NOT NULL,x,y);
+ CREATE TABLE t4(z PRIMARY KEY) WITHOUT ROWID;
+}
+
+foreach {tn blob} {
+ 1 54010174340012000000
+ 2 54fefe8bcb0012000300
+ 3 5480809280808001017434001200fb
+ 4 50af9c939c9c9cb09c9c6400b09c9c6400
+ 5 12000300
+ 6 09847304
+ 7 5401017434001208
+ 8 54010174340012fc0386868600
+ 9 54010174340012FC0386868600
+ 10 548894FEFE
+ 11 54010171340012E703ABFA7433FD1200
+ 12 540101743400120003FFED00010000000000000002120002400C00000000000054040100000074310017000100000000000000050100000000000000030100000000000000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F030378797A005403010000743200090003037838790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572
+ 13 540101743400120003001200010000000000000002120002400C0000000000005404010000007431001700010000000000000005010000000000000003010000000000000004000001000000000000000401000000000000000300170001000000000000000703FC87797A01000000000000000F000001000000000000000F030378797A005403010000743200090003037838790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572
+ 14 540101743400120003001200010000000000000002120002400C00000000000054040100000074310017000100000000000000050100000000000000030100000000000000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F03FC87797A005403010000743200090003037838790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572
+ 15 540101743400120003001200010000000000000002120002400C00000000000054040100000074310017000100000000000000050100000000000000030100000000000000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F030378797A005403010000743200090003FC8738790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572
+ 16 540101743400120003001200010000000000000002120002400C00000000000054040100000074310017000100000000000000050100000000000000030100000000000000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F030378797A00540301000074320009000303783879010000000080000000020000000000000000090003FC87327902400C0000000000000304666F7572
+ 17 540101743400120003FFE3000412F7010000E600000000021202120002400C0000000000005B0401000000743100171C0304646F750002400C000000000000540401000000D3310017000100000000000000050100000000000378797A405403000002F10100000100000000000004090001000100000007030378797A0100000000000D0007000001000000002300000F1B0378797A405403013900743200090003038C3879010000000000000000000002120002400C0000000000005B0401000000743117170003047C5E00FF
+ 18 54010174340012000300120001000000E6FF100000120002401E00000000000054040100000074310017000100040000010000000000000004FFFF7FFF0000000000010000010000001000000007030378797A01000000000000000F000000000000FA0304666F7572
+ 19 540101743400120003001200010000000000000002121B02400C00000000000054040000000074310017000100000000000000050100000000000000030100000000000000040000010000000000000004010000000000000003001700010000000000000007030378817A01000000000000000F000001000000000100000F030378797A005403010000743200090003FFE809000303780000000000000304666F7572
+ 20 5401017D3400120003001200010000000000000002120002400CFC00000000005404010000007431001700010000000000000005010000000000000003010000000000000004000001000000000000000401000000000000000300170001000000000000000703FFFF797A01000000000000000F000001000000000000000F030378797A005403010000743200090003037838790100000000800000000200000000000000000900030378326C02400C0000000000000304666F7572
+ 21 5401017434001200030012000100FFE20000000002120002400C00000000000054040100E0007431001700010000E99D000000020000000003FFE70009000303783279020004000001030000000000002117000003001700012701000100000000743100000100000000008000090003037F387901000000008000000002000000000400000009005303010A00FF7FFFFF00000000000304664F6572
+ 22 540101743400120003FFFF7FFF0000000000000002120002400C00000000000054040100000074310017000100000000000000050100000000000000030100010000000000000000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F030378797A005403010000743200090003037838790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572
+ 23 540101742700120100120003F5FF0300
+ 24 5401017434E312540101743400120003FFFC00
+ 25 540101743400540101743D3D3D3D3D3D3D3D3D3D3D3D3D3400120003FFED000300
+ 26 5401017446EA5301743D1D3D3D01743D1D3D3DCF3D3D3D1A3D3D3D3D3400120003FFFF000000
+ 27 540101743400540101743D3D3D3D3D3D3D3D3D3D251000120003FF81000000000000
+ 28 540101340012000397FF3D7F3D3400120003001200540101743D3D3D3D3D3D393D3D3D12000300
+ 29 500174340050010F74340012000300120003FFE5
+ 30 5004007233E900177FEF0054257F0002EF001200031E12000300
+ 31 5001015001015252525250010174340012EF039A9A0100E351525D52525252525252525252525252525252525250010174340012EF039A0100009A9A9A9A9A9BA3B200120003010040743400
+ 32 5401017400123400120003FFFC00
+ 33 540101743400120003001200010000000000004002120002400C0000000000005404010000007431001700010000000000000005010000000000000003010000000000000004000001000000000000000401000000000000000300170001000000000000000703FC87797A01000000000000000F000001000000000000000F030378797A005403010000743200090003037838790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572
+ 34 54040100000074310017000100000002000015050100000000000000030100000000140000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F030378797A0054030100007432000900030378387901000000008E000000020000000000000000090003FFFF000002400C0000000000000304666F7572
+ 35 540101743400120003001200010000000000000002120002400C00000000000050060100000074310017000100000000000000050100000000000000030100000003001700010000666F7572
+ 36 540101743400120003001200010000000000000002120002400C00000000000050050100000074310017000100000000000000050100000000000000030100000003001700010000666F7572
+ 37 540101743400120003001200010000000000000002120002400C00000000000050040100008074310017000100000000000000050100000000000000030100000003001700010000666F7572
+ 38 540101743400120003001200010000000000000002120002400C00000000000050040100000074310017000000000000000000050100000000000000030100000003001700010000666F7572
+ 39 540101743400120003001200010000000000000002120002400C00000000000050040100018074310017000100000000000000050100000000000000030100000003001700010000666F7572
+ 40 540101743400120003001200010000000000000002120002400C0000000000005004FEFFFFFF74310017000100000000000000050100000000000000030100000003001700010000666F7572
+ 41 540101743400120003001200010000000000000002120002400C00000000000050040100000074310017000004000000000000050100000000000000030100000003001700010000666F7572
+ 42 540101743400120003001200010000000000000002120002400C0000000000005005FFFF050074310017000100000000000000050100000000000000030100000003001700010000666F7572
+ 43 540101743400120003001200010000000000000002120002400C000000000000500401006E0074310017000300000000001221050100000000000000030100000003001700010000666F7572
+ 44 540101743400120003001200010000000000020000120002400C00000000000050050100000074310017000100000000000000050100004000000000030100000025001700010000666F7572
+ 45 540101743400120003001200010000000000ECFF02120002400C000000000000500401F9FF00743100170001000000000000000500E1000000000000030100000003000000000000666F7572
+ 46 54010174340B0B0B0B0B0B0B0B0B0B0B0B0B0B0B00120003001200010000000000000002120002400C00000000000050040100000074310017010000000000000000050100FFE900000000030100000003007F00000000666F7572
+ 47 54010103001200010000000000020002120002400C0000000000005004010000F374310017000100000000000000050100000000000000030100000003001700010000666F8E72
+ 48 540101743400120003001200010000000000000002120002400C00000000000050030012000174310017000700000000000000050100002000000001000000000003001700010000666F7572
+ 49 540101743400120004001200010000000000000002120002400C0000000000005004010000FC733100170001000000000000000501000000000000000301000000F6FF17000100007C6F7572
+ 50 54010174FFDDFF8003001200010000100000000002120002400C000000000000500401000000743100170000000005010000000000000000000003010072
+ 51 540101743200120003001200010000000000000002120002400C00000000000050040100001074310017000000000003010000120300170100000000000000050100000000000000030100000003001700010000666F7572
+ 52 540101745401017434001200010000000000001702120002400C00000000000050040100001A74310017000100000000000100000100000000000000030100000003001700010000666F7572
+ 53 540101743400120003001200010000000000000002120002400C000000000000500401000000743100170001000002400C00000000000050040110000074310017000000000000050100000000000000030100000003001700010000666F7572
+ 54 540101743400120003001200010000000000000002120002400C000000000002120002400C00000000000050040100000074310017FF0050040100000074310017FF7F00000000000000050100000000000000030100000003001700010000666F7572
+ 55 540101743400120003001200010000000000000002120002400C00000000000050040100000074310017000100010080000001000000020003010100000300170100000003001700010000666F7572
+ 56 5487ffffff7f
+} {
+ do_test 2.$tn {
+ set changeset [binary decode hex $blob]
+#set fd [open x.change w+]
+#fconfigure $fd -encoding binary -translation binary
+#puts -nonewline $fd $changeset
+#close $fd
+ list [catch { sqlite3changeset_apply db $changeset xConflict } msg] $msg
+ } {1 SQLITE_CORRUPT}
+}
+
finish_test
diff --git a/ext/session/session_common.tcl b/ext/session/session_common.tcl
index d4804d9..d08d142 100644
--- a/ext/session/session_common.tcl
+++ b/ext/session/session_common.tcl
@@ -169,3 +169,4 @@ proc changeset_to_list {c} {
sqlite3session_foreach elem $c { lappend list $elem }
lsort $list
}
+
diff --git a/ext/session/sessionfault2.test b/ext/session/sessionfault2.test
index ffdc57b..fb848e1 100644
--- a/ext/session/sessionfault2.test
+++ b/ext/session/sessionfault2.test
@@ -20,6 +20,8 @@ source $testdir/tester.tcl
ifcapable !session {finish_test; return}
set testprefix sessionfault2
+if 1 {
+
do_execsql_test 1.0.0 {
CREATE TABLE t1(a PRIMARY KEY, b UNIQUE);
INSERT INTO t1 VALUES(1, 1);
@@ -103,5 +105,181 @@ do_faultsim_test 2 -faults oom-p* -prep {
faultsim_integrity_check
}
+#-------------------------------------------------------------------------
+# OOM when collecting and apply a changeset that uses sqlite_stat1.
+#
+reset_db
+forcedelete test.db2
+sqlite3 db2 test.db2
+do_common_sql {
+ CREATE TABLE t1(a PRIMARY KEY, b UNIQUE, c);
+ CREATE INDEX i1 ON t1(c);
+ INSERT INTO t1 VALUES(1, 2, 3);
+ INSERT INTO t1 VALUES(4, 5, 6);
+ INSERT INTO t1 VALUES(7, 8, 9);
+ CREATE TABLE t2(a, b, c);
+ INSERT INTO t2 VALUES(1, 2, 3);
+ INSERT INTO t2 VALUES(4, 5, 6);
+ INSERT INTO t2 VALUES(7, 8, 9);
+ ANALYZE;
+}
+faultsim_save_and_close
+db2 close
+
+do_faultsim_test 1.1 -faults oom-* -prep {
+ catch {db2 close}
+ catch {db close}
+ faultsim_restore_and_reopen
+ sqlite3 db2 test.db2
+} -body {
+ do_then_apply_sql {
+ INSERT INTO sqlite_stat1 VALUES('x', 'y', 45);
+ UPDATE sqlite_stat1 SET stat = 123 WHERE tbl='t1' AND idx='i1';
+ UPDATE sqlite_stat1 SET stat = 456 WHERE tbl='t2';
+ }
+} -test {
+ faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
+ faultsim_integrity_check
+ if {$testrc==0} { compare_db db db2 }
+}
+
+#-------------------------------------------------------------------------
+# OOM when collecting and using a rebase changeset.
+#
+reset_db
+do_execsql_test 2.0 {
+ CREATE TABLE t3(a, b, c, PRIMARY KEY(b, c));
+ CREATE TABLE t4(x PRIMARY KEY, y, z);
+
+ INSERT INTO t3 VALUES(1, 2, 3);
+ INSERT INTO t3 VALUES(4, 2, 5);
+ INSERT INTO t3 VALUES(7, 2, 9);
+
+ INSERT INTO t4 VALUES('a', 'b', 'c');
+ INSERT INTO t4 VALUES('d', 'e', 'f');
+ INSERT INTO t4 VALUES('g', 'h', 'i');
+}
+faultsim_save_and_close
+db2 close
+
+proc xConflict {ret args} { return $ret }
+
+do_test 2.1 {
+ faultsim_restore_and_reopen
+ set C1 [changeset_from_sql {
+ INSERT INTO t3 VALUES(10, 11, 12);
+ UPDATE t4 SET y='j' WHERE x='g';
+ DELETE FROM t4 WHERE x='a';
+ }]
+
+ faultsim_restore_and_reopen
+ set C2 [changeset_from_sql {
+ INSERT INTO t3 VALUES(1000, 11, 12);
+ DELETE FROM t4 WHERE x='g';
+ }]
+
+ faultsim_restore_and_reopen
+ sqlite3changeset_apply db $C1 [list xConflict OMIT]
+ faultsim_save_and_close
+} {}
+
+do_faultsim_test 2.2 -faults oom* -prep {
+ catch {db2 close}
+ catch {db close}
+ faultsim_restore_and_reopen
+ sqlite3 db2 test.db2
+} -body {
+ set rebase [sqlite3changeset_apply_v2 db $::C2 [list xConflict OMIT]]
+ set {} {}
+} -test {
+ faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
+}
+do_faultsim_test 2.3 -faults oom* -prep {
+ catch {db2 close}
+ catch {db close}
+ faultsim_restore_and_reopen
+ sqlite3 db2 test.db2
+} -body {
+ set rebase [sqlite3changeset_apply_v2 db $::C2 [list xConflict REPLACE]]
+ set {} {}
+} -test {
+ faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
+}
+do_faultsim_test 2.4 -faults oom* -prep {
+ catch {db2 close}
+ catch {db close}
+ faultsim_restore_and_reopen
+ set ::rebase [sqlite3changeset_apply_v2 db $::C2 [list xConflict REPLACE]]
+} -body {
+ sqlite3rebaser_create R
+ R configure $::rebase
+ R rebase $::C1
+ set {} {}
+} -test {
+ catch { R delete }
+ faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
+}
+do_faultsim_test 2.5 -faults oom* -prep {
+ catch {db2 close}
+ catch {db close}
+ faultsim_restore_and_reopen
+ set ::rebase [sqlite3changeset_apply_v2 db $::C2 [list xConflict OMIT]]
+} -body {
+ sqlite3rebaser_create R
+ R configure $::rebase
+ R rebase $::C1
+ set {} {}
+} -test {
+ catch { R delete }
+ faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
+}
+
+}
+
+reset_db
+do_execsql_test 3.0 {
+ CREATE TABLE t1(x PRIMARY KEY, y, z);
+ INSERT INTO t1 VALUES(3, 1, 4);
+ INSERT INTO t1 VALUES(1, 5, 9);
+}
+faultsim_save_and_close
+
+proc xConflict {ret args} { return $ret }
+
+do_test 3.1 {
+ faultsim_restore_and_reopen
+
+ execsql { BEGIN; UPDATE t1 SET z=11; }
+ set C1 [changeset_from_sql {
+ UPDATE t1 SET z=10 WHERE x=1;
+ }]
+ execsql { ROLLBACK }
+
+ execsql { BEGIN; UPDATE t1 SET z=11; }
+ set C2 [changeset_from_sql {
+ UPDATE t1 SET z=55 WHERE x=1;
+ }]
+ execsql { ROLLBACK }
+
+ set ::rebase1 [sqlite3changeset_apply_v2 db $::C1 [list xConflict OMIT]]
+ set ::rebase2 [sqlite3changeset_apply_v2 db $::C2 [list xConflict OMIT]]
+ set {} {}
+ execsql { SELECT * FROM t1 }
+} {3 1 4 1 5 9}
+
+
+do_faultsim_test 3.2 -faults oom* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ sqlite3rebaser_create R
+ R configure $::rebase1
+ R configure $::rebase2
+ set {} {}
+} -test {
+ catch { R delete }
+ faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
+}
+
+
finish_test
diff --git a/ext/session/sessionrebase.test b/ext/session/sessionrebase.test
new file mode 100644
index 0000000..ced9f22
--- /dev/null
+++ b/ext/session/sessionrebase.test
@@ -0,0 +1,477 @@
+# 2018 March 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.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+ifcapable !session {finish_test; return}
+
+set testprefix sessionrebase
+
+set ::lConflict [list]
+proc xConflict {args} {
+ set res [lindex $::lConflict 0]
+ set ::lConflict [lrange $::lConflict 1 end]
+ return $res
+}
+
+#-------------------------------------------------------------------------
+# The following test cases - 1.* - test that the rebase blobs output by
+# sqlite3_changeset_apply_v2 look correct in some simple cases. The blob
+# is itself a changeset, containing records determined as follows:
+#
+# * For each conflict resolved with REPLACE, the rebase blob contains
+# a DELETE record. All fields other than the PK fields are undefined.
+#
+# * For each conflict resolved with OMIT, the rebase blob contains an
+# INSERT record. For an INSERT or UPDATE operation, the indirect flag
+# is clear and all updated fields are defined. For a DELETE operation,
+# the indirect flag is set and all non-PK fields left undefined.
+#
+proc do_apply_v2_test {tn sql modsql conflict_handler res} {
+
+ execsql BEGIN
+ sqlite3session S db main
+ S attach *
+ execsql $sql
+ set changeset [S changeset]
+ S delete
+ execsql ROLLBACK
+
+ execsql BEGIN
+ execsql $modsql
+ set ::lConflict $conflict_handler
+ set blob [sqlite3changeset_apply_v2 db $changeset xConflict]
+ execsql ROLLBACK
+
+ uplevel [list do_test $tn [list changeset_to_list $blob] [list {*}$res]]
+}
+
+
+set ::lConflict [list]
+proc xConflict {args} {
+ set res [lindex $::lConflict 0]
+ set ::lConflict [lrange $::lConflict 1 end]
+ return $res
+}
+
+# Take a copy of database test.db in file test.db2. Execute $sql1
+# against test.db and $sql2 against test.db2. Capture a changeset
+# for each. Then send the test.db2 changeset to test.db and apply
+# it with the conflict handlers in $conflict_handler. Patch the
+# test.db changeset and then execute it against test.db2. Test that
+# the two databases come out the same.
+#
+proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} {
+
+ for {set i 1} {$i <= 2} {incr i} {
+ forcedelete test.db2 test.db2-journal test.db2-wal
+ forcecopy test.db test.db2
+ sqlite3 db2 test.db2
+
+ db eval BEGIN
+
+ sqlite3session S1 db main
+ S1 attach *
+ execsql $sql1 db
+ set c1 [S1 changeset]
+ S1 delete
+
+ if {$i==1} {
+ sqlite3session S2 db2 main
+ S2 attach *
+ execsql $sql2 db2
+ set c2 [S2 changeset]
+ S2 delete
+ } else {
+ set c2 [list]
+ foreach sql [split $sql2 ";"] {
+ if {[string is space $sql]} continue
+ sqlite3session S2 db2 main
+ S2 attach *
+ execsql $sql db2
+ lappend c2 [S2 changeset]
+ S2 delete
+ }
+ }
+
+ set ::lConflict $conflict_handler
+ set rebase [list]
+ if {$i==1} {
+ lappend rebase [sqlite3changeset_apply_v2 db $c2 xConflict]
+ } else {
+ foreach c $c2 {
+#puts "apply_v2: [changeset_to_list $c]"
+ lappend rebase [sqlite3changeset_apply_v2 db $c xConflict]
+ }
+ #puts "llength: [llength $rebase]"
+ }
+ #if {$tn=="2.1.4"} { puts [changeset_to_list $rebase] ; breakpoint }
+ #puts [changeset_to_list [lindex $rebase 0]] ; breakpoint
+ #puts [llength $rebase]
+
+ sqlite3rebaser_create R
+ foreach r $rebase {
+#puts [changeset_to_list $r]
+ R configure $r
+ }
+ set c1r [R rebase $c1]
+ R delete
+ #if {$tn=="2.1.4"} { puts [changeset_to_list $c1r] }
+
+ sqlite3changeset_apply_v2 db2 $c1r xConflictAbort
+
+ if {[string range $tn end end]!="*"} {
+ uplevel [list do_test $tn.$i.1 [list compare_db db db2] {}]
+ }
+ db2 close
+
+ if {$testsql!=""} {
+ uplevel [list do_execsql_test $tn.$i.2 $testsql $testres]
+ }
+
+ db eval ROLLBACK
+ }
+}
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES(1, 'value A');
+}
+
+do_apply_v2_test 1.1.1 {
+ UPDATE t1 SET b = 'value B' WHERE a=1;
+} {
+ UPDATE t1 SET b = 'value C' WHERE a=1;
+} {
+ OMIT
+} {
+ {INSERT t1 0 X. {} {i 1 t {value B}}}
+}
+
+do_apply_v2_test 1.1.2 {
+ UPDATE t1 SET b = 'value B' WHERE a=1;
+} {
+ UPDATE t1 SET b = 'value C' WHERE a=1;
+} {
+ REPLACE
+} {
+ {INSERT t1 1 X. {} {i 1 t {value B}}}
+}
+
+do_apply_v2_test 1.2.1 {
+ INSERT INTO t1 VALUES(2, 'first');
+} {
+ INSERT INTO t1 VALUES(2, 'second');
+} {
+ OMIT
+} {
+ {INSERT t1 0 X. {} {i 2 t first}}
+}
+do_apply_v2_test 1.2.2 {
+ INSERT INTO t1 VALUES(2, 'first');
+} {
+ INSERT INTO t1 VALUES(2, 'second');
+} {
+ REPLACE
+} {
+ {INSERT t1 1 X. {} {i 2 t first}}
+}
+
+do_apply_v2_test 1.3.1 {
+ DELETE FROM t1 WHERE a=1;
+} {
+ UPDATE t1 SET b='value D' WHERE a=1;
+} {
+ OMIT
+} {
+ {DELETE t1 0 X. {i 1 t {value A}} {}}
+}
+do_apply_v2_test 1.3.2 {
+ DELETE FROM t1 WHERE a=1;
+} {
+ UPDATE t1 SET b='value D' WHERE a=1;
+} {
+ REPLACE
+} {
+ {DELETE t1 1 X. {i 1 t {value A}} {}}
+}
+
+#-------------------------------------------------------------------------
+# Test cases 2.* - simple tests of rebasing actual changesets.
+#
+# 2.1.1 - 1u2u1r
+# 2.1.2 - 1u2u2r
+# 2.1.3 - 1d2d
+# 2.1.4 - 1d2u1r
+# 2.1.5 - 1d2u2r !!
+# 2.1.6 - 1u2d1r
+# 2.1.7 - 1u2d2r
+#
+# 2.1.8 - 1i2i2r
+# 2.1.9 - 1i2i1r
+#
+
+proc xConflictAbort {args} {
+ return "ABORT"
+}
+
+reset_db
+do_execsql_test 2.1.0 {
+ CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT);
+ INSERT INTO t1 VALUES(1, 'one');
+ INSERT INTO t1 VALUES(2, 'two');
+ INSERT INTO t1 VALUES(3, 'three');
+}
+do_rebase_test 2.1.1 {
+ UPDATE t1 SET b = 'two.1' WHERE a=2
+} {
+ UPDATE t1 SET b = 'two.2' WHERE a=2;
+} {
+ OMIT
+} { SELECT * FROM t1 } {1 one 2 two.1 3 three}
+
+do_rebase_test 2.1.2 {
+ UPDATE t1 SET b = 'two.1' WHERE a=2
+} {
+ UPDATE t1 SET b = 'two.2' WHERE a=2;
+} {
+ REPLACE
+} { SELECT * FROM t1 } {1 one 2 two.2 3 three}
+
+do_rebase_test 2.1.3 {
+ DELETE FROM t1 WHERE a=3
+} {
+ DELETE FROM t1 WHERE a=3;
+} {
+ OMIT
+} { SELECT * FROM t1 } {1 one 2 two}
+
+do_rebase_test 2.1.4 {
+ DELETE FROM t1 WHERE a=1
+} {
+ UPDATE t1 SET b='one.2' WHERE a=1
+} {
+ OMIT
+} { SELECT * FROM t1 } {2 two 3 three}
+
+#do_rebase_test 2.1.5 {
+# DELETE FROM t1 WHERE a=1;
+#} {
+# UPDATE t1 SET b='one.2' WHERE a=1
+#} {
+# REPLACE
+#} { SELECT * FROM t1 } {2 two 3 three}
+
+do_rebase_test 2.1.6 {
+ UPDATE t1 SET b='three.1' WHERE a=3
+} {
+ DELETE FROM t1 WHERE a=3;
+} {
+ OMIT
+} { SELECT * FROM t1 } {1 one 2 two 3 three.1}
+
+do_rebase_test 2.1.7 {
+ UPDATE t1 SET b='three.1' WHERE a=3
+} {
+ DELETE FROM t1 WHERE a=3;
+} {
+ REPLACE
+} { SELECT * FROM t1 } {1 one 2 two}
+
+do_rebase_test 2.1.8 {
+ INSERT INTO t1 VALUES(4, 'four.1')
+} {
+ INSERT INTO t1 VALUES(4, 'four.2');
+} {
+ REPLACE
+} { SELECT * FROM t1 } {1 one 2 two 3 three 4 four.2}
+
+do_rebase_test 2.1.9 {
+ INSERT INTO t1 VALUES(4, 'four.1')
+} {
+ INSERT INTO t1 VALUES(4, 'four.2');
+} {
+ OMIT
+} { SELECT * FROM t1 } {1 one 2 two 3 three 4 four.1}
+
+do_execsql_test 2.2.0 {
+ CREATE TABLE t2(x, y, z PRIMARY KEY);
+ INSERT INTO t2 VALUES('i', 'a', 'A');
+ INSERT INTO t2 VALUES('ii', 'b', 'B');
+ INSERT INTO t2 VALUES('iii', 'c', 'C');
+
+ CREATE TABLE t3(a INTEGER PRIMARY KEY, b, c);
+ INSERT INTO t3 VALUES(-1, 'z', 'Z');
+ INSERT INTO t3 VALUES(-2, 'y', 'Y');
+}
+
+do_rebase_test 2.2.1 {
+ UPDATE t2 SET x=1 WHERE z='A'
+} {
+ UPDATE t2 SET y='one' WHERE z='A';
+} {
+} { SELECT * FROM t2 WHERE z='A' } { 1 one A }
+
+do_rebase_test 2.2.2 {
+ UPDATE t2 SET x=1, y='one' WHERE z='B'
+} {
+ UPDATE t2 SET y='two' WHERE z='B';
+} {
+ REPLACE
+} { SELECT * FROM t2 WHERE z='B' } { 1 two B }
+
+do_rebase_test 2.2.3 {
+ UPDATE t2 SET x=1, y='one' WHERE z='B'
+} {
+ UPDATE t2 SET y='two' WHERE z='B';
+} {
+ OMIT
+} { SELECT * FROM t2 WHERE z='B' } { 1 one B }
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 3.0 {
+ CREATE TABLE t3(a, b, c, PRIMARY KEY(b, c));
+ CREATE TABLE abcdefghijkl(x PRIMARY KEY, y, z);
+
+ INSERT INTO t3 VALUES(1, 2, 3);
+ INSERT INTO t3 VALUES(4, 2, 5);
+ INSERT INTO t3 VALUES(7, 2, 9);
+
+ INSERT INTO abcdefghijkl VALUES('a', 'b', 'c');
+ INSERT INTO abcdefghijkl VALUES('d', 'e', 'f');
+ INSERT INTO abcdefghijkl VALUES('g', 'h', 'i');
+}
+
+breakpoint
+# do_rebase_test 3.6.tn {
+# UPDATE abcdefghijkl SET z='X', y='X' WHERE x='d';
+# } {
+# UPDATE abcdefghijkl SET y=1 WHERE x='d';
+# UPDATE abcdefghijkl SET z=1 WHERE x='d';
+# } [list REPLACE REPLACE REPLACE]
+
+foreach {tn p} {
+ 1 OMIT 2 REPLACE
+} {
+ do_rebase_test 3.1.$tn {
+ INSERT INTO t3 VALUES(1, 1, 1);
+ UPDATE abcdefghijkl SET y=2;
+ } {
+ INSERT INTO t3 VALUES(4, 1, 1);
+ DELETE FROM abcdefghijkl;
+ } [list $p $p $p $p $p $p $p $p]
+
+ do_rebase_test 3.2.$tn {
+ INSERT INTO abcdefghijkl SELECT * FROM t3;
+ UPDATE t3 SET b=b+1;
+ } {
+ INSERT INTO t3 VALUES(3, 3, 3);
+ INSERT INTO abcdefghijkl SELECT * FROM t3;
+ } [list $p $p $p $p $p $p $p $p]
+
+ do_rebase_test 3.3.$tn {
+ INSERT INTO abcdefghijkl VALUES(22, 23, 24);
+ } {
+ INSERT INTO abcdefghijkl VALUES(22, 25, 26);
+ UPDATE abcdefghijkl SET y=400 WHERE x=22;
+ } [list $p $p $p $p $p $p $p $p]
+
+ do_rebase_test 3.4.$tn {
+ INSERT INTO abcdefghijkl VALUES(22, 23, 24);
+ } {
+ INSERT INTO abcdefghijkl VALUES(22, 25, 26);
+ UPDATE abcdefghijkl SET y=400 WHERE x=22;
+ } [list REPLACE $p]
+
+ do_rebase_test 3.5.$tn* {
+ UPDATE abcdefghijkl SET y='X' WHERE x='d';
+ } {
+ DELETE FROM abcdefghijkl WHERE x='d';
+ INSERT INTO abcdefghijkl VALUES('d', NULL, NULL);
+ } [list $p $p $p]
+ do_rebase_test 3.5.$tn {
+ UPDATE abcdefghijkl SET y='X' WHERE x='d';
+ } {
+ DELETE FROM abcdefghijkl WHERE x='d';
+ INSERT INTO abcdefghijkl VALUES('d', NULL, NULL);
+ } [list REPLACE $p $p]
+
+ do_rebase_test 3.6.$tn {
+ UPDATE abcdefghijkl SET z='X', y='X' WHERE x='d';
+ } {
+ UPDATE abcdefghijkl SET y=1 WHERE x='d';
+ UPDATE abcdefghijkl SET z=1 WHERE x='d';
+ } [list REPLACE $p $p]
+}
+
+#-------------------------------------------------------------------------
+# Check that apply_v2() does not create a rebase buffer for a patchset.
+# And that it is not possible to rebase a patchset.
+#
+do_execsql_test 4.0 {
+ CREATE TABLE t5(o PRIMARY KEY, p, q);
+ INSERT INTO t5 VALUES(1, 2, 3);
+ INSERT INTO t5 VALUES(4, 5, 6);
+}
+foreach {tn cmd rebasable} {
+ 1 patchset 0
+ 2 changeset 1
+} {
+ proc xConflict {args} { return "OMIT" }
+ do_test 4.1.$tn {
+ execsql {
+ BEGIN;
+ DELETE FROM t5 WHERE o=4;
+ }
+
+ sqlite3session S db main
+ S attach *
+ execsql {
+ INSERT INTO t5 VALUES(4, 'five', 'six');
+ }
+ set P [S $cmd]
+ S delete
+
+ execsql ROLLBACK;
+
+ set ::rebase [sqlite3changeset_apply_v2 db $P xConflict]
+ expr [llength $::rebase]>0
+ } $rebasable
+}
+
+foreach {tn cmd rebasable} {
+ 1 patchset 0
+ 2 changeset 1
+} {
+ do_test 4.2.$tn {
+ sqlite3session S db main
+ S attach *
+ execsql {
+ INSERT INTO t5 VALUES(5+$tn, 'five', 'six');
+ }
+ set P [S $cmd]
+ S delete
+
+ sqlite3rebaser_create R
+ R configure $::rebase
+ expr [catch {R rebase $P}]==0
+ } $rebasable
+
+ catch { R delete }
+}
+finish_test
+
diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c
index ec1a93e..7a9de6f 100644
--- a/ext/session/sqlite3session.c
+++ b/ext/session/sqlite3session.c
@@ -232,8 +232,8 @@ struct SessionTable {
** statement.
**
** For a DELETE change, all fields within the record except those associated
-** with PRIMARY KEY columns are set to "undefined". The PRIMARY KEY fields
-** contain the values identifying the row to delete.
+** with PRIMARY KEY columns are omitted. The PRIMARY KEY fields contain the
+** values identifying the row to delete.
**
** For an UPDATE change, all fields except those associated with PRIMARY KEY
** columns and columns that are modified by the UPDATE are set to "undefined".
@@ -516,7 +516,7 @@ static int sessionPreupdateHash(
static int sessionSerialLen(u8 *a){
int e = *a;
int n;
- if( e==0 ) return 1;
+ if( e==0 || e==0xFF ) return 1;
if( e==SQLITE_NULL ) return 1;
if( e==SQLITE_INTEGER || e==SQLITE_FLOAT ) return 9;
return sessionVarintGet(&a[1], &n) + 1 + n;
@@ -596,7 +596,7 @@ static int sessionChangeEqual(
int n1 = sessionSerialLen(a1);
int n2 = sessionSerialLen(a2);
- if( pTab->abPK[iCol] && (n1!=n2 || memcmp(a1, a2, n1)) ){
+ if( n1!=n2 || memcmp(a1, a2, n1) ){
return 0;
}
a1 += n1;
@@ -839,7 +839,7 @@ static int sessionPreupdateEqual(
}else{
z = sqlite3_value_blob(pVal);
}
- if( memcmp(a, z, n) ) return 0;
+ if( n>0 && memcmp(a, z, n) ) return 0;
a += n;
}
}
@@ -1115,7 +1115,7 @@ static void sessionPreupdateOneChange(
int iHash;
int bNull = 0;
int rc = SQLITE_OK;
- SessionStat1Ctx stat1;
+ SessionStat1Ctx stat1 = {0};
if( pSession->rc ) return;
@@ -2183,6 +2183,7 @@ static int sessionSelectStmt(
"SELECT tbl, ?2, stat FROM %Q.sqlite_stat1 WHERE tbl IS ?1 AND "
"idx IS (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", zDb
);
+ if( zSql==0 ) rc = SQLITE_NOMEM;
}else{
int i;
const char *zSep = "";
@@ -2720,13 +2721,16 @@ static int sessionReadRecord(
if( abPK && abPK[i]==0 ) continue;
rc = sessionInputBuffer(pIn, 9);
if( rc==SQLITE_OK ){
- eType = pIn->aData[pIn->iNext++];
- }
-
- assert( apOut[i]==0 );
- if( eType ){
- apOut[i] = sqlite3ValueNew(0);
- if( !apOut[i] ) rc = SQLITE_NOMEM;
+ if( pIn->iNext>=pIn->nData ){
+ rc = SQLITE_CORRUPT_BKPT;
+ }else{
+ eType = pIn->aData[pIn->iNext++];
+ assert( apOut[i]==0 );
+ if( eType ){
+ apOut[i] = sqlite3ValueNew(0);
+ if( !apOut[i] ) rc = SQLITE_NOMEM;
+ }
+ }
}
if( rc==SQLITE_OK ){
@@ -2736,10 +2740,14 @@ static int sessionReadRecord(
pIn->iNext += sessionVarintGet(aVal, &nByte);
rc = sessionInputBuffer(pIn, nByte);
if( rc==SQLITE_OK ){
- u8 enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0);
- rc = sessionValueSetStr(apOut[i],&pIn->aData[pIn->iNext],nByte,enc);
+ if( nByte<0 || nByte>pIn->nData-pIn->iNext ){
+ rc = SQLITE_CORRUPT_BKPT;
+ }else{
+ u8 enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0);
+ rc = sessionValueSetStr(apOut[i],&pIn->aData[pIn->iNext],nByte,enc);
+ pIn->iNext += nByte;
+ }
}
- pIn->iNext += nByte;
}
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
sqlite3_int64 v = sessionGetI64(aVal);
@@ -2779,8 +2787,19 @@ static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){
rc = sessionInputBuffer(pIn, 9);
if( rc==SQLITE_OK ){
nRead += sessionVarintGet(&pIn->aData[pIn->iNext + nRead], &nCol);
- rc = sessionInputBuffer(pIn, nRead+nCol+100);
- nRead += nCol;
+ /* The hard upper limit for the number of columns in an SQLite
+ ** database table is, according to sqliteLimit.h, 32676. So
+ ** consider any table-header that purports to have more than 65536
+ ** columns to be corrupt. This is convenient because otherwise,
+ ** if the (nCol>65536) condition below were omitted, a sufficiently
+ ** large value for nCol may cause nRead to wrap around and become
+ ** negative. Leading to a crash. */
+ if( nCol<0 || nCol>65536 ){
+ rc = SQLITE_CORRUPT_BKPT;
+ }else{
+ rc = sessionInputBuffer(pIn, nRead+nCol+100);
+ nRead += nCol;
+ }
}
while( rc==SQLITE_OK ){
@@ -2857,11 +2876,15 @@ static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){
int nByte;
int nVarint;
nVarint = sessionVarintGet(&p->in.aData[p->in.iNext], &p->nCol);
- nCopy -= nVarint;
- p->in.iNext += nVarint;
- nByte = p->nCol * sizeof(sqlite3_value*) * 2 + nCopy;
- p->tblhdr.nBuf = 0;
- sessionBufferGrow(&p->tblhdr, nByte, &rc);
+ if( p->nCol>0 ){
+ nCopy -= nVarint;
+ p->in.iNext += nVarint;
+ nByte = p->nCol * sizeof(sqlite3_value*) * 2 + nCopy;
+ p->tblhdr.nBuf = 0;
+ sessionBufferGrow(&p->tblhdr, nByte, &rc);
+ }else{
+ rc = SQLITE_CORRUPT_BKPT;
+ }
}
if( rc==SQLITE_OK ){
@@ -2896,7 +2919,8 @@ static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){
static int sessionChangesetNext(
sqlite3_changeset_iter *p, /* Changeset iterator */
u8 **paRec, /* If non-NULL, store record pointer here */
- int *pnRec /* If non-NULL, store size of record here */
+ int *pnRec, /* If non-NULL, store size of record here */
+ int *pbNew /* If non-NULL, true if new table */
){
int i;
u8 op;
@@ -2931,6 +2955,7 @@ static int sessionChangesetNext(
op = p->in.aData[p->in.iNext++];
while( op=='T' || op=='P' ){
+ if( pbNew ) *pbNew = 1;
p->bPatchset = (op=='P');
if( sessionChangesetReadTblhdr(p) ) return p->rc;
if( (p->rc = sessionInputBuffer(&p->in, 2)) ) return p->rc;
@@ -2939,6 +2964,13 @@ static int sessionChangesetNext(
op = p->in.aData[p->in.iNext++];
}
+ if( p->zTab==0 ){
+ /* The first record in the changeset is not a table header. Must be a
+ ** corrupt changeset. */
+ assert( p->in.iNext==1 );
+ return (p->rc = SQLITE_CORRUPT_BKPT);
+ }
+
p->op = op;
p->bIndirect = p->in.aData[p->in.iNext++];
if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){
@@ -2981,9 +3013,9 @@ static int sessionChangesetNext(
** new.* to old.*, to accommodate the code that reads these arrays. */
for(i=0; i<p->nCol; i++){
assert( p->apValue[i]==0 );
- assert( p->abPK[i]==0 || p->apValue[i+p->nCol] );
if( p->abPK[i] ){
p->apValue[i] = p->apValue[i+p->nCol];
+ if( p->apValue[i]==0 ) return (p->rc = SQLITE_CORRUPT_BKPT);
p->apValue[i+p->nCol] = 0;
}
}
@@ -3002,7 +3034,7 @@ static int sessionChangesetNext(
** callback by changeset_apply().
*/
int sqlite3changeset_next(sqlite3_changeset_iter *p){
- return sessionChangesetNext(p, 0, 0);
+ return sessionChangesetNext(p, 0, 0, 0);
}
/*
@@ -3381,6 +3413,8 @@ struct SessionApplyCtx {
int bStat1; /* True if table is sqlite_stat1 */
int bDeferConstraints; /* True to defer constraints */
SessionBuffer constraints; /* Deferred constraints are stored here */
+ SessionBuffer rebase; /* Rebase information (if any) here */
+ int bRebaseStarted; /* If table header is already in rebase */
};
/*
@@ -3647,7 +3681,6 @@ static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){
"AND (?4 OR stat IS ?3)"
);
}
- assert( rc==SQLITE_OK );
return rc;
}
@@ -3708,7 +3741,13 @@ static int sessionBindRow(
if( !abPK || abPK[i] ){
sqlite3_value *pVal;
(void)xValue(pIter, i, &pVal);
- rc = sessionBindValue(pStmt, i+1, pVal);
+ if( pVal==0 ){
+ /* The value in the changeset was "undefined". This indicates a
+ ** corrupt changeset blob. */
+ rc = SQLITE_CORRUPT_BKPT;
+ }else{
+ rc = sessionBindValue(pStmt, i+1, pVal);
+ }
}
}
return rc;
@@ -3757,6 +3796,54 @@ static int sessionSeekToRow(
}
/*
+** This function is called from within sqlite3changset_apply_v2() when
+** a conflict is encountered and resolved using conflict resolution
+** mode eType (either SQLITE_CHANGESET_OMIT or SQLITE_CHANGESET_REPLACE)..
+** It adds a conflict resolution record to the buffer in
+** SessionApplyCtx.rebase, which will eventually be returned to the caller
+** of apply_v2() as the "rebase" buffer.
+**
+** Return SQLITE_OK if successful, or an SQLite error code otherwise.
+*/
+static int sessionRebaseAdd(
+ SessionApplyCtx *p, /* Apply context */
+ int eType, /* Conflict resolution (OMIT or REPLACE) */
+ sqlite3_changeset_iter *pIter /* Iterator pointing at current change */
+){
+ int rc = SQLITE_OK;
+ int i;
+ int eOp = pIter->op;
+ if( p->bRebaseStarted==0 ){
+ /* Append a table-header to the rebase buffer */
+ const char *zTab = pIter->zTab;
+ sessionAppendByte(&p->rebase, 'T', &rc);
+ sessionAppendVarint(&p->rebase, p->nCol, &rc);
+ sessionAppendBlob(&p->rebase, p->abPK, p->nCol, &rc);
+ sessionAppendBlob(&p->rebase, (u8*)zTab, (int)strlen(zTab)+1, &rc);
+ p->bRebaseStarted = 1;
+ }
+
+ assert( eType==SQLITE_CHANGESET_REPLACE||eType==SQLITE_CHANGESET_OMIT );
+ assert( eOp==SQLITE_DELETE || eOp==SQLITE_INSERT || eOp==SQLITE_UPDATE );
+
+ sessionAppendByte(&p->rebase,
+ (eOp==SQLITE_DELETE ? SQLITE_DELETE : SQLITE_INSERT), &rc
+ );
+ sessionAppendByte(&p->rebase, (eType==SQLITE_CHANGESET_REPLACE), &rc);
+ for(i=0; i<p->nCol; i++){
+ sqlite3_value *pVal = 0;
+ if( eOp==SQLITE_DELETE || (eOp==SQLITE_UPDATE && p->abPK[i]) ){
+ sqlite3changeset_old(pIter, i, &pVal);
+ }else{
+ sqlite3changeset_new(pIter, i, &pVal);
+ }
+ sessionAppendValue(&p->rebase, pVal, &rc);
+ }
+
+ return rc;
+}
+
+/*
** Invoke the conflict handler for the change that the changeset iterator
** currently points to.
**
@@ -3831,7 +3918,7 @@ static int sessionConflictHandler(
u8 *aBlob = &pIter->in.aData[pIter->in.iCurrent];
int nBlob = pIter->in.iNext - pIter->in.iCurrent;
sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc);
- res = SQLITE_CHANGESET_OMIT;
+ return SQLITE_OK;
}else{
/* No other row with the new.* primary key. */
res = xConflict(pCtx, eType+1, pIter);
@@ -3857,6 +3944,9 @@ static int sessionConflictHandler(
rc = SQLITE_MISUSE;
break;
}
+ if( rc==SQLITE_OK ){
+ rc = sessionRebaseAdd(p, res, pIter);
+ }
}
return rc;
@@ -4032,42 +4122,42 @@ static int sessionApplyOneWithRetry(
int rc;
rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, &bReplace, &bRetry);
- assert( rc==SQLITE_OK || (bRetry==0 && bReplace==0) );
-
- /* If the bRetry flag is set, the change has not been applied due to an
- ** SQLITE_CHANGESET_DATA problem (i.e. this is an UPDATE or DELETE and
- ** a row with the correct PK is present in the db, but one or more other
- ** fields do not contain the expected values) and the conflict handler
- ** returned SQLITE_CHANGESET_REPLACE. In this case retry the operation,
- ** but pass NULL as the final argument so that sessionApplyOneOp() ignores
- ** the SQLITE_CHANGESET_DATA problem. */
- if( bRetry ){
- assert( pIter->op==SQLITE_UPDATE || pIter->op==SQLITE_DELETE );
- rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0);
- }
-
- /* If the bReplace flag is set, the change is an INSERT that has not
- ** been performed because the database already contains a row with the
- ** specified primary key and the conflict handler returned
- ** SQLITE_CHANGESET_REPLACE. In this case remove the conflicting row
- ** before reattempting the INSERT. */
- else if( bReplace ){
- assert( pIter->op==SQLITE_INSERT );
- rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0);
- if( rc==SQLITE_OK ){
- rc = sessionBindRow(pIter,
- sqlite3changeset_new, pApply->nCol, pApply->abPK, pApply->pDelete);
- sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1);
- }
- if( rc==SQLITE_OK ){
- sqlite3_step(pApply->pDelete);
- rc = sqlite3_reset(pApply->pDelete);
- }
- if( rc==SQLITE_OK ){
+ if( rc==SQLITE_OK ){
+ /* If the bRetry flag is set, the change has not been applied due to an
+ ** SQLITE_CHANGESET_DATA problem (i.e. this is an UPDATE or DELETE and
+ ** a row with the correct PK is present in the db, but one or more other
+ ** fields do not contain the expected values) and the conflict handler
+ ** returned SQLITE_CHANGESET_REPLACE. In this case retry the operation,
+ ** but pass NULL as the final argument so that sessionApplyOneOp() ignores
+ ** the SQLITE_CHANGESET_DATA problem. */
+ if( bRetry ){
+ assert( pIter->op==SQLITE_UPDATE || pIter->op==SQLITE_DELETE );
rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0);
}
- if( rc==SQLITE_OK ){
- rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0);
+
+ /* If the bReplace flag is set, the change is an INSERT that has not
+ ** been performed because the database already contains a row with the
+ ** specified primary key and the conflict handler returned
+ ** SQLITE_CHANGESET_REPLACE. In this case remove the conflicting row
+ ** before reattempting the INSERT. */
+ else if( bReplace ){
+ assert( pIter->op==SQLITE_INSERT );
+ rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0);
+ if( rc==SQLITE_OK ){
+ rc = sessionBindRow(pIter,
+ sqlite3changeset_new, pApply->nCol, pApply->abPK, pApply->pDelete);
+ sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1);
+ }
+ if( rc==SQLITE_OK ){
+ sqlite3_step(pApply->pDelete);
+ rc = sqlite3_reset(pApply->pDelete);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0);
+ }
}
}
@@ -4143,7 +4233,8 @@ static int sessionChangesetApply(
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
sqlite3_changeset_iter *p /* Handle describing change and conflict */
),
- void *pCtx /* First argument passed to xConflict */
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase /* OUT: Rebase information */
){
int schemaMismatch = 0;
int rc; /* Return code */
@@ -4181,9 +4272,18 @@ static int sessionChangesetApply(
sqlite3_finalize(sApply.pUpdate);
sqlite3_finalize(sApply.pInsert);
sqlite3_finalize(sApply.pSelect);
- memset(&sApply, 0, sizeof(sApply));
sApply.db = db;
+ sApply.pDelete = 0;
+ sApply.pUpdate = 0;
+ sApply.pInsert = 0;
+ sApply.pSelect = 0;
+ sApply.nCol = 0;
+ sApply.azCol = 0;
+ sApply.abPK = 0;
+ sApply.bStat1 = 0;
sApply.bDeferConstraints = 1;
+ sApply.bRebaseStarted = 0;
+ memset(&sApply.constraints, 0, sizeof(SessionBuffer));
/* If an xFilter() callback was specified, invoke it now. If the
** xFilter callback returns zero, skip this table. If it returns
@@ -4293,17 +4393,53 @@ static int sessionChangesetApply(
sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
}
+ if( rc==SQLITE_OK && bPatchset==0 && ppRebase && pnRebase ){
+ *ppRebase = (void*)sApply.rebase.aBuf;
+ *pnRebase = sApply.rebase.nBuf;
+ sApply.rebase.aBuf = 0;
+ }
sqlite3_finalize(sApply.pInsert);
sqlite3_finalize(sApply.pDelete);
sqlite3_finalize(sApply.pUpdate);
sqlite3_finalize(sApply.pSelect);
sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */
sqlite3_free((char*)sApply.constraints.aBuf);
+ sqlite3_free((char*)sApply.rebase.aBuf);
sqlite3_mutex_leave(sqlite3_db_mutex(db));
return rc;
}
/*
+** Apply the changeset passed via pChangeset/nChangeset to the main
+** database attached to handle "db".
+*/
+int sqlite3changeset_apply_v2(
+ sqlite3 *db, /* Apply change to "main" db of this handle */
+ int nChangeset, /* Size of changeset in bytes */
+ void *pChangeset, /* Changeset blob */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ const char *zTab /* Table name */
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase
+){
+ sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
+ int rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
+ if( rc==SQLITE_OK ){
+ rc = sessionChangesetApply(
+ db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase
+ );
+ }
+ return rc;
+}
+
+/*
** Apply the changeset passed via pChangeset/nChangeset to the main database
** attached to handle "db". Invoke the supplied conflict handler callback
** to resolve any conflicts encountered while applying the change.
@@ -4323,12 +4459,9 @@ int sqlite3changeset_apply(
),
void *pCtx /* First argument passed to xConflict */
){
- sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
- int rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
- if( rc==SQLITE_OK ){
- rc = sessionChangesetApply(db, pIter, xFilter, xConflict, pCtx);
- }
- return rc;
+ return sqlite3changeset_apply_v2(
+ db, nChangeset, pChangeset, xFilter, xConflict, pCtx, 0, 0
+ );
}
/*
@@ -4336,7 +4469,7 @@ int sqlite3changeset_apply(
** attached to handle "db". Invoke the supplied conflict handler callback
** to resolve any conflicts encountered while applying the change.
*/
-int sqlite3changeset_apply_strm(
+int sqlite3changeset_apply_v2_strm(
sqlite3 *db, /* Apply change to "main" db of this handle */
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
void *pIn, /* First arg for xInput */
@@ -4349,15 +4482,37 @@ int sqlite3changeset_apply_strm(
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
sqlite3_changeset_iter *p /* Handle describing change and conflict */
),
- void *pCtx /* First argument passed to xConflict */
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase
){
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
if( rc==SQLITE_OK ){
- rc = sessionChangesetApply(db, pIter, xFilter, xConflict, pCtx);
+ rc = sessionChangesetApply(
+ db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase
+ );
}
return rc;
}
+int sqlite3changeset_apply_strm(
+ sqlite3 *db, /* Apply change to "main" db of this handle */
+ int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
+ void *pIn, /* First arg for xInput */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ const char *zTab /* Table name */
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx /* First argument passed to xConflict */
+){
+ return sqlite3changeset_apply_v2_strm(
+ db, xInput, pIn, xFilter, xConflict, pCtx, 0, 0
+ );
+}
/*
** sqlite3_changegroup handle.
@@ -4375,6 +4530,7 @@ struct sqlite3_changegroup {
*/
static int sessionChangeMerge(
SessionTable *pTab, /* Table structure */
+ int bRebase, /* True for a rebase hash-table */
int bPatchset, /* True for patchsets */
SessionChange *pExist, /* Existing change */
int op2, /* Second change operation */
@@ -4384,6 +4540,7 @@ static int sessionChangeMerge(
SessionChange **ppNew /* OUT: Merged change */
){
SessionChange *pNew = 0;
+ int rc = SQLITE_OK;
if( !pExist ){
pNew = (SessionChange *)sqlite3_malloc(sizeof(SessionChange) + nRec);
@@ -4393,9 +4550,66 @@ static int sessionChangeMerge(
memset(pNew, 0, sizeof(SessionChange));
pNew->op = op2;
pNew->bIndirect = bIndirect;
- pNew->nRecord = nRec;
pNew->aRecord = (u8*)&pNew[1];
- memcpy(pNew->aRecord, aRec, nRec);
+ if( bIndirect==0 || bRebase==0 ){
+ pNew->nRecord = nRec;
+ memcpy(pNew->aRecord, aRec, nRec);
+ }else{
+ int i;
+ u8 *pIn = aRec;
+ u8 *pOut = pNew->aRecord;
+ for(i=0; i<pTab->nCol; i++){
+ int nIn = sessionSerialLen(pIn);
+ if( *pIn==0 ){
+ *pOut++ = 0;
+ }else if( pTab->abPK[i]==0 ){
+ *pOut++ = 0xFF;
+ }else{
+ memcpy(pOut, pIn, nIn);
+ pOut += nIn;
+ }
+ pIn += nIn;
+ }
+ pNew->nRecord = pOut - pNew->aRecord;
+ }
+ }else if( bRebase ){
+ if( pExist->op==SQLITE_DELETE && pExist->bIndirect ){
+ *ppNew = pExist;
+ }else{
+ int nByte = nRec + pExist->nRecord + sizeof(SessionChange);
+ pNew = (SessionChange*)sqlite3_malloc(nByte);
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ int i;
+ u8 *a1 = pExist->aRecord;
+ u8 *a2 = aRec;
+ u8 *pOut;
+
+ memset(pNew, 0, nByte);
+ pNew->bIndirect = bIndirect || pExist->bIndirect;
+ pNew->op = op2;
+ pOut = pNew->aRecord = (u8*)&pNew[1];
+
+ for(i=0; i<pTab->nCol; i++){
+ int n1 = sessionSerialLen(a1);
+ int n2 = sessionSerialLen(a2);
+ if( *a1==0xFF || (pTab->abPK[i]==0 && bIndirect) ){
+ *pOut++ = 0xFF;
+ }else if( *a2==0 ){
+ memcpy(pOut, a1, n1);
+ pOut += n1;
+ }else{
+ memcpy(pOut, a2, n2);
+ pOut += n2;
+ }
+ a1 += n1;
+ a2 += n2;
+ }
+ pNew->nRecord = pOut - pNew->aRecord;
+ }
+ sqlite3_free(pExist);
+ }
}else{
int op1 = pExist->op;
@@ -4489,7 +4703,7 @@ static int sessionChangeMerge(
}
*ppNew = pNew;
- return SQLITE_OK;
+ return rc;
}
/*
@@ -4498,15 +4712,15 @@ static int sessionChangeMerge(
*/
static int sessionChangesetToHash(
sqlite3_changeset_iter *pIter, /* Iterator to read from */
- sqlite3_changegroup *pGrp /* Changegroup object to add changeset to */
+ sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */
+ int bRebase /* True if hash table is for rebasing */
){
u8 *aRec;
int nRec;
int rc = SQLITE_OK;
SessionTable *pTab = 0;
-
- while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec) ){
+ while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){
const char *zNew;
int nCol;
int op;
@@ -4586,7 +4800,7 @@ static int sessionChangesetToHash(
}
}
- rc = sessionChangeMerge(pTab,
+ rc = sessionChangeMerge(pTab, bRebase,
pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange
);
if( rc ) break;
@@ -4694,7 +4908,7 @@ int sqlite3changegroup_add(sqlite3_changegroup *pGrp, int nData, void *pData){
rc = sqlite3changeset_start(&pIter, nData, pData);
if( rc==SQLITE_OK ){
- rc = sessionChangesetToHash(pIter, pGrp);
+ rc = sessionChangesetToHash(pIter, pGrp, 0);
}
sqlite3changeset_finalize(pIter);
return rc;
@@ -4725,7 +4939,7 @@ int sqlite3changegroup_add_strm(
rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
if( rc==SQLITE_OK ){
- rc = sessionChangesetToHash(pIter, pGrp);
+ rc = sessionChangesetToHash(pIter, pGrp, 0);
}
sqlite3changeset_finalize(pIter);
return rc;
@@ -4810,4 +5024,347 @@ int sqlite3changeset_concat_strm(
return rc;
}
+/*
+** Changeset rebaser handle.
+*/
+struct sqlite3_rebaser {
+ sqlite3_changegroup grp; /* Hash table */
+};
+
+/*
+** Buffers a1 and a2 must both contain a sessions module record nCol
+** fields in size. This function appends an nCol sessions module
+** record to buffer pBuf that is a copy of a1, except that for
+** each field that is undefined in a1[], swap in the field from a2[].
+*/
+static void sessionAppendRecordMerge(
+ SessionBuffer *pBuf, /* Buffer to append to */
+ int nCol, /* Number of columns in each record */
+ u8 *a1, int n1, /* Record 1 */
+ u8 *a2, int n2, /* Record 2 */
+ int *pRc /* IN/OUT: error code */
+){
+ sessionBufferGrow(pBuf, n1+n2, pRc);
+ if( *pRc==SQLITE_OK ){
+ int i;
+ u8 *pOut = &pBuf->aBuf[pBuf->nBuf];
+ for(i=0; i<nCol; i++){
+ int nn1 = sessionSerialLen(a1);
+ int nn2 = sessionSerialLen(a2);
+ if( *a1==0 || *a1==0xFF ){
+ memcpy(pOut, a2, nn2);
+ pOut += nn2;
+ }else{
+ memcpy(pOut, a1, nn1);
+ pOut += nn1;
+ }
+ a1 += nn1;
+ a2 += nn2;
+ }
+
+ pBuf->nBuf = pOut-pBuf->aBuf;
+ assert( pBuf->nBuf<=pBuf->nAlloc );
+ }
+}
+
+/*
+** This function is called when rebasing a local UPDATE change against one
+** or more remote UPDATE changes. The aRec/nRec buffer contains the current
+** old.* and new.* records for the change. The rebase buffer (a single
+** record) is in aChange/nChange. The rebased change is appended to buffer
+** pBuf.
+**
+** Rebasing the UPDATE involves:
+**
+** * Removing any changes to fields for which the corresponding field
+** in the rebase buffer is set to "replaced" (type 0xFF). If this
+** means the UPDATE change updates no fields, nothing is appended
+** to the output buffer.
+**
+** * For each field modified by the local change for which the
+** corresponding field in the rebase buffer is not "undefined" (0x00)
+** or "replaced" (0xFF), the old.* value is replaced by the value
+** in the rebase buffer.
+*/
+static void sessionAppendPartialUpdate(
+ SessionBuffer *pBuf, /* Append record here */
+ sqlite3_changeset_iter *pIter, /* Iterator pointed at local change */
+ u8 *aRec, int nRec, /* Local change */
+ u8 *aChange, int nChange, /* Record to rebase against */
+ int *pRc /* IN/OUT: Return Code */
+){
+ sessionBufferGrow(pBuf, 2+nRec+nChange, pRc);
+ if( *pRc==SQLITE_OK ){
+ int bData = 0;
+ u8 *pOut = &pBuf->aBuf[pBuf->nBuf];
+ int i;
+ u8 *a1 = aRec;
+ u8 *a2 = aChange;
+
+ *pOut++ = SQLITE_UPDATE;
+ *pOut++ = pIter->bIndirect;
+ for(i=0; i<pIter->nCol; i++){
+ int n1 = sessionSerialLen(a1);
+ int n2 = sessionSerialLen(a2);
+ if( pIter->abPK[i] || a2[0]==0 ){
+ if( !pIter->abPK[i] ) bData = 1;
+ memcpy(pOut, a1, n1);
+ pOut += n1;
+ }else if( a2[0]!=0xFF ){
+ bData = 1;
+ memcpy(pOut, a2, n2);
+ pOut += n2;
+ }else{
+ *pOut++ = '\0';
+ }
+ a1 += n1;
+ a2 += n2;
+ }
+ if( bData ){
+ a2 = aChange;
+ for(i=0; i<pIter->nCol; i++){
+ int n1 = sessionSerialLen(a1);
+ int n2 = sessionSerialLen(a2);
+ if( pIter->abPK[i] || a2[0]!=0xFF ){
+ memcpy(pOut, a1, n1);
+ pOut += n1;
+ }else{
+ *pOut++ = '\0';
+ }
+ a1 += n1;
+ a2 += n2;
+ }
+ pBuf->nBuf = (pOut - pBuf->aBuf);
+ }
+ }
+}
+
+/*
+** pIter is configured to iterate through a changeset. This function rebases
+** that changeset according to the current configuration of the rebaser
+** object passed as the first argument. If no error occurs and argument xOutput
+** is not NULL, then the changeset is returned to the caller by invoking
+** xOutput zero or more times and SQLITE_OK returned. Or, if xOutput is NULL,
+** then (*ppOut) is set to point to a buffer containing the rebased changeset
+** before this function returns. In this case (*pnOut) is set to the size of
+** the buffer in bytes. It is the responsibility of the caller to eventually
+** free the (*ppOut) buffer using sqlite3_free().
+**
+** If an error occurs, an SQLite error code is returned. If ppOut and
+** pnOut are not NULL, then the two output parameters are set to 0 before
+** returning.
+*/
+static int sessionRebase(
+ sqlite3_rebaser *p, /* Rebaser hash table */
+ sqlite3_changeset_iter *pIter, /* Input data */
+ int (*xOutput)(void *pOut, const void *pData, int nData),
+ void *pOut, /* Context for xOutput callback */
+ int *pnOut, /* OUT: Number of bytes in output changeset */
+ void **ppOut /* OUT: Inverse of pChangeset */
+){
+ int rc = SQLITE_OK;
+ u8 *aRec = 0;
+ int nRec = 0;
+ int bNew = 0;
+ SessionTable *pTab = 0;
+ SessionBuffer sOut = {0,0,0};
+
+ while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, &bNew) ){
+ SessionChange *pChange = 0;
+ int bDone = 0;
+
+ if( bNew ){
+ const char *zTab = pIter->zTab;
+ for(pTab=p->grp.pList; pTab; pTab=pTab->pNext){
+ if( 0==sqlite3_stricmp(pTab->zName, zTab) ) break;
+ }
+ bNew = 0;
+
+ /* A patchset may not be rebased */
+ if( pIter->bPatchset ){
+ rc = SQLITE_ERROR;
+ }
+
+ /* Append a table header to the output for this new table */
+ sessionAppendByte(&sOut, pIter->bPatchset ? 'P' : 'T', &rc);
+ sessionAppendVarint(&sOut, pIter->nCol, &rc);
+ sessionAppendBlob(&sOut, pIter->abPK, pIter->nCol, &rc);
+ sessionAppendBlob(&sOut,(u8*)pIter->zTab,(int)strlen(pIter->zTab)+1,&rc);
+ }
+
+ if( pTab && rc==SQLITE_OK ){
+ int iHash = sessionChangeHash(pTab, 0, aRec, pTab->nChange);
+
+ for(pChange=pTab->apChange[iHash]; pChange; pChange=pChange->pNext){
+ if( sessionChangeEqual(pTab, 0, aRec, 0, pChange->aRecord) ){
+ break;
+ }
+ }
+ }
+
+ if( pChange ){
+ assert( pChange->op==SQLITE_DELETE || pChange->op==SQLITE_INSERT );
+ switch( pIter->op ){
+ case SQLITE_INSERT:
+ if( pChange->op==SQLITE_INSERT ){
+ bDone = 1;
+ if( pChange->bIndirect==0 ){
+ sessionAppendByte(&sOut, SQLITE_UPDATE, &rc);
+ sessionAppendByte(&sOut, pIter->bIndirect, &rc);
+ sessionAppendBlob(&sOut, pChange->aRecord, pChange->nRecord, &rc);
+ sessionAppendBlob(&sOut, aRec, nRec, &rc);
+ }
+ }
+ break;
+
+ case SQLITE_UPDATE:
+ bDone = 1;
+ if( pChange->op==SQLITE_DELETE ){
+ if( pChange->bIndirect==0 ){
+ u8 *pCsr = aRec;
+ sessionSkipRecord(&pCsr, pIter->nCol);
+ sessionAppendByte(&sOut, SQLITE_INSERT, &rc);
+ sessionAppendByte(&sOut, pIter->bIndirect, &rc);
+ sessionAppendRecordMerge(&sOut, pIter->nCol,
+ pCsr, nRec-(pCsr-aRec),
+ pChange->aRecord, pChange->nRecord, &rc
+ );
+ }
+ }else{
+ sessionAppendPartialUpdate(&sOut, pIter,
+ aRec, nRec, pChange->aRecord, pChange->nRecord, &rc
+ );
+ }
+ break;
+
+ default:
+ assert( pIter->op==SQLITE_DELETE );
+ bDone = 1;
+ if( pChange->op==SQLITE_INSERT ){
+ sessionAppendByte(&sOut, SQLITE_DELETE, &rc);
+ sessionAppendByte(&sOut, pIter->bIndirect, &rc);
+ sessionAppendRecordMerge(&sOut, pIter->nCol,
+ pChange->aRecord, pChange->nRecord, aRec, nRec, &rc
+ );
+ }
+ break;
+ }
+ }
+
+ if( bDone==0 ){
+ sessionAppendByte(&sOut, pIter->op, &rc);
+ sessionAppendByte(&sOut, pIter->bIndirect, &rc);
+ sessionAppendBlob(&sOut, aRec, nRec, &rc);
+ }
+ if( rc==SQLITE_OK && xOutput && sOut.nBuf>SESSIONS_STRM_CHUNK_SIZE ){
+ rc = xOutput(pOut, sOut.aBuf, sOut.nBuf);
+ sOut.nBuf = 0;
+ }
+ if( rc ) break;
+ }
+
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(sOut.aBuf);
+ memset(&sOut, 0, sizeof(sOut));
+ }
+
+ if( rc==SQLITE_OK ){
+ if( xOutput ){
+ if( sOut.nBuf>0 ){
+ rc = xOutput(pOut, sOut.aBuf, sOut.nBuf);
+ }
+ }else{
+ *ppOut = (void*)sOut.aBuf;
+ *pnOut = sOut.nBuf;
+ sOut.aBuf = 0;
+ }
+ }
+ sqlite3_free(sOut.aBuf);
+ return rc;
+}
+
+/*
+** Create a new rebaser object.
+*/
+int sqlite3rebaser_create(sqlite3_rebaser **ppNew){
+ int rc = SQLITE_OK;
+ sqlite3_rebaser *pNew;
+
+ pNew = sqlite3_malloc(sizeof(sqlite3_rebaser));
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ memset(pNew, 0, sizeof(sqlite3_rebaser));
+ }
+ *ppNew = pNew;
+ return rc;
+}
+
+/*
+** Call this one or more times to configure a rebaser.
+*/
+int sqlite3rebaser_configure(
+ sqlite3_rebaser *p,
+ int nRebase, const void *pRebase
+){
+ sqlite3_changeset_iter *pIter = 0; /* Iterator opened on pData/nData */
+ int rc; /* Return code */
+ rc = sqlite3changeset_start(&pIter, nRebase, (void*)pRebase);
+ if( rc==SQLITE_OK ){
+ rc = sessionChangesetToHash(pIter, &p->grp, 1);
+ }
+ sqlite3changeset_finalize(pIter);
+ return rc;
+}
+
+/*
+** Rebase a changeset according to current rebaser configuration
+*/
+int sqlite3rebaser_rebase(
+ sqlite3_rebaser *p,
+ int nIn, const void *pIn,
+ int *pnOut, void **ppOut
+){
+ sqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */
+ int rc = sqlite3changeset_start(&pIter, nIn, (void*)pIn);
+
+ if( rc==SQLITE_OK ){
+ rc = sessionRebase(p, pIter, 0, 0, pnOut, ppOut);
+ sqlite3changeset_finalize(pIter);
+ }
+
+ return rc;
+}
+
+/*
+** Rebase a changeset according to current rebaser configuration
+*/
+int sqlite3rebaser_rebase_strm(
+ sqlite3_rebaser *p,
+ int (*xInput)(void *pIn, void *pData, int *pnData),
+ void *pIn,
+ int (*xOutput)(void *pOut, const void *pData, int nData),
+ void *pOut
+){
+ sqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */
+ int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
+
+ if( rc==SQLITE_OK ){
+ rc = sessionRebase(p, pIter, xOutput, pOut, 0, 0);
+ sqlite3changeset_finalize(pIter);
+ }
+
+ return rc;
+}
+
+/*
+** Destroy a rebaser object
+*/
+void sqlite3rebaser_delete(sqlite3_rebaser *p){
+ if( p ){
+ sessionDeleteTable(p->grp.pList);
+ sqlite3_free(p);
+ }
+}
+
#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h
index 9f33855..d4da20d 100644
--- a/ext/session/sqlite3session.h
+++ b/ext/session/sqlite3session.h
@@ -13,16 +13,23 @@ extern "C" {
/*
** CAPI3REF: Session Object Handle
+**
+** An instance of this object is a [session] that can be used to
+** record changes to a database.
*/
typedef struct sqlite3_session sqlite3_session;
/*
** CAPI3REF: Changeset Iterator Handle
+**
+** An instance of this object acts as a cursor for iterating
+** over the elements of a [changeset] or [patchset].
*/
typedef struct sqlite3_changeset_iter sqlite3_changeset_iter;
/*
** CAPI3REF: Create A New Session Object
+** CONSTRUCTOR: sqlite3_session
**
** Create a new session object attached to database handle db. If successful,
** a pointer to the new object is written to *ppSession and SQLITE_OK is
@@ -59,6 +66,7 @@ int sqlite3session_create(
/*
** CAPI3REF: Delete A Session Object
+** DESTRUCTOR: sqlite3_session
**
** Delete a session object previously allocated using
** [sqlite3session_create()]. Once a session object has been deleted, the
@@ -74,6 +82,7 @@ void sqlite3session_delete(sqlite3_session *pSession);
/*
** CAPI3REF: Enable Or Disable A Session Object
+** METHOD: sqlite3_session
**
** Enable or disable the recording of changes by a session object. When
** enabled, a session object records changes made to the database. When
@@ -93,6 +102,7 @@ int sqlite3session_enable(sqlite3_session *pSession, int bEnable);
/*
** CAPI3REF: Set Or Clear the Indirect Change Flag
+** METHOD: sqlite3_session
**
** Each change recorded by a session object is marked as either direct or
** indirect. A change is marked as indirect if either:
@@ -122,6 +132,7 @@ int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);
/*
** CAPI3REF: Attach A Table To A Session Object
+** METHOD: sqlite3_session
**
** If argument zTab is not NULL, then it is the name of a table to attach
** to the session object passed as the first argument. All subsequent changes
@@ -184,6 +195,7 @@ int sqlite3session_attach(
/*
** CAPI3REF: Set a table filter on a Session Object.
+** METHOD: sqlite3_session
**
** The second argument (xFilter) is the "filter callback". For changes to rows
** in tables that are not attached to the Session object, the filter is called
@@ -202,6 +214,7 @@ void sqlite3session_table_filter(
/*
** CAPI3REF: Generate A Changeset From A Session Object
+** METHOD: sqlite3_session
**
** Obtain a changeset containing changes to the tables attached to the
** session object passed as the first argument. If successful,
@@ -311,7 +324,8 @@ int sqlite3session_changeset(
);
/*
-** CAPI3REF: Load The Difference Between Tables Into A Session
+** CAPI3REF: Load The Difference Between Tables Into A Session
+** METHOD: sqlite3_session
**
** If it is not already attached to the session object passed as the first
** argument, this function attaches table zTbl in the same manner as the
@@ -376,6 +390,7 @@ int sqlite3session_diff(
/*
** CAPI3REF: Generate A Patchset From A Session Object
+** METHOD: sqlite3_session
**
** The differences between a patchset and a changeset are that:
**
@@ -427,6 +442,7 @@ int sqlite3session_isempty(sqlite3_session *pSession);
/*
** CAPI3REF: Create An Iterator To Traverse A Changeset
+** CONSTRUCTOR: sqlite3_changeset_iter
**
** Create an iterator used to iterate through the contents of a changeset.
** If successful, *pp is set to point to the iterator handle and SQLITE_OK
@@ -467,6 +483,7 @@ int sqlite3changeset_start(
/*
** CAPI3REF: Advance A Changeset Iterator
+** METHOD: sqlite3_changeset_iter
**
** This function may only be used with iterators created by function
** [sqlite3changeset_start()]. If it is called on an iterator passed to
@@ -491,6 +508,7 @@ int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
/*
** CAPI3REF: Obtain The Current Operation From A Changeset Iterator
+** METHOD: sqlite3_changeset_iter
**
** The pIter argument passed to this function may either be an iterator
** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
@@ -525,6 +543,7 @@ int sqlite3changeset_op(
/*
** CAPI3REF: Obtain The Primary Key Definition Of A Table
+** METHOD: sqlite3_changeset_iter
**
** For each modified table, a changeset includes the following:
**
@@ -556,6 +575,7 @@ int sqlite3changeset_pk(
/*
** CAPI3REF: Obtain old.* Values From A Changeset Iterator
+** METHOD: sqlite3_changeset_iter
**
** The pIter argument passed to this function may either be an iterator
** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
@@ -586,6 +606,7 @@ int sqlite3changeset_old(
/*
** CAPI3REF: Obtain new.* Values From A Changeset Iterator
+** METHOD: sqlite3_changeset_iter
**
** The pIter argument passed to this function may either be an iterator
** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
@@ -619,6 +640,7 @@ int sqlite3changeset_new(
/*
** CAPI3REF: Obtain Conflicting Row Values From A Changeset Iterator
+** METHOD: sqlite3_changeset_iter
**
** This function should only be used with iterator objects passed to a
** conflict-handler callback by [sqlite3changeset_apply()] with either
@@ -646,6 +668,7 @@ int sqlite3changeset_conflict(
/*
** CAPI3REF: Determine The Number Of Foreign Key Constraint Violations
+** METHOD: sqlite3_changeset_iter
**
** This function may only be called with an iterator passed to an
** SQLITE_CHANGESET_FOREIGN_KEY conflict handler callback. In this case
@@ -662,6 +685,7 @@ int sqlite3changeset_fk_conflicts(
/*
** CAPI3REF: Finalize A Changeset Iterator
+** METHOD: sqlite3_changeset_iter
**
** This function is used to finalize an iterator allocated with
** [sqlite3changeset_start()].
@@ -678,6 +702,7 @@ int sqlite3changeset_fk_conflicts(
** to that error is returned by this function. Otherwise, SQLITE_OK is
** returned. This is to allow the following pattern (pseudo-code):
**
+** <pre>
** sqlite3changeset_start();
** while( SQLITE_ROW==sqlite3changeset_next() ){
** // Do something with change.
@@ -686,6 +711,7 @@ int sqlite3changeset_fk_conflicts(
** if( rc!=SQLITE_OK ){
** // An error has occurred
** }
+** </pre>
*/
int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter);
@@ -733,6 +759,7 @@ int sqlite3changeset_invert(
** sqlite3_changegroup object. Calling it produces similar results as the
** following code fragment:
**
+** <pre>
** sqlite3_changegroup *pGrp;
** rc = sqlite3_changegroup_new(&pGrp);
** if( rc==SQLITE_OK ) rc = sqlite3changegroup_add(pGrp, nA, pA);
@@ -743,6 +770,7 @@ int sqlite3changeset_invert(
** *ppOut = 0;
** *pnOut = 0;
** }
+** </pre>
**
** Refer to the sqlite3_changegroup documentation below for details.
*/
@@ -758,11 +786,15 @@ int sqlite3changeset_concat(
/*
** CAPI3REF: Changegroup Handle
+**
+** A changegroup is an object used to combine two or more
+** [changesets] or [patchsets]
*/
typedef struct sqlite3_changegroup sqlite3_changegroup;
/*
** CAPI3REF: Create A New Changegroup Object
+** CONSTRUCTOR: sqlite3_changegroup
**
** An sqlite3_changegroup object is used to combine two or more changesets
** (or patchsets) into a single changeset (or patchset). A single changegroup
@@ -800,6 +832,7 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp);
/*
** CAPI3REF: Add A Changeset To A Changegroup
+** METHOD: sqlite3_changegroup
**
** Add all changes within the changeset (or patchset) in buffer pData (size
** nData bytes) to the changegroup.
@@ -877,6 +910,7 @@ int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
/*
** CAPI3REF: Obtain A Composite Changeset From A Changegroup
+** METHOD: sqlite3_changegroup
**
** Obtain a buffer containing a changeset (or patchset) representing the
** current contents of the changegroup. If the inputs to the changegroup
@@ -907,25 +941,25 @@ int sqlite3changegroup_output(
/*
** CAPI3REF: Delete A Changegroup Object
+** DESTRUCTOR: sqlite3_changegroup
*/
void sqlite3changegroup_delete(sqlite3_changegroup*);
/*
** CAPI3REF: Apply A Changeset To A Database
**
-** Apply a changeset to a database. This function attempts to update the
-** "main" database attached to handle db with the changes found in the
-** changeset passed via the second and third arguments.
+** Apply a changeset or patchset to a database. These functions attempt to
+** update the "main" database attached to handle db with the changes found in
+** the changeset passed via the second and third arguments.
**
-** The fourth argument (xFilter) passed to this function is the "filter
+** The fourth argument (xFilter) passed to these functions is the "filter
** callback". If it is not NULL, then for each table affected by at least one
** change in the changeset, the filter callback is invoked with
** the table name as the second argument, and a copy of the context pointer
-** passed as the sixth argument to this function as the first. If the "filter
-** callback" returns zero, then no attempt is made to apply any changes to
-** the table. Otherwise, if the return value is non-zero or the xFilter
-** argument to this function is NULL, all changes related to the table are
-** attempted.
+** passed as the sixth argument as the first. If the "filter callback"
+** returns zero, then no attempt is made to apply any changes to the table.
+** Otherwise, if the return value is non-zero or the xFilter argument to
+** is NULL, all changes related to the table are attempted.
**
** For each table that is not excluded by the filter callback, this function
** tests that the target database contains a compatible table. A table is
@@ -970,7 +1004,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
**
** <dl>
** <dt>DELETE Changes<dd>
-** For each DELETE change, this function checks if the target database
+** For each DELETE change, the function checks if the target database
** contains a row with the same primary key value (or values) as the
** original row values stored in the changeset. If it does, and the values
** stored in all non-primary key columns also match the values stored in
@@ -1015,7 +1049,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** [SQLITE_CHANGESET_REPLACE].
**
** <dt>UPDATE Changes<dd>
-** For each UPDATE change, this function checks if the target database
+** For each UPDATE change, the function checks if the target database
** contains a row with the same primary key value (or values) as the
** original row values stored in the changeset. If it does, and the values
** stored in all modified non-primary key columns also match the values
@@ -1046,11 +1080,21 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** This can be used to further customize the applications conflict
** resolution strategy.
**
-** All changes made by this function are enclosed in a savepoint transaction.
+** All changes made by these functions are enclosed in a savepoint transaction.
** If any other error (aside from a constraint failure when attempting to
** write to the target database) occurs, then the savepoint transaction is
** rolled back, restoring the target database to its original state, and an
** SQLite error code returned.
+**
+** If the output parameters (ppRebase) and (pnRebase) are non-NULL and
+** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2()
+** may set (*ppRebase) to point to a "rebase" that may be used with the
+** sqlite3_rebaser APIs buffer before returning. In this case (*pnRebase)
+** is set to the size of the buffer in bytes. It is the responsibility of the
+** caller to eventually free any such buffer using sqlite3_free(). The buffer
+** is only allocated and populated if one or more conflicts were encountered
+** while applying the patchset. See comments surrounding the sqlite3_rebaser
+** APIs for further details.
*/
int sqlite3changeset_apply(
sqlite3 *db, /* Apply change to "main" db of this handle */
@@ -1067,6 +1111,22 @@ int sqlite3changeset_apply(
),
void *pCtx /* First argument passed to xConflict */
);
+int sqlite3changeset_apply_v2(
+ sqlite3 *db, /* Apply change to "main" db of this handle */
+ int nChangeset, /* Size of changeset in bytes */
+ void *pChangeset, /* Changeset blob */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ const char *zTab /* Table name */
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase
+);
/*
** CAPI3REF: Constants Passed To The Conflict Handler
@@ -1164,6 +1224,161 @@ int sqlite3changeset_apply(
#define SQLITE_CHANGESET_REPLACE 1
#define SQLITE_CHANGESET_ABORT 2
+/*
+** CAPI3REF: Rebasing changesets
+** EXPERIMENTAL
+**
+** Suppose there is a site hosting a database in state S0. And that
+** modifications are made that move that database to state S1 and a
+** changeset recorded (the "local" changeset). Then, a changeset based
+** on S0 is received from another site (the "remote" changeset) and
+** applied to the database. The database is then in state
+** (S1+"remote"), where the exact state depends on any conflict
+** resolution decisions (OMIT or REPLACE) made while applying "remote".
+** Rebasing a changeset is to update it to take those conflict
+** resolution decisions into account, so that the same conflicts
+** do not have to be resolved elsewhere in the network.
+**
+** For example, if both the local and remote changesets contain an
+** INSERT of the same key on "CREATE TABLE t1(a PRIMARY KEY, b)":
+**
+** local: INSERT INTO t1 VALUES(1, 'v1');
+** remote: INSERT INTO t1 VALUES(1, 'v2');
+**
+** and the conflict resolution is REPLACE, then the INSERT change is
+** removed from the local changeset (it was overridden). Or, if the
+** conflict resolution was "OMIT", then the local changeset is modified
+** to instead contain:
+**
+** UPDATE t1 SET b = 'v2' WHERE a=1;
+**
+** Changes within the local changeset are rebased as follows:
+**
+** <dl>
+** <dt>Local INSERT<dd>
+** This may only conflict with a remote INSERT. If the conflict
+** resolution was OMIT, then add an UPDATE change to the rebased
+** changeset. Or, if the conflict resolution was REPLACE, add
+** nothing to the rebased changeset.
+**
+** <dt>Local DELETE<dd>
+** This may conflict with a remote UPDATE or DELETE. In both cases the
+** only possible resolution is OMIT. If the remote operation was a
+** DELETE, then add no change to the rebased changeset. If the remote
+** operation was an UPDATE, then the old.* fields of change are updated
+** to reflect the new.* values in the UPDATE.
+**
+** <dt>Local UPDATE<dd>
+** This may conflict with a remote UPDATE or DELETE. If it conflicts
+** with a DELETE, and the conflict resolution was OMIT, then the update
+** is changed into an INSERT. Any undefined values in the new.* record
+** from the update change are filled in using the old.* values from
+** the conflicting DELETE. Or, if the conflict resolution was REPLACE,
+** the UPDATE change is simply omitted from the rebased changeset.
+**
+** If conflict is with a remote UPDATE and the resolution is OMIT, then
+** the old.* values are rebased using the new.* values in the remote
+** change. Or, if the resolution is REPLACE, then the change is copied
+** into the rebased changeset with updates to columns also updated by
+** the conflicting remote UPDATE removed. If this means no columns would
+** be updated, the change is omitted.
+** </dl>
+**
+** A local change may be rebased against multiple remote changes
+** simultaneously. If a single key is modified by multiple remote
+** changesets, they are combined as follows before the local changeset
+** is rebased:
+**
+** <ul>
+** <li> If there has been one or more REPLACE resolutions on a
+** key, it is rebased according to a REPLACE.
+**
+** <li> If there have been no REPLACE resolutions on a key, then
+** the local changeset is rebased according to the most recent
+** of the OMIT resolutions.
+** </ul>
+**
+** Note that conflict resolutions from multiple remote changesets are
+** combined on a per-field basis, not per-row. This means that in the
+** case of multiple remote UPDATE operations, some fields of a single
+** local change may be rebased for REPLACE while others are rebased for
+** OMIT.
+**
+** In order to rebase a local changeset, the remote changeset must first
+** be applied to the local database using sqlite3changeset_apply_v2() and
+** the buffer of rebase information captured. Then:
+**
+** <ol>
+** <li> An sqlite3_rebaser object is created by calling
+** sqlite3rebaser_create().
+** <li> The new object is configured with the rebase buffer obtained from
+** sqlite3changeset_apply_v2() by calling sqlite3rebaser_configure().
+** If the local changeset is to be rebased against multiple remote
+** changesets, then sqlite3rebaser_configure() should be called
+** multiple times, in the same order that the multiple
+** sqlite3changeset_apply_v2() calls were made.
+** <li> Each local changeset is rebased by calling sqlite3rebaser_rebase().
+** <li> The sqlite3_rebaser object is deleted by calling
+** sqlite3rebaser_delete().
+** </ol>
+*/
+typedef struct sqlite3_rebaser sqlite3_rebaser;
+
+/*
+** CAPI3REF: Create a changeset rebaser object.
+** EXPERIMENTAL
+**
+** Allocate a new changeset rebaser object. If successful, set (*ppNew) to
+** point to the new object and return SQLITE_OK. Otherwise, if an error
+** occurs, return an SQLite error code (e.g. SQLITE_NOMEM) and set (*ppNew)
+** to NULL.
+*/
+int sqlite3rebaser_create(sqlite3_rebaser **ppNew);
+
+/*
+** CAPI3REF: Configure a changeset rebaser object.
+** EXPERIMENTAL
+**
+** Configure the changeset rebaser object to rebase changesets according
+** to the conflict resolutions described by buffer pRebase (size nRebase
+** bytes), which must have been obtained from a previous call to
+** sqlite3changeset_apply_v2().
+*/
+int sqlite3rebaser_configure(
+ sqlite3_rebaser*,
+ int nRebase, const void *pRebase
+);
+
+/*
+** CAPI3REF: Rebase a changeset
+** EXPERIMENTAL
+**
+** Argument pIn must point to a buffer containing a changeset nIn bytes
+** in size. This function allocates and populates a buffer with a copy
+** of the changeset rebased rebased according to the configuration of the
+** rebaser object passed as the first argument. If successful, (*ppOut)
+** is set to point to the new buffer containing the rebased changset and
+** (*pnOut) to its size in bytes and SQLITE_OK returned. It is the
+** responsibility of the caller to eventually free the new buffer using
+** sqlite3_free(). Otherwise, if an error occurs, (*ppOut) and (*pnOut)
+** are set to zero and an SQLite error code returned.
+*/
+int sqlite3rebaser_rebase(
+ sqlite3_rebaser*,
+ int nIn, const void *pIn,
+ int *pnOut, void **ppOut
+);
+
+/*
+** CAPI3REF: Delete a changeset rebaser object.
+** EXPERIMENTAL
+**
+** Delete the changeset rebaser object and all associated resources. There
+** should be one call to this function for each successful invocation
+** of sqlite3rebaser_create().
+*/
+void sqlite3rebaser_delete(sqlite3_rebaser *p);
+
/*
** CAPI3REF: Streaming Versions of API functions.
**
@@ -1268,6 +1483,22 @@ int sqlite3changeset_apply_strm(
),
void *pCtx /* First argument passed to xConflict */
);
+int sqlite3changeset_apply_v2_strm(
+ sqlite3 *db, /* Apply change to "main" db of this handle */
+ int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
+ void *pIn, /* First arg for xInput */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ const char *zTab /* Table name */
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase
+);
int sqlite3changeset_concat_strm(
int (*xInputA)(void *pIn, void *pData, int *pnData),
void *pInA,
@@ -1305,6 +1536,13 @@ int sqlite3changegroup_output_strm(sqlite3_changegroup*,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
+int sqlite3rebaser_rebase_strm(
+ sqlite3_rebaser *pRebaser,
+ int (*xInput)(void *pIn, void *pData, int *pnData),
+ void *pIn,
+ int (*xOutput)(void *pOut, const void *pData, int nData),
+ void *pOut
+);
/*
diff --git a/ext/session/test_session.c b/ext/session/test_session.c
index 411354c..bdd144b 100644
--- a/ext/session/test_session.c
+++ b/ext/session/test_session.c
@@ -14,6 +14,10 @@
# endif
#endif
+#ifndef SQLITE_AMALGAMATION
+ typedef unsigned char u8;
+#endif
+
typedef struct TestSession TestSession;
struct TestSession {
sqlite3_session *pSession;
@@ -711,10 +715,8 @@ static int testStreamInput(
}
-/*
-** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
-*/
-static int SQLITE_TCLAPI test_sqlite3changeset_apply(
+static int SQLITE_TCLAPI testSqlite3changesetApply(
+ int bV2,
void * clientData,
Tcl_Interp *interp,
int objc,
@@ -727,6 +729,8 @@ static int SQLITE_TCLAPI test_sqlite3changeset_apply(
int nChangeset; /* Size of buffer aChangeset in bytes */
TestConflictHandler ctx;
TestStreamInput sStr;
+ void *pRebase = 0;
+ int nRebase = 0;
memset(&sStr, 0, sizeof(sStr));
sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
@@ -748,25 +752,69 @@ static int SQLITE_TCLAPI test_sqlite3changeset_apply(
ctx.interp = interp;
if( sStr.nStream==0 ){
- rc = sqlite3changeset_apply(db, nChangeset, pChangeset,
- (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx
- );
+ if( bV2==0 ){
+ rc = sqlite3changeset_apply(db, nChangeset, pChangeset,
+ (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx
+ );
+ }else{
+ rc = sqlite3changeset_apply_v2(db, nChangeset, pChangeset,
+ (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx,
+ &pRebase, &nRebase
+ );
+ }
}else{
sStr.aData = (unsigned char*)pChangeset;
sStr.nData = nChangeset;
- rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
- (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx
- );
+ if( bV2==0 ){
+ rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
+ (objc==5) ? test_filter_handler : 0,
+ test_conflict_handler, (void *)&ctx
+ );
+ }else{
+ rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr,
+ (objc==5) ? test_filter_handler : 0,
+ test_conflict_handler, (void *)&ctx,
+ &pRebase, &nRebase
+ );
+ }
}
if( rc!=SQLITE_OK ){
return test_session_error(interp, rc, 0);
+ }else{
+ Tcl_ResetResult(interp);
+ if( bV2 && pRebase ){
+ Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pRebase, nRebase));
+ }
}
- Tcl_ResetResult(interp);
+ sqlite3_free(pRebase);
return TCL_OK;
}
/*
+** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
+*/
+static int SQLITE_TCLAPI test_sqlite3changeset_apply(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ return testSqlite3changesetApply(0, clientData, interp, objc, objv);
+}
+/*
+** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
+*/
+static int SQLITE_TCLAPI test_sqlite3changeset_apply_v2(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ return testSqlite3changesetApply(1, clientData, interp, objc, objv);
+}
+
+/*
** sqlite3changeset_apply_replace_all DB CHANGESET
*/
static int SQLITE_TCLAPI test_sqlite3changeset_apply_replace_all(
@@ -1019,6 +1067,125 @@ static int SQLITE_TCLAPI test_sqlite3session_foreach(
return TCL_OK;
}
+/*
+** tclcmd: CMD configure REBASE-BLOB
+** tclcmd: CMD rebase CHANGESET
+** tclcmd: CMD delete
+*/
+static int SQLITE_TCLAPI test_rebaser_cmd(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ struct RebaseSubcmd {
+ const char *zSub;
+ int nArg;
+ const char *zMsg;
+ int iSub;
+ } aSub[] = {
+ { "configure", 1, "REBASE-BLOB" }, /* 0 */
+ { "delete", 0, "" }, /* 1 */
+ { "rebase", 1, "CHANGESET" }, /* 2 */
+ { 0 }
+ };
+
+ sqlite3_rebaser *p = (sqlite3_rebaser*)clientData;
+ int iSub;
+ int rc;
+
+ if( objc<2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
+ return TCL_ERROR;
+ }
+ rc = Tcl_GetIndexFromObjStruct(interp,
+ objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
+ );
+ if( rc!=TCL_OK ) return rc;
+ if( objc!=2+aSub[iSub].nArg ){
+ Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
+ return TCL_ERROR;
+ }
+
+ assert( iSub==0 || iSub==1 || iSub==2 );
+ assert( rc==SQLITE_OK );
+ switch( iSub ){
+ case 0: { /* configure */
+ int nRebase = 0;
+ unsigned char *pRebase = Tcl_GetByteArrayFromObj(objv[2], &nRebase);
+ rc = sqlite3rebaser_configure(p, nRebase, pRebase);
+ break;
+ }
+
+ case 1: /* delete */
+ Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
+ break;
+
+ default: { /* rebase */
+ TestStreamInput sStr; /* Input stream */
+ TestSessionsBlob sOut; /* Output blob */
+
+ memset(&sStr, 0, sizeof(sStr));
+ memset(&sOut, 0, sizeof(sOut));
+ sStr.aData = Tcl_GetByteArrayFromObj(objv[2], &sStr.nData);
+ sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
+
+ if( sStr.nStream ){
+ rc = sqlite3rebaser_rebase_strm(p,
+ testStreamInput, (void*)&sStr,
+ testStreamOutput, (void*)&sOut
+ );
+ }else{
+ rc = sqlite3rebaser_rebase(p, sStr.nData, sStr.aData, &sOut.n, &sOut.p);
+ }
+
+ if( rc==SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(sOut.p, sOut.n));
+ }
+ sqlite3_free(sOut.p);
+ break;
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ return test_session_error(interp, rc, 0);
+ }
+ return TCL_OK;
+}
+
+static void SQLITE_TCLAPI test_rebaser_del(void *clientData){
+ sqlite3_rebaser *p = (sqlite3_rebaser*)clientData;
+ sqlite3rebaser_delete(p);
+}
+
+/*
+** tclcmd: sqlite3rebaser_create NAME
+*/
+static int SQLITE_TCLAPI test_sqlite3rebaser_create(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ int rc;
+ sqlite3_rebaser *pNew = 0;
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "NAME");
+ return SQLITE_ERROR;
+ }
+
+ rc = sqlite3rebaser_create(&pNew);
+ if( rc!=SQLITE_OK ){
+ return test_session_error(interp, rc, 0);
+ }
+
+ Tcl_CreateObjCommand(interp, Tcl_GetString(objv[1]), test_rebaser_cmd,
+ (ClientData)pNew, test_rebaser_del
+ );
+ Tcl_SetObjResult(interp, objv[1]);
+ return TCL_OK;
+}
+
int TestSession_Init(Tcl_Interp *interp){
struct Cmd {
const char *zCmd;
@@ -1029,9 +1196,11 @@ int TestSession_Init(Tcl_Interp *interp){
{ "sqlite3changeset_invert", test_sqlite3changeset_invert },
{ "sqlite3changeset_concat", test_sqlite3changeset_concat },
{ "sqlite3changeset_apply", test_sqlite3changeset_apply },
+ { "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2 },
{ "sqlite3changeset_apply_replace_all",
test_sqlite3changeset_apply_replace_all },
{ "sql_exec_changeset", test_sql_exec_changeset },
+ { "sqlite3rebaser_create", test_sqlite3rebaser_create },
};
int i;
diff --git a/main.mk b/main.mk
index f2640b4..e5722c5 100644
--- a/main.mk
+++ b/main.mk
@@ -65,7 +65,7 @@ LIBOBJ+= vdbe.o parse.o \
fts3_write.o fts5.o func.o global.o hash.o \
icu.o insert.o json1.o legacy.o loadext.o \
main.o malloc.o mem0.o mem1.o mem2.o mem3.o mem5.o \
- memjournal.o \
+ memdb.o memjournal.o \
mutex.o mutex_noop.o mutex_unix.o mutex_w32.o \
notify.o opcodes.o os.o os_unix.o os_win.o \
pager.o pcache.o pcache1.o pragma.o prepare.o printf.o \
@@ -118,6 +118,7 @@ SRC = \
$(TOP)/src/mem2.c \
$(TOP)/src/mem3.c \
$(TOP)/src/mem5.c \
+ $(TOP)/src/memdb.c \
$(TOP)/src/memjournal.c \
$(TOP)/src/msvc.h \
$(TOP)/src/mutex.c \
@@ -361,6 +362,7 @@ TESTSRC += \
$(TOP)/ext/misc/ieee754.c \
$(TOP)/ext/misc/mmapwarm.c \
$(TOP)/ext/misc/nextchar.c \
+ $(TOP)/ext/misc/normalize.c \
$(TOP)/ext/misc/percentile.c \
$(TOP)/ext/misc/regexp.c \
$(TOP)/ext/misc/remember.c \
@@ -518,6 +520,7 @@ SHELL_OPT += -DSQLITE_INTROSPECTION_PRAGMAS
FUZZERSHELL_OPT = -DSQLITE_ENABLE_JSON1
FUZZCHECK_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5
FUZZCHECK_OPT += -DSQLITE_MAX_MEMORY=50000000
+FUZZCHECK_OPT += -DSQLITE_PRINTF_PRECISION_LIMIT=1000
DBFUZZ_OPT =
KV_OPT = -DSQLITE_THREADSAFE=0 -DSQLITE_DIRECT_OVERFLOW_READ
ST_OPT = -DSQLITE_THREADSAFE=0
@@ -572,6 +575,9 @@ ossshell$(EXE): $(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c sqlite3.h
-DSQLITE_ENABLE_MEMSYS5 $(FUZZCHECK_OPT) \
$(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c $(TLIBS) $(THREADLIB)
+sessionfuzz$(EXE): $(TOP)/test/sessionfuzz.c sqlite3.c sqlite3.h
+ $(TCC) -o sessionfuzz$(EXE) $(TOP)/test/sessionfuzz.c -lz $(TLIBS) $(THREADLIB)
+
mptester$(EXE): sqlite3.c $(TOP)/mptest/mptest.c
$(TCCX) -o $@ -I. $(TOP)/mptest/mptest.c sqlite3.c \
$(TLIBS) $(THREADLIB)
@@ -892,14 +898,17 @@ fulltestonly: $(TESTPROGS) fuzztest
queryplantest: testfixture$(EXE) sqlite3$(EXE)
./testfixture$(EXE) $(TOP)/test/permutations.test queryplanner $(TESTOPTS)
-fuzztest: fuzzcheck$(EXE) $(FUZZDATA)
+fuzztest: fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) $(TOP)/test/sessionfuzz-data1.db
./fuzzcheck$(EXE) $(FUZZDATA)
+ ./sessionfuzz run $(TOP)/test/sessionfuzz-data1.db
-fastfuzztest: fuzzcheck$(EXE) $(FUZZDATA)
+fastfuzztest: fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) $(TOP)/test/sessionfuzz-data1.db
./fuzzcheck$(EXE) --limit-mem 100M $(FUZZDATA)
+ ./sessionfuzz run $(TOP)/test/sessionfuzz-data1.db
-valgrindfuzz: fuzzcheck$(EXE) $(FUZZDATA)
+valgrindfuzz: fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) $(TOP)/test/sessionfuzz-data1.db
valgrind ./fuzzcheck$(EXE) --cell-size-check --limit-mem 10M --timeout 600 $(FUZZDATA)
+ valgrind ./sessionfuzz run $(TOP)/test/sessionfuzz-data1.db
# The veryquick.test TCL tests.
#
@@ -1063,6 +1072,7 @@ clean:
rm -f mptester mptester.exe
rm -f fuzzershell fuzzershell.exe
rm -f fuzzcheck fuzzcheck.exe
+ rm -f sessionfuzz
rm -f sqldiff sqldiff.exe
rm -f fts5.* fts5parse.*
rm -f lsm.h lsm1.c
diff --git a/manifest b/manifest
index e85112c..b01b4e6 100644
--- a/manifest
+++ b/manifest
@@ -1,22 +1,22 @@
-C Version\s3.22.0
-D 2018-01-22T18:45:57.681
+C Version\s3.23.0
+D 2018-04-02T11:04:16.044
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
-F Makefile.in 38f84f301cbef443b2d269f67a74b8cc536469831f70df7c3e912acc04932cc2
+F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
-F Makefile.msc ede26e3fb675e0b3b07627640ce5917154a6ee7f8f2c97424eb5ab5f651cbd56
-F README.md d748f58e3ab0fe0307fb4ae0942b415d93dcc4288756e366cc9e7cf8260c093f
-F VERSION 0c10cdfed866fdd2d80434f64f042c3330f1daaed12e54287beb104f04b3faaf
+F Makefile.msc bdcad21b027a56a73e54a1121cfb9edd0a35c0abfa53aa12c2f996006ff99960
+F README.md 1d5342ebda97420f114283e604e5fe99b0da939d63b76d492eabbaae23488276
+F VERSION cdf91ac446255ecf3d8f6d8c3ee40d64123235ae5b3cef29d344e61b45ec3759
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2
F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90
F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2
F autoconf/INSTALL 83e4a25da9fd053c7b3665eaaaf7919707915903
-F autoconf/Makefile.am 6cca3f797c649b40c762484ce26491839fec54de72d376d774969e76ed13931f
-F autoconf/Makefile.msc 2c50a59319af7da4eaca8c13e3240881b1bc245fd175845a055faab7d03d6e67
+F autoconf/Makefile.am 2c274948734e03c51790ff51468f91db8d570bcca864284d9c6d6e777264cd7e
+F autoconf/Makefile.msc 1223d1520e0b833041ad87b377fae61cc3e08d14c5aae4c1a9e36249225bd4e6
F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7
F autoconf/README.txt 4f04b0819303aabaa35fff5f7b257fb0c1ef95f1
-F autoconf/configure.ac aeeed858e5e54e79052ae44ba774e56595dcb787f23a2155aa98a8aa27327b66
+F autoconf/configure.ac 18fca06f884213be062dd5e07c5297079cc45893d9cd3f522ce426e715033e3d
F autoconf/tea/Makefile.in b438a7020446c8a8156e8d97c8914a04833da6fd
F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873
F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43
@@ -32,7 +32,7 @@ F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63
F config.guess 226d9a188c6196f3033ffc651cbc9dcee1a42977
F config.h.in 6376abec766e9a0785178b1823b5a587e9f1ccbc
F config.sub 9ebe4c3b3dab6431ece34f16828b594fb420da55
-F configure 9af547be0e0e1a8fca8553b82599b5a3be1528a3d78deb68cb49d3b611215cb7 x
+F configure 2c71f331b463e987567a2dd942f728534f1aa7a174551e08a7b31b328e9da4ff x
F configure.ac d4529ebb26ae046269334f1dac65f2b1d6927c2efe22b2ec24dce24dfe4f83dd
F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad
F doc/lemon.html 278113807f49d12d04179a93fab92b5b917a08771152ca7949d34e928efa3941
@@ -43,7 +43,7 @@ F ext/async/README.txt e12275968f6fde133a80e04387d0e839b0c51f91
F ext/async/sqlite3async.c 0f3070cc3f5ede78f2b9361fb3b629ce200d7d74
F ext/async/sqlite3async.h f489b080af7e72aec0e1ee6f1d98ab6cf2e4dcef
F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3
-F ext/expert/expert.c 4791c5e064aea81b2b829fa95228b22283380ee370ea88a1e580103b75516ebf
+F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4
F ext/expert/expert1.test fd21496d8e52c817a7741f467f42b0502c0ac7e07dcdd1d6e15a3e8154ed4e41
F ext/expert/sqlite3expert.c 1dfa561e64dc0f89d56b96e6afda87468c34b43604c2df50c47e3f4362778fb2
F ext/expert/sqlite3expert.h af6354f8ee5c9e025024e63fec3bd640a802afcc3099a44d804752cf0791d811
@@ -96,7 +96,7 @@ F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3
F ext/fts3/fts3_tokenizer1.c 5c98225a53705e5ee34824087478cf477bdb7004
F ext/fts3/fts3_unicode.c 525a3bd9a7564603c5c061b7de55403a565307758a94600e8a2f6b00d1c40d9d
F ext/fts3/fts3_unicode2.c cc04fc672bfd42b1e650398cb0bf71f64f9aae032cfe75bbcfe75b9cf966029c
-F ext/fts3/fts3_write.c a3f7bf869622d1d0aa66661ba71d88e6f9646d69a2c335f40a0addf25974db47
+F ext/fts3/fts3_write.c b583dede85eb0c3c3026f8d7ccb781ea4e845ae583754fecb2ca425b5907d87d
F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9
F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100
F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87fea4ac01b73
@@ -111,11 +111,11 @@ F ext/fts5/fts5Int.h eda28e3a0a5d87c412e8355fe35da875b04cb389908c8eb0d867ad662ad
F ext/fts5/fts5_aux.c ca666a3bbe07c5a3bbe9fffaea19c935a1efaf337333e28bad7bdd1971ffd093
F ext/fts5/fts5_buffer.c 1dd1ec0446b3acfc2d7d407eb894762a461613e2695273f48e449bfd13e973ff
F ext/fts5/fts5_config.c 5af9c360e99669d29f06492c370892394aba0857
-F ext/fts5/fts5_expr.c 01048018d21524e2c302b063ff5c3cdcf546e03297215e577205d85b47499deb
+F ext/fts5/fts5_expr.c c23a2e4c14c401a147c4a730460e5b37057627bf4be95515ee281cd87f4d277c
F ext/fts5/fts5_hash.c 32be400cf761868c9db33efe81a06eb19a17c5402ad477ee9efb51301546dd55
-F ext/fts5/fts5_index.c 5fe14375a29e8a7aa8f3e863babe180a19269206c254c8f47b216821d4ac1e15
+F ext/fts5/fts5_index.c 22b71d0e9e4b3ddd123a39ae27174e0012da2806f91b64087a68584f13f189de
F ext/fts5/fts5_main.c 24868f88ab2a865defbba7a92eebeb726cc991eb092b71b5f5508f180c72605b
-F ext/fts5/fts5_storage.c fb5ef3c27073f67ade2e1bea08405f9e43f68f5f3676ed0ab7013bce5ba10be6
+F ext/fts5/fts5_storage.c 4bec8a1b3905978b22a67bca5f4a3cfdb94af234cf51efb36f4f2d733d278634
F ext/fts5/fts5_tcl.c 39bcbae507f594aad778172fa914cad0f585bf92fd3b078c686e249282db0d95
F ext/fts5/fts5_test_mi.c 65864ba1e5c34a61d409c4c587e0bbe0466eb4f8f478d85dc42a92caad1338e6
F ext/fts5/fts5_test_tok.c ffd657dd67e7fcdb31bf63fb60b6d867299a581d0f46e97086abacd66c2a9b26
@@ -126,7 +126,7 @@ F ext/fts5/fts5_vocab.c 1cd79854cb21543e66507b25b0578bc1b20aa6a1349b7feceb8e8fed
F ext/fts5/fts5parse.y eb526940f892ade5693f22ffd6c4f2702543a9059942772526eac1fde256bb05
F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba
F ext/fts5/test/fts5_common.tcl b01c584144b5064f30e6c648145a2dd6bc440841
-F ext/fts5/test/fts5aa.test cba3fae6466446980caf1b9f5f26df77f95a999d35db7d932d6e82ae7ba0ede9
+F ext/fts5/test/fts5aa.test 6e2fdb0ee667c05f41921e7ec345cae874be651670900918e9ccc539514b9356
F ext/fts5/test/fts5ab.test 9205c839332c908aaad2b01ab8670ece8b161e8f2ec8a9fabf18ca9385880bb7
F ext/fts5/test/fts5ac.test a7aa7e1fefc6e1918aa4d3111d5c44a09177168e962c5fd2cca9620de8a7ed6d
F ext/fts5/test/fts5ad.test e8cf959dfcd57c8e46d6f5f25665686f3b6627130a9a981371dafdf6482790de
@@ -217,7 +217,7 @@ F ext/fts5/tool/fts5txt2db.tcl 526a9979c963f1c54fd50976a05a502e533a4c59
F ext/fts5/tool/loadfts5.tcl 95b03429ee6b138645703c6ca192c3ac96eaf093
F ext/fts5/tool/mkfts5c.tcl d1c2a9ab8e0ec690a52316f33dd9b1d379942f45
F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c
-F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43
+F ext/icu/README.txt a295e91db742b153e8dce8f7efd31d28ad1eea4df31ef4daa3eedc85be2f5138
F ext/icu/icu.c c2c7592574c08cd1270d909b8fb8797f6ea1f49e931e71dbcc25506b9b224580
F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
F ext/lsm1/Makefile 98b0a24b45e248283d6bea4b6cb3e58d7b394edd8e96a0ac28c5fa5104813bad
@@ -269,31 +269,32 @@ F ext/misc/README.md d6dd0fe1d8af77040216798a6a2b0c46c73054d2f0ea544fbbcdccf6f23
F ext/misc/amatch.c 6db4607cb17c54b853a2d7c7c36046d004853f65b9b733e6f019d543d5dfae87
F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a22525eddb
F ext/misc/appendvfs.c 3777f22ec1057dc4e5fd89f2fbddcc7a29fbeef1ad038c736c54411bb1967af7
-F ext/misc/btreeinfo.c d7fd9a2fe2fa33ba28488e2fce703ebecc759219ea9e0bb3b254784866c0a676
+F ext/misc/btreeinfo.c 78c8c57d325185ccc04b7679e5b020e34a4d9c87453e6b7ac943d0a26cee3256
F ext/misc/carray.c ed96c218ea940b85c9a274c4d9c59fe9491c299147a38a8bba537687bd6c6005
F ext/misc/closure.c 0d2a038df8fbae7f19de42e7c7d71f2e4dc88704
-F ext/misc/completion.c 52c3f01523e3e387eb321b4739a89d1fe47cbe6025aa1f2d8d3685e9e365df0f
+F ext/misc/completion.c 0d0bd16378415b982e7119baddef52a0d2cc25860c238a9d2832b0cc6a84a16d
F ext/misc/compress.c dd4f8a6d0baccff3c694757db5b430f3bbd821d8686d1fc24df55cf9f035b189
F ext/misc/csv.c 1a009b93650732e22334edc92459c4630b9fa703397cbb3c8ca279921a36ca11
-F ext/misc/dbdump.c 3509fa6b8932d04e932d6b6b827b6a82ca362781b8e8f3c77336f416793e215e
+F ext/misc/dbdump.c 22018e00eb50e9ebf9067c92d4e7162dc5006a3efc4e0c19bc3829825a1043b0
F ext/misc/eval.c f971962e92ebb8b0a4e6b62949463ee454d88fa2
-F ext/misc/fileio.c 06bd79dcc43d0887da27ffaadd69b8a698b1bafe203d1d134a3a2964f69368f9
+F ext/misc/fileio.c 48c7751c78fc4cdd29d8c862fd2f3f98bbfefa2a3cf1ca1496df4bf02eb8cded
F ext/misc/fuzzer.c 7c64b8197bb77b7d64eff7cac7848870235d4c25
F ext/misc/ieee754.c f190d0cc5182529acb15babd177781be1ac1718c
F ext/misc/json1.c dbe086615b9546c156bf32b9378fc09383b58bd17513b866cfd24c1e15281984
F ext/misc/memvfs.c ab36f49e02ebcdf85a1e08dc4d8599ea8f343e073ac9e0bca18a98b7e1ec9567
F ext/misc/mmapwarm.c 70b618f2d0bde43fae288ad0b7498a629f2b6f61b50a27e06fae3cd23c83af29
F ext/misc/nextchar.c 35c8b8baacb96d92abbb34a83a997b797075b342
+F ext/misc/normalize.c 19262ef3ef29d4de2f281b423326865c8916c63d0cb09f1dc98d24d5c1e8ba64
F ext/misc/percentile.c 92699c8cd7d517ff610e6037e56506f8904dae2e
F ext/misc/regexp.c a68d25c659bd2d893cd1215667bbf75ecb9dc7d4
F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c
F ext/misc/rot13.c 540a169cb0d74f15522a8930b0cccdcb37a4fd071d219a5a083a319fc6e8db77
-F ext/misc/scrub.c 1c5bfb8b0cd18b602fcb55755e84abf0023ac2fb
+F ext/misc/scrub.c db9fff56fed322ca587d73727c6021b11ae79ce3f31b389e1d82891d144f22ad
F ext/misc/series.c f3c0dba5c5c749ce1782b53076108f87cf0b71041eb6023f727a9c50681da564
F ext/misc/sha1.c 0b9e9b855354910d3ca467bf39099d570e73db56
F ext/misc/shathree.c 9e960ba50483214c6a7a4b1517f8d8cef799e9db381195178c3fd3ad207e10c0
F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
-F ext/misc/spellfix.c 41cf26c6b89fcaa8798ae10ae64d39c1f1d9d6995152e545bd491c13058b8fac
+F ext/misc/spellfix.c 54d650f44f3a69a851814791bd4d304575cdbbf78d96d4f0801b44a8f31a58c5
F ext/misc/sqlar.c 57d5bc45cd5492208e451f697404be88f8612527d64c9d42f96b325b64983d74
F ext/misc/stmt.c 6f16443abb3551e3f5813bb13ba19a30e7032830015b0f92fe0c0453045c0a11
F ext/misc/totype.c 4a167594e791abeed95e0a8db028822b5e8fe512
@@ -303,7 +304,8 @@ F ext/misc/vfsstat.c bf10ef0bc51e1ad6756629e1edb142f7a8db1178
F ext/misc/vtablog.c 31d0d8f4406795679dcd3a67917c213d3a2a5fb3ea5de35f6e773491ed7e13c9
F ext/misc/vtshim.c 1976e6dd68dd0d64508c91a6dfab8e75f8aaf6cd
F ext/misc/wholenumber.c 784b12543d60702ebdd47da936e278aa03076212
-F ext/misc/zipfile.c d99efb67ecdfcae7e1855984c218c8c33d0d46a833eaa4b5a5c3d7a4f6690ce4
+F ext/misc/zipfile.c c4de8f0ad446ce4a49aae11ff7b771cd7af60d7136c0bcfb53da1475b9075e79
+F ext/misc/zorder.c b0ff58fa643afa1d846786d51ea8d5c4b6b35aa0254ab5a82617db92f3adda64
F ext/rbu/rbu.c ea7d1b7eb44c123a2a619332e19fe5313500705c4a58aaa1887905c0d83ffc2e
F ext/rbu/rbu1.test 43836fac8c7179a358eaf38a8a1ef3d6e6285842
F ext/rbu/rbu10.test 1846519a438697f45e9dcb246908af81b551c29e1078d0304fae83f1fed7e9ee
@@ -320,7 +322,8 @@ F ext/rbu/rbu9.test 0806d1772c9f4981774ff028de6656e4183082af
F ext/rbu/rbuA.test 4e58e46e60d4064248614c43303d71f1b18cc804dd834ce6a913b3861828b28d
F ext/rbu/rbuB.test c25bc325b8072a766e56bb76c001866b405925c2
F ext/rbu/rbuC.test efe47db508a0269b683cb2a1913a425ffd39a831
-F ext/rbu/rbu_common.tcl a38e8e2d4a50fd6aaf151633714c1b1d2fae3ead
+F ext/rbu/rbu_common.tcl ebb8d81f44dc20e360cff1f34eb2ad0def33128805c5b36afcc44ab338509589
+F ext/rbu/rbucollate.test 86d6fc9b8f59a27b7b5a6e20b5e29816d338a0dbdea8c54bfcc549a0d437f3ea
F ext/rbu/rbucrash.test 61470d977a06a0abc2ec35b05d82a1d7d87d10f4ffabad14c1c231edc942ad66
F ext/rbu/rbucrash2.test b2ecbdd7bb72c88bd217c65bd00dafa07f7f2d4d
F ext/rbu/rbudiff.test 3e605cf624d00d04d0fb1316a3acec4fbe3b3ac5
@@ -337,27 +340,27 @@ F ext/rbu/rbusave.test 0f43b6686084f426ddd040b878426452fd2c2f48
F ext/rbu/rbutemplimit.test cd553a9288d515d0b5f87d277e76fd18c4aa740b761e7880fab11ce986ea18d1
F ext/rbu/rbuvacuum.test ff357e9b556ca7ad4673da0ff7f244def919ff858e0f9f350d3e30fdd83a62a8
F ext/rbu/rbuvacuum2.test 2074ab14fe66e1c7e7210c62562650dcd215bbaa
-F ext/rbu/sqlite3rbu.c 64bd08c1011456f90564ed167abce3a9c2af421a924b21eb57231e078da04feb
+F ext/rbu/sqlite3rbu.c f6e9ca388b5d4680fbf266a4d10a21aec11d6baf48f6d06fd53f6b205fad959f
F ext/rbu/sqlite3rbu.h b42bcd4d8357268c6c39ab2a60b29c091e89328fa8cc49c8fac5ab8d007e79b2
-F ext/rbu/test_rbu.c 7073979b9cc80912bb03599ac8d85ab5d3bf03cfacd3463f2dcdd7822997533a
+F ext/rbu/test_rbu.c baa23eb28457580673d2175e5f0c29ced0cd320ee819b13ad362398c53b96e90
F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15
F ext/repair/checkfreelist.c 0dbae18c1b552f58d64f8969e4fb1e7f11930c60a8c2a9a8d50b7f15bdfd54bd
F ext/repair/checkindex.c 7d28c01a2e012ac64257d230fc452b2cafb78311a91a343633d01d95220f66f3
F ext/repair/sqlite3_checker.c.in 4a5a3af3f450fe503e5a2985e98516dc2a6b9ad247449e284c1cf140fc91720f
-F ext/repair/sqlite3_checker.tcl cc69e7fbc163f94da4a6400609be001543442d9f8f57a797d1eeb7b897585730
+F ext/repair/sqlite3_checker.tcl a9a2caa9660567257c177a91124d8c0dccdfa341e25c51e6da7f1fd9e601eafa
F ext/repair/test/README.md 34b2f542cf5be7bffe479242b33ee3492cea30711e447cc4a1a86cb5915f419e
F ext/repair/test/checkfreelist01.test 3e8aa6aeb4007680c94a8d07b41c339aa635cc78249442da72ff3f8297398a69
F ext/repair/test/checkindex01.test 6945d0ffc0c1dc993b2ce88036b26e0f5d6fcc65da70fc9df27c2647bb358b0f
F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c
F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
-F ext/rtree/rtree.c d941e44ad901da039caebb9f9fa99d81f2a4fc822e67cafe33fa4f6f789074a0
+F ext/rtree/rtree.c bc61010e978b5b8ae6dbb90274a2fbb5db5ff5e2880b5c6e8abd48eea77264db
F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412
-F ext/rtree/rtree1.test 82a353747fcab1083d114b2ac84723dfefdbf86c1a6e1df57bf588c7d4285436
+F ext/rtree/rtree1.test 47e2095bebea6813754fd7afa6a20e2b7b4ebcd5cb7dbcb6932b6c9f86bbf972
F ext/rtree/rtree2.test 5f25b01acd03470067a2d52783b2eb0a50bf836803d4342d20ca39e541220fe2
F ext/rtree/rtree3.test 2cafe8265d1ff28f206fce88d114f208349df482
-F ext/rtree/rtree4.test 67b021858ba4334c8d49b3449476942c2ce0e5ef7123538f2e9dd508ed03a12d
-F ext/rtree/rtree5.test 8aaa4bcdc42f718fe165572f5623e4732831aca95a2bc32482d33d4d2cf1325d
-F ext/rtree/rtree6.test 773a90db2dce6a8353dd0d5b64bca69b29761196
+F ext/rtree/rtree4.test 304de65d484540111b896827e4261815e5dca4ce28eeecd58be648cd73452c4b
+F ext/rtree/rtree5.test 49c9041d713d54560b315c2c7ef7207ee287eba1b20f8266968a06f2e55d3142
+F ext/rtree/rtree6.test 916a641d2beac01b9880871ff07612d56c1e466190a27c82ab36ffd58be03b9f
F ext/rtree/rtree7.test c8fb2e555b128dd0f0bdb520c61380014f497f8a23c40f2e820acc9f9e4fdce5
F ext/rtree/rtree8.test 649f5a37ec656028a4a32674b9b1183104285a7625a09d2a8f52a1cef72c93f2
F ext/rtree/rtree9.test c646f12c8c1c68ef015c6c043d86a0c42488e2e68ed1bb1b0771a7ca246cbabf
@@ -367,7 +370,7 @@ F ext/rtree/rtreeC.test d9d06dda1aee68b4dc227dfcc899f335f8b621e9d1920ee3d4e5dab8
F ext/rtree/rtreeD.test fe46aa7f012e137bd58294409b16c0d43976c3bb92c8f710481e577c4a1100dc
F ext/rtree/rtreeE.test e65d3fc625da1800b412fc8785817327d43ccfec5f5973912d8c9e471928caa9
F ext/rtree/rtreeF.test 81ffa7ef51c4e4618d497a57328c265bf576990c7070633b623b23cd450ed331
-F ext/rtree/rtreeG.test fd3af1ca944a0bdb0cbb5455a4905c9f012e2fffcab6b791f07afa0dcbbcae0e
+F ext/rtree/rtreeG.test 1b9ca6e3effb48f4161edaa463ddeaa8fca4b2526d084f9cbf5dbe4e0184939c
F ext/rtree/rtree_perf.tcl 6c18c1f23cd48e0f948930c98dfdd37dfccb5195
F ext/rtree/rtree_util.tcl db734b4c5e75fed6acc56d9701f2235345acfdec750b5fc7b587936f5f6bceed
F ext/rtree/rtreecheck.test 4d29103d1e16fcbf90135d1c637b833688492b063b2971dfb5dc6ba76555cfee
@@ -379,7 +382,7 @@ F ext/session/changeset.c 4ccbaa4531944c24584bf6a61ba3a39c62b6267a
F ext/session/session1.test 736d7ff178662f0b717c37f46531b84a5ce0210ccb0c4edf629c55dbcbbc3ea1
F ext/session/session2.test 284de45abae4cc1082bc52012ee81521d5ac58e0
F ext/session/session3.test ce9ce3dfa489473987f899e9f6a0f2db9bde3479
-F ext/session/session4.test 457b02bdc349eb01151e54de014df77abd3c08c8
+F ext/session/session4.test 6778997065b44d99c51ff9cece047ff9244a32856b328735ae27ddef68979c40
F ext/session/session5.test 716bc6fafd625ce60dfa62ae128971628c1a1169
F ext/session/session6.test 443789bc2fca12e4f7075cf692c60b8a2bea1a26
F ext/session/session8.test 8e194b3f655d861ca36de5d4de53f702751bab3b
@@ -392,24 +395,25 @@ F ext/session/sessionE.test 0a616c4ad8fd2c05f23217ebb6212ef80b7fef30f5f086a6633a
F ext/session/sessionF.test c2f178d4dfd723a5fd94a730ea2ccb44c669e3ce
F ext/session/sessionG.test 63f9a744341d670775af29e4f19c1ef09a4810798400f28cd76704803a2e56ff
F ext/session/sessionH.test 332b60e4c2e0a680105e11936201cabe378216f307e2747803cea56fa7d9ebae
-F ext/session/session_common.tcl 7776eda579773113b30c7abfd4545c445228cb73
+F ext/session/session_common.tcl 748141b02042b942e04a7afad9ffb2212a3997de536ed95f6dec7bb5018ede2c
F ext/session/session_speed_test.c edc1f96fd5e0e4b16eb03e2a73041013d59e8723
F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28f0c1cc142c3ec
F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec
F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7
-F ext/session/sessionfault2.test 04aa0bc9aa70ea43d8de82c4f648db4de1e990b0
+F ext/session/sessionfault2.test 555a8504de03d59b369ef20209585da5aeb2671dedabc4584e9ffe6269689185
+F ext/session/sessionrebase.test 4e1bcfd26fd8ed8ac571746f56cceeb45184f4d65490ea0d405227cfc8a9cba8
F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e
F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc
-F ext/session/sqlite3session.c 989466bba4dff0ede8d4c450b1fc65ca222b87e31193eddbf3931b88bf898a57
-F ext/session/sqlite3session.h 01774161cbd328fe3d496323655b9cc142317ff1fb1ae15c1232075ea240e3a4
-F ext/session/test_session.c eb0bd6c1ea791c1d66ee4ef94c16500dad936386
+F ext/session/sqlite3session.c 4e21db8d2abb7960ded6f66e745671442e3ae2156a5ff8f7cf07567c507c324e
+F ext/session/sqlite3session.h 85fd2dc3df1532b0695beb345e2ff375c2745a4654b405fcbe33afa18baa6cc7
+F ext/session/test_session.c f253742ea01b089326f189b5ae15a5b55c1c9e97452e4a195ee759ba51b404d5
F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
F ext/userauth/userauth.c 3410be31283abba70255d71fd24734e017a4497f
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
-F main.mk 4d19895cb268021474ade6244119c80b8a3f1d910cb5b13cf5e04f5f37c3d61b
+F main.mk 63668484c95454af7fc04a384da27ac556f27368d6d0c345e405e1677c66768f
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
@@ -422,81 +426,82 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b
F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786
F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a
F src/alter.c cf7a8af45cb0ace672f47a1b29ab24092a9e8cd8d945a9974e3b5d925f548594
-F src/analyze.c 6b42e36a5dcc2703a771f2411bd5e99524bd62c7ecde209bb88dfb04c72f046e
-F src/attach.c 84c477e856b24c2b9a0983b438a707c0cf4d616cee7a425401d418e58afec24c
+F src/analyze.c 71fbbeb7b25417592f54d869fe90c28b48e4cecb9926ef9b06d90fb0aec48941
+F src/attach.c f6f212c43dddba79dfcb723fb9470785f3ff55bde8953cd9d2546f3022070a41
F src/auth.c 6277d63837357549fe14e723490d6dc1a38768d71c795c5eb5c0f8a99f918f73
F src/backup.c faf17e60b43233c214aae6a8179d24503a61e83b
F src/bitvec.c 17ea48eff8ba979f1f5b04cc484c7bb2be632f33
F src/btmutex.c 0e9ce2d56159b89b9bc8e197e023ee11e39ff8ca
-F src/btree.c bfc453babec9aa8196ab5db5e588ac5d3f0c398d72faa37296167a84a61c9f2f
+F src/btree.c 9eb9531c65346bbfccf5325384b7db1849daf4db6601dcfe21ba5c5b20623b64
F src/btree.h 0866c0a08255142ea0e754aabd211c843cab32045c978a592a43152405ed0c84
-F src/btreeInt.h 55b702efce17e5d1941865464227d3802cfc9c7c832fac81d4c94dced47a71fc
-F src/build.c 9f9647454f236cab097f266ae970f899b53c71cadab6756c47e2b2e81392c2a1
+F src/btreeInt.h 620ab4c7235f43572cf3ac2ac8723cbdf68073be4d29da24897c7b77dda5fd96
+F src/build.c 5ac9eb8afcd3e8b8088cc954d867bd7860bdb19558d48d5220e3c4050e721626
F src/callback.c fe677cb5f5abb02f7a772a62a98c2f516426081df68856e8f2d5f950929b966a
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
-F src/ctime.c ff1be3eed7bdd75aaca61ca8dc848f7c9f850ef2fb9cb56f2734e922a098f9c0
+F src/ctime.c bd9da3f1ff21b432564a16ef0b154cff03585dc43742842e99c58907c6cb4bef
F src/date.c ebe1dc7c8a347117bb02570f1a931c62dd78f4a2b1b516f4837d45b7d6426957
F src/dbpage.c 8db4c97f630e7d83f884ea75caf1ffd0988c160e9d530194d93721c80821e0f6
-F src/dbstat.c 7a4ba8518b6369ef3600c49cf9c918ad979acba610b2aebef1b656d649b96720
+F src/dbstat.c edabb82611143727511a45ca0859b8cd037851ebe756ae3db289859dd18b6f91
F src/delete.c 20c8788451dc737a967c87ea53ad43544d617f5b57d32ccce8bd52a0daf9e89b
-F src/expr.c 9e06de431c09f144438aa6895ea4d4290fa3c6875bfcc3ba331012ca78deadf0
+F src/expr.c 51500461dcd4d0873a938bf188d03076d6712d70ff00a0f0d65621edf4e521c6
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
F src/fkey.c d617daf66b5515e2b42c1405b2b4984c30ca50fb705ab164271a9bf66c69e331
-F src/func.c bd528d5ed68ce5cbf78a762e3b735fa75009f7197ff07fab07fd771f35ebaa1b
-F src/global.c ac3094f1dc59fbeb919aef7cc0cc827a8459d1fb1adb7972ef75bd9e0c10b75b
+F src/func.c 94f42cba2cc1c34aeaa441022ba0170ec3fec4bba54db4e0ded085c6dc0fdc51
+F src/global.c 01506976bd75e5e7b977207a6a05062e2dd0050012f8071be06bbea22ec6d69a
F src/hash.c a12580e143f10301ed5166ea4964ae2853d3905a511d4e0c44497245c7ce1f7a
F src/hash.h ab34c5c54a9e9de2e790b24349ba5aab3dbb4fd4
F src/hwtime.h 747c1bbe9df21a92e9c50f3bbec1de841dc5e5da
F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
-F src/insert.c 14686083cedc198540b15a79586cdd4be2acf6d5fa97627e355f817ab07e9fee
+F src/insert.c b9ff71cc2913d1d57698a1e22bf853261a9a642baf62bdf40ddeb3809adb85b5
F src/legacy.c 134ab3e3fae00a0f67a5187981d6935b24b337bcf0f4b3e5c9fa5763da95bf4e
F src/loadext.c f6e4e416a736369f9e80eba609f0acda97148a8b0453784d670c78d3eed2f302
-F src/main.c 26918d50dd4a61b8f6f210320a522f46b5e7e592335b6aa664ab15b80b7c239b
+F src/main.c 1648fc7a9bcfdbfd9a9a04af96ff2796c3164b3f3c7e56ed63a3c51cd11d198d
F src/malloc.c 07295435093ce354c6d9063ac05a2eeae28bd251d2e63c48b3d67c12c76f7e18
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de
F src/mem2.c f1940d9e91948dd6a908fbb9ce3835c36b5d83c3
F src/mem3.c 8768ac94694f31ffaf8b4d0ea5dc08af7010a35a
F src/mem5.c 9bf955937b07f8c32541c8a9991f33ce3173d944
+F src/memdb.c e94c478a757c4307fd170fe0a7650ef4cf722c59e5a95a8a7896ffedc1679139
F src/memjournal.c 6f3d36a0a8f72f48f6c3c722f04301ac64f2515435fa42924293e46fc7994661
F src/msvc.h 4942752b6a253116baaa8de75256c51a459a5e81
F src/mutex.c b021263554c8a3995e9d53193b8194b96d1ed28e06c3b532dd7f7d29cf0c7d53
F src/mutex.h 779d588e3b7756ec3ecf7d78cde1d84aba414f85
F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4
-F src/mutex_unix.c 27bb6cc49485ee46711a6580ab7b3f1402211d23
-F src/mutex_w32.c a898fa969823b100c0f5fdc57e54c9a1e419ab4d
+F src/mutex_unix.c aaf9ebc3f89df28483c52208497a99a02cc3650011422fc9d4c57e4392f7fe58
+F src/mutex_w32.c 7670d770c94bbfe8289bec9d7f1394c5a00a57c37f892aab6b6612d085255235
F src/notify.c 9711a7575036f0d3040ba61bc6e217f13a9888e7
-F src/os.c 22d31db3ca5a96a408fbf1ceeaaebcaf64c87024d2ff9fe1cf2ddbec3e75c104
+F src/os.c 4d83f700d77ac5ad598c970041669040cb3c21147356dffa558f5f8b9291fda7
F src/os.h 48388821692e87da174ea198bf96b1b2d9d83be5dfc908f673ee21fafbe0d432
F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85
F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586
-F src/os_unix.c a82505be158d8ce42b38dcc9b426187d776904c12cdc68dc8925e1dfcc5cb6ce
-F src/os_win.c 501dde1ee770f4ffa458bfe1cf376a556de3ab00bb8320d659c5984403991d62
+F src/os_unix.c 2b53b0b8ddc580db096252c721729e5f5f2f355b4fc056f8f3fb328aeb3c9e8a
+F src/os_win.c eb03c6d52f893bcd7fdd4c6006674c13c1b5e49543fec98d605201af2997171c
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
-F src/pager.c 9b9cb4e06c03d43d62480a7a685a012d645fcf3a39e7767ccb505fb41ee083ec
-F src/pager.h 581698f2177e8bd4008fe4760898ce20b6133d1df22139b9101b5155f900df7a
-F src/parse.y 4e750e1b261ff9f1d0b6b5d40a829c66d691899f48953fde839d8b52d41aa148
-F src/pcache.c 7ae91a4557a43d77d449accbfdc68846e6516f8e2eda46e8bbe4536fb669b201
+F src/pager.c 1bb6a57fa0465296a4d6109a1a64610a0e7adde1f3acf3ef539a9d972908ce8f
+F src/pager.h c571b064df842ec8f2e90855dead9acf4cbe0d1b2c05afe0ef0d0145f7fd0388
+F src/parse.y 140bbc53b5f67f731239f7fc8704a4f1e60cbbc10fb84bf9577322f974725f19
+F src/pcache.c 135ef0bc6fb2e3b7178d49ab5c9176254c8a691832c1bceb1156b2fbdd0869bd
F src/pcache.h 072f94d29281cffd99e46c1539849f248c4b56ae7684c1f36626797fee375170
F src/pcache1.c 716975564c15eb6679e97f734cec1bfd6c16ac3d4010f05f1f8e509fc7d19880
F src/pragma.c bea56df3ae0637768c0da4fbbb8f2492f780980d95000034a105ff291bf7ca69
F src/pragma.h bb83728944b42f6d409c77f5838a8edbdb0fe83046c5496ffc9602b40340a324
-F src/prepare.c 259f4e7960c47082c9653f3d5f0c294abd68bb9c3aab86de7630700cba1c20fb
-F src/printf.c 9506b4b96e59c0467047155f09015750cb2878aeda3d39e5610c1192ddc3c41c
+F src/prepare.c b086fea6a1952db88beca31fdd621201ee5e4ce3f02905248cc3035a8174aa89
+F src/printf.c d3b7844ddeb11fbbdd38dd84d09c9c1ac171d21fb038473c3aa97981201cc660
F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
-F src/resolve.c bbee7e31d369a18a2f4836644769882e9c5d40ef4a3af911db06410b65cb3730
+F src/resolve.c 66c73fcb7719b8ff0e841b58338f13604ff3e2b50a723f9b8f383595735262f6
F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
-F src/select.c bebe7cce45d899d2237c76bce059d525abf5b861f2fce92f6b53914a961c01ba
-F src/shell.c.in 4e1bcf8c70b8fb97c7cbaca6602e2a291d7fe17eff23a5de003d6fabd87f27d1
-F src/sqlite.h.in 959deaad89679e31d7f68fda668b0c5d1f592fffed7a9c1740fb8ded4e4e754a
+F src/select.c 16d1f3510cef527e25067d04253e347c899346800d74f31629735295e9fac8ae
+F src/shell.c.in d6a07811aa9f3b10200c15ab8dd4b6b998849a3b0c8b125bfa980329a33c26a6
+F src/sqlite.h.in e0be726ea6e4e6571724d39d242472ecd8bd1ba6f84ade88e1641bde98a6d02b
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
-F src/sqlite3ext.h 99189e7611eb0bf98f21c7835dc74730a84e2e809c98e1e31c33896dee7a2849
-F src/sqliteInt.h 9c70315598b34810a83e4894455acb18e95cf63ce4e6cbb451ac2d17eabc2544
+F src/sqlite3ext.h 83a3c4ce93d650bedfd1aa558cb85a516bd6d094445ee989740827d0d944368d
+F src/sqliteInt.h a4837c57f9a3e2af100bc59f4be60d16b823f18131f8cef6a6685440f775eebd
F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b
-F src/status.c 9737ed017279a9e0c5da748701c3c7bf1e8ae0dae459aad20dd64fcff97a7e35
+F src/status.c 46e7aec11f79dad50965a5ca5fa9de009f7d6bde08be2156f1538a0a296d4d0e
F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34
-F src/tclsqlite.c 1833388c01e3b77f4c712185ee7250b9423ee0981ce6ae7e401e47db0319a696
-F src/test1.c b52f9e7fe62016d357c3266fcfa0793cc1883d3cb2b11dfa39fcba2e70b0305c
+F src/tclsqlite.c 916a92de77ec5cbe27818ca194d8cf0c58aa7ad5b87527098f6aa5a6068800ce
+F src/test1.c 1ab7cbbb6693e08364c1a9241e2aee17f8c4925e4cc52396be77ae6845a05828
F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5
F src/test3.c b8434949dfb8aff8dfa082c8b592109e77844c2135ed3c492113839b6956255b
F src/test4.c 18ec393bb4d0ad1de729f0b94da7267270f3d8e6
@@ -511,7 +516,7 @@ F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0
F src/test_bestindex.c 78809f11026f18a93fcfd798d9479cba37e1201c830260bf1edc674b2fa9b857
F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce
F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274
-F src/test_config.c cc8a1d44648d9392a14f4ecfc841d027daaf61f952b9f70792edf11373aaa3dd
+F src/test_config.c 097c6189803886a1fb26ec37d8bc62b90512cb53ab79a1fb6d35196c1ec42ded
F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf9f8f
F src/test_demovfs.c a0c3bdd45ed044115c2c9f7779e56eafff18741e
F src/test_devsym.c 1960abbb234b97e9b920f07e99503fc04b443f62bbc3c6ff2c2cea2133e3b8a2
@@ -523,7 +528,7 @@ F src/test_intarray.c 988fc61cb0ff539f4172c0d95f15287c92516f64
F src/test_intarray.h f3b7672f5d1056eac563c0d6ea8480a660b1475c
F src/test_journal.c 619f2aa10e0d7a5f87c0f06825bc61dfce1c6b9c7f3ad990fb13de6c3b8874a3
F src/test_loadext.c 337056bae59f80b9eb00ba82088b39d0f4fe6dfd
-F src/test_malloc.c 4f06a805de86be5216a127b3777ca2d5a1ff99d1a9238374ce136a47411be36c
+F src/test_malloc.c 5201422e2403e66a7a9c2b7d8df806acd8d2a0429822adb7e932f324e7b5b3c6
F src/test_md5.c 7268e1e8c399d4a5e181b64ac20e1e6f3bc4dd9fc87abac02db145a3d951fa8c
F src/test_multiplex.c e054459f7633f3ff8ce1245da724f9a8be189e4e
F src/test_multiplex.h 5436d03f2d0501d04f3ed50a75819e190495b635
@@ -545,34 +550,34 @@ F src/test_thread.c 911d15fb14e19c0c542bdc8aabf981c2f10a4858
F src/test_vfs.c f0186261a24de2671d080bcd8050732f0cb64f6e
F src/test_vfstrace.c bab9594adc976cbe696ff3970728830b4c5ed698
F src/test_windirent.c a895e2c068a06644eef91a7f0a32182445a893b9a0f33d0cdb4283dca2486ac1
-F src/test_windirent.h 8782864172ba5ae52c5c313c70faeadb324ff74de9c3dcc6b56a557dccaa1de6
+F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a90484215
F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c
F src/tokenize.c 5b0c661a85f783d35b9883830736eeb63be4aefc4f6b7d9cd081d48782c041e2
-F src/treeview.c eae35972ff44f67064de2eaf35f04afe94e7aea3271a8b3bcebb3f954880fec3
+F src/treeview.c 14d5d1254702ec96876aa52642cb31548612384134970409fae333b25b39d6bb
F src/trigger.c a34539c69433276d37b0da9a89c117726ff2d292c0902895af1f393a983cd3a1
F src/update.c a90a32ffc0100265b0693dbbdbe490756447af181f5ea2c138cce515b08c8795
F src/utf.c 810fbfebe12359f10bc2a011520a6e10879ab2a163bcb26c74768eab82ea62a5
-F src/util.c ef4a5f904d942e660abade7fbf3e6bdb402dabe9e7c27f3361ecf40b945538b5
-F src/vacuum.c 90839322fd5f00df9617eb21b68beda9b6e2a2937576b0d65985e4aeb1c53739
-F src/vdbe.c ccc1e17a30325068ae4f0292e8601997946886d23acc989c68f2a261a2795c70
+F src/util.c d9eb0a6c4aae1b00a7369eadd7ca0bbe946cb4c953b6751aa20d357c2f482157
+F src/vacuum.c 762ee9bbf8733d87d8cd06f58d950e881982e416f8c767334a40ffd341b6bff5
+F src/vdbe.c 066a4e1de2ed83e253adfd2e97a684cf562eaa41d31ee7f3d3e4c8aea4485a55
F src/vdbe.h 134beb7a12a6213c00eba58febaede33447cc4441bc568a0d9c144b33fc3720a
-F src/vdbeInt.h c8cfbbc28e37e67a493c3f892fb0596add56a31a00e7537a06049af9ef2f51b0
-F src/vdbeapi.c 02f773681d06e46454b0606339068d4d4490873dc4a7334bc0c6030552bb2c8c
+F src/vdbeInt.h 95f7adfdc5c8f1353321f55a6c5ec00a90877e3b85af5159e393afb41ff54110
+F src/vdbeapi.c 29d2baf9c1233131ec467d7bed1b7c8a03c27579048d768c4b04acf427838858
F src/vdbeaux.c 2756ac68ac259c416554100598fc291870063288cd7e1af22847f57b3e130e56
F src/vdbeblob.c f5c70f973ea3a9e915d1693278a5f890dc78594300cf4d54e64f2b0917c94191
-F src/vdbemem.c 7548dd5af03d24d534a5dbc41e3bbdf1fab83e9c8856a8d2549ed2ccf33d0e80
+F src/vdbemem.c 414e28d3a7e2a8bee2bb247de115dcbc68e3cbac284d5862d077002f7a93bce1
F src/vdbesort.c 731a09e5cb9e96b70c394c1b7cf3860fbe84acca7682e178615eb941a3a0ef2f
F src/vdbetrace.c 48e11ebe040c6b41d146abed2602e3d00d621d7ebe4eb29b0a0f1617fd3c2f6c
F src/vtab.c 0e4885495172e1bdf54b12cce23b395ac74ef5729031f15e1bc1e3e6b360ed1a
F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
-F src/wal.c 5a3f464edd64596f601683ed321d12e6fd93c5fb9afdfb3653d6ffd0fee9c48f
+F src/wal.c aa9cffc7a2bad6b826a86c8562dd4978398720ed41cb8ee7aa9d054eb8b456a0
F src/wal.h 8de5d2d3de0956d6f6cb48c83a4012d5f227b8fe940f3a349a4b7e85ebcb492a
F src/walker.c da987a20d40145c0a03c07d8fefcb2ed363becc7680d0500d9c79915591f5b1f
-F src/where.c caf0b6c9d31f22f0b2c91aba723858de52b5d665aaa89034099015aaf9bb8219
+F src/where.c 7cae47e813393d70c6d327fdf000fcb30f76b1b0b5a5b52ff6402e0c658de32c
F src/whereInt.h 82c04c5075308abbac59180c8bad5ecb45b07453981f60a53f3c7dee21e1e971
-F src/wherecode.c af1e79154aaa88cd802d6f2e5b945f67eaca7c958d1525fbf8ee19d5bd7b9020
-F src/whereexpr.c 427ea8e96ec24f2a7814c67b8024ad664a9c7656264c4566c34743cb23186e46
+F src/wherecode.c 982b7450c53fb272f61a1d20c93e960260ea4dfe8e2e9bacc190e2a041a1f1a4
+F src/whereexpr.c 53532be687e12f3cd314f1e204cd4fbdac7ad250e918a182b048121e16e828ae
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd
F test/affinity3.test 6a101af2fc945ce2912f6fe54dd646018551710d
@@ -586,7 +591,7 @@ F test/alter3.test 4d79934d812eaeacc6f22781a080f8cfe012fdc3
F test/alter4.test b6d7b86860111864f6cddb54af313f5862dda23b
F test/altermalloc.test e81ac9657ed25c6c5bb09bebfa5a047cd8e4acfc
F test/amatch1.test b5ae7065f042b7f4c1c922933f4700add50cdb9f
-F test/analyze.test 3eb35a4af972f98422e5dc0586501b17d103d321
+F test/analyze.test b3a9c67d00e1df7588a5b7be9a0292899f94fe8cac1f94a017277474ca2e59df
F test/analyze3.test 8b3ef8ba6d1096b76c40e0925c0fe51e700d2b779cdda40914580de3f9b9d80f
F test/analyze4.test eff2df19b8dd84529966420f29ea52edc6b56213
F test/analyze5.test 765c4e284aa69ca172772aa940946f55629bc8c4
@@ -625,7 +630,7 @@ F test/autoindex4.test 49d3cd791a9baa16fb461d7ea3de80d019a819cf
F test/autoindex5.test 96f084a5e6024ea07cace5888df3223f3ea86990
F test/autovacuum.test 0831cd34e14695d297187f7f6519265e3121c5b0a1720e548e86829e796129e9
F test/autovacuum_ioerr2.test 8a367b224183ad801e0e24dcb7d1501f45f244b4
-F test/avtrans.test 0252654f4295ddda3b2cce0e894812259e655a85
+F test/avtrans.test b7dc25459ecbd86c6fa9c606ee3068f59d81e225118617dcf2bbb6ded2ade89e
F test/backcompat.test 3e64cedda754c778ef6bbe417b6e7a295e662a4d
F test/backup.test dd4a5ff756e3df3931dacb1791db0584d4bad989
F test/backup2.test 1fd1ad8c5b3d2d5b9c0cce4143a4fc610d51ddc6ae16a7a122973d43e6b50bbd
@@ -672,7 +677,7 @@ F test/capi3b.test efb2b9cfd127efa84433cd7a2d72ce0454ae0dc4
F test/capi3c.test 7ebed1d8fa2f3190149d556fe8cff5a006be62af437c5c4640db614470126098
F test/capi3d.test 485048dc5cd07bc68011e4917ad035ad6047ab82
F test/capi3e.test 3d49c01ef2a1a55f41d73cba2b23b5059ec460fe
-F test/cast.test 4c275cbdc8202d6f9c54a3596701719868ac7dc3
+F test/cast.test 5ceb920718d280b61163500a7d29e0e0a86458b1cbd92d96f962c9d970aa3857
F test/cffault.test 9d6b20606afe712374952eec4f8fd74b1a8097ef
F test/check.test 33a698e8c63613449d85d624a38ef669bf20331daabebe3891c9405dd6df463a
F test/close.test 799ea4599d2f5704b0a30f477d17c2c760d8523fa5d0c8be4a7df2a8cad787d8
@@ -725,7 +730,7 @@ F test/crash4.test fe2821baf37168dc59dd733dcf7dba2a401487bc
F test/crash5.test 05dd3aa9dbb751a22d5cdaf22a9c49b6667aa219
F test/crash6.test 4c56f1e40d0291e1110790a99807aa875b1647ba
F test/crash7.test 1a194c4900a255258cf94b7fcbfd29536db572df
-F test/crash8.test 63cd5aea313222d7a69637cf7174c34d151676cc187d57193b66d4c89dedede3
+F test/crash8.test 64366e459c28dd62edfb7ad87253a409c7533b92d16fcc479a6a8131bdcc3100
F test/crashM.test d95f59046fa749b0d0822edf18a717788c8f318d
F test/crashtest1.c 09c1c7d728ccf4feb9e481671e29dda5669bbcc2
F test/createtab.test b5de160630b209c4b8925bdcbbaf48cc90b67fe8
@@ -733,13 +738,13 @@ F test/cse.test 277350a26264495e86b1785f34d2d0c8600e021c
F test/csv01.test 526fc6aefd052badd5a0283f86b4b395c3df76bfe98d96c801f494f5e2c7836c
F test/ctime.test 78749e6c9a5f0010d67985be80788f841e3cd2da18114e2ed6010399a7d807f3
F test/cursorhint.test 7bc346788390475e77a345da2b92270d04d35856
-F test/cursorhint2.test 8457e93d97f665f23f97cdbc8477d16e3480331b
+F test/cursorhint2.test 0078ae1ded4afcf5eb80d06e3a72b6e1c3f1a646aab26eeb583b0a9ec6f0d56e
F test/date.test 9b73bbeb1b82d9c1f44dec5cf563bf7da58d2373
F test/date2.test 74c234bece1b016e94dd4ef9c8cc7a199a8806c0e2291cab7ba64bace6350b10
F test/dbfuzz.c 73047c920d6210e5912c87cdffd9a1c281d4252e
F test/dbpage.test dbf50a4d361f9e45a979432c727506065113124478a7d2db12074fa655e65d6c
-F test/dbstatus.test 73149851b3aff14fc6db478e58f9083a66422cf5
-F test/dbstatus2.test e93ab03bfae6d62d4d935f20de928c19ca0ed0ab
+F test/dbstatus.test c15fa97f743dac7ce996814c84b56317e138895ee15ce27f15b608aa6924c90a
+F test/dbstatus2.test f5fe0afed3fa45e57cfa70d1147606c20d2ba23feac78e9a172f2fe8ab5b78ef
F test/default.test 0cb49b1c315a0d81c81d775e407f66906a2a604d
F test/delete.test acc38fca8ee4851467705b1c2cfea64cd26667e5
F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa
@@ -768,7 +773,7 @@ F test/e_fts3.test 8cf40550bb088a6aa187c818c00fabe26ef82900a4cd5c66b427ccafe28be
F test/e_insert.test f02f7f17852b2163732c6611d193f84fc67bc641fb4882c77a464076e5eba80e
F test/e_reindex.test 2bebf7b393e519198b7c654407221cf171a439b8
F test/e_resolve.test a61751c368b109db73df0f20fc75fb47e166b1d8
-F test/e_select.test 16651bb681e83a1a2875ff4a595ed2b4b4dee375
+F test/e_select.test 6fd45fd4a59ec82b6dda7468699dcc0ec1a72538577750b4f90357a62c1d2723
F test/e_select2.test aceb80ab927d46fba5ce7586ebabf23e2bb0604f
F test/e_totalchanges.test b12ee5809d3e63aeb83238dd501a7bca7fd72c10
F test/e_update.test f46c2554d915c9197548681e8d8c33a267e84528
@@ -790,7 +795,7 @@ F test/exclusive.test 1206b87e192497d78c7f35552e86a9d05421498da300fb1cce5ca5351c
F test/exclusive2.test 984090e8e9d1b331d2e8111daf6e5d61dda0bef7
F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7
F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac
-F test/expr.test 66a2c9ac34f74f036faa4092f5402c7d3162fc93
+F test/expr.test 7cb55e80aeb41d65fec968c08212505123063fea60bdc355d764d747670e9eea
F test/extension01.test 00d13cec817f331a687a243e0e5a2d87b0e358c9
F test/extraquick.test cb254400bd42bfb777ff675356aabf3287978f79
F test/fallocate.test 07416bd593a116d5893cb244f45a94d5c6fe030561df3bd972e6135f8106e509
@@ -844,7 +849,7 @@ F test/fts2r.test b154c30b63061d8725e320fba1a39e2201cadd5e
F test/fts2token.test d8070b241a15ff13592a9ae4a8b7c171af6f445a
F test/fts3.test 672a040ea57036fb4b6fdc09027c18d7d24ab654
F test/fts3_common.tcl 99cf6659b87c0f74f55963c2aea03b3a7d66ceb0
-F test/fts3aa.test 39b65c11913d277c91d7426c62cfc1d147d1b4e9a48fecd9e38f60d0b5a5f505
+F test/fts3aa.test f267fcd6aca30fc70b81e5d82b68b34b38f581896020b57ed49e9777c7ebd85f
F test/fts3ab.test 7f6cf260ae80dda064023df8e8e503e9a412b91f
F test/fts3ac.test 636ed7486043055d4f126a0e385f2d5a82ebbf63
F test/fts3ad.test e40570cb6f74f059129ad48bcef3d7cbc20dda49
@@ -894,7 +899,7 @@ F test/fts3offsets.test b85fd382abdc78ebce721d8117bd552dfb75094c
F test/fts3prefix.test fa794eaab0bdae466494947b0b153d7844478ab2
F test/fts3prefix2.test e1f0a822ca661dced7f12ce392e14eaf65609dce
F test/fts3query.test f33eb71a1fe1084ea585eeb7ee76b390729f5170
-F test/fts3rank.test e4d2e16a28c98cae95001a75e2b4b05b19b051ffd6aaab15491c5e0595127b9b
+F test/fts3rank.test cd99bc83a3c923c8d52afd90d86979cf05fc41849f892faeac3988055ef37b99
F test/fts3rnd.test 1320d8826a845e38a96e769562bf83d7a92a15d0
F test/fts3shared.test 57e26a801f21027b7530da77db54286a6fe4997e
F test/fts3snippet.test 01a4231816e03a0660ae53ba2404fe69012fe0db
@@ -916,23 +921,23 @@ F test/fts4merge2.test 5faa558d1b672f82b847d2a337465fa745e46891
F test/fts4merge3.test 8d9ccb4a3d41c4c617a149d6c4b13ad02de797d0
F test/fts4merge4.test d895b1057a7798b67e03455d0fa50e9ea836c47b
F test/fts4noti.test 5553d7bb2e20bf4a06b23e849352efc022ce6309
-F test/fts4onepass.test 7319d61a2ed1325fc54afd0c060a0513b462303a
+F test/fts4onepass.test d69ddc4ee3415e40b0c5d1d0408488a87614d4f63ba9c44f3e52db541d6b7cc7
F test/fts4opt.test fd6a11684b965e1999564ae763797b7fb9e34c96
F test/fts4unicode.test ceca76422abc251818cb25dabe33d3c3970da5f7c90e1540f190824e6b3a7c95
F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d
-F test/func.test ae97561957aba6ca9e3a7b8a13aac41830d701ef
+F test/func.test 09dda479bcfc568f99f3070413e9672a8eeedc1be9c5d819bf55d4788c2583b7
F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f
F test/func3.test d202a7606d23f90988a664e88e268aed1087c11c
F test/func4.test 6beacdfcb0e18c358e6c2dcacf1b65d1fa80955f
F test/func5.test cdd224400bc3e48d891827cc913a57051a426fa4
-F test/func6.test a4281c8fcd42b56f7a60f28e8e4d444e8b2256f9e82658b7ab87699f8318f564
+F test/func6.test 90e42b64c4f9fb6f04f44cb8a1da586c8542502e926b19c76504fe74ff2a9b7c
F test/fuzz-oss1.test e58330d01cbbd8215ee636b17a03fe220b37dbfa
F test/fuzz.test 96083052bf5765e4518c1ba686ce2bab785670d1
F test/fuzz2.test 76dc35b32b6d6f965259508508abce75a6c4d7e1
F test/fuzz3.test 9c813e6613b837cb7a277b0383cd66bfa07042b4cf0317157c35852f30043c31
F test/fuzz_common.tcl a87dfbb88c2a6b08a38e9a070dabd129e617b45b
F test/fuzz_malloc.test 328f70aaca63adf29b4c6f06505ed0cf57ca7c26
-F test/fuzzcheck.c 2152602232c96d9c790eff3013e1369ce59de3203fa0b75bc613531448454e61
+F test/fuzzcheck.c 5eb86c6ac96833ee622f45bf47e8045999c1b4b10d05e4eb809894a4b39f2f84
F test/fuzzdata1.db 7ee3227bad0e7ccdeb08a9e6822916777073c664
F test/fuzzdata2.db f03a420d3b822cc82e4f894ca957618fbe9c4973
F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba
@@ -977,7 +982,7 @@ F test/index7.test 7feababe16f2091b229c22aff2bcc1d4d6b9d2bb
F test/index8.test bc2e3db70e8e62459aaa1bd7e4a9b39664f8f9d7
F test/index9.test 0aa3e509dddf81f93380396e40e9bb386904c1054924ba8fa9bcdfe85a8e7721
F test/indexedby.test faa585e315e868f09bce0eb39c41d6134649b13d2801638294d3ae616edf1609
-F test/indexexpr1.test ace1ad489adc25325ad298434f13b1a515b36bf5dca9fe2a4b66cdf17aea3fa0
+F test/indexexpr1.test 635261197bcdc19b9b2c59bbfa7227d525c00e9587faddb2d293c44d287ce60e
F test/indexexpr2.test 13247bac49143196556eb3f65e97ef301bd3e993f4511558b5db322ddc370ea6
F test/indexfault.test 31d4ab9a7d2f6e9616933eb079722362a883eb1d
F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7
@@ -993,17 +998,18 @@ F test/interrupt.test 16ea879ec728cb76414c148c5f24afd5d1f91054
F test/interrupt2.test e4408ca770a6feafbadb0801e54a0dcd1a8d108d
F test/intpkey.test ac71107a49a06492b69b82aafaf225400598d3c8
F test/io.test f95bca1783b01ea7761671560d023360d2dfa4cc
-F test/ioerr.test 2a24bd6ed5a8b062e64bfe1f6cf94fb25e92210d
+F test/ioerr.test 470fcc78e9cd352d162baf782fe301ea807d764241f58a48fc58109c2dfcdb6b
F test/ioerr2.test 2593563599e2cc6b6b4fcf5878b177bdd5d8df26
F test/ioerr3.test d3cec5e1a11ad6d27527d0d38573fbff14c71bdd
F test/ioerr4.test f130fe9e71008577b342b8874d52984bd04ede2c
F test/ioerr5.test 2edfa4fb0f896f733071303b42224df8bedd9da4
F test/ioerr6.test a395a6ab144b26a9e3e21059a1ab6a7149cca65b
-F test/join.test 442c462eea85cf065d70a663c626b780a95af6e11585d909bb63b87598afe678
-F test/join2.test 1a0c26399910b015d9f8f95b884e9a079fd2cfdccd65f7b1603846508cae0dc6
+F test/istrue.test d6e659764da5ccc03adcdba18fe77d7917ba5e4abd04ef14bd4e4cf43e024b5b
+F test/join.test 730e3e8d511289531efca01f8684f98da1e6de51eacf95c5960d0c46e77719e3
+F test/join2.test f5ea0fd3b0a441c8e439706339dcd17cec63a896a755c04a30bfd442ecce1190
F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0
F test/join4.test 1a352e4e267114444c29266ce79e941af5885916
-F test/join5.test bc98ea4b4e5003f5b1453701ebb8cd7d1c01a550
+F test/join5.test c6bd62effc37a152bea735f9ef241b19bb967bd4593dc99b20e2fc55ae707e38
F test/join6.test cfe6503791ceb0cbb509966740286ec423cbf10b
F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497
F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4
@@ -1011,12 +1017,12 @@ F test/journal3.test c9c29883f5bf535ae82ae21c472df6263806a22e467b6db7cd0d6d54530
F test/jrnlmode.test a6693f2bed4541a21e703aaa37bb3e10de154130645952933b82b2dec0a8b539
F test/jrnlmode2.test 8759a1d4657c064637f8b079592651530db738419e1d649c6df7048cd724363d
F test/jrnlmode3.test 556b447a05be0e0963f4311e95ab1632b11c9eaa
-F test/json101.test d7cdf3e6731d41e0c4bde1c88806abd17f1f478486a1409933c1d8eac9120095
+F test/json101.test 24e97954e3bd6404f3715888c7f8f835e36e19c7ae6513b5d9ab2d381498962d
F test/json102.test eeb54efa221e50b74a2d6fb9259963b48d7414dca3ce2fdfdeed45cb28487bc1
F test/json103.test c5f6b85e69de05f6b3195f9f9d5ce9cd179099a0
F test/json104.test 877d5845f6303899b7889ea5dd1bea99076e3100574d5c536082245c5805dcaa
F test/keyword1.test 37ef6bba5d2ed5b07ecdd6810571de2956599dff
-F test/kvtest.c fcb38ffe3db028a3138b4818fc098359c80dc51a0d1278a91c99c554cc1abb92
+F test/kvtest.c 94da54bb66aae7a54e47cf7e4ea4acecc0f217560f79ad3abfcc0361d6d557ba
F test/lastinsert.test 42e948fd6442f07d60acbd15d33fb86473e0ef63
F test/laststmtchanges.test ae613f53819206b3222771828d024154d51db200
F test/like.test 11cfd7d4ef8625389df9efc46735ff0b0b41d5e62047ef0f3bc24c380d28a7a6
@@ -1037,8 +1043,8 @@ F test/lock_common.tcl 7ffb45accf6ee91c736df9bafe0806a44358f035
F test/lookaside.test b17c99ae3aef96a8c9fa6f6be33cc75b93d657cb791d3827302b6835b71941f7
F test/main.test 6bbb3999fd461eb8fb335cbab97409a3d7f91bbb8da60635e8be3e4a04a77772
F test/make-where7.tcl 05c16b5d4f5d6512881dfec560cb793915932ef9
-F test/malloc.test 21c213365f2cca95ab2d7dc078dc8525f96065f8
-F test/malloc3.test e3b32c724b5a124b57cb0ed177f675249ad0c66a
+F test/malloc.test 18dd1c4188c81ca79cf123527c71b19ee0c31feb9947fdffb0dc6ceb1436816a
+F test/malloc3.test 6e88bae6312854a4adb4ecc2a6a5ea8c59b4db778b724ba718e1c43fc8c3c136
F test/malloc4.test 957337613002b7058a85116493a262f679f3a261
F test/malloc5.test f6eb6eca07a4c75f2897bf43a404689b6295bb95ab2e07d4b52eda743f925a27
F test/malloc6.test 2f039d9821927eacae43e1831f815e157659a151
@@ -1063,6 +1069,7 @@ F test/malloc_common.tcl aac62499b76be719fac31e7a3e54a7fd53272e7f
F test/manydb.test 28385ae2087967aa05c38624cec7d96ec74feb3e
F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f
F test/memdb.test c1f2a343ad14398d5d6debda6ea33e80d0dafcc7
+F test/memdb1.test 61aa1dbdeea6320791d2ff42a9a6149d5716be674bf06ee0ffa0aad1bf3eb5f8
F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2
F test/memsubsys1.test 9e7555a22173b8f1c96c281ce289b338fcba2abe8b157f8798ca195bbf1d347e
F test/memsubsys2.test 3e4a8d0c05fd3e5fa92017c64666730a520c7e08
@@ -1076,7 +1083,7 @@ F test/misc3.test cf3dda47d5dda3e53fc5804a100d3c82be736c9d
F test/misc4.test 0d8be3466adf123a7791a66ba2bc8e8d229e87f3
F test/misc5.test 60e1fc758a93cacd19eb2fafcd1d40d150a05047546c7a92389c98047d621901
F test/misc6.test 953cc693924d88e6117aeba16f46f0bf5abede91
-F test/misc7.test 859894e3192257ce2fc4063b5438b220e352286974b387e485050f0ad1f665d6
+F test/misc7.test 567e223b6497da2226a0340befaf2d663c91ad57a48aede21a35a984a2882d41
F test/misc8.test ba03aaa08f02d62fbb8d3b2f5595c1b33aa9bbc5
F test/misuse.test 9e7f78402005e833af71dcab32d048003869eca5abcaccc985d4f8dc1d86bcc7
F test/mjournal.test 9d86e697dcbc5da2c4e8caba9b176b5765fe65e80c88c278b8c09a917e436795
@@ -1093,17 +1100,21 @@ F test/multiplex4.test e8ae4c4bd70606a5727743241f13b5701990abe4
F test/mutex1.test ea2cc74d97f077b9e74c84cbd024f14d79a8126f
F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660
F test/nan.test 437d40e6d0778b050d7750726c0cbd2c9936b81962926e8f8c48ca698f00f4d1
-F test/nockpt.test 9a436a7213ba5ef7a32304998d386d3ea3f76c9d
+F test/nockpt.test 8c43b25af63b0bd620cf1b003529e37b6f1dc53bd22690e96a1bd73f78dde53a
F test/nolock.test f196cf8b8fbea4e2ca345140a2b3f3b0da45c76e
+F test/normalize.test 501630ab49b0b26b65c74124bf03e3374c1b57fa97aae750f84803609141d167
F test/notify1.test 669b2b743618efdc18ca4b02f45423d5d2304abf
F test/notify2.test 2ecabaa1305083856b7c39cf32816b612740c161
F test/notify3.test 10ff25cde502e72a92053a2f215d64bece4ef934
-F test/notnull.test f8fcf58669ddba79274daa2770d61dfad8274f62
+F test/notnull.test b6999231221df3534827e45e2005dd7a815fdd5f2c2e1afb9be21ead410816f8
F test/null.test 0dcce4f04284ec66108c503327ad6d224c0752b3
F test/numcast.test 5d126f7f581432e86a90d1e35cac625164aec4a1
F test/numindex1.test 20a5450d4b056e48cd5db30e659f13347a099823
F test/offset1.test f06b83657bcf26f9ce805e67450e189e282143b2
F test/openv2.test 0d3040974bf402e19b7df4b783e447289d7ab394
+F test/optfuzz-db01.c a0c256905c8ac79f9a5de2f374a3d9f757bef0dca2a238dc7c10cc8a38031834
+F test/optfuzz-db01.txt 21f6bdeadc701cf11528276e2a55c70bfcb846ba42df327f979bd9e7b6ce7041
+F test/optfuzz.c 50e330304eb1992e15ddd11f3daaad9bcc0d9aaad09cb2bcc77f9515df2e88b1
F test/orderby1.test 4d22a7c75f6a83fc1f188cc7bb5192285fdf2552
F test/orderby2.test bc11009f7cd99d96b1b11e57b199b00633eb5b04
F test/orderby3.test 8619d06a3debdcd80a27c0fdea5c40b468854b99
@@ -1114,14 +1125,14 @@ F test/orderby7.test 3d1383d52ade5b9eb3a173b3147fdd296f0202da
F test/orderby8.test 23ef1a5d72bd3adcc2f65561c654295d1b8047bd
F test/orderby9.test 87fb9548debcc2cd141c5299002dd94672fa76a3
F test/oserror.test b32dc34f2363ef18532e3a0a7358e3e7e321974f
-F test/ossfuzz.c 7f5cc87a0280a5854c1bfa7d5c4d07d34731f08ec34dc9c916aa35ed292b1468
-F test/ossshell.c 296ab63067841bd1b1e97b46a0b2af48ee7f69d50d1a723008bee12dd7122622
+F test/ossfuzz.c c4c4547e2c92ac52f10038b073a03248251a23c1c559728f63a18aeca0e79f03
+F test/ossshell.c f125c5bd16e537a2549aa579b328dd1c59905e7ab1338dfc210e755bb7b69f17
F test/ovfl.test 199c482696defceacee8c8e0e0ef36da62726b2f
F test/pager1.test f596d3bd53ce96e1d87d44d223d2ae6c8867dd782c425e5eb28b5721fa6aaa97
F test/pager2.test 67b8f40ae98112bcdba1f2b2d03ea83266418c71
F test/pager3.test 4e9a83d6ca0838d7c602c9eb93d1357562d9059c1e02ffb138a8271020838370
F test/pager4.test a122e9e6925d5b23b31e3dfef8c6a44bbf19590e
-F test/pagerfault.test 263c5442c06caf0b9b9e3fe42acdeb11f254dcebe533f69f401aaef9111eaf20
+F test/pagerfault.test 63c5da625562c66345ab4528790327ca63db2f6f9cbae2aba8cb7c51de3d1628
F test/pagerfault2.test caf4c7facb914fd3b03a17b31ae2b180c8d6ca1f
F test/pagerfault3.test 1003fcda009bf48a8e22a516e193b6ef0dd1bbd8
F test/pageropt.test 84e4cc5cbca285357f7906e99b21be4f2bf5abc0
@@ -1138,7 +1149,7 @@ F test/pragma4.test 3046501bee2f652dc2a4f9c87781e2741361d6864439c8381aba6c3b774b
F test/pragma5.test 824ce6ced5d6b7ec71abe37fc6005ff836fe39d638273dc5192b39864b9ee983
F test/pragmafault.test 275edaf3161771d37de60e5c2b412627ac94cef11739236bec12ed1258b240f8
F test/printf.test b3ff34e73d59124140eaf89f7672e21bc2ca5fcc
-F test/printf2.test 9e6db85f81c63f2367c34a9d7db384088bd374ad
+F test/printf2.test 30b5dd0b4b992dc5626496846ecce17ff592cacbcb11c3e589f3ac4d7e129dae
F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb
F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc
F test/pushdown.test 5e72c51c5e33253ed639ccee1e01ce62d62b6eee5ca893cd82334e4ee7b1d7fc
@@ -1155,7 +1166,7 @@ F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8
F test/regexp1.test 497ea812f264d12b6198d6e50a76be4a1973a9d8
F test/regexp2.test 40e894223b3d6672655481493f1be12012f2b33c
F test/reindex.test 44edd3966b474468b823d481eafef0c305022254
-F test/releasetest.tcl 6aaa853f7a7bbdc458d4cb42c0425228729b0f3e5769e9b41088c08eee999a49 x
+F test/releasetest.tcl 5f15ab8056799e9a6e26a310d49236d2e774d6a30d0ec74601e18d4ce146b79c x
F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb
F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa
F test/rollback2.test 8435d6ff0f13f51d2a4181c232e706005fa90fc5
@@ -1163,7 +1174,7 @@ F test/rollbackfault.test 0e646aeab8840c399cfbfa43daab46fd609cf04a
F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc
F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81
F test/rowid.test 5b7509f384f4f6fae1af3c8c104c8ca299fea18d
-F test/rowvalue.test 44f3492f415cc9f374e8388a5eb61503eaca5230
+F test/rowvalue.test 32861d6a933ded868035f2ec79aeb993a2a46eb7a6d282ae13415a4c2e369463
F test/rowvalue2.test 060d238b7e5639a7c5630cb5e63e311b44efef2b
F test/rowvalue3.test 3068f508753af69884b12125995f023da0dbb256
F test/rowvalue4.test 4b556d7de161a0dd8cff095c336e913986398bea
@@ -1209,6 +1220,8 @@ F test/selectF.test 21c94e6438f76537b72532fa9fd4710cdd455fc3
F test/selectG.test 089f7d3d7e6db91566f00b036cb353107a2cca6220eb1cb264085a836dae8840
F test/server1.test 46803bd3fe8b99b30dbc5ff38ffc756f5c13a118
F test/session.test 78fa2365e93d3663a6e933f86e7afc395adf18be
+F test/sessionfuzz-data1.db 1f8d5def831f19b1c74571037f0d53a588ea49a6c4ca2a028fc0c27ef896dbcb
+F test/sessionfuzz.c b0fcdcf757451957e17396a3af5171f1fdf9b2babc81da9fa35675df46c4729a
F test/shared.test 1da9dbad400cee0d93f252ccf76e1ae007a63746
F test/shared2.test 03eb4a8d372e290107d34b6ce1809919a698e879
F test/shared3.test ab693f9b6e156b8bfb2a0ad94f29fe69602a5d38
@@ -1221,7 +1234,7 @@ F test/sharedA.test 49d87ec54ab640fbbc3786ee3c01de94aaa482a3a9f834ad3fe92770eb69
F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e
F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
-F test/shell1.test 9f8b8da05a79b134e252a5e1d8d411245ad83ac7126c262900b9f42b43108ffd
+F test/shell1.test e2f7d375ae80eb16590af23d6c40cd0140b6d1f92642c1b71eb94630a144ee08
F test/shell2.test e242a9912f44f4c23c3d1d802a83e934e84c853b
F test/shell3.test ac8c2b744014c3e9a0e26bfd829ab65f00923dc1a91ffd044863e9423cc91494
F test/shell4.test 89ad573879a745974ff2df20ff97c5d6ffffbd5d
@@ -1257,11 +1270,12 @@ F test/speed2.test 53177056baf6556dcbdcf032bbdfc41c1aa74ded
F test/speed3.test 694affeb9100526007436334cf7d08f3d74b85ef
F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715
F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa
-F test/speed4p.test 0e51908951677de5a969b723e03a27a1c45db38b
+F test/speed4p.test 377a0c48e5a92e0b11c1c5ebb1bc9d83a7312c922bc0cb05970ef5d6a96d1f0c
F test/speedtest1.c a5faf4cbe5769eee4b721b3875cb3f12520a9b99d9026b1063b47c39603375b8
-F test/spellfix.test f9c1f431e2c096c8775fec032952320c0e4700db
+F test/spellfix.test 951a6405d49d1a23d6b78027d3877b4a33eeb8221dcab5704b499755bb4f552e
F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3
F test/spellfix3.test 0f9efaaa502a0e0a09848028518a6fb096c8ad33
+F test/spellfix4.test 51c7c26514ade169855c66bcf130bd5acfb4d7fd090cc624645ab275ae6a41fb
F test/sqldiff1.test 28cd737cf1b0078b1ec1bbf425e674c47785835e
F test/sqllimits1.test a74ee2a3740b9f9c2437c246d8fb77354862a142
F test/sqllog.test 6af6cb0b09f4e44e1917e06ce85be7670302517a
@@ -1271,7 +1285,7 @@ F test/stmt.test 54ed2cc0764bf3e48a058331813c3dbd19fc1d0827c3d8369914a5d8f564ec7
F test/stmtvtab1.test 6873dfb24f8e79cbb5b799b95c2e4349060eb7a3b811982749a84b359468e2d5
F test/subjournal.test 8d4e2572c0ee9a15549f0d8e40863161295107e52f07a3e8012a2e1fdd093c49
F test/subquery.test d7268d193dd33d5505df965399d3a594e76ae13f
-F test/subquery2.test 438f8a7da1457277b22e4176510f7659b286995f
+F test/subquery2.test 8250dfd6a773b04c7a5c37ac63276f62b329157ce171244d0cbe1acc365e3303
F test/subselect.test 0966aa8e720224dbd6a5e769a3ec2a723e332303
F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a
F test/subtype1.test 7fe09496352f97053af1437150751be2d0a0cae8
@@ -1289,7 +1303,7 @@ F test/tabfunc01.test c47171c36b3d411df2bd49719dcaa5d034f8d277477fd41d253940723b
F test/table.test b708f3e5fa2542fa51dfab21fc07b36ea445cb2f
F test/tableapi.test 2674633fa95d80da917571ebdd759a14d9819126
F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930
-F test/tclsqlite.test c3d7ac9449634b9f17fd048a3c0212e88a7448be810a9c5bd051acc1ffa00d2f
+F test/tclsqlite.test 5337e8890b96dad1ee541b15fbeec32e6bac2fe7fa096f91089057385aadba9b
F test/tempdb.test 4cdaa23ddd8acb4d79cbb1b68ccdfd09b0537aaba909ca69a876157c2a2cbd08
F test/tempdb2.test 27e41ed540b2f9b056c2e77e9bddc1b875358507
F test/tempfault.test 0c0d349c9a99bf5f374655742577f8712c647900
@@ -1297,8 +1311,8 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30
F test/temptable2.test cd396beb41117a5302fff61767c35fa4270a0d5e
F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc
-F test/tester.tcl 3ed81b9e1d9718a8d9603596c8a877793d054294053c4277a3d3897eabab3866
-F test/thread001.test 9f22fd3525a307ff42a326b6bc7b0465be1745a5
+F test/tester.tcl 94901a4625d9a2229666dd5c44120ddf7f0fb639470710ef74a4cefc7b039e07
+F test/thread001.test b61a29dd87cf669f5f6ac96124a7c97d71b0c80d9012746072055877055cf9ef
F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58
F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
F test/thread004.test f51dfc3936184aaf73ee85f315224baad272a87f
@@ -1455,7 +1469,7 @@ F test/tokenize.test ce430a7aed48fc98301611429595883fdfcab5d7
F test/tpch01.test 04adbf8d8300fa60a222f28d901abd76e7be6dd4
F test/trace.test a659a9862957f4789e37a92b3bf6d2caf5c86b02cdeefc41e850ae53acf6992a
F test/trace2.test f5cb67ad3bc09e0c58e8cca78dfd0b5639259983
-F test/trace3.test 56ab944fddacf628b118cc298503fc45c2e50ab0
+F test/trace3.test 1dff966888773ff1bfea01c080caf15417892b3f998408fe920c4791f7337144
F test/trans.test 6e1b4c6a42dba31bd65f8fa5e61a2708e08ddde6
F test/trans2.test 62bd045bfc7a1c14c5ba83ba64d21ade31583f76
F test/trans3.test 91a100e5412b488e22a655fe423a14c26403ab94
@@ -1553,11 +1567,11 @@ F test/waloverwrite.test dad2f26567f1b45174e54fbf9a8dc1cb876a7f03
F test/walpersist.test 8c6b7e3ec1ba91b5e4dc4e0921d6d3f87cd356a6
F test/walprotocol.test a112aba0b79e3adeaa485fed09484b32c654e97df58e454aa8489ac2cd57bf84
F test/walro.test cb438d05ba0d191f10b688e39c4f0cd5b71569a1d1f4440e5bdf3c6880e08c20
-F test/walro2.test 6c73e8e4b5ccc55f907f4603ba36458b45c085fb6dfb04f30e3c0babbc1c2f41
+F test/walro2.test 0e79dd15cbdb4f482c01ea248373669c732414a726b357d04846a816afafb768
F test/walrofault.test c70cb6e308c443867701856cce92ad8288cd99488fa52afab77cca6cfd51af68
F test/walshared.test 0befc811dcf0b287efae21612304d15576e35417
F test/walslow.test c05c68d4dc2700a982f89133ce103a1a84cc285f
-F test/walthread.test de8dbaf6d9e41481c460ba31ca61e163d7348f8e
+F test/walthread.test 14b20fcfa6ae152f5d8e12f5dc8a8a724b7ef189f5d8ef1e2ceab79f2af51747
F test/where.test f0c325563acde44f2c4ea6ba348e9e29f7121757
F test/where2.test 478d2170637b9211f593120648858593bf2445a1
F test/where3.test 54cdeb02157acc979de41530b804ae7b09552bf1
@@ -1572,7 +1586,7 @@ F test/whereB.test 0def95db3bdec220a731c7e4bec5930327c1d8c5
F test/whereC.test cae295158703cb3fc23bf1a108a9ab730efff0f6
F test/whereD.test 711d4df58d6d4fb9b3f5ce040b818564198be002
F test/whereE.test b3a055eef928c992b0a33198a7b8dc10eea5ad2f
-F test/whereF.test d44b58338fe5ddd7286023e9bedb255aa264a6c4d2168b49591b167371c675c7
+F test/whereF.test 3d9412b1199d3e2bed34fcb76b4c48d0bf4df95d27e3f8dd27b6f8b4716d0d89
F test/whereG.test dde4c52a97385a55be6a7cd46be8373f0cf35501
F test/whereH.test e4b07f7a3c2f5d31195cd33710054c78667573b2
F test/whereI.test eab5b226bbc344ac70d7dc09b963a064860ae6d7
@@ -1590,6 +1604,7 @@ F test/win32nolock.test ac4f08811a562e45a5755e661f45ca85892bdbbc
F test/with1.test ca08e291249a810a2ec9b72ceef5575e07d5925b360fcf6652ae6fe06ac4dced
F test/with2.test e0030e2f0267a910d6c0e4f46f2dfe941c1cc0d4f659ba69b3597728e7e8f1ab
F test/with3.test e71604a0e53cba82bc04c703987cb1d6751ec0b6
+F test/with4.test 257be66c0c67fee1defbbac0f685c3465e2cad037f21ce65f23f86084f198205
F test/withM.test 693b61765f2b387b5e3e24a4536e2e82de15ff64
F test/without_rowid1.test 06b7215130882d6a072233820dd364c874c4fd69221e8fc756ec471009192874
F test/without_rowid2.test af260339f79d13cb220288b67cd287fbcf81ad99
@@ -1601,11 +1616,13 @@ F test/wordcount.c cb589cec469a1d90add05b1f8cee75c7210338d87a5afd65260ed5c0f4bbf
F test/writecrash.test f1da7f7adfe8d7f09ea79b42e5ca6dcc41102f27f8e334ad71539501ddd910cc
F test/zeroblob.test 3857870fe681b8185654414a9bccfde80b62a0fa
F test/zerodamage.test 9c41628db7e8d9e8a0181e59ea5f189df311a9f6ce99cc376dc461f66db6f8dc
-F test/zipfile.test cb42e8fa6ba5db4a03ce6baa4401fc6236baf6eb5e62b44f3e463bf6aafd631d
+F test/zipfile.test a61f6ba6dbaaf4983849df84a31df140c7ddd1362e2fa9ecd3cdf5cd123b7f18
+F test/zipfile2.test fc2f08d5ec19c18c83289fbed32e378dc5116519972166e57a244da7bf2e5805
+F test/zipfilefault.test 44d4d7a7f7cca7521d569d7f71026b241d65a6b1757aa409c1a168827edbbc2c
F tool/GetFile.cs a15e08acb5dd7539b75ba23501581d7c2b462cb5
F tool/GetTclKit.bat 8995df40c4209808b31f24de0b58f90930239a234f7591e3675d45bfbb990c5d
F tool/Replace.cs 02c67258801c2fb5f63231e0ac0f220b4b36ba91
-F tool/addopcodes.tcl 7181c041d495e3f26acc36d15c86923ed722285f9015f017f41a3efdb9a0dab4
+F tool/addopcodes.tcl 0288d5b26b9b35f4cb5affb76eec63f1dfce117bbc2020066708069ef60b86ff
F tool/build-all-msvc.bat c12328d06c45fec8baada5949e3d5af54bf8c887 x
F tool/build-shell.sh 950f47c6174f1eea171319438b93ba67ff5bf367
F tool/cg_anno.tcl f95b0006c52cf7f0496b506343415b6ee3cdcdd3 x
@@ -1620,7 +1637,7 @@ F tool/genfkey.test 4196a8928b78f51d54ef58e99e99401ab2f0a7e5
F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce
F tool/kvtest-speed.sh 4761a9c4b3530907562314d7757995787f7aef8f
F tool/lemon.c 7f7735326ca9c3b48327b241063cee52d35d44e20ebe1b3624a81658052a4d39
-F tool/lempar.c da840fc8a6fbac23599a65ff075e6e3d01320417c794ff577088e09f5d74b689
+F tool/lempar.c 468a155e8729cfbccfe1d85bf60d064f1dab76167a51149ec5c7928a2de63953
F tool/libvers.c caafc3b689638a1d88d44bc5f526c2278760d9b9
F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862
F tool/logest.c 11346aa019e2e77a00902aa7d0cabd27bd2e8cca
@@ -1632,14 +1649,14 @@ F tool/mkkeywordhash.c 2e852ac0dfdc5af18886dc1ce7e9676d11714ae3df0a282dc7d90b3a0
F tool/mkmsvcmin.tcl 8baf26690b80d861d0ac341b29880eec6ade39e4f11fe690271ded9cb90563a3
F tool/mkopcodec.tcl d1b6362bd3aa80d5520d4d6f3765badf01f6c43c
F tool/mkopcodeh.tcl 4ee2a30ccbd900dc4d5cdb61bdab87cd2166cd2affcc78c9cc0b8d22a65b2eee
-F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e
+F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa
F tool/mkpragmatab.tcl 2144bc8550a6471a029db262a132d2df4b9e0db61b90398bf64f5b7b3f8d92cd
F tool/mkshellc.tcl 1f45770aea226ac093a9c72f718efbb88a2a2833409ec2e1c4cecae4202626f5
F tool/mksourceid.c d458f9004c837bee87a6382228ac20d3eae3c49ea3b0a5aace936f8b60748d3b
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
F tool/mksqlite3c-noext.tcl fef88397668ae83166735c41af99d79f56afaabb
-F tool/mksqlite3c.tcl 1fb69d39166f52d802a70ec37d99bca51d011c8ab30be27bc495be493196ae41
-F tool/mksqlite3h.tcl f92f994d9709aeb9e2b6e6f9fc8b069d2f55202c8e23f453edc44390a25982dc
+F tool/mksqlite3c.tcl a03cee30de81a2e67b93e5c659f24113a003677c557daeb008205c8e6d4345d6
+F tool/mksqlite3h.tcl 080873e3856eceb9d289a08a00c4b30f875ea3feadcbece796bd509b1532792c
F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b
F tool/mkvsix.tcl b9e0777a213c23156b6542842c238479e496ebf5
F tool/offsets.c fe4262fdfa378e8f5499a42136d17bf3b98f6091
@@ -1658,7 +1675,7 @@ F tool/showstat4.c 0682ebea7abf4d3657f53c4a243f2e7eab48eab344ed36a94bb75dcd19a5c
F tool/showwal.c ad9d768f96ca6199ad3a8c9562d679680bd032dd01204ea3e5ea6fb931d81847
F tool/soak1.tcl 8d407956e1a45b485a8e072470a3e629a27037fe
F tool/spaceanal.tcl 4bfd19aad7eb3ce0372ef0255f58035e0bba4ff5e9acfd763a10c6fb365c8dec
-F tool/speed-check.sh 9ae425da8819e54e780cf494fc6d8175dfb16e109ae3214a45a5c9bb2b74e2c4
+F tool/speed-check.sh 4ff9b095cf1a7643f0264e7fb7d23f0b12b7cce587a9de315877c378e90eeaf4
F tool/speedtest.tcl 06c76698485ccf597b9e7dbb1ac70706eb873355
F tool/speedtest16.c ecb6542862151c3e6509bbc00509b234562ae81e
F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
@@ -1700,11 +1717,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 395f8ea790e6e295800fa8927f0585b2419b9521ef4fd591d51d2a48db2a90c4
-R e7cccb4f976b9eb07c1586193cc26ec5
+P b850dd159918af568c55c00ff146ba9c9c04d33ff384fe1eb0ad74164bc401bb
+R 7b7a30931830e96714db59b77704b857
T +bgcolor * #d0c0ff
T +sym-release *
-T +sym-version-3.22.0 *
+T +sym-version-3.23.0 *
U drh
-Z 9c0e858ece53e1ee810a8a479ae4dc82
+Z 386b4344740e610e0c3078bd00d99ed8
# Remove this line to create a well-formed manifest.
diff --git a/manifest.uuid b/manifest.uuid
index c5f08c6..5c293fb 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-0c55d179733b46d8d0ba4d88e01a25e10677046ee3da1d5b1581e86726f2171d
+736b53f57f70b23172c30880186dce7ad9baa3b74e3838cae5847cffb98f5cd2
diff --git a/src/analyze.c b/src/analyze.c
index 0d13d77..48fd495 100644
--- a/src/analyze.c
+++ b/src/analyze.c
@@ -1015,7 +1015,7 @@ static void analyzeOneTable(
/* Do not gather statistics on views or virtual tables */
return;
}
- if( sqlite3_strlike("sqlite_%", pTab->zName, 0)==0 ){
+ if( sqlite3_strlike("sqlite\\_%", pTab->zName, '\\')==0 ){
/* Do not gather statistics on system tables */
return;
}
diff --git a/src/attach.c b/src/attach.c
index fa38e84..f85952f 100644
--- a/src/attach.c
+++ b/src/attach.c
@@ -55,6 +55,10 @@ static int resolveAttachExpr(NameContext *pName, Expr *pExpr)
**
** If the optional "KEY z" syntax is omitted, an SQL NULL is passed as the
** third argument.
+**
+** If the db->init.reopenMemdb flags is set, then instead of attaching a
+** new database, close the database on db->init.iDb and reopen it as an
+** empty MemDB.
*/
static void attachFunc(
sqlite3_context *context,
@@ -75,65 +79,85 @@ static void attachFunc(
sqlite3_vfs *pVfs;
UNUSED_PARAMETER(NotUsed);
-
zFile = (const char *)sqlite3_value_text(argv[0]);
zName = (const char *)sqlite3_value_text(argv[1]);
if( zFile==0 ) zFile = "";
if( zName==0 ) zName = "";
- /* Check for the following errors:
- **
- ** * Too many attached databases,
- ** * Transaction currently open
- ** * Specified database name already being used.
- */
- if( db->nDb>=db->aLimit[SQLITE_LIMIT_ATTACHED]+2 ){
- zErrDyn = sqlite3MPrintf(db, "too many attached databases - max %d",
- db->aLimit[SQLITE_LIMIT_ATTACHED]
- );
- goto attach_error;
- }
- for(i=0; i<db->nDb; i++){
- char *z = db->aDb[i].zDbSName;
- assert( z && zName );
- if( sqlite3StrICmp(z, zName)==0 ){
- zErrDyn = sqlite3MPrintf(db, "database %s is already in use", zName);
- goto attach_error;
- }
- }
+#ifdef SQLITE_ENABLE_DESERIALIZE
+# define REOPEN_AS_MEMDB(db) (db->init.reopenMemdb)
+#else
+# define REOPEN_AS_MEMDB(db) (0)
+#endif
- /* Allocate the new entry in the db->aDb[] array and initialize the schema
- ** hash tables.
- */
- if( db->aDb==db->aDbStatic ){
- aNew = sqlite3DbMallocRawNN(db, sizeof(db->aDb[0])*3 );
- if( aNew==0 ) return;
- memcpy(aNew, db->aDb, sizeof(db->aDb[0])*2);
+ if( REOPEN_AS_MEMDB(db) ){
+ /* This is not a real ATTACH. Instead, this routine is being called
+ ** from sqlite3_deserialize() to close database db->init.iDb and
+ ** reopen it as a MemDB */
+ pVfs = sqlite3_vfs_find("memdb");
+ if( pVfs==0 ) return;
+ pNew = &db->aDb[db->init.iDb];
+ if( pNew->pBt ) sqlite3BtreeClose(pNew->pBt);
+ pNew->pBt = 0;
+ pNew->pSchema = 0;
+ rc = sqlite3BtreeOpen(pVfs, "x", db, &pNew->pBt, 0, SQLITE_OPEN_MAIN_DB);
}else{
- aNew = sqlite3DbRealloc(db, db->aDb, sizeof(db->aDb[0])*(db->nDb+1) );
- if( aNew==0 ) return;
- }
- db->aDb = aNew;
- pNew = &db->aDb[db->nDb];
- memset(pNew, 0, sizeof(*pNew));
-
- /* Open the database file. If the btree is successfully opened, use
- ** it to obtain the database schema. At this point the schema may
- ** or may not be initialized.
- */
- flags = db->openFlags;
- rc = sqlite3ParseUri(db->pVfs->zName, zFile, &flags, &pVfs, &zPath, &zErr);
- if( rc!=SQLITE_OK ){
- if( rc==SQLITE_NOMEM ) sqlite3OomFault(db);
- sqlite3_result_error(context, zErr, -1);
- sqlite3_free(zErr);
- return;
+ /* This is a real ATTACH
+ **
+ ** Check for the following errors:
+ **
+ ** * Too many attached databases,
+ ** * Transaction currently open
+ ** * Specified database name already being used.
+ */
+ if( db->nDb>=db->aLimit[SQLITE_LIMIT_ATTACHED]+2 ){
+ zErrDyn = sqlite3MPrintf(db, "too many attached databases - max %d",
+ db->aLimit[SQLITE_LIMIT_ATTACHED]
+ );
+ goto attach_error;
+ }
+ for(i=0; i<db->nDb; i++){
+ char *z = db->aDb[i].zDbSName;
+ assert( z && zName );
+ if( sqlite3StrICmp(z, zName)==0 ){
+ zErrDyn = sqlite3MPrintf(db, "database %s is already in use", zName);
+ goto attach_error;
+ }
+ }
+
+ /* Allocate the new entry in the db->aDb[] array and initialize the schema
+ ** hash tables.
+ */
+ if( db->aDb==db->aDbStatic ){
+ aNew = sqlite3DbMallocRawNN(db, sizeof(db->aDb[0])*3 );
+ if( aNew==0 ) return;
+ memcpy(aNew, db->aDb, sizeof(db->aDb[0])*2);
+ }else{
+ aNew = sqlite3DbRealloc(db, db->aDb, sizeof(db->aDb[0])*(db->nDb+1) );
+ if( aNew==0 ) return;
+ }
+ db->aDb = aNew;
+ pNew = &db->aDb[db->nDb];
+ memset(pNew, 0, sizeof(*pNew));
+
+ /* Open the database file. If the btree is successfully opened, use
+ ** it to obtain the database schema. At this point the schema may
+ ** or may not be initialized.
+ */
+ flags = db->openFlags;
+ rc = sqlite3ParseUri(db->pVfs->zName, zFile, &flags, &pVfs, &zPath, &zErr);
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_NOMEM ) sqlite3OomFault(db);
+ sqlite3_result_error(context, zErr, -1);
+ sqlite3_free(zErr);
+ return;
+ }
+ assert( pVfs );
+ flags |= SQLITE_OPEN_MAIN_DB;
+ rc = sqlite3BtreeOpen(pVfs, zPath, db, &pNew->pBt, 0, flags);
+ sqlite3_free( zPath );
+ db->nDb++;
}
- assert( pVfs );
- flags |= SQLITE_OPEN_MAIN_DB;
- rc = sqlite3BtreeOpen(pVfs, zPath, db, &pNew->pBt, 0, flags);
- sqlite3_free( zPath );
- db->nDb++;
db->skipBtreeMutex = 0;
if( rc==SQLITE_CONSTRAINT ){
rc = SQLITE_ERROR;
@@ -160,7 +184,7 @@ static void attachFunc(
sqlite3BtreeLeave(pNew->pBt);
}
pNew->safety_level = SQLITE_DEFAULT_SYNCHRONOUS+1;
- pNew->zDbSName = sqlite3DbStrDup(db, zName);
+ if( !REOPEN_AS_MEMDB(db) ) pNew->zDbSName = sqlite3DbStrDup(db, zName);
if( rc==SQLITE_OK && pNew->zDbSName==0 ){
rc = SQLITE_NOMEM_BKPT;
}
@@ -200,13 +224,15 @@ static void attachFunc(
/* If the file was opened successfully, read the schema for the new database.
** If this fails, or if opening the file failed, then close the file and
- ** remove the entry from the db->aDb[] array. i.e. put everything back the way
- ** we found it.
+ ** remove the entry from the db->aDb[] array. i.e. put everything back the
+ ** way we found it.
*/
if( rc==SQLITE_OK ){
sqlite3BtreeEnterAll(db);
+ db->init.iDb = 0;
rc = sqlite3Init(db, &zErrDyn);
sqlite3BtreeLeaveAll(db);
+ assert( zErrDyn==0 || rc!=SQLITE_OK );
}
#ifdef SQLITE_USER_AUTHENTICATION
if( rc==SQLITE_OK ){
@@ -218,21 +244,23 @@ static void attachFunc(
}
#endif
if( rc ){
- int iDb = db->nDb - 1;
- assert( iDb>=2 );
- if( db->aDb[iDb].pBt ){
- sqlite3BtreeClose(db->aDb[iDb].pBt);
- db->aDb[iDb].pBt = 0;
- db->aDb[iDb].pSchema = 0;
- }
- sqlite3ResetAllSchemasOfConnection(db);
- db->nDb = iDb;
- if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){
- sqlite3OomFault(db);
- sqlite3DbFree(db, zErrDyn);
- zErrDyn = sqlite3MPrintf(db, "out of memory");
- }else if( zErrDyn==0 ){
- zErrDyn = sqlite3MPrintf(db, "unable to open database: %s", zFile);
+ if( !REOPEN_AS_MEMDB(db) ){
+ int iDb = db->nDb - 1;
+ assert( iDb>=2 );
+ if( db->aDb[iDb].pBt ){
+ sqlite3BtreeClose(db->aDb[iDb].pBt);
+ db->aDb[iDb].pBt = 0;
+ db->aDb[iDb].pSchema = 0;
+ }
+ sqlite3ResetAllSchemasOfConnection(db);
+ db->nDb = iDb;
+ if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){
+ sqlite3OomFault(db);
+ sqlite3DbFree(db, zErrDyn);
+ zErrDyn = sqlite3MPrintf(db, "out of memory");
+ }else if( zErrDyn==0 ){
+ zErrDyn = sqlite3MPrintf(db, "unable to open database: %s", zFile);
+ }
}
goto attach_error;
}
@@ -504,6 +532,14 @@ int sqlite3FixSelect(
if( sqlite3FixExpr(pFix, pSelect->pLimit) ){
return 1;
}
+ if( pSelect->pWith ){
+ int i;
+ for(i=0; i<pSelect->pWith->nCte; i++){
+ if( sqlite3FixSelect(pFix, pSelect->pWith->a[i].pSelect) ){
+ return 1;
+ }
+ }
+ }
pSelect = pSelect->pPrior;
}
return 0;
diff --git a/src/btree.c b/src/btree.c
index 3b42f68..4025039 100644
--- a/src/btree.c
+++ b/src/btree.c
@@ -2231,7 +2231,8 @@ static int btreeInvokeBusyHandler(void *pArg){
BtShared *pBt = (BtShared*)pArg;
assert( pBt->db );
assert( sqlite3_mutex_held(pBt->db->mutex) );
- return sqlite3InvokeBusyHandler(&pBt->db->busyHandler);
+ return sqlite3InvokeBusyHandler(&pBt->db->busyHandler,
+ sqlite3PagerFile(pBt->pPager));
}
/*
@@ -2409,7 +2410,7 @@ int sqlite3BtreeOpen(
}
pBt->openFlags = (u8)flags;
pBt->db = db;
- sqlite3PagerSetBusyhandler(pBt->pPager, btreeInvokeBusyHandler, pBt);
+ sqlite3PagerSetBusyHandler(pBt->pPager, btreeInvokeBusyHandler, pBt);
p->pBt = pBt;
pBt->pCursor = 0;
@@ -3372,6 +3373,7 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
}
}while( (rc&0xFF)==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE &&
btreeInvokeBusyHandler(pBt) );
+ sqlite3PagerResetLockTimeout(pBt->pPager);
if( rc==SQLITE_OK ){
if( p->inTrans==TRANS_NONE ){
@@ -4345,7 +4347,7 @@ int sqlite3BtreeCursorSize(void){
** of run-time by skipping the initialization of those elements.
*/
void sqlite3BtreeCursorZero(BtCursor *p){
- memset(p, 0, offsetof(BtCursor, iPage));
+ memset(p, 0, offsetof(BtCursor, BTCURSOR_FIRST_UNINIT));
}
/*
@@ -4388,11 +4390,19 @@ int sqlite3BtreeCloseCursor(BtCursor *pCur){
** Using this cache reduces the number of calls to btreeParseCell().
*/
#ifndef NDEBUG
+ static int cellInfoEqual(CellInfo *a, CellInfo *b){
+ if( a->nKey!=b->nKey ) return 0;
+ if( a->pPayload!=b->pPayload ) return 0;
+ if( a->nPayload!=b->nPayload ) return 0;
+ if( a->nLocal!=b->nLocal ) return 0;
+ if( a->nSize!=b->nSize ) return 0;
+ return 1;
+ }
static void assertCellInfo(BtCursor *pCur){
CellInfo info;
memset(&info, 0, sizeof(info));
btreeParseCell(pCur->pPage, pCur->ix, &info);
- assert( CORRUPT_DB || memcmp(&info, &pCur->info, sizeof(info))==0 );
+ assert( CORRUPT_DB || cellInfoEqual(&info, &pCur->info) );
}
#else
#define assertCellInfo(x)
@@ -4668,14 +4678,15 @@ static int accessPayload(
*/
if( (pCur->curFlags & BTCF_ValidOvfl)==0 ){
int nOvfl = (pCur->info.nPayload-pCur->info.nLocal+ovflSize-1)/ovflSize;
- if( nOvfl>pCur->nOvflAlloc ){
+ if( pCur->aOverflow==0
+ || nOvfl*(int)sizeof(Pgno) > sqlite3MallocSize(pCur->aOverflow)
+ ){
Pgno *aNew = (Pgno*)sqlite3Realloc(
pCur->aOverflow, nOvfl*2*sizeof(Pgno)
);
if( aNew==0 ){
return SQLITE_NOMEM_BKPT;
}else{
- pCur->nOvflAlloc = nOvfl*2;
pCur->aOverflow = aNew;
}
}
@@ -6189,9 +6200,8 @@ static void freePage(MemPage *pPage, int *pRC){
}
/*
-** Free any overflow pages associated with the given Cell. Write the
-** local Cell size (the number of bytes on the original page, omitting
-** overflow) into *pnSize.
+** Free any overflow pages associated with the given Cell. Store
+** size information about the cell in pInfo.
*/
static int clearCell(
MemPage *pPage, /* The page that contains the Cell */
@@ -7395,7 +7405,7 @@ static int balance_nonroot(
}
/* Load b.apCell[] with pointers to all cells in pOld. If pOld
- ** constains overflow cells, include them in the b.apCell[] array
+ ** contains overflow cells, include them in the b.apCell[] array
** in the correct spot.
**
** Note that when there are multiple overflow cells, it is always the
diff --git a/src/btreeInt.h b/src/btreeInt.h
index ac7a3c0..d1e2c08 100644
--- a/src/btreeInt.h
+++ b/src/btreeInt.h
@@ -503,20 +503,20 @@ struct BtCursor {
u8 curFlags; /* zero or more BTCF_* flags defined below */
u8 curPagerFlags; /* Flags to send to sqlite3PagerGet() */
u8 hints; /* As configured by CursorSetHints() */
- int nOvflAlloc; /* Allocated size of aOverflow[] array */
+ int skipNext; /* Prev() is noop if negative. Next() is noop if positive.
+ ** Error code if eState==CURSOR_FAULT */
Btree *pBtree; /* The Btree to which this cursor belongs */
- BtShared *pBt; /* The BtShared this cursor points to */
- BtCursor *pNext; /* Forms a linked list of all cursors */
Pgno *aOverflow; /* Cache of overflow page locations */
- CellInfo info; /* A parse of the cell we are pointing at */
- i64 nKey; /* Size of pKey, or last integer key */
void *pKey; /* Saved key that was cursor last known position */
- Pgno pgnoRoot; /* The root page of this tree */
- int skipNext; /* Prev() is noop if negative. Next() is noop if positive.
- ** Error code if eState==CURSOR_FAULT */
/* All fields above are zeroed when the cursor is allocated. See
** sqlite3BtreeCursorZero(). Fields that follow must be manually
** initialized. */
+#define BTCURSOR_FIRST_UNINIT pBt /* Name of first uninitialized field */
+ BtShared *pBt; /* The BtShared this cursor points to */
+ BtCursor *pNext; /* Forms a linked list of all cursors */
+ CellInfo info; /* A parse of the cell we are pointing at */
+ i64 nKey; /* Size of pKey, or last integer key */
+ Pgno pgnoRoot; /* The root page of this tree */
i8 iPage; /* Index of current page in apPage */
u8 curIntKey; /* Value of apPage[0]->intKey */
u16 ix; /* Current index for apPage[iPage] */
@@ -566,8 +566,8 @@ struct BtCursor {
** Do nothing else with this cursor. Any attempt to use the cursor
** should return the error code stored in BtCursor.skipNext
*/
-#define CURSOR_INVALID 0
-#define CURSOR_VALID 1
+#define CURSOR_VALID 0
+#define CURSOR_INVALID 1
#define CURSOR_SKIPNEXT 2
#define CURSOR_REQUIRESEEK 3
#define CURSOR_FAULT 4
diff --git a/src/build.c b/src/build.c
index 58b39d6..211ccf2 100644
--- a/src/build.c
+++ b/src/build.c
@@ -1118,10 +1118,24 @@ void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){
*/
void sqlite3AddNotNull(Parse *pParse, int onError){
Table *p;
+ Column *pCol;
p = pParse->pNewTable;
if( p==0 || NEVER(p->nCol<1) ) return;
- p->aCol[p->nCol-1].notNull = (u8)onError;
+ pCol = &p->aCol[p->nCol-1];
+ pCol->notNull = (u8)onError;
p->tabFlags |= TF_HasNotNull;
+
+ /* Set the uniqNotNull flag on any UNIQUE or PK indexes already created
+ ** on this column. */
+ if( pCol->colFlags & COLFLAG_UNIQUE ){
+ Index *pIdx;
+ for(pIdx=p->pIndex; pIdx; pIdx=pIdx->pNext){
+ assert( pIdx->nKeyCol==1 && pIdx->onError!=OE_None );
+ if( pIdx->aiColumn[0]==p->nCol-1 ){
+ pIdx->uniqNotNull = 1;
+ }
+ }
+ }
}
/*
@@ -1238,7 +1252,7 @@ void sqlite3AddDefaultValue(
pCol->zName);
}else{
/* A copy of pExpr is used instead of the original, as pExpr contains
- ** tokens that point to volatile memory.
+ ** tokens that point to volatile memory.
*/
Expr x;
sqlite3ExprDelete(db, pCol->pDflt);
@@ -1856,8 +1870,6 @@ void sqlite3EndTable(
p = pParse->pNewTable;
if( p==0 ) return;
- assert( !db->init.busy || !pSelect );
-
/* If the db->init.busy is 1 it means we are reading the SQL off the
** "sqlite_master" or "sqlite_temp_master" table on the disk.
** So do not write to the disk again. Extract the root page number
@@ -1868,6 +1880,10 @@ void sqlite3EndTable(
** table itself. So mark it read-only.
*/
if( db->init.busy ){
+ if( pSelect ){
+ sqlite3ErrorMsg(pParse, "");
+ return;
+ }
p->tnum = db->init.newTnum;
if( p->tnum==1 ) p->tabFlags |= TF_Readonly;
}
@@ -2158,7 +2174,7 @@ int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
int nErr = 0; /* Number of errors encountered */
int n; /* Temporarily holds the number of cursors assigned */
sqlite3 *db = pParse->db; /* Database connection for malloc errors */
-#ifndef SQLITE_OMIT_VIRTUALTABLE
+#ifndef SQLITE_OMIT_VIRTUALTABLE
int rc;
#endif
#ifndef SQLITE_OMIT_AUTHORIZATION
@@ -3085,7 +3101,9 @@ void sqlite3CreateIndex(
*/
if( pList==0 ){
Token prevCol;
- sqlite3TokenInit(&prevCol, pTab->aCol[pTab->nCol-1].zName);
+ Column *pCol = &pTab->aCol[pTab->nCol-1];
+ pCol->colFlags |= COLFLAG_UNIQUE;
+ sqlite3TokenInit(&prevCol, pCol->zName);
pList = sqlite3ExprListAppend(pParse, 0,
sqlite3ExprAlloc(db, TK_ID, &prevCol, 0));
if( pList==0 ) goto exit_create_index;
diff --git a/src/ctime.c b/src/ctime.c
index e8f4e7f..1877aee 100644
--- a/src/ctime.c
+++ b/src/ctime.c
@@ -188,7 +188,7 @@ static const char * const sqlite3azCompileOpt[] = {
"ENABLE_BATCH_ATOMIC_WRITE",
#endif
#if SQLITE_ENABLE_CEROD
- "ENABLE_CEROD",
+ "ENABLE_CEROD=" CTIMEOPT_VAL(SQLITE_ENABLE_CEROD),
#endif
#if SQLITE_ENABLE_COLUMN_METADATA
"ENABLE_COLUMN_METADATA",
diff --git a/src/dbstat.c b/src/dbstat.c
index c9f0fa8..0f1fd0a 100644
--- a/src/dbstat.c
+++ b/src/dbstat.c
@@ -424,7 +424,7 @@ static void statSizeAndOffset(StatCursor *pCsr){
*/
fd = sqlite3PagerFile(pPager);
x[0] = pCsr->iPageno;
- if( fd->pMethods!=0 && sqlite3OsFileControl(fd, 230440, &x)==SQLITE_OK ){
+ if( sqlite3OsFileControl(fd, 230440, &x)==SQLITE_OK ){
pCsr->iOffset = x[0];
pCsr->szPage = (int)x[1];
}
diff --git a/src/expr.c b/src/expr.c
index 1b87734..14bd06a 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -1733,6 +1733,34 @@ int sqlite3SelectWalkFail(Walker *pWalker, Select *NotUsed){
}
/*
+** If the input expression is an ID with the name "true" or "false"
+** then convert it into an TK_TRUEFALSE term. Return non-zero if
+** the conversion happened, and zero if the expression is unaltered.
+*/
+int sqlite3ExprIdToTrueFalse(Expr *pExpr){
+ assert( pExpr->op==TK_ID || pExpr->op==TK_STRING );
+ if( sqlite3StrICmp(pExpr->u.zToken, "true")==0
+ || sqlite3StrICmp(pExpr->u.zToken, "false")==0
+ ){
+ pExpr->op = TK_TRUEFALSE;
+ return 1;
+ }
+ return 0;
+}
+
+/*
+** The argument must be a TK_TRUEFALSE Expr node. Return 1 if it is TRUE
+** and 0 if it is FALSE.
+*/
+int sqlite3ExprTruthValue(const Expr *pExpr){
+ assert( pExpr->op==TK_TRUEFALSE );
+ assert( sqlite3StrICmp(pExpr->u.zToken,"true")==0
+ || sqlite3StrICmp(pExpr->u.zToken,"false")==0 );
+ return pExpr->u.zToken[4]==0;
+}
+
+
+/*
** These routines are Walker callbacks used to check expressions to
** see if they are "constant" for some definition of constant. The
** Walker.eCode value determines the type of "constant" we are looking
@@ -1779,6 +1807,12 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){
return WRC_Abort;
}
case TK_ID:
+ /* Convert "true" or "false" in a DEFAULT clause into the
+ ** appropriate TK_TRUEFALSE operator */
+ if( sqlite3ExprIdToTrueFalse(pExpr) ){
+ return WRC_Prune;
+ }
+ /* Fall thru */
case TK_COLUMN:
case TK_AGG_FUNCTION:
case TK_AGG_COLUMN:
@@ -3543,6 +3577,10 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
codeInteger(pParse, pExpr, 0, target);
return target;
}
+ case TK_TRUEFALSE: {
+ sqlite3VdbeAddOp2(v, OP_Integer, sqlite3ExprTruthValue(pExpr), target);
+ return target;
+ }
#ifndef SQLITE_OMIT_FLOATING_POINT
case TK_FLOAT: {
assert( !ExprHasProperty(pExpr, EP_IntValue) );
@@ -3698,6 +3736,18 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
sqlite3VdbeAddOp2(v, op, r1, inReg);
break;
}
+ case TK_TRUTH: {
+ int isTrue; /* IS TRUE or IS NOT TRUE */
+ int bNormal; /* IS TRUE or IS FALSE */
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ testcase( regFree1==0 );
+ isTrue = sqlite3ExprTruthValue(pExpr->pRight);
+ bNormal = pExpr->op2==TK_IS;
+ testcase( isTrue && bNormal);
+ testcase( !isTrue && bNormal);
+ sqlite3VdbeAddOp4Int(v, OP_IsTrue, r1, inReg, !isTrue, isTrue ^ bNormal);
+ break;
+ }
case TK_ISNULL:
case TK_NOTNULL: {
int addr;
@@ -4473,6 +4523,23 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull);
break;
}
+ case TK_TRUTH: {
+ int isNot; /* IS NOT TRUE or IS NOT FALSE */
+ int isTrue; /* IS TRUE or IS NOT TRUE */
+ testcase( jumpIfNull==0 );
+ isNot = pExpr->op2==TK_ISNOT;
+ isTrue = sqlite3ExprTruthValue(pExpr->pRight);
+ testcase( isTrue && isNot );
+ testcase( !isTrue && isNot );
+ if( isTrue ^ isNot ){
+ sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest,
+ isNot ? SQLITE_JUMPIFNULL : 0);
+ }else{
+ sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest,
+ isNot ? SQLITE_JUMPIFNULL : 0);
+ }
+ break;
+ }
case TK_IS:
case TK_ISNOT:
testcase( op==TK_IS );
@@ -4627,6 +4694,26 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull);
break;
}
+ case TK_TRUTH: {
+ int isNot; /* IS NOT TRUE or IS NOT FALSE */
+ int isTrue; /* IS TRUE or IS NOT TRUE */
+ testcase( jumpIfNull==0 );
+ isNot = pExpr->op2==TK_ISNOT;
+ isTrue = sqlite3ExprTruthValue(pExpr->pRight);
+ testcase( isTrue && isNot );
+ testcase( !isTrue && isNot );
+ if( isTrue ^ isNot ){
+ /* IS TRUE and IS NOT FALSE */
+ sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest,
+ isNot ? 0 : SQLITE_JUMPIFNULL);
+
+ }else{
+ /* IS FALSE and IS NOT TRUE */
+ sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest,
+ isNot ? 0 : SQLITE_JUMPIFNULL);
+ }
+ break;
+ }
case TK_IS:
case TK_ISNOT:
testcase( pExpr->op==TK_IS );
@@ -4915,6 +5002,80 @@ int sqlite3ExprImpliesExpr(Parse *pParse, Expr *pE1, Expr *pE2, int iTab){
}
/*
+** This is the Expr node callback for sqlite3ExprImpliesNotNullRow().
+** If the expression node requires that the table at pWalker->iCur
+** have a non-NULL column, then set pWalker->eCode to 1 and abort.
+*/
+static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){
+ /* This routine is only called for WHERE clause expressions and so it
+ ** cannot have any TK_AGG_COLUMN entries because those are only found
+ ** in HAVING clauses. We can get a TK_AGG_FUNCTION in a WHERE clause,
+ ** but that is an illegal construct and the query will be rejected at
+ ** a later stage of processing, so the TK_AGG_FUNCTION case does not
+ ** need to be considered here. */
+ assert( pExpr->op!=TK_AGG_COLUMN );
+ testcase( pExpr->op==TK_AGG_FUNCTION );
+
+ if( ExprHasProperty(pExpr, EP_FromJoin) ) return WRC_Prune;
+ switch( pExpr->op ){
+ case TK_ISNULL:
+ case TK_IS:
+ case TK_OR:
+ case TK_CASE:
+ case TK_IN:
+ case TK_FUNCTION:
+ testcase( pExpr->op==TK_ISNULL );
+ testcase( pExpr->op==TK_IS );
+ testcase( pExpr->op==TK_OR );
+ testcase( pExpr->op==TK_CASE );
+ testcase( pExpr->op==TK_IN );
+ testcase( pExpr->op==TK_FUNCTION );
+ return WRC_Prune;
+ case TK_COLUMN:
+ if( pWalker->u.iCur==pExpr->iTable ){
+ pWalker->eCode = 1;
+ return WRC_Abort;
+ }
+ return WRC_Prune;
+ default:
+ return WRC_Continue;
+ }
+}
+
+/*
+** Return true (non-zero) if expression p can only be true if at least
+** one column of table iTab is non-null. In other words, return true
+** if expression p will always be NULL or false if every column of iTab
+** is NULL.
+**
+** False negatives are acceptable. In other words, it is ok to return
+** zero even if expression p will never be true of every column of iTab
+** is NULL. A false negative is merely a missed optimization opportunity.
+**
+** False positives are not allowed, however. A false positive may result
+** in an incorrect answer.
+**
+** Terms of p that are marked with EP_FromJoin (and hence that come from
+** the ON or USING clauses of LEFT JOINS) are excluded from the analysis.
+**
+** This routine is used to check if a LEFT JOIN can be converted into
+** an ordinary JOIN. The p argument is the WHERE clause. If the WHERE
+** clause requires that some column of the right table of the LEFT JOIN
+** be non-NULL, then the LEFT JOIN can be safely converted into an
+** ordinary join.
+*/
+int sqlite3ExprImpliesNonNullRow(Expr *p, int iTab){
+ Walker w;
+ w.xExprCallback = impliesNotNullRow;
+ w.xSelectCallback = 0;
+ w.xSelectCallback2 = 0;
+ w.eCode = 0;
+ w.u.iCur = iTab;
+ sqlite3WalkExpr(&w, p);
+ return w.eCode;
+}
+
+/*
** An instance of the following structure is used by the tree walker
** to determine if an expression can be evaluated by reference to the
** index only, without having to do a search for the corresponding
diff --git a/src/func.c b/src/func.c
index 1076b97..deb7e74 100644
--- a/src/func.c
+++ b/src/func.c
@@ -35,6 +35,8 @@ static CollSeq *sqlite3GetFuncCollSeq(sqlite3_context *context){
** iteration of the aggregate loop.
*/
static void sqlite3SkipAccumulatorLoad(sqlite3_context *context){
+ assert( context->isError<=0 );
+ context->isError = -1;
context->skipFlag = 1;
}
@@ -101,8 +103,6 @@ static void lengthFunc(
int argc,
sqlite3_value **argv
){
- int len;
-
assert( argc==1 );
UNUSED_PARAMETER(argc);
switch( sqlite3_value_type(argv[0]) ){
@@ -114,13 +114,17 @@ static void lengthFunc(
}
case SQLITE_TEXT: {
const unsigned char *z = sqlite3_value_text(argv[0]);
+ const unsigned char *z0;
+ unsigned char c;
if( z==0 ) return;
- len = 0;
- while( *z ){
- len++;
- SQLITE_SKIP_UTF8(z);
+ z0 = z;
+ while( (c = *z)!=0 ){
+ z++;
+ if( c>=0xc0 ){
+ while( (*z & 0xc0)==0x80 ){ z++; z0++; }
+ }
}
- sqlite3_result_int(context, len);
+ sqlite3_result_int(context, (int)(z-z0));
break;
}
default: {
@@ -1195,6 +1199,8 @@ static void replaceFunc(
i64 nOut; /* Maximum size of zOut */
int loopLimit; /* Last zStr[] that might match zPattern[] */
int i, j; /* Loop counters */
+ unsigned cntExpand; /* Number zOut expansions */
+ sqlite3 *db = sqlite3_context_db_handle(context);
assert( argc==3 );
UNUSED_PARAMETER(argc);
@@ -1226,33 +1232,40 @@ static void replaceFunc(
return;
}
loopLimit = nStr - nPattern;
+ cntExpand = 0;
for(i=j=0; i<=loopLimit; i++){
if( zStr[i]!=zPattern[0] || memcmp(&zStr[i], zPattern, nPattern) ){
zOut[j++] = zStr[i];
}else{
- u8 *zOld;
- sqlite3 *db = sqlite3_context_db_handle(context);
- nOut += nRep - nPattern;
- testcase( nOut-1==db->aLimit[SQLITE_LIMIT_LENGTH] );
- testcase( nOut-2==db->aLimit[SQLITE_LIMIT_LENGTH] );
- if( nOut-1>db->aLimit[SQLITE_LIMIT_LENGTH] ){
- sqlite3_result_error_toobig(context);
- sqlite3_free(zOut);
- return;
- }
- zOld = zOut;
- zOut = sqlite3_realloc64(zOut, (int)nOut);
- if( zOut==0 ){
- sqlite3_result_error_nomem(context);
- sqlite3_free(zOld);
- return;
+ if( nRep>nPattern ){
+ nOut += nRep - nPattern;
+ testcase( nOut-1==db->aLimit[SQLITE_LIMIT_LENGTH] );
+ testcase( nOut-2==db->aLimit[SQLITE_LIMIT_LENGTH] );
+ if( nOut-1>db->aLimit[SQLITE_LIMIT_LENGTH] ){
+ sqlite3_result_error_toobig(context);
+ sqlite3_free(zOut);
+ return;
+ }
+ cntExpand++;
+ if( (cntExpand&(cntExpand-1))==0 ){
+ /* Grow the size of the output buffer only on substitutions
+ ** whose index is a power of two: 1, 2, 4, 8, 16, 32, ... */
+ u8 *zOld;
+ zOld = zOut;
+ zOut = sqlite3_realloc64(zOut, (int)nOut + (nOut - nStr - 1));
+ if( zOut==0 ){
+ sqlite3_result_error_nomem(context);
+ sqlite3_free(zOld);
+ return;
+ }
+ }
}
memcpy(&zOut[j], zRep, nRep);
j += nRep;
i += nPattern-1;
}
}
- assert( j+nStr-i+1==nOut );
+ assert( j+nStr-i+1<=nOut );
memcpy(&zOut[j], &zStr[i], nStr-i);
j += nStr - i;
assert( j<=nOut );
diff --git a/src/global.c b/src/global.c
index e66be81..04a3d18 100644
--- a/src/global.c
+++ b/src/global.c
@@ -258,6 +258,13 @@ const Token sqlite3IntTokens[] = {
{ "1", 1 }
};
+#ifdef VDBE_PROFILE
+/*
+** The following performance counter can be used in place of
+** sqlite3Hwtime() for profiling. This is a no-op on standard builds.
+*/
+sqlite3_uint64 sqlite3NProfileCnt = 0;
+#endif
/*
** The value of the "pending" byte must be 0x40000000 (1 byte past the
diff --git a/src/insert.c b/src/insert.c
index e151469..9f7032c 100644
--- a/src/insert.c
+++ b/src/insert.c
@@ -210,11 +210,12 @@ static int readsTable(Parse *p, int iDb, Table *pTab){
** first use of table pTab. On 2nd and subsequent uses, the original
** AutoincInfo structure is used.
**
-** Three memory locations are allocated:
+** Four consecutive registers are allocated:
**
-** (1) Register to hold the name of the pTab table.
-** (2) Register to hold the maximum ROWID of pTab.
-** (3) Register to hold the rowid in sqlite_sequence of pTab
+** (1) The name of the pTab table.
+** (2) The maximum ROWID of pTab.
+** (3) The rowid in sqlite_sequence of pTab
+** (4) The original value of the max ROWID in pTab, or NULL if none
**
** The 2nd register is the one that is returned. That is all the
** insert routine needs to know about.
@@ -242,7 +243,7 @@ static int autoIncBegin(
pInfo->iDb = iDb;
pToplevel->nMem++; /* Register to hold name of table */
pInfo->regCtr = ++pToplevel->nMem; /* Max rowid register */
- pToplevel->nMem++; /* Rowid in sqlite_sequence */
+ pToplevel->nMem +=2; /* Rowid in sqlite_sequence + orig max val */
}
memId = pInfo->regCtr;
}
@@ -270,15 +271,17 @@ void sqlite3AutoincrementBegin(Parse *pParse){
static const int iLn = VDBE_OFFSET_LINENO(2);
static const VdbeOpList autoInc[] = {
/* 0 */ {OP_Null, 0, 0, 0},
- /* 1 */ {OP_Rewind, 0, 9, 0},
+ /* 1 */ {OP_Rewind, 0, 10, 0},
/* 2 */ {OP_Column, 0, 0, 0},
- /* 3 */ {OP_Ne, 0, 7, 0},
+ /* 3 */ {OP_Ne, 0, 9, 0},
/* 4 */ {OP_Rowid, 0, 0, 0},
/* 5 */ {OP_Column, 0, 1, 0},
- /* 6 */ {OP_Goto, 0, 9, 0},
- /* 7 */ {OP_Next, 0, 2, 0},
- /* 8 */ {OP_Integer, 0, 0, 0},
- /* 9 */ {OP_Close, 0, 0, 0}
+ /* 6 */ {OP_AddImm, 0, 0, 0},
+ /* 7 */ {OP_Copy, 0, 0, 0},
+ /* 8 */ {OP_Goto, 0, 11, 0},
+ /* 9 */ {OP_Next, 0, 2, 0},
+ /* 10 */ {OP_Integer, 0, 0, 0},
+ /* 11 */ {OP_Close, 0, 0, 0}
};
VdbeOp *aOp;
pDb = &db->aDb[p->iDb];
@@ -289,14 +292,17 @@ void sqlite3AutoincrementBegin(Parse *pParse){
aOp = sqlite3VdbeAddOpList(v, ArraySize(autoInc), autoInc, iLn);
if( aOp==0 ) break;
aOp[0].p2 = memId;
- aOp[0].p3 = memId+1;
+ aOp[0].p3 = memId+2;
aOp[2].p3 = memId;
aOp[3].p1 = memId-1;
aOp[3].p3 = memId;
aOp[3].p5 = SQLITE_JUMPIFNULL;
aOp[4].p2 = memId+1;
aOp[5].p3 = memId;
- aOp[8].p2 = memId;
+ aOp[6].p1 = memId;
+ aOp[7].p2 = memId+2;
+ aOp[7].p1 = memId;
+ aOp[10].p2 = memId;
}
}
@@ -343,6 +349,8 @@ static SQLITE_NOINLINE void autoIncrementEnd(Parse *pParse){
iRec = sqlite3GetTempReg(pParse);
assert( sqlite3SchemaMutexHeld(db, 0, pDb->pSchema) );
+ sqlite3VdbeAddOp3(v, OP_Le, memId+2, sqlite3VdbeCurrentAddr(v)+7, memId);
+ VdbeCoverage(v);
sqlite3OpenTable(pParse, 0, p->iDb, pDb->pSchema->pSeqTab, OP_OpenWrite);
aOp = sqlite3VdbeAddOpList(v, ArraySize(autoIncEnd), autoIncEnd, iLn);
if( aOp==0 ) break;
diff --git a/src/main.c b/src/main.c
index ed41f98..5b6c867 100644
--- a/src/main.c
+++ b/src/main.c
@@ -239,6 +239,11 @@ int sqlite3_initialize(void){
sqlite3GlobalConfig.isPCacheInit = 1;
rc = sqlite3OsInit();
}
+#ifdef SQLITE_ENABLE_DESERIALIZE
+ if( rc==SQLITE_OK ){
+ rc = sqlite3MemdbInit();
+ }
+#endif
if( rc==SQLITE_OK ){
sqlite3PCacheBufferSetup( sqlite3GlobalConfig.pPage,
sqlite3GlobalConfig.szPage, sqlite3GlobalConfig.nPage);
@@ -271,7 +276,7 @@ int sqlite3_initialize(void){
#ifndef NDEBUG
#ifndef SQLITE_OMIT_FLOATING_POINT
/* This section of code's only "output" is via assert() statements. */
- if ( rc==SQLITE_OK ){
+ if( rc==SQLITE_OK ){
u64 x = (((u64)1)<<63)-1;
double y;
assert(sizeof(x)==8);
@@ -1438,6 +1443,8 @@ const char *sqlite3ErrStr(int rc){
/* SQLITE_FORMAT */ 0,
/* SQLITE_RANGE */ "column index out of range",
/* SQLITE_NOTADB */ "file is not a database",
+ /* SQLITE_NOTICE */ "notification message",
+ /* SQLITE_WARNING */ "warning message",
};
const char *zErr = "unknown error";
switch( rc ){
@@ -1445,6 +1452,14 @@ const char *sqlite3ErrStr(int rc){
zErr = "abort due to ROLLBACK";
break;
}
+ case SQLITE_ROW: {
+ zErr = "another row available";
+ break;
+ }
+ case SQLITE_DONE: {
+ zErr = "no more rows available";
+ break;
+ }
default: {
rc &= 0xff;
if( ALWAYS(rc>=0) && rc<ArraySize(aMsg) && aMsg[rc]!=0 ){
@@ -1461,21 +1476,40 @@ const char *sqlite3ErrStr(int rc){
** again until a timeout value is reached. The timeout value is
** an integer number of milliseconds passed in as the first
** argument.
+**
+** Return non-zero to retry the lock. Return zero to stop trying
+** and cause SQLite to return SQLITE_BUSY.
*/
static int sqliteDefaultBusyCallback(
- void *ptr, /* Database connection */
- int count /* Number of times table has been busy */
+ void *ptr, /* Database connection */
+ int count, /* Number of times table has been busy */
+ sqlite3_file *pFile /* The file on which the lock occurred */
){
#if SQLITE_OS_WIN || HAVE_USLEEP
+ /* This case is for systems that have support for sleeping for fractions of
+ ** a second. Examples: All windows systems, unix systems with usleep() */
static const u8 delays[] =
{ 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 };
static const u8 totals[] =
{ 0, 1, 3, 8, 18, 33, 53, 78, 103, 128, 178, 228 };
# define NDELAY ArraySize(delays)
sqlite3 *db = (sqlite3 *)ptr;
- int timeout = db->busyTimeout;
+ int tmout = db->busyTimeout;
int delay, prior;
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+ if( sqlite3OsFileControl(pFile,SQLITE_FCNTL_LOCK_TIMEOUT,&tmout)==SQLITE_OK ){
+ if( count ){
+ tmout = 0;
+ sqlite3OsFileControl(pFile, SQLITE_FCNTL_LOCK_TIMEOUT, &tmout);
+ return 0;
+ }else{
+ return 1;
+ }
+ }
+#else
+ UNUSED_PARAMETER(pFile);
+#endif
assert( count>=0 );
if( count < NDELAY ){
delay = delays[count];
@@ -1484,16 +1518,19 @@ static int sqliteDefaultBusyCallback(
delay = delays[NDELAY-1];
prior = totals[NDELAY-1] + delay*(count-(NDELAY-1));
}
- if( prior + delay > timeout ){
- delay = timeout - prior;
+ if( prior + delay > tmout ){
+ delay = tmout - prior;
if( delay<=0 ) return 0;
}
sqlite3OsSleep(db->pVfs, delay*1000);
return 1;
#else
+ /* This case for unix systems that lack usleep() support. Sleeping
+ ** must be done in increments of whole seconds */
sqlite3 *db = (sqlite3 *)ptr;
- int timeout = ((sqlite3 *)ptr)->busyTimeout;
- if( (count+1)*1000 > timeout ){
+ int tmout = ((sqlite3 *)ptr)->busyTimeout;
+ UNUSED_PARAMETER(pFile);
+ if( (count+1)*1000 > tmout ){
return 0;
}
sqlite3OsSleep(db->pVfs, 1000000);
@@ -1504,14 +1541,25 @@ static int sqliteDefaultBusyCallback(
/*
** Invoke the given busy handler.
**
-** This routine is called when an operation failed with a lock.
+** This routine is called when an operation failed to acquire a
+** lock on VFS file pFile.
+**
** If this routine returns non-zero, the lock is retried. If it
** returns 0, the operation aborts with an SQLITE_BUSY error.
*/
-int sqlite3InvokeBusyHandler(BusyHandler *p){
+int sqlite3InvokeBusyHandler(BusyHandler *p, sqlite3_file *pFile){
int rc;
- if( NEVER(p==0) || p->xFunc==0 || p->nBusy<0 ) return 0;
- rc = p->xFunc(p->pArg, p->nBusy);
+ if( p->xBusyHandler==0 || p->nBusy<0 ) return 0;
+ if( p->bExtraFileArg ){
+ /* Add an extra parameter with the pFile pointer to the end of the
+ ** callback argument list */
+ int (*xTra)(void*,int,sqlite3_file*);
+ xTra = (int(*)(void*,int,sqlite3_file*))p->xBusyHandler;
+ rc = xTra(p->pBusyArg, p->nBusy, pFile);
+ }else{
+ /* Legacy style busy handler callback */
+ rc = p->xBusyHandler(p->pBusyArg, p->nBusy);
+ }
if( rc==0 ){
p->nBusy = -1;
}else{
@@ -1533,9 +1581,10 @@ int sqlite3_busy_handler(
if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
sqlite3_mutex_enter(db->mutex);
- db->busyHandler.xFunc = xBusy;
- db->busyHandler.pArg = pArg;
+ db->busyHandler.xBusyHandler = xBusy;
+ db->busyHandler.pBusyArg = pArg;
db->busyHandler.nBusy = 0;
+ db->busyHandler.bExtraFileArg = 0;
db->busyTimeout = 0;
sqlite3_mutex_leave(db->mutex);
return SQLITE_OK;
@@ -1583,8 +1632,10 @@ int sqlite3_busy_timeout(sqlite3 *db, int ms){
if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
if( ms>0 ){
- sqlite3_busy_handler(db, sqliteDefaultBusyCallback, (void*)db);
+ sqlite3_busy_handler(db, (int(*)(void*,int))sqliteDefaultBusyCallback,
+ (void*)db);
db->busyTimeout = ms;
+ db->busyHandler.bExtraFileArg = 1;
}else{
sqlite3_busy_handler(db, 0, 0);
}
@@ -3577,10 +3628,8 @@ int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, void *pArg){
}else if( op==SQLITE_FCNTL_JOURNAL_POINTER ){
*(sqlite3_file**)pArg = sqlite3PagerJrnlFile(pPager);
rc = SQLITE_OK;
- }else if( fd->pMethods ){
- rc = sqlite3OsFileControl(fd, op, pArg);
}else{
- rc = SQLITE_NOTFOUND;
+ rc = sqlite3OsFileControl(fd, op, pArg);
}
sqlite3BtreeLeave(pBtree);
}
diff --git a/src/memdb.c b/src/memdb.c
new file mode 100644
index 0000000..4418985
--- /dev/null
+++ b/src/memdb.c
@@ -0,0 +1,589 @@
+/*
+** 2016-09-07
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file implements an in-memory VFS. A database is held as a contiguous
+** block of memory.
+**
+** This file also implements interface sqlite3_serialize() and
+** sqlite3_deserialize().
+*/
+#ifdef SQLITE_ENABLE_DESERIALIZE
+#include "sqliteInt.h"
+
+/*
+** Forward declaration of objects used by this utility
+*/
+typedef struct sqlite3_vfs MemVfs;
+typedef struct MemFile MemFile;
+
+/* Access to a lower-level VFS that (might) implement dynamic loading,
+** access to randomness, etc.
+*/
+#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData))
+
+/* An open file */
+struct MemFile {
+ sqlite3_file base; /* IO methods */
+ sqlite3_int64 sz; /* Size of the file */
+ sqlite3_int64 szMax; /* Space allocated to aData */
+ unsigned char *aData; /* content of the file */
+ int nMmap; /* Number of memory mapped pages */
+ unsigned mFlags; /* Flags */
+ int eLock; /* Most recent lock against this file */
+};
+
+/*
+** Methods for MemFile
+*/
+static int memdbClose(sqlite3_file*);
+static int memdbRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+static int memdbWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst);
+static int memdbTruncate(sqlite3_file*, sqlite3_int64 size);
+static int memdbSync(sqlite3_file*, int flags);
+static int memdbFileSize(sqlite3_file*, sqlite3_int64 *pSize);
+static int memdbLock(sqlite3_file*, int);
+/* static int memdbCheckReservedLock(sqlite3_file*, int *pResOut);// not used */
+static int memdbFileControl(sqlite3_file*, int op, void *pArg);
+/* static int memdbSectorSize(sqlite3_file*); // not used */
+static int memdbDeviceCharacteristics(sqlite3_file*);
+static int memdbFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp);
+static int memdbUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p);
+
+/*
+** Methods for MemVfs
+*/
+static int memdbOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
+/* static int memdbDelete(sqlite3_vfs*, const char *zName, int syncDir); */
+static int memdbAccess(sqlite3_vfs*, const char *zName, int flags, int *);
+static int memdbFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
+static void *memdbDlOpen(sqlite3_vfs*, const char *zFilename);
+static void memdbDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
+static void (*memdbDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void);
+static void memdbDlClose(sqlite3_vfs*, void*);
+static int memdbRandomness(sqlite3_vfs*, int nByte, char *zOut);
+static int memdbSleep(sqlite3_vfs*, int microseconds);
+/* static int memdbCurrentTime(sqlite3_vfs*, double*); */
+static int memdbGetLastError(sqlite3_vfs*, int, char *);
+static int memdbCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
+
+static sqlite3_vfs memdb_vfs = {
+ 2, /* iVersion */
+ 0, /* szOsFile (set when registered) */
+ 1024, /* mxPathname */
+ 0, /* pNext */
+ "memdb", /* zName */
+ 0, /* pAppData (set when registered) */
+ memdbOpen, /* xOpen */
+ 0, /* memdbDelete, */ /* xDelete */
+ memdbAccess, /* xAccess */
+ memdbFullPathname, /* xFullPathname */
+ memdbDlOpen, /* xDlOpen */
+ memdbDlError, /* xDlError */
+ memdbDlSym, /* xDlSym */
+ memdbDlClose, /* xDlClose */
+ memdbRandomness, /* xRandomness */
+ memdbSleep, /* xSleep */
+ 0, /* memdbCurrentTime, */ /* xCurrentTime */
+ memdbGetLastError, /* xGetLastError */
+ memdbCurrentTimeInt64 /* xCurrentTimeInt64 */
+};
+
+static const sqlite3_io_methods memdb_io_methods = {
+ 3, /* iVersion */
+ memdbClose, /* xClose */
+ memdbRead, /* xRead */
+ memdbWrite, /* xWrite */
+ memdbTruncate, /* xTruncate */
+ memdbSync, /* xSync */
+ memdbFileSize, /* xFileSize */
+ memdbLock, /* xLock */
+ memdbLock, /* xUnlock - same as xLock in this case */
+ 0, /* memdbCheckReservedLock, */ /* xCheckReservedLock */
+ memdbFileControl, /* xFileControl */
+ 0, /* memdbSectorSize,*/ /* xSectorSize */
+ memdbDeviceCharacteristics, /* xDeviceCharacteristics */
+ 0, /* xShmMap */
+ 0, /* xShmLock */
+ 0, /* xShmBarrier */
+ 0, /* xShmUnmap */
+ memdbFetch, /* xFetch */
+ memdbUnfetch /* xUnfetch */
+};
+
+
+
+/*
+** Close an memdb-file.
+**
+** The pData pointer is owned by the application, so there is nothing
+** to free.
+*/
+static int memdbClose(sqlite3_file *pFile){
+ MemFile *p = (MemFile *)pFile;
+ if( p->mFlags & SQLITE_DESERIALIZE_FREEONCLOSE ) sqlite3_free(p->aData);
+ return SQLITE_OK;
+}
+
+/*
+** Read data from an memdb-file.
+*/
+static int memdbRead(
+ sqlite3_file *pFile,
+ void *zBuf,
+ int iAmt,
+ sqlite_int64 iOfst
+){
+ MemFile *p = (MemFile *)pFile;
+ if( iOfst+iAmt>p->sz ){
+ memset(zBuf, 0, iAmt);
+ if( iOfst<p->sz ) memcpy(zBuf, p->aData+iOfst, p->sz - iOfst);
+ return SQLITE_IOERR_SHORT_READ;
+ }
+ memcpy(zBuf, p->aData+iOfst, iAmt);
+ return SQLITE_OK;
+}
+
+/*
+** Try to enlarge the memory allocation to hold at least sz bytes
+*/
+static int memdbEnlarge(MemFile *p, sqlite3_int64 newSz){
+ unsigned char *pNew;
+ if( (p->mFlags & SQLITE_DESERIALIZE_RESIZEABLE)==0 || p->nMmap>0 ){
+ return SQLITE_FULL;
+ }
+ pNew = sqlite3_realloc64(p->aData, newSz);
+ if( pNew==0 ) return SQLITE_NOMEM;
+ p->aData = pNew;
+ p->szMax = newSz;
+ return SQLITE_OK;
+}
+
+/*
+** Write data to an memdb-file.
+*/
+static int memdbWrite(
+ sqlite3_file *pFile,
+ const void *z,
+ int iAmt,
+ sqlite_int64 iOfst
+){
+ MemFile *p = (MemFile *)pFile;
+ if( iOfst+iAmt>p->sz ){
+ int rc;
+ if( iOfst+iAmt>p->szMax
+ && (rc = memdbEnlarge(p, (iOfst+iAmt)*2))!=SQLITE_OK
+ ){
+ return rc;
+ }
+ if( iOfst>p->sz ) memset(p->aData+p->sz, 0, iOfst-p->sz);
+ p->sz = iOfst+iAmt;
+ }
+ memcpy(p->aData+iOfst, z, iAmt);
+ return SQLITE_OK;
+}
+
+/*
+** Truncate an memdb-file.
+**
+** In rollback mode (which is always the case for memdb, as it does not
+** support WAL mode) the truncate() method is only used to reduce
+** the size of a file, never to increase the size.
+*/
+static int memdbTruncate(sqlite3_file *pFile, sqlite_int64 size){
+ MemFile *p = (MemFile *)pFile;
+ if( NEVER(size>p->sz) ) return SQLITE_FULL;
+ p->sz = size;
+ return SQLITE_OK;
+}
+
+/*
+** Sync an memdb-file.
+*/
+static int memdbSync(sqlite3_file *pFile, int flags){
+ return SQLITE_OK;
+}
+
+/*
+** Return the current file-size of an memdb-file.
+*/
+static int memdbFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
+ MemFile *p = (MemFile *)pFile;
+ *pSize = p->sz;
+ return SQLITE_OK;
+}
+
+/*
+** Lock an memdb-file.
+*/
+static int memdbLock(sqlite3_file *pFile, int eLock){
+ MemFile *p = (MemFile *)pFile;
+ p->eLock = eLock;
+ return SQLITE_OK;
+}
+
+#if 0 /* Never used because memdbAccess() always returns false */
+/*
+** Check if another file-handle holds a RESERVED lock on an memdb-file.
+*/
+static int memdbCheckReservedLock(sqlite3_file *pFile, int *pResOut){
+ *pResOut = 0;
+ return SQLITE_OK;
+}
+#endif
+
+/*
+** File control method. For custom operations on an memdb-file.
+*/
+static int memdbFileControl(sqlite3_file *pFile, int op, void *pArg){
+ MemFile *p = (MemFile *)pFile;
+ int rc = SQLITE_NOTFOUND;
+ if( op==SQLITE_FCNTL_VFSNAME ){
+ *(char**)pArg = sqlite3_mprintf("memdb(%p,%lld)", p->aData, p->sz);
+ rc = SQLITE_OK;
+ }
+ return rc;
+}
+
+#if 0 /* Not used because of SQLITE_IOCAP_POWERSAFE_OVERWRITE */
+/*
+** Return the sector-size in bytes for an memdb-file.
+*/
+static int memdbSectorSize(sqlite3_file *pFile){
+ return 1024;
+}
+#endif
+
+/*
+** Return the device characteristic flags supported by an memdb-file.
+*/
+static int memdbDeviceCharacteristics(sqlite3_file *pFile){
+ return SQLITE_IOCAP_ATOMIC |
+ SQLITE_IOCAP_POWERSAFE_OVERWRITE |
+ SQLITE_IOCAP_SAFE_APPEND |
+ SQLITE_IOCAP_SEQUENTIAL;
+}
+
+/* Fetch a page of a memory-mapped file */
+static int memdbFetch(
+ sqlite3_file *pFile,
+ sqlite3_int64 iOfst,
+ int iAmt,
+ void **pp
+){
+ MemFile *p = (MemFile *)pFile;
+ p->nMmap++;
+ *pp = (void*)(p->aData + iOfst);
+ return SQLITE_OK;
+}
+
+/* Release a memory-mapped page */
+static int memdbUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){
+ MemFile *p = (MemFile *)pFile;
+ p->nMmap--;
+ return SQLITE_OK;
+}
+
+/*
+** Open an mem file handle.
+*/
+static int memdbOpen(
+ sqlite3_vfs *pVfs,
+ const char *zName,
+ sqlite3_file *pFile,
+ int flags,
+ int *pOutFlags
+){
+ MemFile *p = (MemFile*)pFile;
+ if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){
+ return ORIGVFS(pVfs)->xOpen(ORIGVFS(pVfs), zName, pFile, flags, pOutFlags);
+ }
+ memset(p, 0, sizeof(*p));
+ p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE | SQLITE_DESERIALIZE_FREEONCLOSE;
+ assert( pOutFlags!=0 ); /* True because flags==SQLITE_OPEN_MAIN_DB */
+ *pOutFlags = flags | SQLITE_OPEN_MEMORY;
+ p->base.pMethods = &memdb_io_methods;
+ return SQLITE_OK;
+}
+
+#if 0 /* Only used to delete rollback journals, master journals, and WAL
+ ** files, none of which exist in memdb. So this routine is never used */
+/*
+** Delete the file located at zPath. If the dirSync argument is true,
+** ensure the file-system modifications are synced to disk before
+** returning.
+*/
+static int memdbDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+ return SQLITE_IOERR_DELETE;
+}
+#endif
+
+/*
+** Test for access permissions. Return true if the requested permission
+** is available, or false otherwise.
+**
+** With memdb, no files ever exist on disk. So always return false.
+*/
+static int memdbAccess(
+ sqlite3_vfs *pVfs,
+ const char *zPath,
+ int flags,
+ int *pResOut
+){
+ *pResOut = 0;
+ return SQLITE_OK;
+}
+
+/*
+** Populate buffer zOut with the full canonical pathname corresponding
+** to the pathname in zPath. zOut is guaranteed to point to a buffer
+** of at least (INST_MAX_PATHNAME+1) bytes.
+*/
+static int memdbFullPathname(
+ sqlite3_vfs *pVfs,
+ const char *zPath,
+ int nOut,
+ char *zOut
+){
+ sqlite3_snprintf(nOut, zOut, "%s", zPath);
+ return SQLITE_OK;
+}
+
+/*
+** Open the dynamic library located at zPath and return a handle.
+*/
+static void *memdbDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+ return ORIGVFS(pVfs)->xDlOpen(ORIGVFS(pVfs), zPath);
+}
+
+/*
+** Populate the buffer zErrMsg (size nByte bytes) with a human readable
+** utf-8 string describing the most recent error encountered associated
+** with dynamic libraries.
+*/
+static void memdbDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
+ ORIGVFS(pVfs)->xDlError(ORIGVFS(pVfs), nByte, zErrMsg);
+}
+
+/*
+** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
+*/
+static void (*memdbDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){
+ return ORIGVFS(pVfs)->xDlSym(ORIGVFS(pVfs), p, zSym);
+}
+
+/*
+** Close the dynamic library handle pHandle.
+*/
+static void memdbDlClose(sqlite3_vfs *pVfs, void *pHandle){
+ ORIGVFS(pVfs)->xDlClose(ORIGVFS(pVfs), pHandle);
+}
+
+/*
+** Populate the buffer pointed to by zBufOut with nByte bytes of
+** random data.
+*/
+static int memdbRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
+ return ORIGVFS(pVfs)->xRandomness(ORIGVFS(pVfs), nByte, zBufOut);
+}
+
+/*
+** Sleep for nMicro microseconds. Return the number of microseconds
+** actually slept.
+*/
+static int memdbSleep(sqlite3_vfs *pVfs, int nMicro){
+ return ORIGVFS(pVfs)->xSleep(ORIGVFS(pVfs), nMicro);
+}
+
+#if 0 /* Never used. Modern cores only call xCurrentTimeInt64() */
+/*
+** Return the current time as a Julian Day number in *pTimeOut.
+*/
+static int memdbCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
+ return ORIGVFS(pVfs)->xCurrentTime(ORIGVFS(pVfs), pTimeOut);
+}
+#endif
+
+static int memdbGetLastError(sqlite3_vfs *pVfs, int a, char *b){
+ return ORIGVFS(pVfs)->xGetLastError(ORIGVFS(pVfs), a, b);
+}
+static int memdbCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){
+ return ORIGVFS(pVfs)->xCurrentTimeInt64(ORIGVFS(pVfs), p);
+}
+
+/*
+** Translate a database connection pointer and schema name into a
+** MemFile pointer.
+*/
+static MemFile *memdbFromDbSchema(sqlite3 *db, const char *zSchema){
+ MemFile *p = 0;
+ int rc = sqlite3_file_control(db, zSchema, SQLITE_FCNTL_FILE_POINTER, &p);
+ if( rc ) return 0;
+ if( p->base.pMethods!=&memdb_io_methods ) return 0;
+ return p;
+}
+
+/*
+** Return the serialization of a database
+*/
+unsigned char *sqlite3_serialize(
+ sqlite3 *db, /* The database connection */
+ const char *zSchema, /* Which database within the connection */
+ sqlite3_int64 *piSize, /* Write size here, if not NULL */
+ unsigned int mFlags /* Maybe SQLITE_SERIALIZE_NOCOPY */
+){
+ MemFile *p;
+ int iDb;
+ Btree *pBt;
+ sqlite3_int64 sz;
+ int szPage = 0;
+ sqlite3_stmt *pStmt = 0;
+ unsigned char *pOut;
+ char *zSql;
+ int rc;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
+
+ if( zSchema==0 ) zSchema = db->aDb[0].zDbSName;
+ p = memdbFromDbSchema(db, zSchema);
+ iDb = sqlite3FindDbName(db, zSchema);
+ if( piSize ) *piSize = -1;
+ if( iDb<0 ) return 0;
+ if( p ){
+ if( piSize ) *piSize = p->sz;
+ if( mFlags & SQLITE_SERIALIZE_NOCOPY ){
+ pOut = p->aData;
+ }else{
+ pOut = sqlite3_malloc64( p->sz );
+ if( pOut ) memcpy(pOut, p->aData, p->sz);
+ }
+ return pOut;
+ }
+ pBt = db->aDb[iDb].pBt;
+ if( pBt==0 ) return 0;
+ szPage = sqlite3BtreeGetPageSize(pBt);
+ zSql = sqlite3_mprintf("PRAGMA \"%w\".page_count", zSchema);
+ rc = zSql ? sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0) : SQLITE_NOMEM;
+ sqlite3_free(zSql);
+ if( rc ) return 0;
+ rc = sqlite3_step(pStmt);
+ if( rc!=SQLITE_ROW ){
+ pOut = 0;
+ }else{
+ sz = sqlite3_column_int64(pStmt, 0)*szPage;
+ if( piSize ) *piSize = sz;
+ if( mFlags & SQLITE_SERIALIZE_NOCOPY ){
+ pOut = 0;
+ }else{
+ pOut = sqlite3_malloc64( sz );
+ if( pOut ){
+ int nPage = sqlite3_column_int(pStmt, 0);
+ Pager *pPager = sqlite3BtreePager(pBt);
+ int pgno;
+ for(pgno=1; pgno<=nPage; pgno++){
+ DbPage *pPage = 0;
+ unsigned char *pTo = pOut + szPage*(sqlite3_int64)(pgno-1);
+ rc = sqlite3PagerGet(pPager, pgno, (DbPage**)&pPage, 0);
+ if( rc==SQLITE_OK ){
+ memcpy(pTo, sqlite3PagerGetData(pPage), szPage);
+ }else{
+ memset(pTo, 0, szPage);
+ }
+ sqlite3PagerUnref(pPage);
+ }
+ }
+ }
+ }
+ sqlite3_finalize(pStmt);
+ return pOut;
+}
+
+/* Convert zSchema to a MemDB and initialize its content.
+*/
+int sqlite3_deserialize(
+ sqlite3 *db, /* The database connection */
+ const char *zSchema, /* Which DB to reopen with the deserialization */
+ unsigned char *pData, /* The serialized database content */
+ sqlite3_int64 szDb, /* Number bytes in the deserialization */
+ sqlite3_int64 szBuf, /* Total size of buffer pData[] */
+ unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */
+){
+ MemFile *p;
+ char *zSql;
+ sqlite3_stmt *pStmt = 0;
+ int rc;
+ int iDb;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ return SQLITE_MISUSE_BKPT;
+ }
+ if( szDb<0 ) return SQLITE_MISUSE_BKPT;
+ if( szBuf<0 ) return SQLITE_MISUSE_BKPT;
+#endif
+
+ sqlite3_mutex_enter(db->mutex);
+ if( zSchema==0 ) zSchema = db->aDb[0].zDbSName;
+ iDb = sqlite3FindDbName(db, zSchema);
+ if( iDb<0 ){
+ rc = SQLITE_ERROR;
+ goto end_deserialize;
+ }
+ zSql = sqlite3_mprintf("ATTACH x AS %Q", zSchema);
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ sqlite3_free(zSql);
+ if( rc ) goto end_deserialize;
+ db->init.iDb = (u8)iDb;
+ db->init.reopenMemdb = 1;
+ rc = sqlite3_step(pStmt);
+ db->init.reopenMemdb = 0;
+ if( rc!=SQLITE_DONE ){
+ rc = SQLITE_ERROR;
+ goto end_deserialize;
+ }
+ p = memdbFromDbSchema(db, zSchema);
+ if( p==0 ){
+ rc = SQLITE_ERROR;
+ }else{
+ p->aData = pData;
+ p->sz = szDb;
+ p->szMax = szBuf;
+ p->mFlags = mFlags;
+ rc = SQLITE_OK;
+ }
+
+end_deserialize:
+ sqlite3_finalize(pStmt);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+/*
+** This routine is called when the extension is loaded.
+** Register the new VFS.
+*/
+int sqlite3MemdbInit(void){
+ sqlite3_vfs *pLower = sqlite3_vfs_find(0);
+ int sz = pLower->szOsFile;
+ memdb_vfs.pAppData = pLower;
+ /* In all known configurations of SQLite, the size of a default
+ ** sqlite3_file is greater than the size of a memdb sqlite3_file.
+ ** Should that ever change, remove the following NEVER() */
+ if( NEVER(sz<sizeof(MemFile)) ) sz = sizeof(MemFile);
+ memdb_vfs.szOsFile = sz;
+ return sqlite3_vfs_register(&memdb_vfs, 0);
+}
+#endif /* SQLITE_ENABLE_DESERIALIZE */
diff --git a/src/mutex_unix.c b/src/mutex_unix.c
index 55d08c8..9282d28 100644
--- a/src/mutex_unix.c
+++ b/src/mutex_unix.c
@@ -50,11 +50,12 @@ struct sqlite3_mutex {
#endif
};
#if SQLITE_MUTEX_NREF
-#define SQLITE3_MUTEX_INITIALIZER {PTHREAD_MUTEX_INITIALIZER,0,0,(pthread_t)0,0}
+# define SQLITE3_MUTEX_INITIALIZER(id) \
+ {PTHREAD_MUTEX_INITIALIZER,id,0,(pthread_t)0,0}
#elif defined(SQLITE_ENABLE_API_ARMOR)
-#define SQLITE3_MUTEX_INITIALIZER { PTHREAD_MUTEX_INITIALIZER, 0 }
+# define SQLITE3_MUTEX_INITIALIZER(id) { PTHREAD_MUTEX_INITIALIZER, id }
#else
-#define SQLITE3_MUTEX_INITIALIZER { PTHREAD_MUTEX_INITIALIZER }
+#define SQLITE3_MUTEX_INITIALIZER(id) { PTHREAD_MUTEX_INITIALIZER }
#endif
/*
@@ -151,18 +152,18 @@ static int pthreadMutexEnd(void){ return SQLITE_OK; }
*/
static sqlite3_mutex *pthreadMutexAlloc(int iType){
static sqlite3_mutex staticMutexes[] = {
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER
+ SQLITE3_MUTEX_INITIALIZER(2),
+ SQLITE3_MUTEX_INITIALIZER(3),
+ SQLITE3_MUTEX_INITIALIZER(4),
+ SQLITE3_MUTEX_INITIALIZER(5),
+ SQLITE3_MUTEX_INITIALIZER(6),
+ SQLITE3_MUTEX_INITIALIZER(7),
+ SQLITE3_MUTEX_INITIALIZER(8),
+ SQLITE3_MUTEX_INITIALIZER(9),
+ SQLITE3_MUTEX_INITIALIZER(10),
+ SQLITE3_MUTEX_INITIALIZER(11),
+ SQLITE3_MUTEX_INITIALIZER(12),
+ SQLITE3_MUTEX_INITIALIZER(13)
};
sqlite3_mutex *p;
switch( iType ){
@@ -181,6 +182,9 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){
pthread_mutex_init(&p->mutex, &recursiveAttr);
pthread_mutexattr_destroy(&recursiveAttr);
#endif
+#if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR)
+ p->id = SQLITE_MUTEX_RECURSIVE;
+#endif
}
break;
}
@@ -188,6 +192,9 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){
p = sqlite3MallocZero( sizeof(*p) );
if( p ){
pthread_mutex_init(&p->mutex, 0);
+#if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR)
+ p->id = SQLITE_MUTEX_FAST;
+#endif
}
break;
}
@@ -203,7 +210,7 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){
}
}
#if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR)
- if( p ) p->id = iType;
+ assert( p==0 || p->id==iType );
#endif
return p;
}
diff --git a/src/mutex_w32.c b/src/mutex_w32.c
index 9da93cf..8a8ae28 100644
--- a/src/mutex_w32.c
+++ b/src/mutex_w32.c
@@ -40,7 +40,7 @@ struct sqlite3_mutex {
#ifdef SQLITE_DEBUG
volatile int nRef; /* Number of enterances */
volatile DWORD owner; /* Thread holding this mutex */
- volatile int trace; /* True to trace changes */
+ volatile LONG trace; /* True to trace changes */
#endif
};
@@ -52,10 +52,10 @@ struct sqlite3_mutex {
#define SQLITE_W32_MUTEX_INITIALIZER { 0 }
#ifdef SQLITE_DEBUG
-#define SQLITE3_MUTEX_INITIALIZER { SQLITE_W32_MUTEX_INITIALIZER, 0, \
+#define SQLITE3_MUTEX_INITIALIZER(id) { SQLITE_W32_MUTEX_INITIALIZER, id, \
0L, (DWORD)0, 0 }
#else
-#define SQLITE3_MUTEX_INITIALIZER { SQLITE_W32_MUTEX_INITIALIZER, 0 }
+#define SQLITE3_MUTEX_INITIALIZER(id) { SQLITE_W32_MUTEX_INITIALIZER, id }
#endif
#ifdef SQLITE_DEBUG
@@ -98,18 +98,18 @@ void sqlite3MemoryBarrier(void){
** Initialize and deinitialize the mutex subsystem.
*/
static sqlite3_mutex winMutex_staticMutexes[] = {
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER
+ SQLITE3_MUTEX_INITIALIZER(2),
+ SQLITE3_MUTEX_INITIALIZER(3),
+ SQLITE3_MUTEX_INITIALIZER(4),
+ SQLITE3_MUTEX_INITIALIZER(5),
+ SQLITE3_MUTEX_INITIALIZER(6),
+ SQLITE3_MUTEX_INITIALIZER(7),
+ SQLITE3_MUTEX_INITIALIZER(8),
+ SQLITE3_MUTEX_INITIALIZER(9),
+ SQLITE3_MUTEX_INITIALIZER(10),
+ SQLITE3_MUTEX_INITIALIZER(11),
+ SQLITE3_MUTEX_INITIALIZER(12),
+ SQLITE3_MUTEX_INITIALIZER(13)
};
static int winMutex_isInit = 0;
@@ -239,15 +239,15 @@ static sqlite3_mutex *winMutexAlloc(int iType){
}
#endif
p = &winMutex_staticMutexes[iType-2];
- p->id = iType;
#ifdef SQLITE_DEBUG
#ifdef SQLITE_WIN32_MUTEX_TRACE_STATIC
- p->trace = 1;
+ InterlockedCompareExchange(&p->trace, 1, 0);
#endif
#endif
break;
}
}
+ assert( p==0 || p->id==iType );
return p;
}
diff --git a/src/os.c b/src/os.c
index 26c8065..1637c09 100644
--- a/src/os.c
+++ b/src/os.c
@@ -126,7 +126,9 @@ int sqlite3OsCheckReservedLock(sqlite3_file *id, int *pResOut){
*/
int sqlite3OsFileControl(sqlite3_file *id, int op, void *pArg){
#ifdef SQLITE_TEST
- if( op!=SQLITE_FCNTL_COMMIT_PHASETWO ){
+ if( op!=SQLITE_FCNTL_COMMIT_PHASETWO
+ && op!=SQLITE_FCNTL_LOCK_TIMEOUT
+ ){
/* Faults are not injected into COMMIT_PHASETWO because, assuming SQLite
** is using a regular VFS, it is called after the corresponding
** transaction has been committed. Injecting a fault at this point
@@ -140,10 +142,11 @@ int sqlite3OsFileControl(sqlite3_file *id, int op, void *pArg){
DO_OS_MALLOC_TEST(id);
}
#endif
+ if( id->pMethods==0 ) return SQLITE_NOTFOUND;
return id->pMethods->xFileControl(id, op, pArg);
}
void sqlite3OsFileControlHint(sqlite3_file *id, int op, void *pArg){
- (void)id->pMethods->xFileControl(id, op, pArg);
+ if( id->pMethods ) (void)id->pMethods->xFileControl(id, op, pArg);
}
int sqlite3OsSectorSize(sqlite3_file *id){
diff --git a/src/os_unix.c b/src/os_unix.c
index 94b1efd..2b9c117 100644
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -229,6 +229,9 @@ struct unixFile {
#if SQLITE_ENABLE_LOCKING_STYLE || defined(__APPLE__)
unsigned fsFlags; /* cached details from statfs() */
#endif
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+ unsigned iBusyTimeout; /* Wait this many millisec on locks */
+#endif
#if OS_VXWORKS
struct vxworksFileId *pId; /* Unique file ID */
#endif
@@ -468,7 +471,11 @@ static struct unix_syscall {
#endif
#define osFchown ((int(*)(int,uid_t,gid_t))aSyscall[20].pCurrent)
+#if defined(HAVE_FCHOWN)
{ "geteuid", (sqlite3_syscall_ptr)geteuid, 0 },
+#else
+ { "geteuid", (sqlite3_syscall_ptr)0, 0 },
+#endif
#define osGeteuid ((uid_t(*)(void))aSyscall[21].pCurrent)
#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0
@@ -696,15 +703,16 @@ static int robust_open(const char *z, int f, mode_t m){
** assert( unixMutexHeld() );
** unixEnterLeave()
*/
+static sqlite3_mutex *unixBigLock = 0;
static void unixEnterMutex(void){
- sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
+ sqlite3_mutex_enter(unixBigLock);
}
static void unixLeaveMutex(void){
- sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
+ sqlite3_mutex_leave(unixBigLock);
}
#ifdef SQLITE_DEBUG
static int unixMutexHeld(void) {
- return sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
+ return sqlite3_mutex_held(unixBigLock);
}
#endif
@@ -1461,6 +1469,43 @@ static int unixCheckReservedLock(sqlite3_file *id, int *pResOut){
}
/*
+** Set a posix-advisory-lock.
+**
+** There are two versions of this routine. If compiled with
+** SQLITE_ENABLE_SETLK_TIMEOUT then the routine has an extra parameter
+** which is a pointer to a unixFile. If the unixFile->iBusyTimeout
+** value is set, then it is the number of milliseconds to wait before
+** failing the lock. The iBusyTimeout value is always reset back to
+** zero on each call.
+**
+** If SQLITE_ENABLE_SETLK_TIMEOUT is not defined, then do a non-blocking
+** attempt to set the lock.
+*/
+#ifndef SQLITE_ENABLE_SETLK_TIMEOUT
+# define osSetPosixAdvisoryLock(h,x,t) osFcntl(h,F_SETLK,x)
+#else
+static int osSetPosixAdvisoryLock(
+ int h, /* The file descriptor on which to take the lock */
+ struct flock *pLock, /* The description of the lock */
+ unixFile *pFile /* Structure holding timeout value */
+){
+ int rc = osFcntl(h,F_SETLK,pLock);
+ while( rc<0 && pFile->iBusyTimeout>0 ){
+ /* On systems that support some kind of blocking file lock with a timeout,
+ ** make appropriate changes here to invoke that blocking file lock. On
+ ** generic posix, however, there is no such API. So we simply try the
+ ** lock once every millisecond until either the timeout expires, or until
+ ** the lock is obtained. */
+ usleep(1000);
+ rc = osFcntl(h,F_SETLK,pLock);
+ pFile->iBusyTimeout--;
+ }
+ return rc;
+}
+#endif /* SQLITE_ENABLE_SETLK_TIMEOUT */
+
+
+/*
** Attempt to set a system-lock on the file pFile. The lock is
** described by pLock.
**
@@ -1492,7 +1537,7 @@ static int unixFileLock(unixFile *pFile, struct flock *pLock){
lock.l_start = SHARED_FIRST;
lock.l_len = SHARED_SIZE;
lock.l_type = F_WRLCK;
- rc = osFcntl(pFile->h, F_SETLK, &lock);
+ rc = osSetPosixAdvisoryLock(pFile->h, &lock, pFile);
if( rc<0 ) return rc;
pInode->bProcessLock = 1;
pInode->nLock++;
@@ -1500,7 +1545,7 @@ static int unixFileLock(unixFile *pFile, struct flock *pLock){
rc = 0;
}
}else{
- rc = osFcntl(pFile->h, F_SETLK, pLock);
+ rc = osSetPosixAdvisoryLock(pFile->h, pLock, pFile);
}
return rc;
}
@@ -3860,6 +3905,12 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){
*(int*)pArg = fileHasMoved(pFile);
return SQLITE_OK;
}
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+ case SQLITE_FCNTL_LOCK_TIMEOUT: {
+ pFile->iBusyTimeout = *(int*)pArg;
+ return SQLITE_OK;
+ }
+#endif
#if SQLITE_MAX_MMAP_SIZE>0
case SQLITE_FCNTL_MMAP_SIZE: {
i64 newLimit = *(i64*)pArg;
@@ -4175,13 +4226,11 @@ static int unixShmSystemLock(
if( pShmNode->h>=0 ){
/* Initialize the locking parameters */
- memset(&f, 0, sizeof(f));
f.l_type = lockType;
f.l_whence = SEEK_SET;
f.l_start = ofst;
f.l_len = n;
-
- rc = osFcntl(pShmNode->h, F_SETLK, &f);
+ rc = osSetPosixAdvisoryLock(pShmNode->h, &f, pFile);
rc = (rc!=(-1)) ? SQLITE_OK : SQLITE_BUSY;
}
@@ -5846,7 +5895,6 @@ static int unixOpen(
randomnessPid = osGetpid(0);
sqlite3_randomness(0,0);
}
-
memset(p, 0, sizeof(unixFile));
if( eType==SQLITE_OPEN_MAIN_DB ){
@@ -7721,6 +7769,7 @@ int sqlite3_os_init(void){
for(i=0; i<(sizeof(aVfs)/sizeof(sqlite3_vfs)); i++){
sqlite3_vfs_register(&aVfs[i], i==0);
}
+ unixBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1);
return SQLITE_OK;
}
@@ -7732,6 +7781,7 @@ int sqlite3_os_init(void){
** This routine is a no-op for unix.
*/
int sqlite3_os_end(void){
+ unixBigLock = 0;
return SQLITE_OK;
}
diff --git a/src/os_win.c b/src/os_win.c
index 2b2b8eb..5344269 100644
--- a/src/os_win.c
+++ b/src/os_win.c
@@ -3631,15 +3631,16 @@ static SYSTEM_INFO winSysInfo;
** assert( winShmMutexHeld() );
** winShmLeaveMutex()
*/
+static sqlite3_mutex *winBigLock = 0;
static void winShmEnterMutex(void){
- sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
+ sqlite3_mutex_enter(winBigLock);
}
static void winShmLeaveMutex(void){
- sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
+ sqlite3_mutex_leave(winBigLock);
}
#ifndef NDEBUG
static int winShmMutexHeld(void) {
- return sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
+ return sqlite3_mutex_held(winBigLock);
}
#endif
@@ -6062,6 +6063,10 @@ int sqlite3_os_init(void){
sqlite3_vfs_register(&winLongPathNolockVfs, 0);
#endif
+#ifndef SQLITE_OMIT_WAL
+ winBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1);
+#endif
+
return SQLITE_OK;
}
@@ -6072,6 +6077,11 @@ int sqlite3_os_end(void){
sleepObj = NULL;
}
#endif
+
+#ifndef SQLITE_OMIT_WAL
+ winBigLock = 0;
+#endif
+
return SQLITE_OK;
}
diff --git a/src/pager.c b/src/pager.c
index 295cbe0..a5e1edf 100644
--- a/src/pager.c
+++ b/src/pager.c
@@ -699,7 +699,7 @@ struct Pager {
char *zJournal; /* Name of the journal file */
int (*xBusyHandler)(void*); /* Function to call when busy */
void *pBusyHandlerArg; /* Context argument for xBusyHandler */
- int aStat[3]; /* Total cache hits, misses and writes */
+ int aStat[4]; /* Total cache hits, misses, writes, spills */
#ifdef SQLITE_TEST
int nRead; /* Database pages read */
#endif
@@ -727,6 +727,7 @@ struct Pager {
#define PAGER_STAT_HIT 0
#define PAGER_STAT_MISS 1
#define PAGER_STAT_WRITE 2
+#define PAGER_STAT_SPILL 3
/*
** The following global variables hold counters used for
@@ -1213,7 +1214,7 @@ static int jrnlBufferSize(Pager *pPager){
#endif
#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE
- if( dc&SQLITE_IOCAP_BATCH_ATOMIC ){
+ if( pPager->dbSize>0 && (dc&SQLITE_IOCAP_BATCH_ATOMIC) ){
return -1;
}
#endif
@@ -2129,7 +2130,7 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){
rc = pager_truncate(pPager, pPager->dbSize);
}
- if( rc==SQLITE_OK && bCommit && isOpen(pPager->fd) ){
+ if( rc==SQLITE_OK && bCommit ){
rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_COMMIT_PHASETWO, 0);
if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK;
}
@@ -2948,9 +2949,7 @@ end_playback:
** assertion that the transaction counter was modified.
*/
#ifdef SQLITE_DEBUG
- if( pPager->fd->pMethods ){
- sqlite3OsFileControlHint(pPager->fd,SQLITE_FCNTL_DB_UNCHANGED,0);
- }
+ sqlite3OsFileControlHint(pPager->fd,SQLITE_FCNTL_DB_UNCHANGED,0);
#endif
/* If this playback is happening automatically as a result of an IO or
@@ -3703,20 +3702,18 @@ static int pagerOpentemp(
** retried. If it returns zero, then the SQLITE_BUSY error is
** returned to the caller of the pager API function.
*/
-void sqlite3PagerSetBusyhandler(
+void sqlite3PagerSetBusyHandler(
Pager *pPager, /* Pager object */
int (*xBusyHandler)(void *), /* Pointer to busy-handler function */
void *pBusyHandlerArg /* Argument to pass to xBusyHandler */
){
+ void **ap;
pPager->xBusyHandler = xBusyHandler;
pPager->pBusyHandlerArg = pBusyHandlerArg;
-
- if( isOpen(pPager->fd) ){
- void **ap = (void **)&pPager->xBusyHandler;
- assert( ((int(*)(void *))(ap[0]))==xBusyHandler );
- assert( ap[1]==pBusyHandlerArg );
- sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_BUSYHANDLER, (void *)ap);
- }
+ ap = (void **)&pPager->xBusyHandler;
+ assert( ((int(*)(void *))(ap[0]))==xBusyHandler );
+ assert( ap[1]==pBusyHandlerArg );
+ sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_BUSYHANDLER, (void *)ap);
}
/*
@@ -4102,6 +4099,30 @@ static void pagerFreeMapHdrs(Pager *pPager){
}
}
+/* Verify that the database file has not be deleted or renamed out from
+** under the pager. Return SQLITE_OK if the database is still where it ought
+** to be on disk. Return non-zero (SQLITE_READONLY_DBMOVED or some other error
+** code from sqlite3OsAccess()) if the database has gone missing.
+*/
+static int databaseIsUnmoved(Pager *pPager){
+ int bHasMoved = 0;
+ int rc;
+
+ if( pPager->tempFile ) return SQLITE_OK;
+ if( pPager->dbSize==0 ) return SQLITE_OK;
+ assert( pPager->zFilename && pPager->zFilename[0] );
+ rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_HAS_MOVED, &bHasMoved);
+ if( rc==SQLITE_NOTFOUND ){
+ /* If the HAS_MOVED file-control is unimplemented, assume that the file
+ ** has not been moved. That is the historical behavior of SQLite: prior to
+ ** version 3.8.3, it never checked */
+ rc = SQLITE_OK;
+ }else if( rc==SQLITE_OK && bHasMoved ){
+ rc = SQLITE_READONLY_DBMOVED;
+ }
+ return rc;
+}
+
/*
** Shutdown the page cache. Free all memory and close all files.
@@ -4118,8 +4139,7 @@ static void pagerFreeMapHdrs(Pager *pPager){
** to the caller.
*/
int sqlite3PagerClose(Pager *pPager, sqlite3 *db){
- u8 *pTmp = (u8 *)pPager->pTmpSpace;
-
+ u8 *pTmp = (u8*)pPager->pTmpSpace;
assert( db || pagerUseWal(pPager)==0 );
assert( assert_pager_state(pPager) );
disable_simulated_io_errors();
@@ -4128,11 +4148,17 @@ int sqlite3PagerClose(Pager *pPager, sqlite3 *db){
/* pPager->errCode = 0; */
pPager->exclusiveMode = 0;
#ifndef SQLITE_OMIT_WAL
- assert( db || pPager->pWal==0 );
- sqlite3WalClose(pPager->pWal, db, pPager->walSyncFlags, pPager->pageSize,
- (db && (db->flags & SQLITE_NoCkptOnClose) ? 0 : pTmp)
- );
- pPager->pWal = 0;
+ {
+ u8 *a = 0;
+ assert( db || pPager->pWal==0 );
+ if( db && 0==(db->flags & SQLITE_NoCkptOnClose)
+ && SQLITE_OK==databaseIsUnmoved(pPager)
+ ){
+ a = pTmp;
+ }
+ sqlite3WalClose(pPager->pWal, db, pPager->walSyncFlags, pPager->pageSize,a);
+ pPager->pWal = 0;
+ }
#endif
pager_reset(pPager);
if( MEMDB ){
@@ -4589,6 +4615,7 @@ static int pagerStress(void *p, PgHdr *pPg){
return SQLITE_OK;
}
+ pPager->aStat[PAGER_STAT_SPILL]++;
pPg->pDirty = 0;
if( pagerUseWal(pPager) ){
/* Write a single frame for this page to the log. */
@@ -4694,6 +4721,11 @@ int sqlite3PagerOpen(
int rc = SQLITE_OK; /* Return code */
int tempFile = 0; /* True for temp files (incl. in-memory files) */
int memDb = 0; /* True if this is an in-memory file */
+#ifdef SQLITE_ENABLE_DESERIALIZE
+ int memJM = 0; /* Memory journal mode */
+#else
+# define memJM 0
+#endif
int readOnly = 0; /* True if this is a read-only file */
int journalFileSize; /* Bytes to allocate for each journal fd */
char *zPathname = 0; /* Full path to database file */
@@ -4821,7 +4853,10 @@ int sqlite3PagerOpen(
int fout = 0; /* VFS flags returned by xOpen() */
rc = sqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd, vfsFlags, &fout);
assert( !memDb );
- readOnly = (fout&SQLITE_OPEN_READONLY);
+#ifdef SQLITE_ENABLE_DESERIALIZE
+ memJM = (fout&SQLITE_OPEN_MEMORY)!=0;
+#endif
+ readOnly = (fout&SQLITE_OPEN_READONLY)!=0;
/* If the file was successfully opened for read/write access,
** choose a default page size in case we have to create the
@@ -4952,7 +4987,7 @@ act_like_temp_file:
setSectorSize(pPager);
if( !useJournal ){
pPager->journalMode = PAGER_JOURNALMODE_OFF;
- }else if( memDb ){
+ }else if( memDb || memJM ){
pPager->journalMode = PAGER_JOURNALMODE_MEMORY;
}
/* pPager->xBusyHandler = 0; */
@@ -4967,30 +5002,6 @@ act_like_temp_file:
}
-/* Verify that the database file has not be deleted or renamed out from
-** under the pager. Return SQLITE_OK if the database is still were it ought
-** to be on disk. Return non-zero (SQLITE_READONLY_DBMOVED or some other error
-** code from sqlite3OsAccess()) if the database has gone missing.
-*/
-static int databaseIsUnmoved(Pager *pPager){
- int bHasMoved = 0;
- int rc;
-
- if( pPager->tempFile ) return SQLITE_OK;
- if( pPager->dbSize==0 ) return SQLITE_OK;
- assert( pPager->zFilename && pPager->zFilename[0] );
- rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_HAS_MOVED, &bHasMoved);
- if( rc==SQLITE_NOTFOUND ){
- /* If the HAS_MOVED file-control is unimplemented, assume that the file
- ** has not been moved. That is the historical behavior of SQLite: prior to
- ** version 3.8.3, it never checked */
- rc = SQLITE_OK;
- }else if( rc==SQLITE_OK && bHasMoved ){
- rc = SQLITE_READONLY_DBMOVED;
- }
- return rc;
-}
-
/*
** This function is called after transitioning from PAGER_UNLOCK to
@@ -5678,6 +5689,7 @@ void sqlite3PagerUnrefPageOne(DbPage *pPg){
assert( pPg->pgno==1 );
assert( (pPg->flags & PGHDR_MMAP)==0 ); /* Page1 is never memory mapped */
pPager = pPg->pPager;
+ sqlite3PagerResetLockTimeout(pPager);
sqlite3PcacheRelease(pPg);
pagerUnlockIfUnused(pPager);
}
@@ -6273,12 +6285,9 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){
*/
int sqlite3PagerSync(Pager *pPager, const char *zMaster){
int rc = SQLITE_OK;
-
- if( isOpen(pPager->fd) ){
- void *pArg = (void*)zMaster;
- rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_SYNC, pArg);
- if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK;
- }
+ void *pArg = (void*)zMaster;
+ rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_SYNC, pArg);
+ if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK;
if( rc==SQLITE_OK && !pPager->noSync ){
assert( !MEMDB );
rc = sqlite3OsSync(pPager->fd, pPager->syncFlags);
@@ -6499,8 +6508,9 @@ int sqlite3PagerCommitPhaseOne(
if( bBatch ){
if( rc==SQLITE_OK ){
rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_COMMIT_ATOMIC_WRITE, 0);
- }else{
- sqlite3OsFileControl(fd, SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE, 0);
+ }
+ if( rc!=SQLITE_OK ){
+ sqlite3OsFileControlHint(fd, SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE, 0);
}
}
@@ -6724,8 +6734,12 @@ int *sqlite3PagerStats(Pager *pPager){
#endif
/*
-** Parameter eStat must be either SQLITE_DBSTATUS_CACHE_HIT or
-** SQLITE_DBSTATUS_CACHE_MISS. Before returning, *pnVal is incremented by the
+** Parameter eStat must be one of SQLITE_DBSTATUS_CACHE_HIT, _MISS, _WRITE,
+** or _WRITE+1. The SQLITE_DBSTATUS_CACHE_WRITE+1 case is a translation
+** of SQLITE_DBSTATUS_CACHE_SPILL. The _SPILL case is not contiguous because
+** it was added later.
+**
+** Before returning, *pnVal is incremented by the
** current cache hit or miss count, according to the value of eStat. If the
** reset parameter is non-zero, the cache hit or miss count is zeroed before
** returning.
@@ -6735,15 +6749,18 @@ void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, int *pnVal){
assert( eStat==SQLITE_DBSTATUS_CACHE_HIT
|| eStat==SQLITE_DBSTATUS_CACHE_MISS
|| eStat==SQLITE_DBSTATUS_CACHE_WRITE
+ || eStat==SQLITE_DBSTATUS_CACHE_WRITE+1
);
assert( SQLITE_DBSTATUS_CACHE_HIT+1==SQLITE_DBSTATUS_CACHE_MISS );
assert( SQLITE_DBSTATUS_CACHE_HIT+2==SQLITE_DBSTATUS_CACHE_WRITE );
- assert( PAGER_STAT_HIT==0 && PAGER_STAT_MISS==1 && PAGER_STAT_WRITE==2 );
+ assert( PAGER_STAT_HIT==0 && PAGER_STAT_MISS==1
+ && PAGER_STAT_WRITE==2 && PAGER_STAT_SPILL==3 );
- *pnVal += pPager->aStat[eStat - SQLITE_DBSTATUS_CACHE_HIT];
+ eStat -= SQLITE_DBSTATUS_CACHE_HIT;
+ *pnVal += pPager->aStat[eStat];
if( reset ){
- pPager->aStat[eStat - SQLITE_DBSTATUS_CACHE_HIT] = 0;
+ pPager->aStat[eStat] = 0;
}
}
@@ -6947,6 +6964,16 @@ sqlite3_file *sqlite3PagerFile(Pager *pPager){
return pPager->fd;
}
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+/*
+** Reset the lock timeout for pager.
+*/
+void sqlite3PagerResetLockTimeout(Pager *pPager){
+ int x = 0;
+ sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_LOCK_TIMEOUT, &x);
+}
+#endif
+
/*
** Return the file handle for the journal file (if it exists).
** This will be either the rollback journal or the WAL file.
@@ -7407,6 +7434,7 @@ int sqlite3PagerCheckpoint(
pPager->walSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace,
pnLog, pnCkpt
);
+ sqlite3PagerResetLockTimeout(pPager);
}
return rc;
}
diff --git a/src/pager.h b/src/pager.h
index 126267b..730e366 100644
--- a/src/pager.h
+++ b/src/pager.h
@@ -126,7 +126,7 @@ int sqlite3PagerClose(Pager *pPager, sqlite3*);
int sqlite3PagerReadFileheader(Pager*, int, unsigned char*);
/* Functions used to configure a Pager object. */
-void sqlite3PagerSetBusyhandler(Pager*, int(*)(void *), void *);
+void sqlite3PagerSetBusyHandler(Pager*, int(*)(void *), void *);
int sqlite3PagerSetPagesize(Pager*, u32*, int);
#ifdef SQLITE_HAS_CODEC
void sqlite3PagerAlignReserve(Pager*,Pager*);
@@ -212,6 +212,11 @@ int sqlite3PagerIsMemdb(Pager*);
void sqlite3PagerCacheStat(Pager *, int, int, int *);
void sqlite3PagerClearCache(Pager*);
int sqlite3SectorSize(sqlite3_file *);
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+void sqlite3PagerResetLockTimeout(Pager *pPager);
+#else
+# define sqlite3PagerResetLockTimeout(X)
+#endif
/* Functions used to truncate the database file. */
void sqlite3PagerTruncateImage(Pager*,Pgno);
diff --git a/src/parse.y b/src/parse.y
index d9cf1cb..6b31e4c 100644
--- a/src/parse.y
+++ b/src/parse.y
@@ -313,6 +313,10 @@ ccons ::= DEFAULT MINUS(A) term(X) scanpt(Z). {
}
ccons ::= DEFAULT scanpt id(X). {
Expr *p = tokenExpr(pParse, TK_STRING, X);
+ if( p ){
+ sqlite3ExprIdToTrueFalse(p);
+ testcase( p->op==TK_TRUEFALSE && sqlite3ExprTruthValue(p) );
+ }
sqlite3AddDefaultValue(pParse,p,X.z,X.z+X.n);
}
@@ -519,8 +523,7 @@ oneselect(A) ::= SELECT(S) distinct(D) selcollist(W) from(X) where_opt(Y)
if( A!=0 ){
const char *z = s.z+6;
int i;
- sqlite3_snprintf(sizeof(A->zSelName), A->zSelName, "#%d",
- ++pParse->nSelect);
+ sqlite3_snprintf(sizeof(A->zSelName), A->zSelName,"#%d",++pParse->nSelect);
while( z[0]==' ' ) z++;
if( z[0]=='/' && z[1]=='*' ){
z += 2;
diff --git a/src/pcache.c b/src/pcache.c
index 4b2e481..41fb031 100644
--- a/src/pcache.c
+++ b/src/pcache.c
@@ -431,7 +431,7 @@ int sqlite3PcacheFetchStress(
sqlite3_log(SQLITE_FULL,
"spill page %d making room for %d - cache used: %d/%d",
pPg->pgno, pgno,
- sqlite3GlobalConfig.pcache.xPagecount(pCache->pCache),
+ sqlite3GlobalConfig.pcache2.xPagecount(pCache->pCache),
numberOfCachePages(pCache));
#endif
pcacheTrace(("%p.SPILL %d\n",pCache,pPg->pgno));
diff --git a/src/prepare.c b/src/prepare.c
index 65a4afc..c1bd20f 100644
--- a/src/prepare.c
+++ b/src/prepare.c
@@ -29,7 +29,7 @@ static void corruptSchema(
char *z;
if( zObj==0 ) zObj = "?";
z = sqlite3MPrintf(db, "malformed database schema (%s)", zObj);
- if( zExtra ) z = sqlite3MPrintf(db, "%z - %s", z, zExtra);
+ if( zExtra && zExtra[0] ) z = sqlite3MPrintf(db, "%z - %s", z, zExtra);
sqlite3DbFree(db, *pData->pzErrMsg);
*pData->pzErrMsg = z;
}
diff --git a/src/printf.c b/src/printf.c
index ca8d26e..fcbd7fd 100644
--- a/src/printf.c
+++ b/src/printf.c
@@ -206,6 +206,11 @@ void sqlite3VXPrintf(
PrintfArguments *pArgList = 0; /* Arguments for SQLITE_PRINTF_SQLFUNC */
char buf[etBUFSIZE]; /* Conversion buffer */
+ /* pAccum never starts out with an empty buffer that was obtained from
+ ** malloc(). This precondition is required by the mprintf("%z...")
+ ** optimization. */
+ assert( pAccum->nChar>0 || (pAccum->printfFlags&SQLITE_PRINTF_MALLOCED)==0 );
+
bufpt = 0;
if( (pAccum->printfFlags & SQLITE_PRINTF_SQLFUNC)!=0 ){
pArgList = va_arg(ap, PrintfArguments*);
@@ -624,9 +629,38 @@ void sqlite3VXPrintf(
case etCHARX:
if( bArgList ){
bufpt = getTextArg(pArgList);
- c = bufpt ? bufpt[0] : 0;
+ length = 1;
+ if( bufpt ){
+ buf[0] = c = *(bufpt++);
+ if( (c&0xc0)==0xc0 ){
+ while( length<4 && (bufpt[0]&0xc0)==0x80 ){
+ buf[length++] = *(bufpt++);
+ }
+ }
+ }else{
+ buf[0] = 0;
+ }
}else{
- c = va_arg(ap,int);
+ unsigned int ch = va_arg(ap,unsigned int);
+ if( ch<0x00080 ){
+ buf[0] = ch & 0xff;
+ length = 1;
+ }else if( ch<0x00800 ){
+ buf[0] = 0xc0 + (u8)((ch>>6)&0x1f);
+ buf[1] = 0x80 + (u8)(ch & 0x3f);
+ length = 2;
+ }else if( ch<0x10000 ){
+ buf[0] = 0xe0 + (u8)((ch>>12)&0x0f);
+ buf[1] = 0x80 + (u8)((ch>>6) & 0x3f);
+ buf[2] = 0x80 + (u8)(ch & 0x3f);
+ length = 3;
+ }else{
+ buf[0] = 0xf0 + (u8)((ch>>18) & 0x07);
+ buf[1] = 0x80 + (u8)((ch>>12) & 0x3f);
+ buf[2] = 0x80 + (u8)((ch>>6) & 0x3f);
+ buf[3] = 0x80 + (u8)(ch & 0x3f);
+ length = 4;
+ }
}
if( precision>1 ){
width -= precision-1;
@@ -634,12 +668,13 @@ void sqlite3VXPrintf(
sqlite3AppendChar(pAccum, width-1, ' ');
width = 0;
}
- sqlite3AppendChar(pAccum, precision-1, c);
+ while( precision-- > 1 ){
+ sqlite3StrAccumAppend(pAccum, buf, length);
+ }
}
- length = 1;
- buf[0] = c;
bufpt = buf;
- break;
+ flag_altform2 = 1;
+ goto adjust_width_for_utf8;
case etSTRING:
case etDYNSTRING:
if( bArgList ){
@@ -651,17 +686,45 @@ void sqlite3VXPrintf(
if( bufpt==0 ){
bufpt = "";
}else if( xtype==etDYNSTRING ){
+ if( pAccum->nChar==0 && pAccum->mxAlloc && width==0 && precision<0 ){
+ /* Special optimization for sqlite3_mprintf("%z..."):
+ ** Extend an existing memory allocation rather than creating
+ ** a new one. */
+ assert( (pAccum->printfFlags&SQLITE_PRINTF_MALLOCED)==0 );
+ pAccum->zText = bufpt;
+ pAccum->nAlloc = sqlite3DbMallocSize(pAccum->db, bufpt);
+ pAccum->nChar = 0x7fffffff & (int)strlen(bufpt);
+ pAccum->printfFlags |= SQLITE_PRINTF_MALLOCED;
+ length = 0;
+ break;
+ }
zExtra = bufpt;
}
if( precision>=0 ){
- for(length=0; length<precision && bufpt[length]; length++){}
+ if( flag_altform2 ){
+ /* Set length to the number of bytes needed in order to display
+ ** precision characters */
+ unsigned char *z = (unsigned char*)bufpt;
+ while( precision-- > 0 && z[0] ){
+ SQLITE_SKIP_UTF8(z);
+ }
+ length = (int)(z - (unsigned char*)bufpt);
+ }else{
+ for(length=0; length<precision && bufpt[length]; length++){}
+ }
}else{
length = 0x7fffffff & (int)strlen(bufpt);
}
+ adjust_width_for_utf8:
+ if( flag_altform2 && width>0 ){
+ /* Adjust width to account for extra bytes in UTF-8 characters */
+ int ii = length - 1;
+ while( ii>=0 ) if( (bufpt[ii--] & 0xc0)==0x80 ) width++;
+ }
break;
- case etSQLESCAPE: /* Escape ' characters */
- case etSQLESCAPE2: /* Escape ' and enclose in '...' */
- case etSQLESCAPE3: { /* Escape " characters */
+ case etSQLESCAPE: /* %q: Escape ' characters */
+ case etSQLESCAPE2: /* %Q: Escape ' and enclose in '...' */
+ case etSQLESCAPE3: { /* %w: Escape " characters */
int i, j, k, n, isnull;
int needQuote;
char ch;
@@ -675,9 +738,17 @@ void sqlite3VXPrintf(
}
isnull = escarg==0;
if( isnull ) escarg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)");
+ /* For %q, %Q, and %w, the precision is the number of byte (or
+ ** characters if the ! flags is present) to use from the input.
+ ** Because of the extra quoting characters inserted, the number
+ ** of output characters may be larger than the precision.
+ */
k = precision;
for(i=n=0; k!=0 && (ch=escarg[i])!=0; i++, k--){
if( ch==q ) n++;
+ if( flag_altform2 && (ch&0xc0)==0xc0 ){
+ while( (escarg[i+1]&0xc0)==0x80 ){ i++; }
+ }
}
needQuote = !isnull && xtype==etSQLESCAPE2;
n += i + 3;
@@ -700,10 +771,7 @@ void sqlite3VXPrintf(
if( needQuote ) bufpt[j++] = q;
bufpt[j] = 0;
length = j;
- /* The precision in %q and %Q means how many input characters to
- ** consume, not the length of the output...
- ** if( precision>=0 && precision<length ) length = precision; */
- break;
+ goto adjust_width_for_utf8;
}
case etTOKEN: {
Token *pToken;
@@ -742,7 +810,10 @@ void sqlite3VXPrintf(
/*
** The text of the conversion is pointed to by "bufpt" and is
** "length" characters long. The field width is "width". Do
- ** the output.
+ ** the output. Both length and width are in bytes, not characters,
+ ** at this point. If the "!" flag was present on string conversions
+ ** indicating that width and precision should be expressed in characters,
+ ** then the values have been translated prior to reaching this point.
*/
width -= length;
if( width>0 ){
diff --git a/src/resolve.c b/src/resolve.c
index f735fff..7ae49bd 100644
--- a/src/resolve.c
+++ b/src/resolve.c
@@ -431,10 +431,16 @@ static int lookupName(
** Because no reference was made to outer contexts, the pNC->nRef
** fields are not changed in any context.
*/
- if( cnt==0 && zTab==0 && ExprHasProperty(pExpr,EP_DblQuoted) ){
- pExpr->op = TK_STRING;
- pExpr->pTab = 0;
- return WRC_Prune;
+ if( cnt==0 && zTab==0 ){
+ assert( pExpr->op==TK_ID );
+ if( ExprHasProperty(pExpr,EP_DblQuoted) ){
+ pExpr->op = TK_STRING;
+ pExpr->pTab = 0;
+ return WRC_Prune;
+ }
+ if( sqlite3ExprIdToTrueFalse(pExpr) ){
+ return WRC_Prune;
+ }
}
/*
@@ -783,15 +789,30 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
notValid(pParse, pNC, "parameters", NC_IsCheck|NC_PartIdx|NC_IdxExpr);
break;
}
+ case TK_IS:
+ case TK_ISNOT: {
+ Expr *pRight;
+ assert( !ExprHasProperty(pExpr, EP_Reduced) );
+ /* Handle special cases of "x IS TRUE", "x IS FALSE", "x IS NOT TRUE",
+ ** and "x IS NOT FALSE". */
+ if( (pRight = pExpr->pRight)->op==TK_ID ){
+ int rc = resolveExprStep(pWalker, pRight);
+ if( rc==WRC_Abort ) return WRC_Abort;
+ if( pRight->op==TK_TRUEFALSE ){
+ pExpr->op2 = pExpr->op;
+ pExpr->op = TK_TRUTH;
+ return WRC_Continue;
+ }
+ }
+ /* Fall thru */
+ }
case TK_BETWEEN:
case TK_EQ:
case TK_NE:
case TK_LT:
case TK_LE:
case TK_GT:
- case TK_GE:
- case TK_IS:
- case TK_ISNOT: {
+ case TK_GE: {
int nLeft, nRight;
if( pParse->db->mallocFailed ) break;
assert( pExpr->pLeft!=0 );
diff --git a/src/select.c b/src/select.c
index c3cb408..fa804f1 100644
--- a/src/select.c
+++ b/src/select.c
@@ -21,8 +21,7 @@
/***/ int sqlite3SelectTrace = 0;
# define SELECTTRACE(K,P,S,X) \
if(sqlite3SelectTrace&(K)) \
- sqlite3DebugPrintf("%*s%s.%p: ",(P)->nSelectIndent*2-2,"",\
- (S)->zSelName,(S)),\
+ sqlite3DebugPrintf("%s/%p: ",(S)->zSelName,(S)),\
sqlite3DebugPrintf X
#else
# define SELECTTRACE(K,P,S,X)
@@ -383,6 +382,29 @@ static void setJoinExpr(Expr *p, int iTable){
}
}
+/* Undo the work of setJoinExpr(). In the expression tree p, convert every
+** term that is marked with EP_FromJoin and iRightJoinTable==iTable into
+** an ordinary term that omits the EP_FromJoin mark.
+**
+** This happens when a LEFT JOIN is simplified into an ordinary JOIN.
+*/
+static void unsetJoinExpr(Expr *p, int iTable){
+ while( p ){
+ if( ExprHasProperty(p, EP_FromJoin)
+ && (iTable<0 || p->iRightJoinTable==iTable) ){
+ ExprClearProperty(p, EP_FromJoin);
+ }
+ if( p->op==TK_FUNCTION && p->x.pList ){
+ int i;
+ for(i=0; i<p->x.pList->nExpr; i++){
+ unsetJoinExpr(p->x.pList->a[i].pExpr, iTable);
+ }
+ }
+ unsetJoinExpr(p->pLeft, iTable);
+ p = p->pRight;
+ }
+}
+
/*
** This routine processes the join information for a SELECT statement.
** ON and USING clauses are converted into extra terms of the WHERE clause.
@@ -1266,12 +1288,15 @@ static void generateSortTail(
iSortTab = iTab;
bSeq = 1;
}
- for(i=0, iCol=nKey+bSeq; i<nSortData; i++){
+ for(i=0, iCol=nKey+bSeq-1; i<nSortData; i++){
+ if( aOutEx[i].u.x.iOrderByCol==0 ) iCol++;
+ }
+ for(i=nSortData-1; i>=0; i--){
int iRead;
if( aOutEx[i].u.x.iOrderByCol ){
iRead = aOutEx[i].u.x.iOrderByCol-1;
}else{
- iRead = iCol++;
+ iRead = iCol--;
}
sqlite3VdbeAddOp3(v, OP_Column, iSortTab, iRead, regRow+i);
VdbeComment((v, "%s", aOutEx[i].zName ? aOutEx[i].zName : aOutEx[i].zSpan));
@@ -3748,7 +3773,6 @@ static int flattenSubquery(
pOrderBy->a[i].u.x.iOrderByCol = 0;
}
assert( pParent->pOrderBy==0 );
- assert( pSub->pPrior==0 );
pParent->pOrderBy = pOrderBy;
pSub->pOrderBy = 0;
}
@@ -3832,12 +3856,22 @@ static int flattenSubquery(
** (3) The inner query has a LIMIT clause (since the changes to the WHERE
** close would change the meaning of the LIMIT).
**
-** (4) The inner query is the right operand of a LEFT JOIN. (The caller
-** enforces this restriction since this routine does not have enough
-** information to know.)
+** (4) The inner query is the right operand of a LEFT JOIN and the
+** expression to be pushed down does not come from the ON clause
+** on that LEFT JOIN.
**
** (5) The WHERE clause expression originates in the ON or USING clause
-** of a LEFT JOIN.
+** of a LEFT JOIN where iCursor is not the right-hand table of that
+** left join. An example:
+**
+** SELECT *
+** FROM (SELECT 1 AS a1 UNION ALL SELECT 2) AS aa
+** JOIN (SELECT 1 AS b2 UNION ALL SELECT 2) AS bb ON (a1=b2)
+** LEFT JOIN (SELECT 8 AS c3 UNION ALL SELECT 9) AS cc ON (b2=2);
+**
+** The correct answer is three rows: (1,1,NULL),(2,2,8),(2,2,9).
+** But if the (b2=2) term were to be pushed down into the bb subquery,
+** then the (1,1,NULL) row would be suppressed.
**
** Return 0 if no changes are made and non-zero if one or more WHERE clause
** terms are duplicated into the subquery.
@@ -3846,7 +3880,8 @@ static int pushDownWhereTerms(
Parse *pParse, /* Parse context (for malloc() and error reporting) */
Select *pSubq, /* The subquery whose WHERE clause is to be augmented */
Expr *pWhere, /* The WHERE clause of the outer query */
- int iCursor /* Cursor number of the subquery */
+ int iCursor, /* Cursor number of the subquery */
+ int isLeftJoin /* True if pSubq is the right term of a LEFT JOIN */
){
Expr *pNew;
int nChng = 0;
@@ -3870,15 +3905,25 @@ static int pushDownWhereTerms(
return 0; /* restriction (3) */
}
while( pWhere->op==TK_AND ){
- nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight, iCursor);
+ nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight,
+ iCursor, isLeftJoin);
pWhere = pWhere->pLeft;
}
- if( ExprHasProperty(pWhere,EP_FromJoin) ) return 0; /* restriction (5) */
+ if( isLeftJoin
+ && (ExprHasProperty(pWhere,EP_FromJoin)==0
+ || pWhere->iRightJoinTable!=iCursor)
+ ){
+ return 0; /* restriction (4) */
+ }
+ if( ExprHasProperty(pWhere,EP_FromJoin) && pWhere->iRightJoinTable!=iCursor ){
+ return 0; /* restriction (5) */
+ }
if( sqlite3ExprIsTableConstant(pWhere, iCursor) ){
nChng++;
while( pSubq ){
SubstContext x;
pNew = sqlite3ExprDup(pParse->db, pWhere, 0);
+ unsetJoinExpr(pNew, -1);
x.pParse = pParse;
x.iTable = iCursor;
x.iNewTable = iCursor;
@@ -4911,14 +4956,6 @@ static void explainSimpleCount(
#endif
/*
-** Context object for havingToWhereExprCb().
-*/
-struct HavingToWhereCtx {
- Expr **ppWhere;
- ExprList *pGroupBy;
-};
-
-/*
** sqlite3WalkExpr() callback used by havingToWhere().
**
** If the node passed to the callback is a TK_AND node, return
@@ -4931,15 +4968,16 @@ struct HavingToWhereCtx {
*/
static int havingToWhereExprCb(Walker *pWalker, Expr *pExpr){
if( pExpr->op!=TK_AND ){
- struct HavingToWhereCtx *p = pWalker->u.pHavingCtx;
- if( sqlite3ExprIsConstantOrGroupBy(pWalker->pParse, pExpr, p->pGroupBy) ){
+ Select *pS = pWalker->u.pSelect;
+ if( sqlite3ExprIsConstantOrGroupBy(pWalker->pParse, pExpr, pS->pGroupBy) ){
sqlite3 *db = pWalker->pParse->db;
Expr *pNew = sqlite3ExprAlloc(db, TK_INTEGER, &sqlite3IntTokens[1], 0);
if( pNew ){
- Expr *pWhere = *(p->ppWhere);
+ Expr *pWhere = pS->pWhere;
SWAP(Expr, *pNew, *pExpr);
pNew = sqlite3ExprAnd(db, pWhere, pNew);
- *(p->ppWhere) = pNew;
+ pS->pWhere = pNew;
+ pWalker->eCode = 1;
}
}
return WRC_Prune;
@@ -4962,23 +5000,19 @@ static int havingToWhereExprCb(Walker *pWalker, Expr *pExpr){
** entirely of constants and expressions that are also GROUP BY terms that
** use the "BINARY" collation sequence.
*/
-static void havingToWhere(
- Parse *pParse,
- ExprList *pGroupBy,
- Expr *pHaving,
- Expr **ppWhere
-){
- struct HavingToWhereCtx sCtx;
+static void havingToWhere(Parse *pParse, Select *p){
Walker sWalker;
-
- sCtx.ppWhere = ppWhere;
- sCtx.pGroupBy = pGroupBy;
-
memset(&sWalker, 0, sizeof(sWalker));
sWalker.pParse = pParse;
sWalker.xExprCallback = havingToWhereExprCb;
- sWalker.u.pHavingCtx = &sCtx;
- sqlite3WalkExpr(&sWalker, pHaving);
+ sWalker.u.pSelect = p;
+ sqlite3WalkExpr(&sWalker, p->pHaving);
+#if SELECTTRACE_ENABLED
+ if( sWalker.eCode && (sqlite3SelectTrace & 0x100)!=0 ){
+ SELECTTRACE(0x100,pParse,p,("Move HAVING terms into WHERE:\n"));
+ sqlite3TreeViewSelect(0, p, 0);
+ }
+#endif
}
/*
@@ -5139,7 +5173,6 @@ int sqlite3Select(
if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1;
memset(&sAggInfo, 0, sizeof(sAggInfo));
#if SELECTTRACE_ENABLED
- pParse->nSelectIndent++;
SELECTTRACE(1,pParse,p, ("begin processing:\n"));
if( sqlite3SelectTrace & 0x100 ){
sqlite3TreeViewSelect(0, p, 0);
@@ -5185,13 +5218,29 @@ int sqlite3Select(
generateColumnNames(pParse, p);
}
- /* Try to flatten subqueries in the FROM clause up into the main query
+ /* Try to various optimizations (flattening subqueries, and strength
+ ** reduction of join operators) in the FROM clause up into the main query
*/
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
for(i=0; !p->pPrior && i<pTabList->nSrc; i++){
struct SrcList_item *pItem = &pTabList->a[i];
Select *pSub = pItem->pSelect;
Table *pTab = pItem->pTab;
+
+ /* Convert LEFT JOIN into JOIN if there are terms of the right table
+ ** of the LEFT JOIN used in the WHERE clause.
+ */
+ if( (pItem->fg.jointype & JT_LEFT)!=0
+ && sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor)
+ && OptimizationEnabled(db, SQLITE_SimplifyJoin)
+ ){
+ SELECTTRACE(0x100,pParse,p,
+ ("LEFT-JOIN simplifies to JOIN on term %d\n",i));
+ pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER);
+ unsetJoinExpr(p->pWhere, pItem->iCursor);
+ }
+
+ /* No futher action if this term of the FROM clause is no a subquery */
if( pSub==0 ) continue;
/* Catch mismatch in the declared columns of a view and the number of
@@ -5260,7 +5309,6 @@ int sqlite3Select(
explainSetInteger(pParse->iSelectId, iRestoreSelectId);
#if SELECTTRACE_ENABLED
SELECTTRACE(1,pParse,p,("end compound-select processing\n"));
- pParse->nSelectIndent--;
#endif
return rc;
}
@@ -5333,8 +5381,9 @@ int sqlite3Select(
/* Make copies of constant WHERE-clause terms in the outer query down
** inside the subquery. This can help the subquery to run more efficiently.
*/
- if( (pItem->fg.jointype & JT_OUTER)==0
- && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem->iCursor)
+ if( OptimizationEnabled(db, SQLITE_PushDown)
+ && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem->iCursor,
+ (pItem->fg.jointype & JT_OUTER)!=0)
){
#if SELECTTRACE_ENABLED
if( sqlite3SelectTrace & 0x100 ){
@@ -5342,6 +5391,8 @@ int sqlite3Select(
sqlite3TreeViewSelect(0, p, 0);
}
#endif
+ }else{
+ SELECTTRACE(0x100,pParse,p,("Push-down not possible\n"));
}
zSavedAuthContext = pParse->zAuthContext;
@@ -5544,6 +5595,7 @@ int sqlite3Select(
wctrlFlags |= p->selFlags & SF_FixedLimit;
/* Begin the database scan. */
+ SELECTTRACE(1,pParse,p,("WhereBegin\n"));
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy,
p->pEList, wctrlFlags, p->nSelectRow);
if( pWInfo==0 ) goto select_end;
@@ -5645,7 +5697,9 @@ int sqlite3Select(
if( pHaving ){
if( pGroupBy ){
assert( pWhere==p->pWhere );
- havingToWhere(pParse, pGroupBy, pHaving, &p->pWhere);
+ assert( pHaving==p->pHaving );
+ assert( pGroupBy==p->pGroupBy );
+ havingToWhere(pParse, p);
pWhere = p->pWhere;
}
sqlite3ExprAnalyzeAggregates(&sNC, pHaving);
@@ -5732,6 +5786,7 @@ int sqlite3Select(
** in the right order to begin with.
*/
sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset);
+ SELECTTRACE(1,pParse,p,("WhereBegin\n"));
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, 0,
WHERE_GROUPBY | (orderByGrp ? WHERE_SORTBYGROUP : 0), 0
);
@@ -5987,6 +6042,7 @@ int sqlite3Select(
assert( minMaxFlag==WHERE_ORDERBY_NORMAL || pMinMaxOrderBy!=0 );
assert( pMinMaxOrderBy==0 || pMinMaxOrderBy->nExpr==1 );
+ SELECTTRACE(1,pParse,p,("WhereBegin\n"));
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pMinMaxOrderBy,
0, minMaxFlag, 0);
if( pWInfo==0 ){
@@ -6042,7 +6098,6 @@ select_end:
sqlite3DbFree(db, sAggInfo.aFunc);
#if SELECTTRACE_ENABLED
SELECTTRACE(1,pParse,p,("end processing\n"));
- pParse->nSelectIndent--;
#endif
return rc;
}
diff --git a/src/shell.c.in b/src/shell.c.in
index d9c8705..2a90eae 100644
--- a/src/shell.c.in
+++ b/src/shell.c.in
@@ -132,6 +132,9 @@ typedef unsigned char u8;
# ifndef access
# define access(f,m) _access((f),(m))
# endif
+# ifndef unlink
+# define unlink _unlink
+# endif
# undef popen
# define popen _popen
# undef pclose
@@ -1069,6 +1072,7 @@ struct ShellState {
#define SHELL_OPEN_NORMAL 1 /* Normal database file */
#define SHELL_OPEN_APPENDVFS 2 /* Use appendvfs */
#define SHELL_OPEN_ZIPFILE 3 /* Use the zipfile virtual table */
+#define SHELL_OPEN_READONLY 4 /* Open a normal database read-only */
/*
** These are the allowed shellFlgs values
@@ -1175,6 +1179,7 @@ static void shellPutsFunc(
**
** Also throw an error if the EDITOR program returns a non-zero exit code.
*/
+#ifndef SQLITE_NOHAVE_SYSTEM
static void editFunc(
sqlite3_context *context,
int argc,
@@ -1272,9 +1277,10 @@ static void editFunc(
goto edit_func_end;
}
if( bBin ){
- sqlite3_result_blob(context, p, sz, sqlite3_free);
+ sqlite3_result_blob64(context, p, sz, sqlite3_free);
}else{
- sqlite3_result_text(context, (const char*)p, sz, sqlite3_free);
+ sqlite3_result_text64(context, (const char*)p, sz,
+ sqlite3_free, SQLITE_UTF8);
}
p = 0;
@@ -1284,6 +1290,7 @@ edit_func_end:
sqlite3_free(zTempFile);
sqlite3_free(p);
}
+#endif /* SQLITE_NOHAVE_SYSTEM */
/*
** Save or restore the current output mode
@@ -2269,29 +2276,54 @@ static int display_stats(
){
int iCur;
int iHiwtr;
+ FILE *out;
+ if( pArg==0 || pArg->out==0 ) return 0;
+ out = pArg->out;
+
+ if( pArg->pStmt && (pArg->statsOn & 2) ){
+ int nCol, i, x;
+ sqlite3_stmt *pStmt = pArg->pStmt;
+ char z[100];
+ nCol = sqlite3_column_count(pStmt);
+ raw_printf(out, "%-36s %d\n", "Number of output columns:", nCol);
+ for(i=0; i<nCol; i++){
+ sqlite3_snprintf(sizeof(z),z,"Column %d %nname:", i, &x);
+ utf8_printf(out, "%-36s %s\n", z, sqlite3_column_name(pStmt,i));
+#ifndef SQLITE_OMIT_DECLTYPE
+ sqlite3_snprintf(30, z+x, "declared type:");
+ utf8_printf(out, "%-36s %s\n", z, sqlite3_column_decltype(pStmt, i));
+#endif
+#ifdef SQLITE_ENABLE_COLUMN_METADATA
+ sqlite3_snprintf(30, z+x, "database name:");
+ utf8_printf(out, "%-36s %s\n", z, sqlite3_column_database_name(pStmt,i));
+ sqlite3_snprintf(30, z+x, "table name:");
+ utf8_printf(out, "%-36s %s\n", z, sqlite3_column_table_name(pStmt,i));
+ sqlite3_snprintf(30, z+x, "origin name:");
+ utf8_printf(out, "%-36s %s\n", z, sqlite3_column_origin_name(pStmt,i));
+#endif
+ }
+ }
- if( pArg && pArg->out ){
- displayStatLine(pArg, "Memory Used:",
- "%lld (max %lld) bytes", SQLITE_STATUS_MEMORY_USED, bReset);
- displayStatLine(pArg, "Number of Outstanding Allocations:",
- "%lld (max %lld)", SQLITE_STATUS_MALLOC_COUNT, bReset);
- if( pArg->shellFlgs & SHFLG_Pagecache ){
- displayStatLine(pArg, "Number of Pcache Pages Used:",
- "%lld (max %lld) pages", SQLITE_STATUS_PAGECACHE_USED, bReset);
- }
- displayStatLine(pArg, "Number of Pcache Overflow Bytes:",
- "%lld (max %lld) bytes", SQLITE_STATUS_PAGECACHE_OVERFLOW, bReset);
- displayStatLine(pArg, "Largest Allocation:",
- "%lld bytes", SQLITE_STATUS_MALLOC_SIZE, bReset);
- displayStatLine(pArg, "Largest Pcache Allocation:",
- "%lld bytes", SQLITE_STATUS_PAGECACHE_SIZE, bReset);
+ displayStatLine(pArg, "Memory Used:",
+ "%lld (max %lld) bytes", SQLITE_STATUS_MEMORY_USED, bReset);
+ displayStatLine(pArg, "Number of Outstanding Allocations:",
+ "%lld (max %lld)", SQLITE_STATUS_MALLOC_COUNT, bReset);
+ if( pArg->shellFlgs & SHFLG_Pagecache ){
+ displayStatLine(pArg, "Number of Pcache Pages Used:",
+ "%lld (max %lld) pages", SQLITE_STATUS_PAGECACHE_USED, bReset);
+ }
+ displayStatLine(pArg, "Number of Pcache Overflow Bytes:",
+ "%lld (max %lld) bytes", SQLITE_STATUS_PAGECACHE_OVERFLOW, bReset);
+ displayStatLine(pArg, "Largest Allocation:",
+ "%lld bytes", SQLITE_STATUS_MALLOC_SIZE, bReset);
+ displayStatLine(pArg, "Largest Pcache Allocation:",
+ "%lld bytes", SQLITE_STATUS_PAGECACHE_SIZE, bReset);
#ifdef YYTRACKMAXSTACKDEPTH
- displayStatLine(pArg, "Deepest Parser Stack:",
- "%lld (max %lld)", SQLITE_STATUS_PARSER_STACK, bReset);
+ displayStatLine(pArg, "Deepest Parser Stack:",
+ "%lld (max %lld)", SQLITE_STATUS_PARSER_STACK, bReset);
#endif
- }
- if( pArg && pArg->out && db ){
+ if( db ){
if( pArg->shellFlgs & SHFLG_Lookaside ){
iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED,
@@ -2326,6 +2358,9 @@ static int display_stats(
sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1);
raw_printf(pArg->out, "Page cache writes: %d\n", iCur);
iHiwtr = iCur = -1;
+ sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1);
+ raw_printf(pArg->out, "Page cache spills: %d\n", iCur);
+ iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset);
raw_printf(pArg->out, "Schema Heap Usage: %d bytes\n",
iCur);
@@ -2335,7 +2370,7 @@ static int display_stats(
iCur);
}
- if( pArg && pArg->out && db && pArg->pStmt ){
+ if( pArg->pStmt ){
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP,
bReset);
raw_printf(pArg->out, "Fullscan Steps: %d\n", iCur);
@@ -2345,6 +2380,12 @@ static int display_stats(
raw_printf(pArg->out, "Autoindex Inserts: %d\n", iCur);
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
raw_printf(pArg->out, "Virtual Machine Steps: %d\n", iCur);
+ iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_REPREPARE, bReset);
+ raw_printf(pArg->out, "Reprepare operations: %d\n", iCur);
+ iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_RUN, bReset);
+ raw_printf(pArg->out, "Number of times run: %d\n", iCur);
+ iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_MEMUSED, bReset);
+ raw_printf(pArg->out, "Memory used by prepared stmt: %d\n", iCur);
}
#ifdef __linux__
@@ -2560,8 +2601,7 @@ static void restore_debug_trace_modes(void){
*/
static void exec_prepared_stmt(
ShellState *pArg, /* Pointer to ShellState */
- sqlite3_stmt *pStmt, /* Statment to run */
- int (*xCallback)(void*,int,char**,char**,int*) /* Callback function */
+ sqlite3_stmt *pStmt /* Statment to run */
){
int rc;
@@ -2571,54 +2611,47 @@ static void exec_prepared_stmt(
rc = sqlite3_step(pStmt);
/* if we have a result set... */
if( SQLITE_ROW == rc ){
- /* if we have a callback... */
- if( xCallback ){
- /* allocate space for col name ptr, value ptr, and type */
- int nCol = sqlite3_column_count(pStmt);
- void *pData = sqlite3_malloc64(3*nCol*sizeof(const char*) + 1);
- if( !pData ){
- rc = SQLITE_NOMEM;
- }else{
- char **azCols = (char **)pData; /* Names of result columns */
- char **azVals = &azCols[nCol]; /* Results */
- int *aiTypes = (int *)&azVals[nCol]; /* Result types */
- int i, x;
- assert(sizeof(int) <= sizeof(char *));
- /* save off ptrs to column names */
+ /* allocate space for col name ptr, value ptr, and type */
+ int nCol = sqlite3_column_count(pStmt);
+ void *pData = sqlite3_malloc64(3*nCol*sizeof(const char*) + 1);
+ if( !pData ){
+ rc = SQLITE_NOMEM;
+ }else{
+ char **azCols = (char **)pData; /* Names of result columns */
+ char **azVals = &azCols[nCol]; /* Results */
+ int *aiTypes = (int *)&azVals[nCol]; /* Result types */
+ int i, x;
+ assert(sizeof(int) <= sizeof(char *));
+ /* save off ptrs to column names */
+ for(i=0; i<nCol; i++){
+ azCols[i] = (char *)sqlite3_column_name(pStmt, i);
+ }
+ do{
+ /* extract the data and data types */
for(i=0; i<nCol; i++){
- azCols[i] = (char *)sqlite3_column_name(pStmt, i);
- }
- do{
- /* extract the data and data types */
- for(i=0; i<nCol; i++){
- aiTypes[i] = x = sqlite3_column_type(pStmt, i);
- if( x==SQLITE_BLOB && pArg && pArg->cMode==MODE_Insert ){
- azVals[i] = "";
- }else{
- azVals[i] = (char*)sqlite3_column_text(pStmt, i);
- }
- if( !azVals[i] && (aiTypes[i]!=SQLITE_NULL) ){
- rc = SQLITE_NOMEM;
- break; /* from for */
- }
- } /* end for */
+ aiTypes[i] = x = sqlite3_column_type(pStmt, i);
+ if( x==SQLITE_BLOB && pArg && pArg->cMode==MODE_Insert ){
+ azVals[i] = "";
+ }else{
+ azVals[i] = (char*)sqlite3_column_text(pStmt, i);
+ }
+ if( !azVals[i] && (aiTypes[i]!=SQLITE_NULL) ){
+ rc = SQLITE_NOMEM;
+ break; /* from for */
+ }
+ } /* end for */
- /* if data and types extracted successfully... */
- if( SQLITE_ROW == rc ){
- /* call the supplied callback with the result row data */
- if( xCallback(pArg, nCol, azVals, azCols, aiTypes) ){
- rc = SQLITE_ABORT;
- }else{
- rc = sqlite3_step(pStmt);
- }
+ /* if data and types extracted successfully... */
+ if( SQLITE_ROW == rc ){
+ /* call the supplied callback with the result row data */
+ if( shell_callback(pArg, nCol, azVals, azCols, aiTypes) ){
+ rc = SQLITE_ABORT;
+ }else{
+ rc = sqlite3_step(pStmt);
}
- } while( SQLITE_ROW == rc );
- sqlite3_free(pData);
- }
- }else{
- do{
- rc = sqlite3_step(pStmt);
- } while( rc == SQLITE_ROW );
+ }
+ } while( SQLITE_ROW == rc );
+ sqlite3_free(pData);
}
}
}
@@ -2764,17 +2797,15 @@ static int expertDotCommand(
** and callback data argument.
*/
static int shell_exec(
- sqlite3 *db, /* An open database */
- const char *zSql, /* SQL to be evaluated */
- int (*xCallback)(void*,int,char**,char**,int*), /* Callback function */
- /* (not the same as sqlite3_exec) */
ShellState *pArg, /* Pointer to ShellState */
+ const char *zSql, /* SQL to be evaluated */
char **pzErrMsg /* Error msg written here */
){
sqlite3_stmt *pStmt = NULL; /* Statement to execute. */
int rc = SQLITE_OK; /* Return Code */
int rc2;
const char *zLeftover; /* Tail of unprocessed SQL */
+ sqlite3 *db = pArg->db;
if( pzErrMsg ){
*pzErrMsg = NULL;
@@ -2845,13 +2876,18 @@ static int shell_exec(
if( rc==SQLITE_OK ){
pArg->cMode = MODE_Explain;
explain_data_prepare(pArg, pExplain);
- exec_prepared_stmt(pArg, pExplain, xCallback);
+ exec_prepared_stmt(pArg, pExplain);
explain_data_delete(pArg);
}
sqlite3_finalize(pExplain);
sqlite3_free(zEQP);
}
- sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, triggerEQP, 0);
+ if( pArg->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){
+ sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 0, 0);
+ /* Reprepare pStmt before reactiving trace modes */
+ sqlite3_finalize(pStmt);
+ sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ }
restore_debug_trace_modes();
}
@@ -2871,7 +2907,7 @@ static int shell_exec(
}
}
- exec_prepared_stmt(pArg, pStmt, xCallback);
+ exec_prepared_stmt(pArg, pStmt);
explain_data_delete(pArg);
/* print usage stats if stats on */
@@ -3134,11 +3170,11 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
savedMode = p->mode;
p->zDestTable = sTable.z;
p->mode = p->cMode = MODE_Insert;
- rc = shell_exec(p->db, sSelect.z, shell_callback, p, 0);
+ rc = shell_exec(p, sSelect.z, 0);
if( (rc&0xff)==SQLITE_CORRUPT ){
raw_printf(p->out, "/****** CORRUPTION ERROR *******/\n");
toggleSelectOrder(p->db);
- shell_exec(p->db, sSelect.z, shell_callback, p, 0);
+ shell_exec(p, sSelect.z, 0);
toggleSelectOrder(p->db);
}
p->zDestTable = savedDestTable;
@@ -3255,6 +3291,7 @@ static char zHelp[] =
" on the output.\n"
".open ?OPTIONS? ?FILE? Close existing database and reopen FILE\n"
" The --new option starts with an empty file\n"
+ " Other options: --readonly --append --zip\n"
".output ?FILE? Send output to FILE or stdout\n"
".print STRING... Print literal STRING\n"
".prompt MAIN CONTINUE Replace the standard prompts\n"
@@ -3272,10 +3309,14 @@ static char zHelp[] =
".session CMD ... Create or control sessions\n"
#endif
".sha3sum ?OPTIONS...? Compute a SHA3 hash of database content\n"
+#ifndef SQLITE_NOHAVE_SYSTEM
".shell CMD ARGS... Run CMD ARGS... in a system shell\n"
+#endif
".show Show the current values for various settings\n"
".stats ?on|off? Show stats or turn stats on or off\n"
+#ifndef SQLITE_NOHAVE_SYSTEM
".system CMD ARGS... Run CMD ARGS... in a system shell\n"
+#endif
".tables ?TABLE? List names of tables\n"
" If TABLE specified, only list tables matching\n"
" LIKE pattern TABLE.\n"
@@ -3404,13 +3445,21 @@ static int session_filter(void *pCtx, const char *zTab){
/*
** Try to deduce the type of file for zName based on its content. Return
** one of the SHELL_OPEN_* constants.
+**
+** If the file does not exist or is empty but its name looks like a ZIP
+** archive and the dfltZip flag is true, then assume it is a ZIP archive.
+** Otherwise, assume an ordinary database regardless of the filename if
+** the type cannot be determined from content.
*/
-static int deduceDatabaseType(const char *zName){
+static int deduceDatabaseType(const char *zName, int dfltZip){
FILE *f = fopen(zName, "rb");
size_t n;
int rc = SHELL_OPEN_UNSPEC;
char zBuf[100];
- if( f==0 ) return SHELL_OPEN_NORMAL;
+ if( f==0 ){
+ if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ) return SHELL_OPEN_ZIPFILE;
+ return SHELL_OPEN_NORMAL;
+ }
fseek(f, -25, SEEK_END);
n = fread(zBuf, 25, 1, f);
if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){
@@ -3421,6 +3470,8 @@ static int deduceDatabaseType(const char *zName){
if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05
&& zBuf[3]==0x06 ){
rc = SHELL_OPEN_ZIPFILE;
+ }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
+ return SHELL_OPEN_ZIPFILE;
}
}
fclose(f);
@@ -3435,7 +3486,7 @@ 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 = deduceDatabaseType(p->zDbFilename);
+ p->openMode = (u8)deduceDatabaseType(p->zDbFilename, 0);
}
switch( p->openMode ){
case SHELL_OPEN_APPENDVFS: {
@@ -3447,6 +3498,10 @@ static void open_db(ShellState *p, int keepAlive){
sqlite3_open(":memory:", &p->db);
break;
}
+ case SHELL_OPEN_READONLY: {
+ sqlite3_open_v2(p->zDbFilename, &p->db, SQLITE_OPEN_READONLY, 0);
+ break;
+ }
case SHELL_OPEN_UNSPEC:
case SHELL_OPEN_NORMAL: {
sqlite3_open(p->zDbFilename, &p->db);
@@ -3476,10 +3531,12 @@ static void open_db(ShellState *p, int keepAlive){
shellModuleSchema, 0, 0);
sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p,
shellPutsFunc, 0, 0);
+#ifndef SQLITE_NOHAVE_SYSTEM
sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0,
editFunc, 0, 0);
sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0,
editFunc, 0, 0);
+#endif
if( p->openMode==SHELL_OPEN_ZIPFILE ){
char *zSql = sqlite3_mprintf(
"CREATE VIRTUAL TABLE zip USING zipfile(%Q);", p->zDbFilename);
@@ -4094,6 +4151,7 @@ static void output_reset(ShellState *p){
#endif
}else{
output_file_close(p->out);
+#ifndef SQLITE_NOHAVE_SYSTEM
if( p->doXdgOpen ){
const char *zXdgOpenCmd =
#if defined(_WIN32)
@@ -4112,6 +4170,7 @@ static void output_reset(ShellState *p){
outputModePop(p);
p->doXdgOpen = 0;
}
+#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */
}
p->outfile[0] = 0;
p->out = stdout;
@@ -5222,8 +5281,8 @@ static int arCreateOrUpdateCommand(
" data BLOB -- compressed content\n"
")";
const char *zDrop = "DROP TABLE IF EXISTS sqlar";
- const char *zInsertFmt =
- "REPLACE INTO sqlar(name,mode,mtime,sz,data)\n"
+ const char *zInsertFmt[2] = {
+ "REPLACE INTO %s(name,mode,mtime,sz,data)\n"
" SELECT\n"
" %s,\n"
" mode,\n"
@@ -5232,30 +5291,70 @@ static int arCreateOrUpdateCommand(
" WHEN '-' THEN length(data)\n"
" WHEN 'd' THEN 0\n"
" ELSE -1 END,\n"
- " CASE WHEN lsmode(mode) LIKE 'd%%' THEN NULL else data END\n"
+ " sqlar_compress(data)\n"
+ " FROM fsdir(%Q,%Q)\n"
+ " WHERE lsmode(mode) NOT LIKE '?%%';",
+ "REPLACE INTO %s(name,mode,mtime,data)\n"
+ " SELECT\n"
+ " %s,\n"
+ " mode,\n"
+ " mtime,\n"
+ " data\n"
" FROM fsdir(%Q,%Q)\n"
- " WHERE lsmode(mode) NOT LIKE '?%%';";
+ " WHERE lsmode(mode) NOT LIKE '?%%';"
+ };
int i; /* For iterating through azFile[] */
int rc; /* Return code */
+ const char *zTab = 0; /* SQL table into which to insert */
+ char *zSql;
+ char zTemp[50];
+ arExecSql(pAr, "PRAGMA page_size=512");
rc = arExecSql(pAr, "SAVEPOINT ar;");
if( rc!=SQLITE_OK ) return rc;
- if( bUpdate==0 ){
- rc = arExecSql(pAr, zDrop);
- if( rc!=SQLITE_OK ) return rc;
+ zTemp[0] = 0;
+ if( pAr->bZip ){
+ /* Initialize the zipfile virtual table, if necessary */
+ if( pAr->zFile ){
+ sqlite3_uint64 r;
+ sqlite3_randomness(sizeof(r),&r);
+ sqlite3_snprintf(sizeof(zTemp),zTemp,"zip%016llx",r);
+ zTab = zTemp;
+ zSql = sqlite3_mprintf(
+ "CREATE VIRTUAL TABLE temp.%s USING zipfile(%Q)",
+ zTab, pAr->zFile
+ );
+ rc = arExecSql(pAr, zSql);
+ sqlite3_free(zSql);
+ }else{
+ zTab = "zip";
+ }
+ }else{
+ /* Initialize the table for an SQLAR */
+ zTab = "sqlar";
+ if( bUpdate==0 ){
+ rc = arExecSql(pAr, zDrop);
+ if( rc!=SQLITE_OK ) goto end_ar_transaction;
+ }
+ rc = arExecSql(pAr, zCreate);
}
- rc = arExecSql(pAr, zCreate);
for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
- char *zSql = sqlite3_mprintf(zInsertFmt,
+ char *zSql2 = sqlite3_mprintf(zInsertFmt[pAr->bZip], zTab,
pAr->bVerbose ? "shell_putsnl(name)" : "name",
pAr->azArg[i], pAr->zDir);
- rc = arExecSql(pAr, zSql);
- sqlite3_free(zSql);
+ rc = arExecSql(pAr, zSql2);
+ sqlite3_free(zSql2);
}
+end_ar_transaction:
if( rc!=SQLITE_OK ){
arExecSql(pAr, "ROLLBACK TO ar; RELEASE ar;");
}else{
rc = arExecSql(pAr, "RELEASE ar;");
+ if( pAr->bZip && pAr->zFile ){
+ zSql = sqlite3_mprintf("DROP TABLE %s", zTemp);
+ arExecSql(pAr, zSql);
+ sqlite3_free(zSql);
+ }
}
return rc;
}
@@ -5277,20 +5376,17 @@ static int arDotCommand(
cmd.p = pState;
cmd.db = pState->db;
if( cmd.zFile ){
- eDbType = deduceDatabaseType(cmd.zFile);
+ eDbType = deduceDatabaseType(cmd.zFile, 1);
}else{
eDbType = pState->openMode;
}
if( eDbType==SHELL_OPEN_ZIPFILE ){
- if( cmd.zFile==0 ){
- cmd.zSrcTable = sqlite3_mprintf("zip");
- }else{
- cmd.zSrcTable = sqlite3_mprintf("zipfile(%Q)", cmd.zFile);
- }
- if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_UPDATE ){
- utf8_printf(stderr, "zip archives are read-only\n");
- rc = SQLITE_ERROR;
- goto end_ar_command;
+ if( cmd.eCmd==AR_CMD_EXTRACT || cmd.eCmd==AR_CMD_LIST ){
+ if( cmd.zFile==0 ){
+ cmd.zSrcTable = sqlite3_mprintf("zip");
+ }else{
+ cmd.zSrcTable = sqlite3_mprintf("zipfile(%Q)", cmd.zFile);
+ }
}
cmd.bZip = 1;
}else if( cmd.zFile ){
@@ -5315,14 +5411,12 @@ static int arDotCommand(
goto end_ar_command;
}
sqlite3_fileio_init(cmd.db, 0, 0);
-#ifdef SQLITE_HAVE_ZLIB
sqlite3_sqlar_init(cmd.db, 0, 0);
-#endif
sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p,
shellPutsFunc, 0, 0);
}
- if( cmd.zSrcTable==0 ){
+ if( cmd.zSrcTable==0 && cmd.bZip==0 ){
if( cmd.eCmd!=AR_CMD_CREATE
&& sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0)
){
@@ -5715,7 +5809,7 @@ static int do_meta_command(char *zLine, ShellState *p){
}else if( strcmp(azArg[1],"trigger")==0 ){
p->autoEQP = AUTOEQP_trigger;
}else{
- p->autoEQP = booleanValue(azArg[1]);
+ p->autoEQP = (u8)booleanValue(azArg[1]);
}
}else{
raw_printf(stderr, "Usage: .eqp off|on|trigger|full\n");
@@ -5802,14 +5896,11 @@ static int do_meta_command(char *zLine, ShellState *p){
callback, &data, &zErrMsg);
data.cMode = data.mode = MODE_Insert;
data.zDestTable = "sqlite_stat1";
- shell_exec(p->db, "SELECT * FROM sqlite_stat1",
- shell_callback, &data,&zErrMsg);
+ shell_exec(p, "SELECT * FROM sqlite_stat1", &zErrMsg);
data.zDestTable = "sqlite_stat3";
- shell_exec(p->db, "SELECT * FROM sqlite_stat3",
- shell_callback, &data,&zErrMsg);
+ shell_exec(p, "SELECT * FROM sqlite_stat3", &zErrMsg);
data.zDestTable = "sqlite_stat4";
- shell_exec(p->db, "SELECT * FROM sqlite_stat4",
- shell_callback, &data, &zErrMsg);
+ shell_exec(p, "SELECT * FROM sqlite_stat4", &zErrMsg);
raw_printf(p->out, "ANALYZE sqlite_master;\n");
}
}else
@@ -6290,12 +6381,14 @@ static int do_meta_command(char *zLine, ShellState *p){
const char *z = azArg[iName];
if( optionMatch(z,"new") ){
newFlag = 1;
-#ifdef SQLITE_HAVE_ZIP
+#ifdef SQLITE_HAVE_ZLIB
}else if( optionMatch(z, "zip") ){
p->openMode = SHELL_OPEN_ZIPFILE;
#endif
}else if( optionMatch(z, "append") ){
p->openMode = SHELL_OPEN_APPENDVFS;
+ }else if( optionMatch(z, "readonly") ){
+ p->openMode = SHELL_OPEN_READONLY;
}else if( z[0]=='-' ){
utf8_printf(stderr, "unknown option: %s\n", z);
rc = 1;
@@ -6352,6 +6445,7 @@ static int do_meta_command(char *zLine, ShellState *p){
}
output_reset(p);
if( zFile[0]=='-' && zFile[1]=='-' ) zFile++;
+#ifndef SQLITE_NOHAVE_SYSTEM
if( strcmp(zFile, "-e")==0 || strcmp(zFile, "-x")==0 ){
p->doXdgOpen = 1;
outputModePush(p);
@@ -6366,6 +6460,7 @@ static int do_meta_command(char *zLine, ShellState *p){
}
zFile = p->zTempFile;
}
+#endif /* SQLITE_NOHAVE_SYSTEM */
if( zFile[0]=='|' ){
#ifdef SQLITE_OMIT_POPEN
raw_printf(stderr, "Error: pipes are not supported in this OS\n");
@@ -6485,10 +6580,9 @@ static int do_meta_command(char *zLine, ShellState *p){
sqlite3_close(pSrc);
}else
-
if( c=='s' && strncmp(azArg[0], "scanstats", n)==0 ){
if( nArg==2 ){
- p->scanstatsOn = booleanValue(azArg[1]);
+ p->scanstatsOn = (u8)booleanValue(azArg[1]);
#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
raw_printf(stderr, "Warning: .scanstats not available in this build.\n");
#endif
@@ -6527,8 +6621,8 @@ static int do_meta_command(char *zLine, ShellState *p){
}
}
if( zName!=0 ){
- int isMaster = sqlite3_strlike(zName, "sqlite_master", 0)==0;
- if( isMaster || sqlite3_strlike(zName,"sqlite_temp_master",0)==0 ){
+ int isMaster = sqlite3_strlike(zName, "sqlite_master", '\\')==0;
+ if( isMaster || sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0 ){
char *new_argv[2], *new_colv[2];
new_argv[0] = sqlite3_mprintf(
"CREATE TABLE %s (\n"
@@ -6588,13 +6682,18 @@ static int do_meta_command(char *zLine, ShellState *p){
appendText(&sSelect, ") WHERE ", 0);
if( zName ){
char *zQarg = sqlite3_mprintf("%Q", zName);
+ int bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0 ||
+ strchr(zName, '[') != 0;
if( strchr(zName, '.') ){
appendText(&sSelect, "lower(printf('%s.%s',sname,tbl_name))", 0);
}else{
appendText(&sSelect, "lower(tbl_name)", 0);
}
- appendText(&sSelect, strchr(zName, '*') ? " GLOB " : " LIKE ", 0);
+ appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0);
appendText(&sSelect, zQarg, 0);
+ if( !bGlob ){
+ appendText(&sSelect, " ESCAPE '\\' ", 0);
+ }
appendText(&sSelect, " AND ", 0);
sqlite3_free(zQarg);
}
@@ -7009,7 +7108,7 @@ static int do_meta_command(char *zLine, ShellState *p){
}else{
zLike = z;
bSeparate = 1;
- if( sqlite3_strlike("sqlite_%", zLike, 0)==0 ) bSchema = 1;
+ if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1;
}
}
if( bSchema ){
@@ -7076,11 +7175,12 @@ static int do_meta_command(char *zLine, ShellState *p){
if( bDebug ){
utf8_printf(p->out, "%s\n", zSql);
}else{
- shell_exec(p->db, zSql, shell_callback, p, 0);
+ shell_exec(p, zSql, 0);
}
sqlite3_free(zSql);
}else
+#ifndef SQLITE_NOHAVE_SYSTEM
if( c=='s'
&& (strncmp(azArg[0], "shell", n)==0 || strncmp(azArg[0],"system",n)==0)
){
@@ -7100,6 +7200,7 @@ static int do_meta_command(char *zLine, ShellState *p){
sqlite3_free(zCmd);
if( x ) raw_printf(stderr, "System command returns %d\n", x);
}else
+#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */
if( c=='s' && strncmp(azArg[0], "show", n)==0 ){
static const char *azBool[] = { "off", "on", "trigger", "full"};
@@ -7139,7 +7240,7 @@ static int do_meta_command(char *zLine, ShellState *p){
if( c=='s' && strncmp(azArg[0], "stats", n)==0 ){
if( nArg==2 ){
- p->statsOn = booleanValue(azArg[1]);
+ p->statsOn = (u8)booleanValue(azArg[1]);
}else if( nArg==1 ){
display_stats(p->db, p, 0);
}else{
@@ -7680,6 +7781,16 @@ static int line_is_command_terminator(const char *zLine){
}
/*
+** We need a default sqlite3_complete() implementation to use in case
+** the shell is compiled with SQLITE_OMIT_COMPLETE. The default assumes
+** any arbitrary text is a complete SQL statement. This is not very
+** user-friendly, but it does seem to work.
+*/
+#ifdef SQLITE_OMIT_COMPLETE
+int sqlite3_complete(const char *zSql){ return 1; }
+#endif
+
+/*
** Return true if zSql is a complete SQL statement. Return false if it
** ends in the middle of a string literal or C-style comment.
*/
@@ -7703,7 +7814,7 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){
open_db(p, 0);
if( ShellHasFlag(p,SHFLG_Backslash) ) resolve_backslashes(zSql);
BEGIN_TIMER;
- rc = shell_exec(p->db, zSql, shell_callback, p, &zErrMsg);
+ rc = shell_exec(p, zSql, &zErrMsg);
END_TIMER;
if( rc || zErrMsg ){
char zPrefix[100];
@@ -7935,6 +8046,10 @@ static void process_sqliterc(
** Show available command line options
*/
static const char zOptions[] =
+#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE)
+ " -A ARGS... run \".archive ARGS\" and exit\n"
+#endif
+ " -append append the database to the end of the file\n"
" -ascii set output mode to 'ascii'\n"
" -bail stop after hitting an error\n"
" -batch force batch I/O\n"
@@ -7961,6 +8076,7 @@ static const char zOptions[] =
" -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"
" -stats print memory stats before each finalize\n"
" -version show SQLite version\n"
@@ -7968,6 +8084,9 @@ static const char zOptions[] =
#ifdef SQLITE_ENABLE_VFSTRACE
" -vfstrace enable tracing of all VFS calls\n"
#endif
+#ifdef SQLITE_HAVE_ZLIB
+ " -zip open the file as a ZIP Archive\n"
+#endif
;
static void usage(int showDetail){
utf8_printf(stderr,
@@ -8070,21 +8189,39 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
}
#endif
main_init(&data);
+
+ /* On Windows, we must translate command-line arguments into UTF-8.
+ ** The SQLite memory allocator subsystem has to be enabled in order to
+ ** do this. But we want to run an sqlite3_shutdown() afterwards so that
+ ** subsequent sqlite3_config() calls will work. So copy all results into
+ ** memory that does not come from the SQLite memory allocator.
+ */
#if !SQLITE_SHELL_IS_UTF8
sqlite3_initialize();
- argv = sqlite3_malloc64(sizeof(argv[0])*argc);
+ argv = malloc(sizeof(argv[0])*argc);
if( argv==0 ){
raw_printf(stderr, "out of memory\n");
exit(1);
}
for(i=0; i<argc; i++){
- argv[i] = sqlite3_win32_unicode_to_utf8(wargv[i]);
+ char *z = sqlite3_win32_unicode_to_utf8(wargv[i]);
+ int n;
+ if( z==0 ){
+ raw_printf(stderr, "out of memory\n");
+ exit(1);
+ }
+ n = (int)strlen(z);
+ argv[i] = malloc( n+1 );
if( argv[i]==0 ){
raw_printf(stderr, "out of memory\n");
exit(1);
}
+ memcpy(argv[i], z, n+1);
+ sqlite3_free(z);
}
+ sqlite3_shutdown();
#endif
+
assert( argc>=1 && argv && argv[0] );
Argv0 = argv[0];
@@ -8204,12 +8341,20 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
utf8_printf(stderr, "no such VFS: \"%s\"\n", argv[i]);
exit(1);
}
-#ifdef SQLITE_HAVE_ZIP
+#ifdef SQLITE_HAVE_ZLIB
}else if( strcmp(z,"-zip")==0 ){
data.openMode = SHELL_OPEN_ZIPFILE;
#endif
}else if( strcmp(z,"-append")==0 ){
data.openMode = SHELL_OPEN_APPENDVFS;
+ }else if( strcmp(z,"-readonly")==0 ){
+ data.openMode = SHELL_OPEN_READONLY;
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
+ }else if( strncmp(z, "-A",2)==0 ){
+ /* All remaining command-line arguments are passed to the ".archive"
+ ** command, so ignore them */
+ break;
+#endif
}
}
if( data.zDbFilename==0 ){
@@ -8263,12 +8408,14 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
}else if( strcmp(z,"-csv")==0 ){
data.mode = MODE_Csv;
memcpy(data.colSeparator,",",2);
-#ifdef SQLITE_HAVE_ZIP
+#ifdef SQLITE_HAVE_ZLIB
}else if( strcmp(z,"-zip")==0 ){
data.openMode = SHELL_OPEN_ZIPFILE;
#endif
}else if( strcmp(z,"-append")==0 ){
data.openMode = SHELL_OPEN_APPENDVFS;
+ }else if( strcmp(z,"-readonly")==0 ){
+ data.openMode = SHELL_OPEN_READONLY;
}else if( strcmp(z,"-ascii")==0 ){
data.mode = MODE_Ascii;
sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,
@@ -8346,7 +8493,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
if( rc && bail_on_error ) return rc==2 ? 0 : rc;
}else{
open_db(&data, 0);
- rc = shell_exec(data.db, z, shell_callback, &data, &zErrMsg);
+ rc = shell_exec(&data, z, &zErrMsg);
if( zErrMsg!=0 ){
utf8_printf(stderr,"Error: %s\n", zErrMsg);
if( bail_on_error ) return rc!=0 ? rc : 1;
@@ -8355,6 +8502,23 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
if( bail_on_error ) return rc;
}
}
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
+ }else if( strncmp(z, "-A", 2)==0 ){
+ if( nCmd>0 ){
+ utf8_printf(stderr, "Error: cannot mix regular SQL or dot-commands"
+ " with \"%s\"\n", z);
+ return 1;
+ }
+ open_db(&data, 0);
+ if( z[2] ){
+ argv[i] = &z[2];
+ arDotCommand(&data, argv+(i-1), argc-(i-1));
+ }else{
+ arDotCommand(&data, argv+i, argc-i);
+ }
+ readStdin = 0;
+ break;
+#endif
}else{
utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z);
raw_printf(stderr,"Use -help for a list of options.\n");
@@ -8374,7 +8538,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
if( rc ) return rc==2 ? 0 : rc;
}else{
open_db(&data, 0);
- rc = shell_exec(data.db, azCmd[i], shell_callback, &data, &zErrMsg);
+ rc = shell_exec(&data, azCmd[i], &zErrMsg);
if( zErrMsg!=0 ){
utf8_printf(stderr,"Error: %s\n", zErrMsg);
return rc!=0 ? rc : 1;
@@ -8437,8 +8601,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
data.doXdgOpen = 0;
clearTempFile(&data);
#if !SQLITE_SHELL_IS_UTF8
- for(i=0; i<argc; i++) sqlite3_free(argv[i]);
- sqlite3_free(argv);
+ for(i=0; i<argc; i++) free(argv[i]);
+ free(argv);
#endif
return rc;
}
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index ed1c24d..202155d 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -1064,6 +1064,12 @@ struct sqlite3_io_methods {
** so that all subsequent write operations are independent.
** ^SQLite will never invoke SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE without
** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE].
+**
+** <li>[[SQLITE_FCNTL_LOCK_TIMEOUT]]
+** The [SQLITE_FCNTL_LOCK_TIMEOUT] opcode causes attempts to obtain
+** a file lock using the xLock or xShmLock methods of the VFS to wait
+** for up to M milliseconds before failing, where M is the single
+** unsigned integer parameter.
** </ul>
*/
#define SQLITE_FCNTL_LOCKSTATE 1
@@ -1098,6 +1104,7 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31
#define SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32
#define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33
+#define SQLITE_FCNTL_LOCK_TIMEOUT 34
/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@@ -2054,11 +2061,13 @@ struct sqlite3_mem_methods {
** connections at all to the database. If so, it performs a checkpoint
** operation before closing the connection. This option may be used to
** override this behaviour. The first parameter passed to this operation
-** is an integer - non-zero to disable checkpoints-on-close, or zero (the
-** default) to enable them. The second parameter is a pointer to an integer
+** is an integer - positive to disable checkpoints-on-close, or zero (the
+** default) to enable them, and 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 checkpoints-on-close
** have been disabled - 0 if they are not disabled, 1 if they are.
** </dd>
+**
** <dt>SQLITE_DBCONFIG_ENABLE_QPSG</dt>
** <dd>^(The SQLITE_DBCONFIG_ENABLE_QPSG option activates or deactivates
** the [query planner stability guarantee] (QPSG). When the QPSG is active,
@@ -2068,13 +2077,20 @@ struct sqlite3_mem_methods {
** slower. But the QPSG has the advantage of more predictable behavior. With
** the QPSG active, SQLite will always use the same query plan in the field as
** was used during testing in the lab.
+** The first argument to this setting is an integer which is 0 to disable
+** the QPSG, positive to enable QPSG, 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 the QPSG is disabled or enabled
+** following this call.
** </dd>
+**
** <dt>SQLITE_DBCONFIG_TRIGGER_EQP</dt>
** <dd> By default, the output of EXPLAIN QUERY PLAN commands does not
** include output for any operations performed by trigger programs. This
** option is used to set or clear (the default) a flag that governs this
** behavior. The first parameter passed to this operation is an integer -
-** non-zero to enable output for trigger programs, or zero to disable it.
+** positive to enable output for trigger programs, or zero to disable it,
+** 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.
@@ -2496,16 +2512,16 @@ void sqlite3_free_table(char **result);
**
** These routines are work-alikes of the "printf()" family of functions
** from the standard C library.
-** These routines understand most of the common K&R formatting options,
-** plus some additional non-standard formats, detailed below.
-** Note that some of the more obscure formatting options from recent
-** C-library standards are omitted from this implementation.
+** These routines understand most of the common formatting options from
+** the standard library printf()
+** plus some additional non-standard formats ([%q], [%Q], [%w], and [%z]).
+** See the [built-in printf()] documentation for details.
**
** ^The sqlite3_mprintf() and sqlite3_vmprintf() routines write their
-** results into memory obtained from [sqlite3_malloc()].
+** results into memory obtained from [sqlite3_malloc64()].
** The strings returned by these two routines should be
** released by [sqlite3_free()]. ^Both routines return a
-** NULL pointer if [sqlite3_malloc()] is unable to allocate enough
+** NULL pointer if [sqlite3_malloc64()] is unable to allocate enough
** memory to hold the resulting string.
**
** ^(The sqlite3_snprintf() routine is similar to "snprintf()" from
@@ -2529,71 +2545,7 @@ void sqlite3_free_table(char **result);
**
** ^The sqlite3_vsnprintf() routine is a varargs version of sqlite3_snprintf().
**
-** These routines all implement some additional formatting
-** options that are useful for constructing SQL statements.
-** All of the usual printf() formatting options apply. In addition, there
-** is are "%q", "%Q", "%w" and "%z" options.
-**
-** ^(The %q option works like %s in that it substitutes a nul-terminated
-** string from the argument list. But %q also doubles every '\'' character.
-** %q is designed for use inside a string literal.)^ By doubling each '\''
-** character it escapes that character and allows it to be inserted into
-** the string.
-**
-** For example, assume the string variable zText contains text as follows:
-**
-** <blockquote><pre>
-** char *zText = "It's a happy day!";
-** </pre></blockquote>
-**
-** One can use this text in an SQL statement as follows:
-**
-** <blockquote><pre>
-** char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES('%q')", zText);
-** sqlite3_exec(db, zSQL, 0, 0, 0);
-** sqlite3_free(zSQL);
-** </pre></blockquote>
-**
-** Because the %q format string is used, the '\'' character in zText
-** is escaped and the SQL generated is as follows:
-**
-** <blockquote><pre>
-** INSERT INTO table1 VALUES('It''s a happy day!')
-** </pre></blockquote>
-**
-** This is correct. Had we used %s instead of %q, the generated SQL
-** would have looked like this:
-**
-** <blockquote><pre>
-** INSERT INTO table1 VALUES('It's a happy day!');
-** </pre></blockquote>
-**
-** This second example is an SQL syntax error. As a general rule you should
-** always use %q instead of %s when inserting text into a string literal.
-**
-** ^(The %Q option works like %q except it also adds single quotes around
-** the outside of the total string. Additionally, if the parameter in the
-** argument list is a NULL pointer, %Q substitutes the text "NULL" (without
-** single quotes).)^ So, for example, one could say:
-**
-** <blockquote><pre>
-** char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES(%Q)", zText);
-** sqlite3_exec(db, zSQL, 0, 0, 0);
-** sqlite3_free(zSQL);
-** </pre></blockquote>
-**
-** The code above will render a correct SQL statement in the zSQL
-** variable even if the zText variable is a NULL pointer.
-**
-** ^(The "%w" formatting option is like "%q" except that it expects to
-** be contained within double-quotes instead of single quotes, and it
-** escapes the double-quote character instead of the single-quote
-** character.)^ The "%w" formatting option is intended for safely inserting
-** table and column names into a constructed SQL statement.
-**
-** ^(The "%z" formatting option works like "%s" but with the
-** addition that after the string has been read and copied into
-** the result, [sqlite3_free()] is called on the input string.)^
+** See also: [built-in printf()], [printf() SQL function]
*/
char *sqlite3_mprintf(const char*,...);
char *sqlite3_vmprintf(const char*, va_list);
@@ -3659,13 +3611,13 @@ int sqlite3_limit(sqlite3*, int id, int newVal);
** or [GLOB] operator or if the parameter is compared to an indexed column
** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled.
** </li>
+** </ol>
**
** <p>^sqlite3_prepare_v3() differs from sqlite3_prepare_v2() only in having
** the extra prepFlags parameter, which is a bit array consisting of zero or
** more of the [SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_*] flags. ^The
** sqlite3_prepare_v2() interface works exactly the same as
** sqlite3_prepare_v3() with a zero prepFlags parameter.
-** </ol>
*/
int sqlite3_prepare(
sqlite3 *db, /* Database handle */
@@ -7294,6 +7246,15 @@ int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg);
** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0.
** </dd>
**
+** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt>
+** <dd>This parameter returns the number of dirty cache entries that have
+** been written to disk in the middle of a transaction due to the page
+** cache overflowing. Transactions are more efficient if they are written
+** to disk all at once. When pages spill mid-transaction, that introduces
+** additional overhead. This parameter can be used help identify
+** inefficiencies that can be resolve by increasing the cache size.
+** </dd>
+**
** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(<dt>SQLITE_DBSTATUS_DEFERRED_FKS</dt>
** <dd>This parameter returns zero for the current value if and only if
** all foreign key constraints (deferred or immediate) have been
@@ -7313,7 +7274,8 @@ int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg);
#define SQLITE_DBSTATUS_CACHE_WRITE 9
#define SQLITE_DBSTATUS_DEFERRED_FKS 10
#define SQLITE_DBSTATUS_CACHE_USED_SHARED 11
-#define SQLITE_DBSTATUS_MAX 11 /* Largest defined DBSTATUS */
+#define SQLITE_DBSTATUS_CACHE_SPILL 12
+#define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */
/*
@@ -8794,6 +8756,128 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
/*
+** CAPI3REF: Serialize a database
+**
+** The sqlite3_serialize(D,S,P,F) interface returns a pointer to memory
+** that is a serialization of the S database on [database connection] D.
+** If P is not a NULL pointer, then the size of the database in bytes
+** is written into *P.
+**
+** For an ordinary on-disk database file, the serialization is just a
+** copy of the disk file. For an in-memory database or a "TEMP" database,
+** the serialization is the same sequence of bytes which would be written
+** to disk if that database where backed up to disk.
+**
+** The usual case is that sqlite3_serialize() copies the serialization of
+** the database into memory obtained from [sqlite3_malloc64()] and returns
+** a pointer to that memory. The caller is responsible for freeing the
+** returned value to avoid a memory leak. However, if the F argument
+** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations
+** are made, and the sqlite3_serialize() function will return a pointer
+** to the contiguous memory representation of the database that SQLite
+** is currently using for that database, or NULL if the no such contiguous
+** memory representation of the database exists. A contiguous memory
+** representation of the database will usually only exist if there has
+** been a prior call to [sqlite3_deserialize(D,S,...)] with the same
+** values of D and S.
+** The size of the database is written into *P even if the
+** SQLITE_SERIALIZE_NOCOPY bit is set but no contigious copy
+** of the database exists.
+**
+** A call to sqlite3_serialize(D,S,P,F) might return NULL even if the
+** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory
+** allocation error occurs.
+**
+** This interface is only available if SQLite is compiled with the
+** [SQLITE_ENABLE_DESERIALIZE] option.
+*/
+unsigned char *sqlite3_serialize(
+ sqlite3 *db, /* The database connection */
+ const char *zSchema, /* Which DB to serialize. ex: "main", "temp", ... */
+ sqlite3_int64 *piSize, /* Write size of the DB here, if not NULL */
+ unsigned int mFlags /* Zero or more SQLITE_SERIALIZE_* flags */
+);
+
+/*
+** CAPI3REF: Flags for sqlite3_serialize
+**
+** Zero or more of the following constants can be OR-ed together for
+** the F argument to [sqlite3_serialize(D,S,P,F)].
+**
+** SQLITE_SERIALIZE_NOCOPY means that [sqlite3_serialize()] will return
+** a pointer to contiguous in-memory database that it is currently using,
+** without making a copy of the database. If SQLite is not currently using
+** a contiguous in-memory database, then this option causes
+** [sqlite3_serialize()] to return a NULL pointer. SQLite will only be
+** using a contiguous in-memory database if it has been initialized by a
+** prior call to [sqlite3_deserialize()].
+*/
+#define SQLITE_SERIALIZE_NOCOPY 0x001 /* Do no memory allocations */
+
+/*
+** CAPI3REF: Deserialize a database
+**
+** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the
+** [database connection] D to disconnect from database S and then
+** reopen S as an in-memory database based on the serialization contained
+** in P. The serialized database P is N bytes in size. M is the size of
+** the buffer P, which might be larger than N. If M is larger than N, and
+** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is
+** permitted to add content to the in-memory database as long as the total
+** size does not exceed M bytes.
+**
+** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will
+** invoke sqlite3_free() on the serialization buffer when the database
+** connection closes. If the SQLITE_DESERIALIZE_RESIZEABLE bit is set, then
+** SQLite will try to increase the buffer size using sqlite3_realloc64()
+** if writes on the database cause it to grow larger than M bytes.
+**
+** The sqlite3_deserialize() interface will fail with SQLITE_BUSY if the
+** database is currently in a read transaction or is involved in a backup
+** operation.
+**
+** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the
+** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then
+** [sqlite3_free()] is invoked on argument P prior to returning.
+**
+** This interface is only available if SQLite is compiled with the
+** [SQLITE_ENABLE_DESERIALIZE] option.
+*/
+int sqlite3_deserialize(
+ sqlite3 *db, /* The database connection */
+ const char *zSchema, /* Which DB to reopen with the deserialization */
+ unsigned char *pData, /* The serialized database content */
+ sqlite3_int64 szDb, /* Number bytes in the deserialization */
+ sqlite3_int64 szBuf, /* Total size of buffer pData[] */
+ unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */
+);
+
+/*
+** CAPI3REF: Flags for sqlite3_deserialize()
+**
+** The following are allowed values for 6th argument (the F argument) to
+** the [sqlite3_deserialize(D,S,P,N,M,F)] interface.
+**
+** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization
+** in the P argument is held in memory obtained from [sqlite3_malloc64()]
+** and that SQLite should take ownership of this memory and automatically
+** free it when it has finished using it. Without this flag, the caller
+** is resposible for freeing any dynamically allocated memory.
+**
+** The SQLITE_DESERIALIZE_RESIZEABLE flag means that SQLite is allowed to
+** grow the size of the database using calls to [sqlite3_realloc64()]. This
+** flag should only be used if SQLITE_DESERIALIZE_FREEONCLOSE is also used.
+** Without this flag, the deserialized database cannot increase in size beyond
+** the number of bytes specified by the M parameter.
+**
+** The SQLITE_DESERIALIZE_READONLY flag means that the deserialized database
+** should be treated as read-only.
+*/
+#define SQLITE_DESERIALIZE_FREEONCLOSE 1 /* Call sqlite3_free() on close */
+#define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */
+#define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */
+
+/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
*/
diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h
index ac92a74..1409370 100644
--- a/src/sqlite3ext.h
+++ b/src/sqlite3ext.h
@@ -563,8 +563,8 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_value_pointer sqlite3_api->value_pointer
/* Version 3.22.0 and later */
#define sqlite3_vtab_nochange sqlite3_api->vtab_nochange
-#define sqlite3_value_nochange sqltie3_api->value_nochange
-#define sqlite3_vtab_collation sqltie3_api->vtab_collation
+#define sqlite3_value_nochange sqlite3_api->value_nochange
+#define sqlite3_vtab_collation sqlite3_api->vtab_collation
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index b7b402b..d932ae8 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -961,9 +961,10 @@ typedef INT16_TYPE LogEst;
*/
typedef struct BusyHandler BusyHandler;
struct BusyHandler {
- int (*xFunc)(void *,int); /* The busy callback */
- void *pArg; /* First arg to busy callback */
- int nBusy; /* Incremented with each busy call */
+ int (*xBusyHandler)(void *,int); /* The busy callback */
+ void *pBusyArg; /* First arg to busy callback */
+ int nBusy; /* Incremented with each busy call */
+ u8 bExtraFileArg; /* Include sqlite3_file as callback arg */
};
/*
@@ -1365,8 +1366,9 @@ struct sqlite3 {
int newTnum; /* Rootpage of table being initialized */
u8 iDb; /* Which db file is being initialized */
u8 busy; /* TRUE if currently initializing */
- u8 orphanTrigger; /* Last statement is orphaned TEMP trigger */
- u8 imposterTable; /* Building an imposter table */
+ unsigned orphanTrigger : 1; /* Last statement is orphaned TEMP trigger */
+ unsigned imposterTable : 1; /* Building an imposter table */
+ unsigned reopenMemdb : 1; /* ATTACH is really a reopen using MemDB */
} init;
int nVdbeActive; /* Number of VDBEs currently running */
int nVdbeRead; /* Number of active VDBEs that read or write */
@@ -1531,6 +1533,8 @@ struct sqlite3 {
#define SQLITE_CursorHints 0x0400 /* Add OP_CursorHint opcodes */
#define SQLITE_Stat34 0x0800 /* Use STAT3 or STAT4 data */
/* TH3 expects the Stat34 ^^^^^^ value to be 0x0800. Don't change it */
+#define SQLITE_PushDown 0x1000 /* The push-down optimization */
+#define SQLITE_SimplifyJoin 0x2000 /* Convert LEFT JOIN to JOIN */
#define SQLITE_AllOpts 0xffff /* All optimizations */
/*
@@ -1754,6 +1758,7 @@ struct Column {
#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" */
/*
** A "Collating Sequence" is defined by an instance of the following
@@ -2991,7 +2996,6 @@ struct Parse {
int nMaxArg; /* Max args passed to user function by sub-program */
#if SELECTTRACE_ENABLED
int nSelect; /* Number of SELECT statements seen */
- int nSelectIndent; /* How far to indent SELECTTRACE() output */
#endif
#ifndef SQLITE_OMIT_SHARED_CACHE
int nTableLock; /* Number of locks in aTableLock */
@@ -3355,9 +3359,9 @@ struct Walker {
struct CCurHint *pCCurHint; /* Used by codeCursorHint() */
int *aiCol; /* array of column indexes */
struct IdxCover *pIdxCover; /* Check for index coverage */
- struct IdxExprTrans *pIdxTrans; /* Convert indexed expr to column */
+ struct IdxExprTrans *pIdxTrans; /* Convert idxed expr to column */
ExprList *pGroupBy; /* GROUP BY clause */
- struct HavingToWhereCtx *pHavingCtx; /* HAVING to WHERE clause ctx */
+ Select *pSelect; /* HAVING to WHERE clause ctx */
} u;
};
@@ -3821,6 +3825,7 @@ int sqlite3ExprCompare(Parse*,Expr*, Expr*, int);
int sqlite3ExprCompareSkip(Expr*, Expr*, int);
int sqlite3ExprListCompare(ExprList*, ExprList*, int);
int sqlite3ExprImpliesExpr(Parse*,Expr*, Expr*, int);
+int sqlite3ExprImpliesNonNullRow(Expr*,int);
void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*);
void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*);
int sqlite3ExprCoveredByIndex(Expr*, int iCur, Index *pIdx);
@@ -3838,6 +3843,8 @@ void sqlite3EndTransaction(Parse*,int);
void sqlite3Savepoint(Parse*, int, Token*);
void sqlite3CloseSavepoints(sqlite3 *);
void sqlite3LeaveMutexAndCloseZombie(sqlite3*);
+int sqlite3ExprIdToTrueFalse(Expr*);
+int sqlite3ExprTruthValue(const Expr*);
int sqlite3ExprIsConstant(Expr*);
int sqlite3ExprIsConstantNotJoin(Expr*);
int sqlite3ExprIsConstantOrFunction(Expr*, u8);
@@ -4020,6 +4027,10 @@ int sqlite3TwoPartName(Parse *, Token *, Token *, Token **);
const char *sqlite3ErrName(int);
#endif
+#ifdef SQLITE_ENABLE_DESERIALIZE
+int sqlite3MemdbInit(void);
+#endif
+
const char *sqlite3ErrStr(int);
int sqlite3ReadSchema(Parse *pParse);
CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char*,int);
@@ -4068,6 +4079,9 @@ extern FuncDefHash sqlite3BuiltinFunctions;
extern int sqlite3PendingByte;
#endif
#endif
+#ifdef VDBE_PROFILE
+extern sqlite3_uint64 sqlite3NProfileCnt;
+#endif
void sqlite3RootPageMoved(sqlite3*, int, int, int);
void sqlite3Reindex(Parse*, Token*, Token*);
void sqlite3AlterFunctions(void);
@@ -4090,7 +4104,7 @@ void sqlite3AlterBeginAddColumn(Parse *, SrcList *);
CollSeq *sqlite3GetCollSeq(Parse*, u8, CollSeq *, const char*);
char sqlite3AffinityType(const char*, u8*);
void sqlite3Analyze(Parse*, Token*, Token*);
-int sqlite3InvokeBusyHandler(BusyHandler*);
+int sqlite3InvokeBusyHandler(BusyHandler*, sqlite3_file*);
int sqlite3FindDb(sqlite3*, Token*);
int sqlite3FindDbName(sqlite3 *, const char *);
int sqlite3AnalysisLoad(sqlite3*,int iDB);
diff --git a/src/status.c b/src/status.c
index 6e5b0e5..a5a39f4 100644
--- a/src/status.c
+++ b/src/status.c
@@ -337,6 +337,9 @@ int sqlite3_db_status(
** pagers the database handle is connected to. *pHighwater is always set
** to zero.
*/
+ case SQLITE_DBSTATUS_CACHE_SPILL:
+ op = SQLITE_DBSTATUS_CACHE_WRITE+1;
+ /* Fall through into the next case */
case SQLITE_DBSTATUS_CACHE_HIT:
case SQLITE_DBSTATUS_CACHE_MISS:
case SQLITE_DBSTATUS_CACHE_WRITE:{
diff --git a/src/tclsqlite.c b/src/tclsqlite.c
index eed86ee..e5984ec 100644
--- a/src/tclsqlite.c
+++ b/src/tclsqlite.c
@@ -64,7 +64,9 @@
# define GETPID getpid
#elif !defined(_WIN32_WCE)
# ifndef SQLITE_AMALGAMATION
-# define WIN32_LEAN_AND_MEAN
+# ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+# endif
# include <windows.h>
# endif
# define GETPID (int)GetCurrentProcessId
@@ -646,7 +648,7 @@ static int DbTraceV2Handler(
}
case SQLITE_TRACE_PROFILE: {
sqlite3_stmt *pStmt = (sqlite3_stmt *)pd;
- sqlite3_int64 ns = (sqlite3_int64)xd;
+ sqlite3_int64 ns = *(sqlite3_int64*)xd;
pCmd = Tcl_NewStringObj(pDb->zTraceV2, -1);
Tcl_IncrRefCount(pCmd);
@@ -1846,35 +1848,35 @@ static int SQLITE_TCLAPI DbObjCmd(
int choice;
int rc = TCL_OK;
static const char *DB_strs[] = {
- "authorizer", "backup", "busy",
- "cache", "changes", "close",
- "collate", "collation_needed", "commit_hook",
- "complete", "copy", "enable_load_extension",
- "errorcode", "eval", "exists",
- "function", "incrblob", "interrupt",
- "last_insert_rowid", "nullvalue", "onecolumn",
- "preupdate", "profile", "progress",
- "rekey", "restore", "rollback_hook",
- "status", "timeout", "total_changes",
- "trace", "trace_v2", "transaction",
- "unlock_notify", "update_hook", "version",
- "wal_hook",
- 0
+ "authorizer", "backup", "busy",
+ "cache", "changes", "close",
+ "collate", "collation_needed", "commit_hook",
+ "complete", "copy", "deserialize",
+ "enable_load_extension", "errorcode", "eval",
+ "exists", "function", "incrblob",
+ "interrupt", "last_insert_rowid", "nullvalue",
+ "onecolumn", "preupdate", "profile",
+ "progress", "rekey", "restore",
+ "rollback_hook", "serialize", "status",
+ "timeout", "total_changes", "trace",
+ "trace_v2", "transaction", "unlock_notify",
+ "update_hook", "version", "wal_hook",
+ 0
};
enum DB_enum {
- DB_AUTHORIZER, DB_BACKUP, DB_BUSY,
- DB_CACHE, DB_CHANGES, DB_CLOSE,
- DB_COLLATE, DB_COLLATION_NEEDED, DB_COMMIT_HOOK,
- DB_COMPLETE, DB_COPY, DB_ENABLE_LOAD_EXTENSION,
- DB_ERRORCODE, DB_EVAL, DB_EXISTS,
- DB_FUNCTION, DB_INCRBLOB, DB_INTERRUPT,
- DB_LAST_INSERT_ROWID, DB_NULLVALUE, DB_ONECOLUMN,
- DB_PREUPDATE, DB_PROFILE, DB_PROGRESS,
- DB_REKEY, DB_RESTORE, DB_ROLLBACK_HOOK,
- DB_STATUS, DB_TIMEOUT, DB_TOTAL_CHANGES,
- DB_TRACE, DB_TRACE_V2, DB_TRANSACTION,
- DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK, DB_VERSION,
- DB_WAL_HOOK,
+ DB_AUTHORIZER, DB_BACKUP, DB_BUSY,
+ DB_CACHE, DB_CHANGES, DB_CLOSE,
+ DB_COLLATE, DB_COLLATION_NEEDED, DB_COMMIT_HOOK,
+ DB_COMPLETE, DB_COPY, DB_DESERIALIZE,
+ DB_ENABLE_LOAD_EXTENSION, DB_ERRORCODE, DB_EVAL,
+ DB_EXISTS, DB_FUNCTION, DB_INCRBLOB,
+ DB_INTERRUPT, DB_LAST_INSERT_ROWID, DB_NULLVALUE,
+ DB_ONECOLUMN, DB_PREUPDATE, DB_PROFILE,
+ DB_PROGRESS, DB_REKEY, DB_RESTORE,
+ DB_ROLLBACK_HOOK, DB_SERIALIZE, DB_STATUS,
+ DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE,
+ DB_TRACE_V2, DB_TRANSACTION, DB_UNLOCK_NOTIFY,
+ DB_UPDATE_HOOK, DB_VERSION, DB_WAL_HOOK
};
/* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
@@ -2413,6 +2415,53 @@ static int SQLITE_TCLAPI DbObjCmd(
}
/*
+ ** $db deserialize ?DATABASE? VALUE
+ **
+ ** Reopen DATABASE (default "main") using the content in $VALUE
+ */
+ case DB_DESERIALIZE: {
+#ifndef SQLITE_ENABLE_DESERIALIZE
+ Tcl_AppendResult(interp, "MEMDB not available in this build",
+ (char*)0);
+ rc = TCL_ERROR;
+#else
+ const char *zSchema;
+ Tcl_Obj *pValue;
+ unsigned char *pBA;
+ unsigned char *pData;
+ int len, xrc;
+
+ if( objc==3 ){
+ zSchema = 0;
+ pValue = objv[2];
+ }else if( objc==4 ){
+ zSchema = Tcl_GetString(objv[2]);
+ pValue = objv[3];
+ }else{
+ Tcl_WrongNumArgs(interp, 2, objv, "?DATABASE? VALUE");
+ rc = TCL_ERROR;
+ break;
+ }
+ pBA = Tcl_GetByteArrayFromObj(pValue, &len);
+ pData = sqlite3_malloc64( len );
+ if( pData==0 && len>0 ){
+ Tcl_AppendResult(interp, "out of memory", (char*)0);
+ rc = TCL_ERROR;
+ }else{
+ if( len>0 ) memcpy(pData, pBA, len);
+ xrc = sqlite3_deserialize(pDb->db, zSchema, pData, len, len,
+ SQLITE_DESERIALIZE_FREEONCLOSE |
+ SQLITE_DESERIALIZE_RESIZEABLE);
+ if( xrc ){
+ Tcl_AppendResult(interp, "unable to set MEMDB content", (char*)0);
+ rc = TCL_ERROR;
+ }
+ }
+#endif
+ break;
+ }
+
+ /*
** $db enable_load_extension BOOLEAN
**
** Turn the extension loading feature on or off. It if off by
@@ -2888,6 +2937,39 @@ static int SQLITE_TCLAPI DbObjCmd(
}
/*
+ ** $db serialize ?DATABASE?
+ **
+ ** Return a serialization of a database.
+ */
+ case DB_SERIALIZE: {
+#ifndef SQLITE_ENABLE_DESERIALIZE
+ Tcl_AppendResult(interp, "MEMDB not available in this build",
+ (char*)0);
+ rc = TCL_ERROR;
+#else
+ const char *zSchema = objc>=3 ? Tcl_GetString(objv[2]) : "main";
+ sqlite3_int64 sz = 0;
+ unsigned char *pData;
+ if( objc!=2 && objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "?DATABASE?");
+ rc = TCL_ERROR;
+ }else{
+ int needFree;
+ pData = sqlite3_serialize(pDb->db, zSchema, &sz, SQLITE_SERIALIZE_NOCOPY);
+ if( pData ){
+ needFree = 0;
+ }else{
+ pData = sqlite3_serialize(pDb->db, zSchema, &sz, 0);
+ needFree = 1;
+ }
+ Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pData,sz));
+ if( needFree ) sqlite3_free(pData);
+ }
+#endif
+ break;
+ }
+
+ /*
** $db status (step|sort|autoindex|vmstep)
**
** Display SQLITE_STMTSTATUS_FULLSCAN_STEP or
@@ -3348,6 +3430,24 @@ static int SQLITE_TCLAPI DbObjCmdAdaptor(
#endif /* SQLITE_TCL_NRE */
/*
+** Issue the usage message when the "sqlite3" command arguments are
+** incorrect.
+*/
+static int sqliteCmdUsage(
+ Tcl_Interp *interp,
+ Tcl_Obj *const*objv
+){
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "HANDLE ?FILENAME? ?-vfs VFSNAME? ?-readonly BOOLEAN? ?-create BOOLEAN?"
+ " ?-nomutex BOOLEAN? ?-fullmutex BOOLEAN? ?-uri BOOLEAN?"
+#if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_CODEC_FROM_TCL)
+ " ?-key CODECKEY?"
+#endif
+ );
+ return TCL_ERROR;
+}
+
+/*
** sqlite3 DBNAME FILENAME ?-vfs VFSNAME? ?-key KEY? ?-readonly BOOLEAN?
** ?-create BOOLEAN? ?-nomutex BOOLEAN?
**
@@ -3372,7 +3472,7 @@ static int SQLITE_TCLAPI DbMain(
const char *zArg;
char *zErrMsg;
int i;
- const char *zFile;
+ const char *zFile = 0;
const char *zVfs = 0;
int flags;
Tcl_DString translatedFilename;
@@ -3383,7 +3483,7 @@ static int SQLITE_TCLAPI DbMain(
int rc;
/* In normal use, each TCL interpreter runs in a single thread. So
- ** by default, we can turn of mutexing on SQLite database connections.
+ ** by default, we can turn off mutexing on SQLite database connections.
** However, for testing purposes it is useful to have mutexes turned
** on. So, by default, mutexes default off. But if compiled with
** SQLITE_TCL_DEFAULT_FULLMUTEX then mutexes default on.
@@ -3412,18 +3512,26 @@ static int SQLITE_TCLAPI DbMain(
#endif
return TCL_OK;
}
+ if( zArg[0]=='-' ) return sqliteCmdUsage(interp, objv);
}
- for(i=3; i+1<objc; i+=2){
+ for(i=2; i<objc; i++){
zArg = Tcl_GetString(objv[i]);
+ if( zArg[0]!='-' ){
+ if( zFile!=0 ) return sqliteCmdUsage(interp, objv);
+ zFile = zArg;
+ continue;
+ }
+ if( i==objc-1 ) return sqliteCmdUsage(interp, objv);
+ i++;
if( strcmp(zArg,"-key")==0 ){
#if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_CODEC_FROM_TCL)
- pKey = Tcl_GetByteArrayFromObj(objv[i+1], &nKey);
+ pKey = Tcl_GetByteArrayFromObj(objv[i], &nKey);
#endif
}else if( strcmp(zArg, "-vfs")==0 ){
- zVfs = Tcl_GetString(objv[i+1]);
+ zVfs = Tcl_GetString(objv[i]);
}else if( strcmp(zArg, "-readonly")==0 ){
int b;
- if( Tcl_GetBooleanFromObj(interp, objv[i+1], &b) ) return TCL_ERROR;
+ if( Tcl_GetBooleanFromObj(interp, objv[i], &b) ) return TCL_ERROR;
if( b ){
flags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE);
flags |= SQLITE_OPEN_READONLY;
@@ -3433,7 +3541,7 @@ static int SQLITE_TCLAPI DbMain(
}
}else if( strcmp(zArg, "-create")==0 ){
int b;
- if( Tcl_GetBooleanFromObj(interp, objv[i+1], &b) ) return TCL_ERROR;
+ if( Tcl_GetBooleanFromObj(interp, objv[i], &b) ) return TCL_ERROR;
if( b && (flags & SQLITE_OPEN_READONLY)==0 ){
flags |= SQLITE_OPEN_CREATE;
}else{
@@ -3441,7 +3549,7 @@ static int SQLITE_TCLAPI DbMain(
}
}else if( strcmp(zArg, "-nomutex")==0 ){
int b;
- if( Tcl_GetBooleanFromObj(interp, objv[i+1], &b) ) return TCL_ERROR;
+ if( Tcl_GetBooleanFromObj(interp, objv[i], &b) ) return TCL_ERROR;
if( b ){
flags |= SQLITE_OPEN_NOMUTEX;
flags &= ~SQLITE_OPEN_FULLMUTEX;
@@ -3450,7 +3558,7 @@ static int SQLITE_TCLAPI DbMain(
}
}else if( strcmp(zArg, "-fullmutex")==0 ){
int b;
- if( Tcl_GetBooleanFromObj(interp, objv[i+1], &b) ) return TCL_ERROR;
+ if( Tcl_GetBooleanFromObj(interp, objv[i], &b) ) return TCL_ERROR;
if( b ){
flags |= SQLITE_OPEN_FULLMUTEX;
flags &= ~SQLITE_OPEN_NOMUTEX;
@@ -3459,7 +3567,7 @@ static int SQLITE_TCLAPI DbMain(
}
}else if( strcmp(zArg, "-uri")==0 ){
int b;
- if( Tcl_GetBooleanFromObj(interp, objv[i+1], &b) ) return TCL_ERROR;
+ if( Tcl_GetBooleanFromObj(interp, objv[i], &b) ) return TCL_ERROR;
if( b ){
flags |= SQLITE_OPEN_URI;
}else{
@@ -3470,20 +3578,10 @@ static int SQLITE_TCLAPI DbMain(
return TCL_ERROR;
}
}
- if( objc<3 || (objc&1)!=1 ){
- Tcl_WrongNumArgs(interp, 1, objv,
- "HANDLE FILENAME ?-vfs VFSNAME? ?-readonly BOOLEAN? ?-create BOOLEAN?"
- " ?-nomutex BOOLEAN? ?-fullmutex BOOLEAN? ?-uri BOOLEAN?"
-#if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_CODEC_FROM_TCL)
- " ?-key CODECKEY?"
-#endif
- );
- return TCL_ERROR;
- }
zErrMsg = 0;
p = (SqliteDb*)Tcl_Alloc( sizeof(*p) );
memset(p, 0, sizeof(*p));
- zFile = Tcl_GetStringFromObj(objv[2], 0);
+ if( zFile==0 ) zFile = "";
zFile = Tcl_TranslateFileName(interp, zFile, &translatedFilename);
rc = sqlite3_open_v2(zFile, &p->db, flags, zVfs);
Tcl_DStringFree(&translatedFilename);
diff --git a/src/test1.c b/src/test1.c
index 55d92eb..bc8f389 100644
--- a/src/test1.c
+++ b/src/test1.c
@@ -4560,6 +4560,35 @@ static int SQLITE_TCLAPI test_complete16(
}
/*
+** Usage: sqlite3_normalize SQL
+**
+** Return the normalized value for an SQL statement.
+*/
+static int SQLITE_TCLAPI test_normalize(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ char *zSql;
+ char *zNorm;
+ extern char *sqlite3_normalize(const char*);
+
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "SQL");
+ return TCL_ERROR;
+ }
+
+ zSql = (char*)Tcl_GetString(objv[1]);
+ zNorm = sqlite3_normalize(zSql);
+ if( zNorm ){
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(zNorm, -1));
+ sqlite3_free(zNorm);
+ }
+ return TCL_OK;
+}
+
+/*
** Usage: sqlite3_step STMT
**
** Advance the statement to the next row.
@@ -7547,6 +7576,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
{ "sqlite3_open16", test_open16 ,0 },
{ "sqlite3_open_v2", test_open_v2 ,0 },
{ "sqlite3_complete16", test_complete16 ,0 },
+ { "sqlite3_normalize", test_normalize ,0 },
{ "sqlite3_prepare", test_prepare ,0 },
{ "sqlite3_prepare16", test_prepare16 ,0 },
diff --git a/src/test_config.c b/src/test_config.c
index ad63016..aa0626a 100644
--- a/src/test_config.c
+++ b/src/test_config.c
@@ -148,6 +148,12 @@ static void set_options(Tcl_Interp *interp){
Tcl_SetVar2(interp, "sqlite_options", "hiddencolumns", "0", TCL_GLOBAL_ONLY);
#endif
+#ifdef SQLITE_ENABLE_DESERIALIZE
+ Tcl_SetVar2(interp, "sqlite_options", "deserialize", "1", TCL_GLOBAL_ONLY);
+#else
+ Tcl_SetVar2(interp, "sqlite_options", "deserialize", "0", TCL_GLOBAL_ONLY);
+#endif
+
#ifdef SQLITE_ENABLE_MEMSYS3
Tcl_SetVar2(interp, "sqlite_options", "mem3", "1", TCL_GLOBAL_ONLY);
#else
@@ -501,6 +507,12 @@ Tcl_SetVar2(interp, "sqlite_options", "long_double",
Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY);
+#ifdef SQLITE_ENABLE_NULL_TRIM
+ Tcl_SetVar2(interp, "sqlite_options", "null_trim", "1", TCL_GLOBAL_ONLY);
+#else
+ Tcl_SetVar2(interp, "sqlite_options", "null_trim", "0", TCL_GLOBAL_ONLY);
+#endif
+
#ifdef SQLITE_OMIT_OR_OPTIMIZATION
Tcl_SetVar2(interp, "sqlite_options", "or_opt", "0", TCL_GLOBAL_ONLY);
#else
diff --git a/src/test_malloc.c b/src/test_malloc.c
index b13d9b2..33bc380 100644
--- a/src/test_malloc.c
+++ b/src/test_malloc.c
@@ -1383,6 +1383,7 @@ static int SQLITE_TCLAPI test_db_status(
{ "CACHE_WRITE", SQLITE_DBSTATUS_CACHE_WRITE },
{ "DEFERRED_FKS", SQLITE_DBSTATUS_DEFERRED_FKS },
{ "CACHE_USED_SHARED", SQLITE_DBSTATUS_CACHE_USED_SHARED },
+ { "CACHE_SPILL", SQLITE_DBSTATUS_CACHE_SPILL },
};
Tcl_Obj *pResult;
if( objc!=4 ){
diff --git a/src/test_windirent.h b/src/test_windirent.h
index d71b49f..ada5322 100644
--- a/src/test_windirent.h
+++ b/src/test_windirent.h
@@ -20,7 +20,10 @@
** We need several data types from the Windows SDK header.
*/
+#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
+#endif
+
#include "windows.h"
/*
diff --git a/src/treeview.c b/src/treeview.c
index 6dd386f..2e92c99 100644
--- a/src/treeview.c
+++ b/src/treeview.c
@@ -137,11 +137,21 @@ void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){
sqlite3TreeViewPush(pView, 1);
}
do{
+#if SELECTTRACE_ENABLED
+ sqlite3TreeViewLine(pView,
+ "SELECT%s%s (%s/%p) selFlags=0x%x nSelectRow=%d",
+ ((p->selFlags & SF_Distinct) ? " DISTINCT" : ""),
+ ((p->selFlags & SF_Aggregate) ? " agg_flag" : ""),
+ p->zSelName, p, p->selFlags,
+ (int)p->nSelectRow
+ );
+#else
sqlite3TreeViewLine(pView, "SELECT%s%s (0x%p) selFlags=0x%x nSelectRow=%d",
((p->selFlags & SF_Distinct) ? " DISTINCT" : ""),
((p->selFlags & SF_Aggregate) ? " agg_flag" : ""), p, p->selFlags,
(int)p->nSelectRow
);
+#endif
if( cnt++ ) sqlite3TreeViewPop(pView);
if( p->pPrior ){
n = 1000;
@@ -292,6 +302,11 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
sqlite3TreeViewLine(pView,"NULL");
break;
}
+ case TK_TRUEFALSE: {
+ sqlite3TreeViewLine(pView,
+ sqlite3ExprTruthValue(pExpr) ? "TRUE" : "FALSE");
+ break;
+ }
#ifndef SQLITE_OMIT_BLOB_LITERAL
case TK_BLOB: {
sqlite3TreeViewLine(pView,"%s", pExpr->u.zToken);
@@ -348,6 +363,19 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
case TK_ISNULL: zUniOp = "ISNULL"; break;
case TK_NOTNULL: zUniOp = "NOTNULL"; break;
+ case TK_TRUTH: {
+ int x;
+ const char *azOp[] = {
+ "IS-FALSE", "IS-TRUE", "IS-NOT-FALSE", "IS-NOT-TRUE"
+ };
+ assert( pExpr->op2==TK_IS || pExpr->op2==TK_ISNOT );
+ assert( pExpr->pRight );
+ assert( pExpr->pRight->op==TK_TRUEFALSE );
+ x = (pExpr->op2==TK_ISNOT)*2 + sqlite3ExprTruthValue(pExpr->pRight);
+ zUniOp = azOp[x];
+ break;
+ }
+
case TK_SPAN: {
sqlite3TreeViewLine(pView, "SPAN %Q", pExpr->u.zToken);
sqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
diff --git a/src/util.c b/src/util.c
index 456317c..54f9b93 100644
--- a/src/util.c
+++ b/src/util.c
@@ -595,7 +595,7 @@ static int compare2pow63(const char *zNum, int incr){
** Returns:
**
** 0 Successful transformation. Fits in a 64-bit signed integer.
-** 1 Excess text after the integer value
+** 1 Excess non-space text after the integer value
** 2 Integer too large for a 64-bit signed integer or is malformed
** 3 Special case of 9223372036854775808
**
@@ -638,47 +638,57 @@ int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc){
for(i=0; &zNum[i]<zEnd && (c=zNum[i])>='0' && c<='9'; i+=incr){
u = u*10 + c - '0';
}
+ testcase( i==18*incr );
+ testcase( i==19*incr );
+ testcase( i==20*incr );
if( u>LARGEST_INT64 ){
+ /* This test and assignment is needed only to suppress UB warnings
+ ** from clang and -fsanitize=undefined. This test and assignment make
+ ** the code a little larger and slower, and no harm comes from omitting
+ ** them, but we must appaise the undefined-behavior pharisees. */
*pNum = neg ? SMALLEST_INT64 : LARGEST_INT64;
}else if( neg ){
*pNum = -(i64)u;
}else{
*pNum = (i64)u;
}
- testcase( i==18 );
- testcase( i==19 );
- testcase( i==20 );
- if( &zNum[i]<zEnd /* Extra bytes at the end */
- || (i==0 && zStart==zNum) /* No digits */
+ rc = 0;
+ if( (i==0 && zStart==zNum) /* No digits */
|| nonNum /* UTF16 with high-order bytes non-zero */
){
rc = 1;
- }else{
- rc = 0;
+ }else if( &zNum[i]<zEnd ){ /* Extra bytes at the end */
+ int jj = i;
+ do{
+ if( !sqlite3Isspace(zNum[jj]) ){
+ rc = 1; /* Extra non-space text after the integer */
+ break;
+ }
+ jj += incr;
+ }while( &zNum[jj]<zEnd );
}
- if( i>19*incr ){ /* Too many digits */
- /* zNum is empty or contains non-numeric text or is longer
- ** than 19 digits (thus guaranteeing that it is too large) */
- return 2;
- }else if( i<19*incr ){
+ if( i<19*incr ){
/* Less than 19 digits, so we know that it fits in 64 bits */
assert( u<=LARGEST_INT64 );
return rc;
}else{
/* zNum is a 19-digit numbers. Compare it against 9223372036854775808. */
- c = compare2pow63(zNum, incr);
+ c = i>19*incr ? 1 : compare2pow63(zNum, incr);
if( c<0 ){
/* zNum is less than 9223372036854775808 so it fits */
assert( u<=LARGEST_INT64 );
return rc;
- }else if( c>0 ){
- /* zNum is greater than 9223372036854775808 so it overflows */
- return 2;
}else{
- /* zNum is exactly 9223372036854775808. Fits if negative. The
- ** special case 2 overflow if positive */
- assert( u-1==LARGEST_INT64 );
- return neg ? rc : 3;
+ *pNum = neg ? SMALLEST_INT64 : LARGEST_INT64;
+ if( c>0 ){
+ /* zNum is greater than 9223372036854775808 so it overflows */
+ return 2;
+ }else{
+ /* zNum is exactly 9223372036854775808. Fits if negative. The
+ ** special case 2 overflow if positive */
+ assert( u-1==LARGEST_INT64 );
+ return neg ? rc : 3;
+ }
}
}
}
diff --git a/src/vacuum.c b/src/vacuum.c
index fde08dd..c7a14a6 100644
--- a/src/vacuum.c
+++ b/src/vacuum.c
@@ -39,8 +39,8 @@ static int execSql(sqlite3 *db, char **pzErrMsg, const char *zSql){
while( SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){
const char *zSubSql = (const char*)sqlite3_column_text(pStmt,0);
assert( sqlite3_strnicmp(zSql,"SELECT",6)==0 );
- if( zSubSql ){
- assert( zSubSql[0]!='S' );
+ assert( sqlite3_strnicmp(zSubSql,"SELECT",6)!=0 || CORRUPT_DB );
+ if( zSubSql && zSubSql[0]!='S' ){
rc = execSql(db, pzErrMsg, zSubSql);
if( rc!=SQLITE_OK ) break;
}
diff --git a/src/vdbe.c b/src/vdbe.c
index d878ca8..70537ce 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -264,6 +264,11 @@ static void applyNumericAffinity(Mem *pRec, int bTryForInt){
pRec->flags |= MEM_Real;
if( bTryForInt ) sqlite3VdbeIntegerAffinity(pRec);
}
+ /* TEXT->NUMERIC is many->one. Hence, it is important to invalidate the
+ ** string representation after computing a numeric equivalent, because the
+ ** string representation might not be the canonical representation for the
+ ** numeric value. Ticket [343634942dd54ab57b7024] 2018-01-31. */
+ pRec->flags &= ~MEM_Str;
}
/*
@@ -643,7 +648,7 @@ int sqlite3VdbeExec(
assert( pOp>=aOp && pOp<&aOp[p->nOp]);
#ifdef VDBE_PROFILE
- start = sqlite3Hwtime();
+ start = sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime();
#endif
nVmStep++;
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
@@ -2167,18 +2172,8 @@ case OP_Or: { /* same as TK_OR, in1, in2, out3 */
int v1; /* Left operand: 0==FALSE, 1==TRUE, 2==UNKNOWN or NULL */
int v2; /* Right operand: 0==FALSE, 1==TRUE, 2==UNKNOWN or NULL */
- pIn1 = &aMem[pOp->p1];
- if( pIn1->flags & MEM_Null ){
- v1 = 2;
- }else{
- v1 = sqlite3VdbeIntValue(pIn1)!=0;
- }
- pIn2 = &aMem[pOp->p2];
- if( pIn2->flags & MEM_Null ){
- v2 = 2;
- }else{
- v2 = sqlite3VdbeIntValue(pIn2)!=0;
- }
+ v1 = sqlite3VdbeBooleanValue(&aMem[pOp->p1], 2);
+ v2 = sqlite3VdbeBooleanValue(&aMem[pOp->p2], 2);
if( pOp->opcode==OP_And ){
static const unsigned char and_logic[] = { 0, 0, 0, 0, 1, 2, 0, 2, 2 };
v1 = and_logic[v1*3+v2];
@@ -2196,6 +2191,35 @@ case OP_Or: { /* same as TK_OR, in1, in2, out3 */
break;
}
+/* Opcode: IsTrue P1 P2 P3 P4 *
+** Synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4
+**
+** This opcode implements the IS TRUE, IS FALSE, IS NOT TRUE, and
+** IS NOT FALSE operators.
+**
+** Interpret the value in register P1 as a boolean value. Store that
+** boolean (a 0 or 1) in register P2. Or if the value in register P1 is
+** NULL, then the P3 is stored in register P2. Invert the answer if P4
+** is 1.
+**
+** The logic is summarized like this:
+**
+** <ul>
+** <li> If P3==0 and P4==0 then r[P2] := r[P1] IS TRUE
+** <li> If P3==1 and P4==1 then r[P2] := r[P1] IS FALSE
+** <li> If P3==0 and P4==1 then r[P2] := r[P1] IS NOT TRUE
+** <li> If P3==1 and P4==0 then r[P2] := r[P1] IS NOT FALSE
+** </ul>
+*/
+case OP_IsTrue: { /* in1, out2 */
+ assert( pOp->p4type==P4_INT32 );
+ assert( pOp->p4.i==0 || pOp->p4.i==1 );
+ assert( pOp->p3==0 || pOp->p3==1 );
+ sqlite3VdbeMemSetInt64(&aMem[pOp->p2],
+ sqlite3VdbeBooleanValue(&aMem[pOp->p1], pOp->p3) ^ pOp->p4.i);
+ break;
+}
+
/* Opcode: Not P1 P2 * * *
** Synopsis: r[P2]= !r[P1]
**
@@ -2206,10 +2230,10 @@ case OP_Or: { /* same as TK_OR, in1, in2, out3 */
case OP_Not: { /* same as TK_NOT, in1, out2 */
pIn1 = &aMem[pOp->p1];
pOut = &aMem[pOp->p2];
- sqlite3VdbeMemSetNull(pOut);
if( (pIn1->flags & MEM_Null)==0 ){
- pOut->flags = MEM_Int;
- pOut->u.i = !sqlite3VdbeIntValue(pIn1);
+ sqlite3VdbeMemSetInt64(pOut, !sqlite3VdbeBooleanValue(pIn1,0));
+ }else{
+ sqlite3VdbeMemSetNull(pOut);
}
break;
}
@@ -2276,30 +2300,25 @@ case OP_Once: { /* jump */
** is considered true if it is numeric and non-zero. If the value
** in P1 is NULL then take the jump if and only if P3 is non-zero.
*/
+case OP_If: { /* jump, in1 */
+ int c;
+ c = sqlite3VdbeBooleanValue(&aMem[pOp->p1], pOp->p3);
+ VdbeBranchTaken(c!=0, 2);
+ if( c ) goto jump_to_p2;
+ break;
+}
+
/* Opcode: IfNot P1 P2 P3 * *
**
** Jump to P2 if the value in register P1 is False. The value
** is considered false if it has a numeric value of zero. If the value
** in P1 is NULL then take the jump if and only if P3 is non-zero.
*/
-case OP_If: /* jump, in1 */
case OP_IfNot: { /* jump, in1 */
int c;
- pIn1 = &aMem[pOp->p1];
- if( pIn1->flags & MEM_Null ){
- c = pOp->p3;
- }else{
-#ifdef SQLITE_OMIT_FLOATING_POINT
- c = sqlite3VdbeIntValue(pIn1)!=0;
-#else
- c = sqlite3VdbeRealValue(pIn1)!=0.0;
-#endif
- if( pOp->opcode==OP_IfNot ) c = !c;
- }
+ c = !sqlite3VdbeBooleanValue(&aMem[pOp->p1], !pOp->p3);
VdbeBranchTaken(c!=0, 2);
- if( c ){
- goto jump_to_p2;
- }
+ if( c ) goto jump_to_p2;
break;
}
@@ -2360,7 +2379,7 @@ case OP_IfNullRow: { /* jump */
** P2 is the column number for the argument to the sqlite_offset() function.
** This opcode does not use P2 itself, but the P2 value is used by the
** code generator. The P1, P2, and P3 operands to this opcode are the
-** as as for OP_Column.
+** same as for OP_Column.
**
** This opcode is only available if SQLite is compiled with the
** -DSQLITE_ENABLE_OFFSET_SQL_FUNC option.
@@ -4268,6 +4287,10 @@ case OP_NewRowid: { /* out2 */
pOut = out2Prerelease(p, pOp);
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
pC = p->apCsr[pOp->p1];
+ if( !pC->isTable ){
+ rc = SQLITE_CORRUPT_BKPT;
+ goto abort_due_to_error;
+ }
assert( pC!=0 );
assert( pC->eCurType==CURTYPE_BTREE );
assert( pC->uc.pCursor!=0 );
@@ -6204,12 +6227,17 @@ case OP_AggStep0: {
assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) );
assert( n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1) );
assert( pOp->p3<pOp->p2 || pOp->p3>=pOp->p2+n );
- pCtx = sqlite3DbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sqlite3_value*));
+ pCtx = sqlite3DbMallocRawNN(db, n*sizeof(sqlite3_value*) +
+ (sizeof(pCtx[0]) + sizeof(Mem) - sizeof(sqlite3_value*)));
if( pCtx==0 ) goto no_mem;
pCtx->pMem = 0;
+ pCtx->pOut = (Mem*)&(pCtx->argv[n]);
+ sqlite3VdbeMemInit(pCtx->pOut, db, MEM_Null);
pCtx->pFunc = pOp->p4.pFunc;
pCtx->iOp = (int)(pOp - aOp);
pCtx->pVdbe = p;
+ pCtx->skipFlag = 0;
+ pCtx->isError = 0;
pCtx->argc = n;
pOp->p4type = P4_FUNCCTX;
pOp->p4.pCtx = pCtx;
@@ -6220,7 +6248,6 @@ case OP_AggStep: {
int i;
sqlite3_context *pCtx;
Mem *pMem;
- Mem t;
assert( pOp->p4type==P4_FUNCCTX );
pCtx = pOp->p4.pCtx;
@@ -6243,26 +6270,28 @@ case OP_AggStep: {
#endif
pMem->n++;
- sqlite3VdbeMemInit(&t, db, MEM_Null);
- pCtx->pOut = &t;
- pCtx->fErrorOrAux = 0;
- pCtx->skipFlag = 0;
+ assert( pCtx->pOut->flags==MEM_Null );
+ assert( pCtx->isError==0 );
+ assert( pCtx->skipFlag==0 );
(pCtx->pFunc->xSFunc)(pCtx,pCtx->argc,pCtx->argv); /* IMP: R-24505-23230 */
- if( pCtx->fErrorOrAux ){
- if( pCtx->isError ){
- sqlite3VdbeError(p, "%s", sqlite3_value_text(&t));
+ if( pCtx->isError ){
+ if( pCtx->isError>0 ){
+ sqlite3VdbeError(p, "%s", sqlite3_value_text(pCtx->pOut));
rc = pCtx->isError;
}
- sqlite3VdbeMemRelease(&t);
+ if( pCtx->skipFlag ){
+ assert( pOp[-1].opcode==OP_CollSeq );
+ i = pOp[-1].p1;
+ if( i ) sqlite3VdbeMemSetInt64(&aMem[i], 1);
+ pCtx->skipFlag = 0;
+ }
+ sqlite3VdbeMemRelease(pCtx->pOut);
+ pCtx->pOut->flags = MEM_Null;
+ pCtx->isError = 0;
if( rc ) goto abort_due_to_error;
- }else{
- assert( t.flags==MEM_Null );
- }
- if( pCtx->skipFlag ){
- assert( pOp[-1].opcode==OP_CollSeq );
- i = pOp[-1].p1;
- if( i ) sqlite3VdbeMemSetInt64(&aMem[i], 1);
}
+ assert( pCtx->pOut->flags==MEM_Null );
+ assert( pCtx->skipFlag==0 );
break;
}
@@ -6749,7 +6778,8 @@ case OP_VColumn: {
}
rc = pModule->xColumn(pCur->uc.pVCur, &sContext, pOp->p2);
sqlite3VtabImportErrmsg(p, pVtab);
- if( sContext.isError ){
+ if( sContext.isError>0 ){
+ sqlite3VdbeError(p, "%s", sqlite3_value_text(pDest));
rc = sContext.isError;
}
sqlite3VdbeChangeEncoding(pDest, encoding);
@@ -7014,6 +7044,7 @@ case OP_Function0: {
pCtx->pFunc = pOp->p4.pFunc;
pCtx->iOp = (int)(pOp - aOp);
pCtx->pVdbe = p;
+ pCtx->isError = 0;
pCtx->argc = n;
pOp->p4type = P4_FUNCCTX;
pOp->p4.pCtx = pCtx;
@@ -7048,16 +7079,17 @@ case OP_Function: {
}
#endif
MemSetTypeFlag(pOut, MEM_Null);
- pCtx->fErrorOrAux = 0;
+ assert( pCtx->isError==0 );
(*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */
/* If the function returned an error, throw an exception */
- if( pCtx->fErrorOrAux ){
- if( pCtx->isError ){
+ if( pCtx->isError ){
+ if( pCtx->isError>0 ){
sqlite3VdbeError(p, "%s", sqlite3_value_text(pOut));
rc = pCtx->isError;
}
sqlite3VdbeDeleteAuxData(db, &p->pAuxData, pCtx->iOp, pOp->p1);
+ pCtx->isError = 0;
if( rc ) goto abort_due_to_error;
}
@@ -7099,8 +7131,10 @@ case OP_Function: {
*/
case OP_Trace:
case OP_Init: { /* jump */
- char *zTrace;
int i;
+#ifndef SQLITE_OMIT_TRACE
+ char *zTrace;
+#endif
/* If the P4 argument is not NULL, then it must be an SQL comment string.
** The "--" string is broken up to prevent false-positives with srcck1.c.
@@ -7217,7 +7251,7 @@ default: { /* This is really OP_Noop and OP_Explain */
#ifdef VDBE_PROFILE
{
- u64 endTime = sqlite3Hwtime();
+ u64 endTime = sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime();
if( endTime>start ) pOrigOp->cycles += endTime - start;
pOrigOp->cnt++;
}
diff --git a/src/vdbeInt.h b/src/vdbeInt.h
index 976abb3..44f901a 100644
--- a/src/vdbeInt.h
+++ b/src/vdbeInt.h
@@ -224,8 +224,6 @@ struct sqlite3_value {
** If the MEM_Null flag is set, then the value is an SQL NULL value.
** For a pointer type created using sqlite3_bind_pointer() or
** sqlite3_result_pointer() the MEM_Term and MEM_Subtype flags are also set.
-** If both MEM_Null and MEM_Zero are set, that means that the value is
-** an unchanging column value from VColumn.
**
** If the MEM_Str flag is set then Mem.z points at a string representation.
** Usually this is encoded in the same unicode encoding as the main
@@ -319,7 +317,6 @@ struct sqlite3_context {
int iOp; /* Instruction number of OP_Function */
int isError; /* Error code returned by the function. */
u8 skipFlag; /* Skip accumulator loading if true */
- u8 fErrorOrAux; /* isError!=0 or pVdbe->pAuxData modified */
u8 argc; /* Number of arguments */
sqlite3_value *argv[1]; /* Argument set */
};
@@ -489,6 +486,7 @@ int sqlite3VdbeMemStringify(Mem*, u8, u8);
i64 sqlite3VdbeIntValue(Mem*);
int sqlite3VdbeMemIntegerify(Mem*);
double sqlite3VdbeRealValue(Mem*);
+int sqlite3VdbeBooleanValue(Mem*, int ifNull);
void sqlite3VdbeIntegerAffinity(Mem*);
int sqlite3VdbeMemRealify(Mem*);
int sqlite3VdbeMemNumerify(Mem*);
diff --git a/src/vdbeapi.c b/src/vdbeapi.c
index bdd6d1c..2a6e1f8 100644
--- a/src/vdbeapi.c
+++ b/src/vdbeapi.c
@@ -372,14 +372,12 @@ void sqlite3_result_double(sqlite3_context *pCtx, double rVal){
void sqlite3_result_error(sqlite3_context *pCtx, const char *z, int n){
assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
pCtx->isError = SQLITE_ERROR;
- pCtx->fErrorOrAux = 1;
sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF8, SQLITE_TRANSIENT);
}
#ifndef SQLITE_OMIT_UTF16
void sqlite3_result_error16(sqlite3_context *pCtx, const void *z, int n){
assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
pCtx->isError = SQLITE_ERROR;
- pCtx->fErrorOrAux = 1;
sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF16NATIVE, SQLITE_TRANSIENT);
}
#endif
@@ -485,8 +483,7 @@ int sqlite3_result_zeroblob64(sqlite3_context *pCtx, u64 n){
return SQLITE_OK;
}
void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){
- pCtx->isError = errCode;
- pCtx->fErrorOrAux = 1;
+ pCtx->isError = errCode ? errCode : -1;
#ifdef SQLITE_DEBUG
if( pCtx->pVdbe ) pCtx->pVdbe->rcApp = errCode;
#endif
@@ -500,7 +497,6 @@ void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){
void sqlite3_result_error_toobig(sqlite3_context *pCtx){
assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
pCtx->isError = SQLITE_TOOBIG;
- pCtx->fErrorOrAux = 1;
sqlite3VdbeMemSetStr(pCtx->pOut, "string or blob too big", -1,
SQLITE_UTF8, SQLITE_STATIC);
}
@@ -510,7 +506,6 @@ void sqlite3_result_error_nomem(sqlite3_context *pCtx){
assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
sqlite3VdbeMemSetNull(pCtx->pOut);
pCtx->isError = SQLITE_NOMEM_BKPT;
- pCtx->fErrorOrAux = 1;
sqlite3OomFault(pCtx->pOut->db);
}
@@ -917,10 +912,7 @@ void sqlite3_set_auxdata(
pAuxData->iAuxArg = iArg;
pAuxData->pNextAux = pVdbe->pAuxData;
pVdbe->pAuxData = pAuxData;
- if( pCtx->fErrorOrAux==0 ){
- pCtx->isError = 0;
- pCtx->fErrorOrAux = 1;
- }
+ if( pCtx->isError==0 ) pCtx->isError = -1;
}else if( pAuxData->xDeleteAux ){
pAuxData->xDeleteAux(pAuxData->pAux);
}
@@ -1676,7 +1668,9 @@ int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){
Vdbe *pVdbe = (Vdbe*)pStmt;
u32 v;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !pStmt ){
+ if( !pStmt
+ || (op!=SQLITE_STMTSTATUS_MEMUSED && (op<0||op>=ArraySize(pVdbe->aCounter)))
+ ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
diff --git a/src/vdbemem.c b/src/vdbemem.c
index d8f1e64..8df3c0d 100644
--- a/src/vdbemem.c
+++ b/src/vdbemem.c
@@ -93,6 +93,51 @@ int sqlite3VdbeCheckMemInvariants(Mem *p){
}
#endif
+#ifdef SQLITE_DEBUG
+/*
+** Check that string value of pMem agrees with its integer or real value.
+**
+** A single int or real value always converts to the same strings. But
+** many different strings can be converted into the same int or real.
+** If a table contains a numeric value and an index is based on the
+** corresponding string value, then it is important that the string be
+** derived from the numeric value, not the other way around, to ensure
+** that the index and table are consistent. See ticket
+** https://www.sqlite.org/src/info/343634942dd54ab (2018-01-31) for
+** an example.
+**
+** This routine looks at pMem to verify that if it has both a numeric
+** representation and a string representation then the string rep has
+** been derived from the numeric and not the other way around. It returns
+** true if everything is ok and false if there is a problem.
+**
+** This routine is for use inside of assert() statements only.
+*/
+int sqlite3VdbeMemConsistentDualRep(Mem *p){
+ char zBuf[100];
+ char *z;
+ int i, j, incr;
+ if( (p->flags & MEM_Str)==0 ) return 1;
+ if( (p->flags & (MEM_Int|MEM_Real))==0 ) return 1;
+ if( p->flags & MEM_Int ){
+ sqlite3_snprintf(sizeof(zBuf),zBuf,"%lld",p->u.i);
+ }else{
+ sqlite3_snprintf(sizeof(zBuf),zBuf,"%!.15g",p->u.r);
+ }
+ z = p->z;
+ i = j = 0;
+ incr = 1;
+ if( p->enc!=SQLITE_UTF8 ){
+ incr = 2;
+ if( p->enc==SQLITE_UTF16BE ) z++;
+ }
+ while( zBuf[j] ){
+ if( zBuf[j++]!=z[i] ) return 0;
+ i += incr;
+ }
+ return 1;
+}
+#endif /* SQLITE_DEBUG */
/*
** If pMem is an object with a valid string representation, this routine
@@ -528,6 +573,16 @@ double sqlite3VdbeRealValue(Mem *pMem){
}
/*
+** Return 1 if pMem represents true, and return 0 if pMem represents false.
+** Return the value ifNull if pMem is NULL.
+*/
+int sqlite3VdbeBooleanValue(Mem *pMem, int ifNull){
+ if( pMem->flags & MEM_Int ) return pMem->u.i!=0;
+ if( pMem->flags & MEM_Null ) return ifNull;
+ return sqlite3VdbeRealValue(pMem)!=0.0;
+}
+
+/*
** The MEM structure is already a MEM_Real. Try to also make it a
** MEM_Int if we can.
*/
@@ -582,6 +637,18 @@ int sqlite3VdbeMemRealify(Mem *pMem){
return SQLITE_OK;
}
+/* Compare a floating point value to an integer. Return true if the two
+** values are the same within the precision of the floating point value.
+**
+** For some versions of GCC on 32-bit machines, if you do the more obvious
+** comparison of "r1==(double)i" you sometimes get an answer of false even
+** though the r1 and (double)i values are bit-for-bit the same.
+*/
+static int sqlite3RealSameAsInt(double r1, sqlite3_int64 i){
+ double r2 = (double)i;
+ return memcmp(&r1, &r2, sizeof(r1))==0;
+}
+
/*
** Convert pMem so that it has types MEM_Real or MEM_Int or both.
** Invalidate any prior representations.
@@ -601,7 +668,7 @@ int sqlite3VdbeMemNumerify(Mem *pMem){
}else{
i64 i = pMem->u.i;
sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc);
- if( rc==1 && pMem->u.r==(double)i ){
+ if( rc==1 && sqlite3RealSameAsInt(pMem->u.r, i) ){
pMem->u.i = i;
MemSetTypeFlag(pMem, MEM_Int);
}else{
@@ -1084,6 +1151,7 @@ static SQLITE_NOINLINE const void *valueToText(sqlite3_value* pVal, u8 enc){
assert(pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) || pVal->db==0
|| pVal->db->mallocFailed );
if( pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) ){
+ assert( sqlite3VdbeMemConsistentDualRep(pVal) );
return pVal->z;
}else{
return 0;
@@ -1106,6 +1174,7 @@ const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){
assert( (enc&3)==(enc&~SQLITE_UTF16_ALIGNED) );
assert( (pVal->flags & MEM_RowSet)==0 );
if( (pVal->flags&(MEM_Str|MEM_Term))==(MEM_Str|MEM_Term) && pVal->enc==enc ){
+ assert( sqlite3VdbeMemConsistentDualRep(pVal) );
return pVal->z;
}
if( pVal->flags&MEM_Null ){
diff --git a/src/wal.c b/src/wal.c
index 7b49f3d..69a89a7 100644
--- a/src/wal.c
+++ b/src/wal.c
@@ -554,7 +554,11 @@ struct WalIterator {
** page and SQLITE_OK is returned. If an error (an OOM or VFS error) occurs,
** then an SQLite error code is returned and *ppPage is set to 0.
*/
-static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){
+static SQLITE_NOINLINE int walIndexPageRealloc(
+ Wal *pWal, /* The WAL context */
+ int iPage, /* The page we seek */
+ volatile u32 **ppPage /* Write the page pointer here */
+){
int rc = SQLITE_OK;
/* Enlarge the pWal->apWiData[] array if required */
@@ -573,21 +577,20 @@ static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){
}
/* Request a pointer to the required page from the VFS */
- if( pWal->apWiData[iPage]==0 ){
- if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){
- pWal->apWiData[iPage] = (u32 volatile *)sqlite3MallocZero(WALINDEX_PGSZ);
- if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM_BKPT;
- }else{
- rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ,
- pWal->writeLock, (void volatile **)&pWal->apWiData[iPage]
- );
- assert( pWal->apWiData[iPage]!=0 || rc!=SQLITE_OK || pWal->writeLock==0 );
- testcase( pWal->apWiData[iPage]==0 && rc==SQLITE_OK );
- if( (rc&0xff)==SQLITE_READONLY ){
- pWal->readOnly |= WAL_SHM_RDONLY;
- if( rc==SQLITE_READONLY ){
- rc = SQLITE_OK;
- }
+ assert( pWal->apWiData[iPage]==0 );
+ if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){
+ pWal->apWiData[iPage] = (u32 volatile *)sqlite3MallocZero(WALINDEX_PGSZ);
+ if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM_BKPT;
+ }else{
+ rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ,
+ pWal->writeLock, (void volatile **)&pWal->apWiData[iPage]
+ );
+ assert( pWal->apWiData[iPage]!=0 || rc!=SQLITE_OK || pWal->writeLock==0 );
+ testcase( pWal->apWiData[iPage]==0 && rc==SQLITE_OK );
+ if( (rc&0xff)==SQLITE_READONLY ){
+ pWal->readOnly |= WAL_SHM_RDONLY;
+ if( rc==SQLITE_READONLY ){
+ rc = SQLITE_OK;
}
}
}
@@ -596,6 +599,16 @@ static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){
assert( iPage==0 || *ppPage || rc!=SQLITE_OK );
return rc;
}
+static int walIndexPage(
+ Wal *pWal, /* The WAL context */
+ int iPage, /* The page we seek */
+ volatile u32 **ppPage /* Write the page pointer here */
+){
+ if( pWal->nWiData<=iPage || (*ppPage = pWal->apWiData[iPage])==0 ){
+ return walIndexPageRealloc(pWal, iPage, ppPage);
+ }
+ return SQLITE_OK;
+}
/*
** Return a pointer to the WalCkptInfo structure in the wal-index.
@@ -1572,8 +1585,9 @@ static void walIteratorFree(WalIterator *p){
/*
** Construct a WalInterator object that can be used to loop over all
-** pages in the WAL in ascending order. The caller must hold the checkpoint
-** lock.
+** pages in the WAL following frame nBackfill in ascending order. Frames
+** nBackfill or earlier may be included - excluding them is an optimization
+** only. The caller must hold the checkpoint lock.
**
** On success, make *pp point to the newly allocated WalInterator object
** return SQLITE_OK. Otherwise, return an error code. If this routine
@@ -1582,7 +1596,7 @@ static void walIteratorFree(WalIterator *p){
** The calling routine should invoke walIteratorFree() to destroy the
** WalIterator object when it has finished with it.
*/
-static int walIteratorInit(Wal *pWal, WalIterator **pp){
+static int walIteratorInit(Wal *pWal, u32 nBackfill, WalIterator **pp){
WalIterator *p; /* Return value */
int nSegment; /* Number of segments to merge */
u32 iLast; /* Last frame in log */
@@ -1619,7 +1633,7 @@ static int walIteratorInit(Wal *pWal, WalIterator **pp){
rc = SQLITE_NOMEM_BKPT;
}
- for(i=0; rc==SQLITE_OK && i<nSegment; i++){
+ for(i=walFramePage(nBackfill+1); rc==SQLITE_OK && i<nSegment; i++){
volatile ht_slot *aHash;
u32 iZero;
volatile u32 *aPgno;
@@ -1653,6 +1667,7 @@ static int walIteratorInit(Wal *pWal, WalIterator **pp){
if( rc!=SQLITE_OK ){
walIteratorFree(p);
+ p = 0;
}
*pp = p;
return rc;
@@ -1775,13 +1790,6 @@ static int walCheckpoint(
pInfo = walCkptInfo(pWal);
if( pInfo->nBackfill<pWal->hdr.mxFrame ){
- /* Allocate the iterator */
- rc = walIteratorInit(pWal, &pIter);
- if( rc!=SQLITE_OK ){
- return rc;
- }
- assert( pIter );
-
/* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked
** in the SQLITE_CHECKPOINT_PASSIVE mode. */
assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 );
@@ -1818,7 +1826,13 @@ static int walCheckpoint(
}
}
- if( pInfo->nBackfill<mxSafeFrame
+ /* Allocate the iterator */
+ if( pInfo->nBackfill<mxSafeFrame ){
+ rc = walIteratorInit(pWal, pInfo->nBackfill, &pIter);
+ assert( rc==SQLITE_OK || pIter==0 );
+ }
+
+ if( pIter
&& (rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(0),1))==SQLITE_OK
){
i64 nSize; /* Current size of database file */
@@ -2868,7 +2882,7 @@ int sqlite3WalFindFrame(
** table after the current read-transaction had started.
*/
iMinHash = walFramePage(pWal->minFrame);
- for(iHash=walFramePage(iLast); iHash>=iMinHash && iRead==0; iHash--){
+ for(iHash=walFramePage(iLast); iHash>=iMinHash; iHash--){
volatile ht_slot *aHash; /* Pointer to hash table */
volatile u32 *aPgno; /* Pointer to array of page numbers */
u32 iZero; /* Frame number corresponding to aPgno[0] */
@@ -2891,6 +2905,7 @@ int sqlite3WalFindFrame(
return SQLITE_CORRUPT_BKPT;
}
}
+ if( iRead ) break;
}
#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT
diff --git a/src/where.c b/src/where.c
index 3152c8e..8c1e3cd 100644
--- a/src/where.c
+++ b/src/where.c
@@ -2481,10 +2481,12 @@ static int whereLoopAddBtreeIndex(
if( iCol==XN_ROWID
|| (iCol>=0 && nInMul==0 && saved_nEq==pProbe->nKeyCol-1)
){
- if( iCol>=0 && pProbe->uniqNotNull==0 ){
- pNew->wsFlags |= WHERE_UNQ_WANTED;
- }else{
+ if( iCol==XN_ROWID || pProbe->uniqNotNull
+ || (pProbe->nKeyCol==1 && pProbe->onError && eOp==WO_EQ)
+ ){
pNew->wsFlags |= WHERE_ONEROW;
+ }else{
+ pNew->wsFlags |= WHERE_UNQ_WANTED;
}
}
}else if( eOp & WO_ISNULL ){
@@ -4631,6 +4633,7 @@ WhereInfo *sqlite3WhereBegin(
*/
for(ii=0; ii<sWLB.pWC->nTerm; ii++){
WhereTerm *pT = &sWLB.pWC->a[ii];
+ if( pT->wtFlags & TERM_VIRTUAL ) continue;
if( pT->prereqAll==0 && (nTabList==0 || exprIsDeterministic(pT->pExpr)) ){
sqlite3ExprIfFalse(pParse, pT->pExpr, pWInfo->iBreak, SQLITE_JUMPIFNULL);
pT->wtFlags |= TERM_CODED;
diff --git a/src/wherecode.c b/src/wherecode.c
index 32dd204..47fc92f 100644
--- a/src/wherecode.c
+++ b/src/wherecode.c
@@ -1395,7 +1395,15 @@ Bitmask sqlite3WhereCodeOneLoopStart(
if( sqlite3ExprIsVector(pX->pRight) ){
r1 = rTemp = sqlite3GetTempReg(pParse);
codeExprOrVector(pParse, pX->pRight, r1, 1);
- op = aMoveOp[(pX->op - TK_GT) | 0x0001];
+ testcase( pX->op==TK_GT );
+ testcase( pX->op==TK_GE );
+ testcase( pX->op==TK_LT );
+ testcase( pX->op==TK_LE );
+ op = aMoveOp[((pX->op - TK_GT - 1) & 0x3) | 0x1];
+ assert( pX->op!=TK_GT || op==OP_SeekGE );
+ assert( pX->op!=TK_GE || op==OP_SeekGE );
+ assert( pX->op!=TK_LT || op==OP_SeekLE );
+ assert( pX->op!=TK_LE || op==OP_SeekLE );
}else{
r1 = sqlite3ExprCodeTemp(pParse, pX->pRight, &rTemp);
disableTerm(pLevel, pStart);
@@ -2119,7 +2127,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
continue;
}
- if( pTerm->wtFlags & TERM_LIKECOND ){
+ if( (pTerm->wtFlags & TERM_LIKECOND)!=0 ){
/* If the TERM_LIKECOND flag is set, that means that the range search
** is sufficient to guarantee that the LIKE operator is true, so we
** can skip the call to the like(A,B) function. But this only works
@@ -2129,8 +2137,9 @@ Bitmask sqlite3WhereCodeOneLoopStart(
continue;
#else
u32 x = pLevel->iLikeRepCntr;
- assert( x>0 );
- skipLikeAddr = sqlite3VdbeAddOp1(v, (x&1)?OP_IfNot:OP_If, (int)(x>>1));
+ if( x>0 ){
+ skipLikeAddr = sqlite3VdbeAddOp1(v, (x&1)?OP_IfNot:OP_If,(int)(x>>1));
+ }
VdbeCoverage(v);
#endif
}
@@ -2170,6 +2179,12 @@ Bitmask sqlite3WhereCodeOneLoopStart(
WO_EQ|WO_IN|WO_IS, 0);
if( pAlt==0 ) continue;
if( pAlt->wtFlags & (TERM_CODED) ) continue;
+ if( (pAlt->eOperator & WO_IN)
+ && (pAlt->pExpr->flags & EP_xIsSelect)
+ && (pAlt->pExpr->x.pSelect->pEList->nExpr>1)
+ ){
+ continue;
+ }
testcase( pAlt->eOperator & WO_EQ );
testcase( pAlt->eOperator & WO_IS );
testcase( pAlt->eOperator & WO_IN );
diff --git a/src/whereexpr.c b/src/whereexpr.c
index 58f1908..313c5ee 100644
--- a/src/whereexpr.c
+++ b/src/whereexpr.c
@@ -876,6 +876,9 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){
for(i=0; i<pSrc->nSrc; i++){
mask |= exprSelectUsage(pMaskSet, pSrc->a[i].pSelect);
mask |= sqlite3WhereExprUsage(pMaskSet, pSrc->a[i].pOn);
+ if( pSrc->a[i].fg.isTabFunc ){
+ mask |= sqlite3WhereExprListUsage(pMaskSet, pSrc->a[i].u1.pFuncArg);
+ }
}
}
pS = pS->pPrior;
@@ -1288,7 +1291,7 @@ static void exprAnalyze(
exprAnalyze(pSrc, pWC, idxNew);
}
pTerm = &pWC->a[idxTerm];
- pTerm->wtFlags = TERM_CODED|TERM_VIRTUAL; /* Disable the original */
+ pTerm->wtFlags |= TERM_CODED|TERM_VIRTUAL; /* Disable the original */
pTerm->eOperator = 0;
}
diff --git a/test/analyze.test b/test/analyze.test
index af277cc..f3293b2 100644
--- a/test/analyze.test
+++ b/test/analyze.test
@@ -349,7 +349,7 @@ ifcapable stat4||stat3 {
# This test corrupts the database file so it must be the last test
# in the series.
#
-do_test analyze-99.1 {
+do_test analyze-5.99 {
execsql {
PRAGMA writable_schema=on;
UPDATE sqlite_master SET sql='nonsense' WHERE name='sqlite_stat1';
@@ -361,4 +361,20 @@ do_test analyze-99.1 {
}
} {1 {malformed database schema (sqlite_stat1)}}
+# Verify that tables whose names begin with "sqlite" but not
+# "sqlite_" are analyzed.
+#
+db close
+sqlite3 db :memory:
+do_execsql_test analyze-6.1 {
+ CREATE TABLE sqliteDemo(a);
+ INSERT INTO sqliteDemo(a) VALUES(1),(2),(3),(4),(5);
+ CREATE TABLE SQLiteDemo2(a INTEGER PRIMARY KEY AUTOINCREMENT);
+ INSERT INTO SQLiteDemo2 SELECT * FROM sqliteDemo;
+ CREATE TABLE t1(b);
+ INSERT INTO t1(b) SELECT a FROM sqliteDemo;
+ ANALYZE;
+ SELECT tbl FROM sqlite_stat1 WHERE idx IS NULL ORDER BY tbl;
+} {SQLiteDemo2 sqliteDemo t1}
+
finish_test
diff --git a/test/avtrans.test b/test/avtrans.test
index 17a2860..6fc4a3e 100644
--- a/test/avtrans.test
+++ b/test/avtrans.test
@@ -22,7 +22,7 @@ source $testdir/tester.tcl
# Create several tables to work with.
#
do_test avtrans-1.0 {
- execsql { PRAGMA auto_vacuum=ON }
+ execsql { PRAGMA auto_vacuum=full }
wal_set_journal_mode
execsql {
CREATE TABLE one(a int PRIMARY KEY, b text);
@@ -32,6 +32,7 @@ do_test avtrans-1.0 {
SELECT b FROM one ORDER BY a;
}
} {one two three}
+do_test avtrans-1.0.1 { execsql { PRAGMA auto_vacuum } } 1
do_test avtrans-1.1 {
execsql {
CREATE TABLE two(a int PRIMARY KEY, b text);
diff --git a/test/cast.test b/test/cast.test
index f47f4bb..f43aa48 100644
--- a/test/cast.test
+++ b/test/cast.test
@@ -343,4 +343,49 @@ do_test cast-4.4 {
}
} {0 abc 0.0 abc}
+# Added 2018-01-26
+#
+# EVIDENCE-OF: R-48741-32454 If the prefix integer is greater than
+# +9223372036854775807 then the result of the cast is exactly
+# +9223372036854775807.
+do_execsql_test cast-5.1 {
+ SELECT CAST('9223372036854775808' AS integer);
+ SELECT CAST(' +000009223372036854775808' AS integer);
+ SELECT CAST('12345678901234567890123' AS INTEGER);
+} {9223372036854775807 9223372036854775807 9223372036854775807}
+
+# EVIDENCE-OF: R-06028-16857 Similarly, if the prefix integer is less
+# than -9223372036854775808 then the result of the cast is exactly
+# -9223372036854775808.
+do_execsql_test cast-5.2 {
+ SELECT CAST('-9223372036854775808' AS integer);
+ SELECT CAST('-9223372036854775809' AS integer);
+ SELECT CAST('-12345678901234567890123' AS INTEGER);
+} {-9223372036854775808 -9223372036854775808 -9223372036854775808}
+
+# EVIDENCE-OF: R-33990-33527 When casting to INTEGER, if the text looks
+# like a floating point value with an exponent, the exponent will be
+# ignored because it is no part of the integer prefix.
+# EVIDENCE-OF: R-24225-46995 For example, "(CAST '123e+5' AS INTEGER)"
+# results in 123, not in 12300000.
+do_execsql_test case-5.3 {
+ SELECT CAST('123e+5' AS INTEGER);
+ SELECT CAST('123e+5' AS NUMERIC);
+} {123 12300000.0}
+
+
+# The following does not have anything to do with the CAST operator,
+# but it does deal with affinity transformations.
+#
+do_execsql_test case-6.1 {
+ DROP TABLE IF EXISTS t1;
+ CREATE TABLE t1(a NUMERIC);
+ INSERT INTO t1 VALUES
+ ('9000000000000000001'),
+ ('9000000000000000001 '),
+ (' 9000000000000000001'),
+ (' 9000000000000000001 ');
+ SELECT * FROM t1;
+} {9000000000000000001 9000000000000000001 9000000000000000001 9000000000000000001}
+
finish_test
diff --git a/test/crash8.test b/test/crash8.test
index 7916e9b..c078299 100644
--- a/test/crash8.test
+++ b/test/crash8.test
@@ -352,7 +352,11 @@ ifcapable pragma {
# Since the following tests (crash8-5.*) rely upon being able
# to copy a file while open, they will not work on Windows.
#
-if {$::tcl_platform(platform)=="unix"} {
+# They also depend on being able to copy the journal file, which
+# is not created on F2FS file-systems that support atomic
+# write. So do not run these tests in that case either.
+#
+if {$::tcl_platform(platform)=="unix" && [atomic_batch_write test.db]==0 } {
for {set i 1} {$i < 10} {incr i} {
catch { db close }
forcedelete test.db test.db-journal
diff --git a/test/cursorhint2.test b/test/cursorhint2.test
index 616235b..0175568 100644
--- a/test/cursorhint2.test
+++ b/test/cursorhint2.test
@@ -136,45 +136,50 @@ do_extract_hints_test 2.5 {
x2 {EQ(c0,r[2])}
}
-do_extract_hints_test 2.6 {
- SELECT * FROM x1 LEFT JOIN x2 ON (a=x) WHERE 0 = (b IS NOT NULL)
-} {
- x2 {EQ(c0,r[2])}
-}
-
-do_extract_hints_test 2.7 {
- SELECT * FROM x1 LEFT JOIN x2 ON (a=x) WHERE 0 = (b IS NOT +NULL)
-} {
- x2 {EQ(c0,r[2])}
-}
-
-do_extract_hints_test 2.8 {
- SELECT * FROM x1 LEFT JOIN x2 ON (a=x) WHERE b IS NOT +NULL
-} {
- x2 {EQ(c0,r[2])}
-}
-
-do_extract_hints_test 2.9 {
- SELECT * FROM x1 LEFT JOIN x2 ON (a=x) WHERE CASE b WHEN 0 THEN 0 ELSE 1 END;
-} {
- x2 {EQ(c0,r[2])}
-}
-
-do_extract_hints_test 2.10 {
- SELECT * FROM x1 LEFT JOIN x2 ON (a=x) WHERE x2.b = 32+32
-} {
- x2 {AND(EQ(c1,ADD(32,32)),EQ(c0,r[2]))}
-}
-
-ifcapable !icu {
- # This test only works using the built-in LIKE, not the ICU LIKE extension.
- do_extract_hints_test 2.11 {
- SELECT * FROM x1 LEFT JOIN x2 ON (a=x) WHERE x2.b LIKE 'abc%'
+if {0} {
+ # These tests no longer work due to the LEFT-JOIN strength reduction
+ # optimization
+ do_extract_hints_test 2.6 {
+ SELECT * FROM x1 CROSS JOIN x2 ON (a=x) WHERE 0 = (b IS NOT NULL)
+ } {
+ x2 {EQ(c0,r[2])}
+ }
+
+ do_extract_hints_test 2.7 {
+ SELECT * FROM x1 LEFT JOIN x2 ON (a=x) WHERE 0 = (b IS NOT +NULL)
+ } {
+ x2 {EQ(c0,r[2])}
+ }
+
+ do_extract_hints_test 2.8 {
+ SELECT * FROM x1 LEFT JOIN x2 ON (a=x) WHERE b IS NOT +NULL
} {
- x2 {AND(expr,EQ(c0,r[2]))}
+ x2 {EQ(c0,r[2])}
+ }
+
+ do_extract_hints_test 2.9 {
+ SELECT * FROM x1 LEFT JOIN x2 ON (a=x)
+ WHERE CASE b WHEN 0 THEN 0 ELSE 1 END;
+ } {
+ x2 {EQ(c0,r[2])}
+ }
+
+ do_extract_hints_test 2.10 {
+ SELECT * FROM x1 LEFT JOIN x2 ON (a=x) WHERE x2.b = 32+32
+ } {
+ x2 {AND(EQ(c1,ADD(32,32)),EQ(c0,r[2]))}
+ }
+
+ ifcapable !icu {
+ # This test only works using the built-in LIKE, not the ICU LIKE extension.
+ do_extract_hints_test 2.11 {
+ SELECT * FROM x1 LEFT JOIN x2 ON (a=x) WHERE x2.b LIKE 'abc%'
+ } {
+ x2 {AND(expr,EQ(c0,r[2]))}
+ }
}
}
-
+
do_extract_hints_test 2.12 {
SELECT * FROM x1 LEFT JOIN x2 ON (a=x) WHERE coalesce(x2.b, 1)
} {
diff --git a/test/dbstatus.test b/test/dbstatus.test
index 711d66e..57b91cd 100644
--- a/test/dbstatus.test
+++ b/test/dbstatus.test
@@ -415,4 +415,43 @@ ifcapable shared_cache {
}
}
+#-------------------------------------------------------------------------
+# Test that passing an out-of-range value to sqlite3_stmt_status does
+# not cause a crash.
+reset_db
+do_execsql_test 5.0 {
+ CREATE TABLE t1(x, y);
+ INSERT INTO t1 VALUES(1, 2);
+ INSERT INTO t1 VALUES(3, 4);
+}
+
+do_test 5.1 {
+ set ::stmt [sqlite3_prepare db "SELECT * FROM t1" -1 dummy]
+ sqlite3_step $::stmt
+ sqlite3_step $::stmt
+ sqlite3_step $::stmt
+ sqlite3_reset $::stmt
+} {SQLITE_OK}
+
+ifcapable api_armor {
+ do_test 5.2 { sqlite3_stmt_status $::stmt -1 0 } 0
+}
+do_test 5.3 { sqlite3_stmt_status $::stmt 0 0 } 0
+do_test 5.4 {
+ expr [sqlite3_stmt_status $::stmt 99 0]>0
+} 1
+foreach {tn id res} {
+ 1 SQLITE_STMTSTATUS_MEMUSED 1
+ 2 SQLITE_STMTSTATUS_FULLSCAN_STEP 1
+ 3 SQLITE_STMTSTATUS_SORT 0
+ 4 SQLITE_STMTSTATUS_AUTOINDEX 0
+ 5 SQLITE_STMTSTATUS_VM_STEP 1
+ 6 SQLITE_STMTSTATUS_REPREPARE 0
+ 7 SQLITE_STMTSTATUS_RUN 1
+} {
+if {$tn==2} breakpoint
+ do_test 5.5.$tn { expr [sqlite3_stmt_status $::stmt $id 0]>0 } $res
+}
+
+sqlite3_finalize $::stmt
finish_test
diff --git a/test/dbstatus2.test b/test/dbstatus2.test
index eff4b02..5e9ea88 100644
--- a/test/dbstatus2.test
+++ b/test/dbstatus2.test
@@ -37,6 +37,10 @@ proc db_write {db {reset 0}} {
sqlite3_db_status $db CACHE_WRITE $reset
}
+proc db_spill {db {reset 0}} {
+ sqlite3_db_status $db CACHE_SPILL $reset
+}
+
do_test 1.1 {
db close
sqlite3 db test.db
@@ -98,5 +102,14 @@ do_test 2.7 {
} {0 4 0}
do_test 2.8 { db_write db 1 } {0 4 0}
do_test 2.9 { db_write db 0 } {0 0 0}
+
+do_test 3.0 { db_spill db 1 } {0 0 0}
+do_test 3.1 { db_spill db 0 } {0 0 0}
+do_execsql_test 3.2 {
+ PRAGMA journal_mode=DELETE;
+ PRAGMA cache_size=3;
+ UPDATE t1 SET b=randomblob(1000);
+} {delete}
+do_test 3.3 { db_spill db 0 } {0 8 0}
finish_test
diff --git a/test/e_select.test b/test/e_select.test
index 9aa2de6..e88d63b 100644
--- a/test/e_select.test
+++ b/test/e_select.test
@@ -748,7 +748,7 @@ do_execsql_test e_select-3.2.1a {
SELECT k FROM x1 LEFT JOIN x2 USING(k)
} {1 2 3 4 5 6}
do_execsql_test e_select-3.2.1b {
- SELECT k FROM x1 LEFT JOIN x2 USING(k) WHERE x2.k
+ SELECT k FROM x1 LEFT JOIN x2 USING(k) WHERE x2.k ORDER BY +k
} {1 3 5}
do_execsql_test e_select-3.2.2 {
SELECT k FROM x1 LEFT JOIN x2 USING(k) WHERE x2.k IS NULL
diff --git a/test/expr.test b/test/expr.test
index 7a6d477..3cdc918 100644
--- a/